From 22951a9b87c97d5d210fc6b09aa54730fe5cb77c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 6 Mar 2025 16:52:27 +0100 Subject: [PATCH 0001/1014] protocol: document to client-node communication flow a little --- doc/dox/internals/protocol.dox | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/doc/dox/internals/protocol.dox b/doc/dox/internals/protocol.dox index dadebd749..e2ec1c042 100644 --- a/doc/dox/internals/protocol.dox +++ b/doc/dox/internals/protocol.dox @@ -1091,6 +1091,77 @@ It creates a server Node that can be controlled from a client. Processing will h in the client. It is used by pw-stream and pw-filter to implement the PipeWire media processing nodes. +To create a client-node, one must first connect to the server and then make a +ClientNode proxy and do Core::CreateObject with the client-node factory. +This will create a server side ClientNode resource that can be controlled with the +proxy. + +After the proxy is set up, the conversation between client and server goes as +follows: + +``` + client server + | | + |---------------------------------------->| + | ClientNode::Update | send initial node configuration + | | + |<----------------------------------------| + | Core::AddMem | memory for node activation + |<----------------------------------------| + | ClientNode::SetActivation | the node activation + |<----------------------------------------| + | ClientNode::Transport | the node transport + |<----------------------------------------| + | ClientNode::SetIO | clock IO + | | + |---------------------------------------->| + | ClientNode::SetActive(true) | set the node active + | | + |<----------------------------------------| + | ClientNode::SetParam | optional volume restore/settings + |<----------------------------------------| + | ClientNode::SetParam | optional PortConfig if needed + |---------------------------------------->| + | ClientNode::Update | Upload changed params + | | + |---------------------------------------->| + | ClientNode::PortUpdate | config for each port + . . + |<----------------------------------------| + | ClientNode::PortSetMixInfo | mixer inputs for each linked port + |<----------------------------------------| + | ClientNode::PortSetParam | Latency of the ports + |<----------------------------------------| + | ClientNode::SetActivation | activation of port peers + |---------------------------------------->| + | ClientNode::PortUpdate | Ack port updates + . . + |<----------------------------------------| + | ClientNode::PortSetParam | formats of the ports + |---------------------------------------->| + | ClientNode::PortUpdate | Ack port format update + . . + |<----------------------------------------| + | Core::AddMem | memory for the port buffers + |<----------------------------------------| + | ClientNode::PortUseBuffers | buffers for a port + . . + |<----------------------------------------| + | Core::AddMem | memory for driver activation + |<----------------------------------------| + | ClientNode::SetActivation | the driver activation + |<----------------------------------------| + | ClientNode::SetIO | driver Position IO + |<----------------------------------------| + | Core::AddMem | memory for port IO + |<----------------------------------------| + | ClientNode::PortSetIO | buffers/async-buffers port IO + . . + |<----------------------------------------| + | ClientNode::Command | Start command + . . +``` + ## ClientNode methods ### ClientNode::GetNode (Opcode 1) From be1fc5f3a6c86380ef2d8e38aea2e0de8e4a3a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 5 Mar 2025 08:01:11 +0100 Subject: [PATCH 0002/1014] bluez5: backend-native: Fix ECNR support in HFP HF SDP record Sending AT+ECNR is supported by the native backend --- spa/plugins/bluez5/backend-native.c | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 359696903..65cb764cd 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -3318,6 +3318,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag } else if (profile == SPA_BT_PROFILE_HFP_AG) { /* Start SLC connection */ unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_CLIP | SPA_BT_HFP_HF_FEATURE_3WAY | + SPA_BT_HFP_HF_FEATURE_ECNR | SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS | SPA_BT_HFP_HF_FEATURE_ESCO_S4; bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC); From 90b95ae065d9c9218d390982f4b4c813f6f44255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 5 Mar 2025 08:13:01 +0100 Subject: [PATCH 0003/1014] bluez5: backend-native: Fix incoming call crash HFP/HF/TWC/BV-01-C test creates an incoming call as soon as the SLC is completed, i.e. a +CIEV: ,1 event just after AT+CHLD=? reply has been received. This try to parse the rfcomm->telephony_ag->call_list which has not yet been created. This commit move the telephony_ag creation to the SLC completed event. --- spa/plugins/bluez5/backend-native.c | 39 +++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 65cb764cd..74c58d296 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2286,6 +2286,26 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } SPA_FALLTHROUGH; case hfp_hf_chld: + rfcomm->slc_configured = true; + + if (!rfcomm->codec_negotiation_supported) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { + // TODO: We should manage the missing transport + } else { + spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + } + } + + rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); + rfcomm->telephony_ag->address = strdup(rfcomm->device->address); + telephony_ag_set_callbacks(rfcomm->telephony_ag, + &telephony_ag_callbacks, rfcomm); + if (rfcomm->transport) { + rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.state = rfcomm->transport->state; + } + telephony_ag_register(rfcomm->telephony_ag); + rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); rfcomm->hf_state = hfp_hf_clip; break; @@ -2312,25 +2332,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) SPA_FALLTHROUGH; case hfp_hf_nrec: rfcomm->hf_state = hfp_hf_slc1; - rfcomm->slc_configured = true; - - if (!rfcomm->codec_negotiation_supported) { - if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { - // TODO: We should manage the missing transport - } else { - spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); - } - } - - rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); - rfcomm->telephony_ag->address = strdup(rfcomm->device->address); - telephony_ag_set_callbacks(rfcomm->telephony_ag, - &telephony_ag_callbacks, rfcomm); - if (rfcomm->transport) { - rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; - rfcomm->telephony_ag->transport.state = rfcomm->transport->state; - } - telephony_ag_register(rfcomm->telephony_ag); if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, "AT+CLCC"); From 0e92ab9307e05758b3f70b4c0648e29c1d1e50be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 5 Mar 2025 12:19:59 +0100 Subject: [PATCH 0004/1014] bluez5: backend-native: Fix 3way active call hangup HFP/HF/TWC/BV-03-C test, which setup an active and a held calls, expects to receive AT+CHLD=1 (release and swap calls) instead of AT+CHUP on active call hang up request. As this changes the active call to disconnected and held call to being active, the call states should be managed in hfp_hf_hangup instead of waiting for +CIEV (callheld=0) event which will drop the previously held call before AT+CLCC reply can inform this call is now active. --- spa/plugins/bluez5/backend-native.c | 46 ++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 74c58d296..8d5354d31 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1406,18 +1406,44 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call, *tcall; + bool found_held = false; + bool hfp_hf_in_progress = false; char reply[20]; bool res; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) + found_held = true; + } + switch (call_data->call->state) { case CALL_STATE_ACTIVE: case CALL_STATE_DIALING: case CALL_STATE_ALERTING: case CALL_STATE_INCOMING: - rfcomm_send_cmd(rfcomm, "AT+CHUP"); + if (found_held) { + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); + hfp_hf_in_progress = true; + } else { + rfcomm_send_cmd(rfcomm, "AT+CHUP"); + } break; case CALL_STATE_WAITING: + if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); + hfp_hf_in_progress = true; break; default: spa_log_info(backend->log, "Call not incoming, waiting or active: skip hangup"); @@ -1435,6 +1461,24 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t return; } + if (hfp_hf_in_progress) { + if (call_data->call->state != CALL_STATE_WAITING) { + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) { + call->state = CALL_STATE_ACTIVE; + telephony_call_notify_updated_props(call); + } + } + } + rfcomm->hfp_hf_in_progress = true; + } *err = BT_TELEPHONY_ERROR_NONE; } From ef023916b9539aa1d3a11315b8bfabacde62e5de Mon Sep 17 00:00:00 2001 From: Jan Palus Date: Thu, 6 Mar 2025 13:57:21 +0100 Subject: [PATCH 0005/1014] module-roc: require roc >= 0.4.0 3270bd4 introduced changes reyling on features from roc 0.4.0 (upstream commit: https://github.com/roc-streaming/roc-toolkit/commit/d18d342) --- src/modules/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/meson.build b/src/modules/meson.build index 51db77334..be9912dd6 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -620,7 +620,7 @@ if build_module_raop endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') -roc_dep = dependency('roc', version: '>= 0.3.0', required: get_option('roc')) +roc_dep = dependency('roc', version: '>= 0.4.0', required: get_option('roc')) summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', From bc890d3b834549fc8633854cf13477ed41aaaf05 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 11:57:08 +0200 Subject: [PATCH 0006/1014] spa: acp: in SplitPCM probe channel count and allow excess In SplitPCM mode, Focusrite Scarlett Gen 4 (USB 1235:8218) UCM profile specifies "CaptureChannels 2" for the Mic1/2 inputs, but snd_pcm_hw_params_set_channels(2) fails for the HW device. Fix by not requiring the channel count to be exact for SplitPCM, but also allow larger numbers of channels than what UCM profile specifies. --- spa/plugins/alsa/acp/alsa-ucm.c | 45 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 9061a1f26..77e476eb8 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -2383,7 +2383,7 @@ static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) dev->eld_device = pcm_device; } -static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { +static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode, bool max_channels) { snd_pcm_t* pcm; pa_sample_spec try_ss = ucm->default_sample_spec; pa_channel_map try_map; @@ -2391,6 +2391,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, bool exact_channels = m->channel_map.channels > 0; if (!m->split) { + if (max_channels) { + errno = EINVAL; + return NULL; + } + if (exact_channels) { try_map = m->channel_map; try_ss.channels = try_map.channels; @@ -2402,8 +2407,8 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, return NULL; } - exact_channels = true; - try_ss.channels = m->split->hw_channels; + exact_channels = false; + try_ss.channels = max_channels ? PA_CHANNELS_MAX : m->split->hw_channels; pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX); } @@ -2416,15 +2421,34 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (pcm) { - if (!exact_channels) + if (m->split) { + if (try_map.channels < m->split->hw_channels) { + pa_alsa_close(&pcm); + + pa_logl((max_channels ? PA_LOG_WARN : PA_LOG_DEBUG), + "Too few channels in %s for ALSA UCM SplitPCM: avail %d < required %d", + m->device_strings[0], try_map.channels, m->split->hw_channels); + + /* Retry with max channel count, in case ALSA rounded down */ + if (!max_channels) + return mapping_open_pcm(ucm, m, mode, true); + + return NULL; + } else if (try_map.channels > m->split->hw_channels) { + pa_log_debug("Update split PCM channel count for %s: %d -> %d", + m->device_strings[0], m->split->hw_channels, try_map.channels); + m->split->hw_channels = try_map.channels; + } + } else if (!exact_channels) { m->channel_map = try_map; + } mapping_init_eld(m, pcm); } return pcm; } -static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) +static void pa_alsa_init_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) { pa_proplist *props = pa_proplist_new(); uint32_t idx; @@ -2445,6 +2469,9 @@ static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props); else pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props); + + /* Update HW channel count to match probed one */ + m->split->hw_channels = leader->split->hw_channels; } pa_proplist_free(props); @@ -2464,7 +2491,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); else - pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); + pa_alsa_init_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); pa_alsa_close(&m->output_pcm); } @@ -2479,7 +2506,7 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); else - pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); + pa_alsa_init_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); pa_alsa_close(&m->input_pcm); } @@ -2536,7 +2563,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (m->split && !m->split->leader) continue; - m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK); + m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK, false); if (!m->output_pcm) { p->supported = false; break; @@ -2554,7 +2581,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (m->split && !m->split->leader) continue; - m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE); + m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE, false); if (!m->input_pcm) { p->supported = false; break; From 6d51b4bb1117c23b5210503f9ec98c1bad1230e0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 13:04:36 +0200 Subject: [PATCH 0007/1014] spa: acp: be more noisy when UCM profiles fail to be supported Generally ALSA UCM profiles should all work as they're supposed to be device-specific, so be more noisy when the profile fails to be supported due to the PCM device failing to open. Some logging on the probe outcome in failure case also makes spa-acp-tool etc. log output easier to read. --- spa/plugins/alsa/acp/alsa-ucm.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 77e476eb8..f64c573a4 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -2548,7 +2548,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * pa_log_info("Set ucm verb to %s", verb_name); if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { - pa_log("Failed to set verb %s", verb_name); + pa_log("Profile '%s': failed to set verb %s", p->name, verb_name); p->supported = false; continue; } @@ -2565,6 +2565,8 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK, false); if (!m->output_pcm) { + pa_log_info("Profile '%s' mapping '%s': output PCM open failed", + p->name, m->name); p->supported = false; break; } @@ -2583,6 +2585,8 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE, false); if (!m->input_pcm) { + pa_log_info("Profile '%s' mapping '%s': input PCM open failed", + p->name, m->name); p->supported = false; break; } @@ -2591,6 +2595,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if (!p->supported) { profile_finalize_probing(p); + pa_log_info("Profile %s not supported", p->name); continue; } From 5c1d6fb5ad1420adf0654c20ddd2bb79552866a8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 16:14:56 +0200 Subject: [PATCH 0008/1014] spa: acp: make spa-acp-tool debug output easier to read Include log level, file/line numbers, and indent messages for debug levels. --- spa/plugins/alsa/acp-tool.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c index cff9edb80..c12401100 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -132,6 +132,18 @@ static ACP_PRINTF_FUNC(6,0) void log_func(void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) { + static const char * const levels[] = { "E", "W", "N", "I", "D", "T" }; + const char *level_str = levels[SPA_CLAMP(level, 0, (int)SPA_N_ELEMENTS(levels) - 1)]; + + if (file) { + const char *p = strrchr(file, '/'); + if (p) + file = p + 1; + } + + fprintf(stderr, "%s %16s:%-5d ", level_str, file ? file : "", line); + while (level-- > 1) + fprintf(stderr, " "); vfprintf(stderr, fmt, arg); fprintf(stderr, "\n"); } From 4442ab007fded979346ef50a6fca6c3ee77f04ae Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 10 Mar 2025 13:23:10 +0100 Subject: [PATCH 0009/1014] stream: don't emit process when disconnecting Commit b160a72018656505e4ae4ec26308177c033da627 introduced this change before, but it was omitted in e1e0a886d524cbd3cbd339c79b4c38514cb11e39. This makes again sure we don't call process callback while disconnecting stream. Fixes #3314 --- src/pipewire/stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 5e5aed3b7..4af3a3afa 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -446,7 +446,7 @@ static inline void call_process(struct stream *impl) if (impl->n_buffers == 0 || (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0)) return; - if (impl->rt_callbacks.funcs) + if (impl->rt_callbacks.funcs && !impl->disconnecting) spa_callbacks_call_fast(&impl->rt_callbacks, struct pw_stream_events, process, 0); } From 9bc29b4b37f9a1bf544e84058c16acd8b94ef70b Mon Sep 17 00:00:00 2001 From: msizanoen Date: Mon, 10 Mar 2025 20:06:58 +0700 Subject: [PATCH 0010/1014] systemd: Disable pipewire user services for root The `access(2)` based multi-user mediation mechanism doesn't quite work for the root user, which may cause it to conflict with a running foreground user session. Prevent this by not running the user service at all for the root user, which nobody should be doing anyway. --- src/daemon/systemd/user/pipewire.service.in | 1 + src/daemon/systemd/user/pipewire.socket | 1 + 2 files changed, 2 insertions(+) diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in index b9b137351..4236c6bd4 100644 --- a/src/daemon/systemd/user/pipewire.service.in +++ b/src/daemon/systemd/user/pipewire.service.in @@ -14,6 +14,7 @@ Description=PipeWire Multimedia Service # After=pipewire.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). Requires=pipewire.socket +ConditionUser=!root [Service] LockPersonality=yes diff --git a/src/daemon/systemd/user/pipewire.socket b/src/daemon/systemd/user/pipewire.socket index 16e23a7b6..890342abb 100644 --- a/src/daemon/systemd/user/pipewire.socket +++ b/src/daemon/systemd/user/pipewire.socket @@ -1,5 +1,6 @@ [Unit] Description=PipeWire Multimedia System Sockets +ConditionUser=!root [Socket] Priority=6 From 6620c6cde1b5e5bfdb32fdff2daef31cd0c3fda3 Mon Sep 17 00:00:00 2001 From: Nikolay Borodin Date: Sun, 9 Mar 2025 18:52:49 +0200 Subject: [PATCH 0011/1014] spa: alsa: allow building without UMP in libalsa Allow building with older libalsa releases that don't have UMP. --- meson.build | 2 +- spa/meson.build | 2 +- spa/plugins/alsa/alsa-seq.c | 63 ++++++++++++++++++++++++++++++++++++- spa/plugins/alsa/alsa-seq.h | 2 ++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 3dfdc1842..7afffda19 100644 --- a/meson.build +++ b/meson.build @@ -481,7 +481,7 @@ endif summary({'intl support': libintl_dep.found()}, bool_yn: true) need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_option('session-managers') -alsa_dep = dependency('alsa', version : '>=1.2.10', required: need_alsa) +alsa_dep = dependency('alsa', version : '>=1.2.6', required: need_alsa) summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true) if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' diff --git a/spa/meson.build b/spa/meson.build index 9b5f89604..9d0009868 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -43,7 +43,7 @@ if get_option('spa-plugins').allowed() endif # plugin-specific dependencies - alsa_dep = dependency('alsa', version : '>=1.2.10', required: get_option('alsa')) + alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5')) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 2a4ebd2c4..52e869c29 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -37,12 +37,14 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu 0)) < 0) return res; +#ifdef ALSA_UMP if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) { snd_seq_close(conn->hndl); spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res)); return res; } +#endif return 0; } @@ -172,7 +174,11 @@ static void init_ports(struct seq_state *state) } } +#ifdef ALSA_UMP static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) +#else +static void debug_event(struct seq_state *state, snd_seq_event_t *ev) +#endif { if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) return; @@ -199,10 +205,18 @@ static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; +#ifdef ALSA_UMP snd_seq_ump_event_t *ev; +#else + snd_seq_event_t *ev; +#endif int res; +#ifdef ALSA_UMP while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) { +#else + while (snd_seq_event_input(state->sys.hndl, &ev) > 0) { +#endif const snd_seq_addr_t *addr = &ev->data.addr; if (addr->client == state->event.addr.client) @@ -529,15 +543,27 @@ static int process_recycle(struct seq_state *state) static int process_read(struct seq_state *state) { +#ifdef ALSA_UMP snd_seq_ump_event_t *ev; +#else + snd_seq_event_t *ev; +#endif struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; uint32_t i; +#ifdef ALSA_UMP uint32_t *data; +#else + uint8_t data[MAX_EVENT_SIZE]; +#endif long size; - int res; + int res = -1; /* copy all new midi events into their port buffers */ +#ifdef ALSA_UMP while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) { +#else + while (snd_seq_event_input(state->event.hndl, &ev) > 0) { +#endif const snd_seq_addr_t *addr = &ev->source; struct seq_port *port; uint64_t ev_time, diff; @@ -559,8 +585,16 @@ static int process_read(struct seq_state *state) continue; } +#ifdef ALSA_UMP data = (uint32_t*)&ev->ump[0]; size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; +#else + snd_midi_event_reset_decode(stream->codec); + if ((size = snd_midi_event_decode(stream->codec, data, MAX_EVENT_SIZE, ev)) < 0) { + spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); + continue; + } +#endif /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ @@ -661,7 +695,11 @@ static int process_write(struct seq_state *state) struct spa_pod_sequence *pod; struct spa_data *d; struct spa_pod_control *c; +#ifdef ALSA_UMP snd_seq_ump_event_t ev; +#else + snd_seq_event_t ev; +#endif uint64_t out_time; snd_seq_real_time_t out_rt; @@ -688,6 +726,7 @@ static int process_write(struct seq_state *state) SPA_POD_SEQUENCE_FOREACH(pod, c) { size_t body_size; +#ifdef ALSA_UMP uint8_t *body; if (c->type != SPA_CONTROL_UMP) @@ -698,6 +737,21 @@ static int process_write(struct seq_state *state) spa_zero(ev); memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); +#else + if (c->type != SPA_CONTROL_Midi) + continue; + + snd_seq_ev_clear(&ev); + + snd_midi_event_reset_encode(stream->codec); + if ((body_size = snd_midi_event_encode(stream->codec, + SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), &ev)) <= 0) { + spa_log_warn(state->log, "failed to encode event: %s", + snd_strerror(body_size)); + continue; + } +#endif snd_seq_ev_set_source(&ev, state->event.addr.port); snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); @@ -711,10 +765,17 @@ static int process_write(struct seq_state *state) spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d", ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port); +#ifdef ALSA_UMP if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } +#else + if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } +#endif } } snd_seq_drain_output(state->event.hndl); diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 0f2c192c3..906f3c813 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -13,7 +13,9 @@ extern "C" { #include #include +#ifdef ALSA_UMP #include +#endif #include #include From 4379cf446f1d5003fd93e12054503386cd87ecdc Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 19:38:01 +0200 Subject: [PATCH 0012/1014] spa: alsa: support also MIDI-1.0 IO for ALSA seq Support also non-UMP IO with ALSA seq, in case either alsa-lib or the kernel does not have UMP enabled. Add configuration option "api.alsa.seq.ump" for optionally turning UMP I/O off, for easier debugging. --- spa/plugins/alsa/alsa-seq-bridge.c | 4 + spa/plugins/alsa/alsa-seq.c | 243 +++++++++++++++++++---------- spa/plugins/alsa/alsa-seq.h | 1 + 3 files changed, 163 insertions(+), 85 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index f85b41e28..7ec39321c 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -931,6 +931,7 @@ impl_init(const struct spa_handle_factory *factory, this->quantum_limit = 8192; this->min_pool_size = 500; this->max_pool_size = 2000; + this->ump = true; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; @@ -949,6 +950,8 @@ impl_init(const struct spa_handle_factory *factory, spa_atou32(s, &this->min_pool_size, 0); } else if (spa_streq(k, "api.alsa.seq.max-pool")) { spa_atou32(s, &this->max_pool_size, 0); + } else if (spa_streq(k, "api.alsa.seq.ump")) { + this->ump = spa_atob(s); } } @@ -992,6 +995,7 @@ static const struct spa_dict_item info_items[] = { "["SPA_KEY_API_ALSA_DISABLE_LONGNAME"=] " "[ api.alsa.seq.min-pool=] " "[ api.alsa.seq.max-pool=]" + "[ api.alsa.seq.ump = ]" }, }; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 52e869c29..8ca7405d8 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -24,7 +24,7 @@ #define CHECK(s,msg,...) if ((res = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(res)); return res; } -static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue) +static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue, bool probe_ump) { struct props *props = &state->props; int res; @@ -37,14 +37,29 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu 0)) < 0) return res; -#ifdef ALSA_UMP - if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) { - snd_seq_close(conn->hndl); - spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s", - state, snd_strerror(res)); - return res; + if (!state->ump) { + spa_log_info(state->log, "%p: ALSA UMP MIDI disabled", state); + return 0; } + +#ifdef ALSA_UMP + res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0); +#else + res = -EOPNOTSUPP; #endif + if (res < 0) { + spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR), + "%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res)); + if (!probe_ump) { + snd_seq_close(conn->hndl); + return res; /* either all are UMP or none are UMP */ + } + + state->ump = false; + } else { + spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state); + state->ump = true; + } return 0; } @@ -174,11 +189,7 @@ static void init_ports(struct seq_state *state) } } -#ifdef ALSA_UMP -static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) -#else static void debug_event(struct seq_state *state, snd_seq_event_t *ev) -#endif { if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) return; @@ -205,19 +216,33 @@ static void debug_event(struct seq_state *state, snd_seq_event_t *ev) static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; -#ifdef ALSA_UMP - snd_seq_ump_event_t *ev; -#else snd_seq_event_t *ev; -#endif int res; -#ifdef ALSA_UMP - while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) { + while (1) { + const snd_seq_addr_t *addr; + + if (state->ump) { +#if ALSA_UMP + snd_seq_ump_event_t *ump_ev; + + res = snd_seq_ump_event_input(state->sys.hndl, &ump_ev); + if (res <= 0) + break; + + /* Structures are layout-compatible */ + ev = (snd_seq_event_t *)ump_ev; + addr = &ump_ev->data.addr; #else - while (snd_seq_event_input(state->sys.hndl, &ev) > 0) { + spa_assert_not_reached(); #endif - const snd_seq_addr_t *addr = &ev->data.addr; + } else { + res = snd_seq_event_input(state->sys.hndl, &ev); + if (res <= 0) + break; + + addr = &ev->data.addr; + } if (addr->client == state->event.addr.client) continue; @@ -283,8 +308,8 @@ int spa_alsa_seq_open(struct seq_state *state) spa_zero(reserve); for (i = 0; i < 16; i++) { - spa_log_debug(state->log, "close %d", i); - if ((res = seq_open(state, &reserve[i], false)) < 0) + spa_log_debug(state->log, "open %d", i); + if ((res = seq_open(state, &reserve[i], false, (i == 0))) < 0) break; } if (i >= 2) { @@ -543,31 +568,47 @@ static int process_recycle(struct seq_state *state) static int process_read(struct seq_state *state) { -#ifdef ALSA_UMP - snd_seq_ump_event_t *ev; -#else snd_seq_event_t *ev; -#endif struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; uint32_t i; -#ifdef ALSA_UMP uint32_t *data; -#else - uint8_t data[MAX_EVENT_SIZE]; -#endif + uint8_t midi1_data[MAX_EVENT_SIZE]; + uint32_t ump_data[MAX_EVENT_SIZE]; long size; int res = -1; /* copy all new midi events into their port buffers */ -#ifdef ALSA_UMP - while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) { -#else - while (snd_seq_event_input(state->event.hndl, &ev) > 0) { -#endif - const snd_seq_addr_t *addr = &ev->source; + while (1) { + const snd_seq_addr_t *addr; struct seq_port *port; uint64_t ev_time, diff; uint32_t offset; +#ifdef ALSA_UMP + snd_seq_ump_event_t *ump_ev; +#endif + uint8_t *midi1_ptr; + size_t midi1_size; + uint64_t ump_state = 0; + + if (state->ump) { +#ifdef ALSA_UMP + res = snd_seq_ump_event_input(state->event.hndl, &ump_ev); + if (res <= 0) + break; + + /* Structures are layout-compatible */ + ev = (snd_seq_event_t *)ump_ev; + addr = &ump_ev->source; +#else + spa_assert_not_reached(); +#endif + } else { + res = snd_seq_event_input(state->event.hndl, &ev); + if (res <= 0) + break; + + addr = &ev->source; + } debug_event(state, ev); @@ -585,16 +626,23 @@ static int process_read(struct seq_state *state) continue; } + if (state->ump) { #ifdef ALSA_UMP - data = (uint32_t*)&ev->ump[0]; - size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; + data = (uint32_t*)&ump_ev->ump[0]; + size = spa_ump_message_size(snd_ump_msg_hdr_type(ump_ev->ump[0])) * 4; #else - snd_midi_event_reset_decode(stream->codec); - if ((size = snd_midi_event_decode(stream->codec, data, MAX_EVENT_SIZE, ev)) < 0) { - spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); - continue; - } + spa_assert_not_reached(); #endif + } else { + snd_midi_event_reset_decode(stream->codec); + if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) { + spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); + continue; + } + + midi1_ptr = midi1_data; + midi1_size = size; + } /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ @@ -611,19 +659,32 @@ static int process_read(struct seq_state *state) else offset = 0; - spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", - ev->type, ev_time, offset, size, addr->client, addr->port); + do { + if (!state->ump) { + data = ump_data; + size = spa_ump_from_midi(&midi1_ptr, &midi1_size, + ump_data, sizeof(ump_data), 0, &ump_state); + if (size <= 0) + break; + } - spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); - spa_pod_builder_bytes(&port->builder, data, size); + spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", + ev->type, ev_time, offset, size, addr->client, addr->port); - /* make sure we can fit at least one control event of max size otherwise - * we keep the event in the queue and try to copy it in the next cycle */ - if (port->builder.state.offset + - sizeof(struct spa_pod_control) + - MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) - break; + spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&port->builder, data, size); + + /* make sure we can fit at least one control event of max size otherwise + * we keep the event in the queue and try to copy it in the next cycle */ + if (port->builder.state.offset + + sizeof(struct spa_pod_control) + + MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) + goto done; + + } while (!state->ump); } + +done: if (res < 0 && res != -EAGAIN) spa_log_warn(state->log, "event read failed: %s", snd_strerror(res)); @@ -695,11 +756,6 @@ static int process_write(struct seq_state *state) struct spa_pod_sequence *pod; struct spa_data *d; struct spa_pod_control *c; -#ifdef ALSA_UMP - snd_seq_ump_event_t ev; -#else - snd_seq_event_t ev; -#endif uint64_t out_time; snd_seq_real_time_t out_rt; @@ -725,8 +781,12 @@ static int process_write(struct seq_state *state) } SPA_POD_SEQUENCE_FOREACH(pod, c) { - size_t body_size; + snd_seq_event_t *ev; + snd_seq_event_t midi1_ev; #ifdef ALSA_UMP + snd_seq_ump_event_t ump_ev; +#endif + size_t body_size; uint8_t *body; if (c->type != SPA_CONTROL_UMP) @@ -734,48 +794,61 @@ static int process_write(struct seq_state *state) body = SPA_POD_BODY(&c->value); body_size = SPA_POD_BODY_SIZE(&c->value); - spa_zero(ev); - memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); + if (state->ump) { +#ifdef ALSA_UMP + spa_zero(ump_ev); + memcpy(ump_ev.ump, body, SPA_MIN(sizeof(ump_ev.ump), (size_t)body_size)); + ev = (snd_seq_event_t *)&ump_ev; #else - if (c->type != SPA_CONTROL_Midi) - continue; - - snd_seq_ev_clear(&ev); - - snd_midi_event_reset_encode(stream->codec); - if ((body_size = snd_midi_event_encode(stream->codec, - SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), &ev)) <= 0) { - spa_log_warn(state->log, "failed to encode event: %s", - snd_strerror(body_size)); - continue; - } + spa_assert_not_reached(); #endif + } else { + uint8_t data[MAX_EVENT_SIZE]; + int size; - snd_seq_ev_set_source(&ev, state->event.addr.port); - snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) + continue; + + snd_seq_ev_clear(&midi1_ev); + + snd_midi_event_reset_encode(stream->codec); + if ((size = snd_midi_event_encode(stream->codec, data, size, &midi1_ev)) <= 0) { + spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); + continue; + } + + ev = &midi1_ev; + body_size = size; + } + + snd_seq_ev_set_source(ev, state->event.addr.port); + snd_seq_ev_set_dest(ev, port->addr.client, port->addr.port); out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; - snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + snd_seq_ev_schedule_real(ev, state->event.queue_id, 0, &out_rt); spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d", - ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port); + ev->type, out_time, c->offset, body_size, port->addr.client, port->addr.port); + if (state->ump) { #ifdef ALSA_UMP - if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { - spa_log_warn(state->log, "failed to output event: %s", - snd_strerror(err)); - } + if ((err = snd_seq_ump_event_output(state->event.hndl, &ump_ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } #else - if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { - spa_log_warn(state->log, "failed to output event: %s", - snd_strerror(err)); - } + spa_assert_not_reached(); #endif + } else { + if ((err = snd_seq_event_output(state->event.hndl, ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } + } } } snd_seq_drain_output(state->event.hndl); diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 906f3c813..0546459db 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -155,6 +155,7 @@ struct seq_state { unsigned int opened:1; unsigned int started:1; unsigned int following:1; + unsigned int ump:1; struct seq_stream streams[2]; From fd5bd5ca6e999844293578daebe76fe54a54aba3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 20:33:39 +0200 Subject: [PATCH 0013/1014] spa: fix ALSA UMP support detection in meson --- spa/meson.build | 4 ++++ spa/plugins/alsa/alsa-seq.c | 16 ++++++++-------- spa/plugins/alsa/alsa-seq.h | 4 +++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/spa/meson.build b/spa/meson.build index 9d0009868..9faaae48a 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -46,6 +46,10 @@ if get_option('spa-plugins').allowed() alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') + if alsa_dep.version().version_compare('>=1.2.10') + cdata.set('HAVE_ALSA_UMP', true) + endif + bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5')) bluez_gio_dep = dependency('gio-2.0', required : get_option('bluez5')) bluez_gio_unix_dep = dependency('gio-unix-2.0', required : get_option('bluez5')) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 8ca7405d8..fcf6dd09a 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -42,7 +42,7 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu return 0; } -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0); #else res = -EOPNOTSUPP; @@ -223,7 +223,7 @@ static void alsa_seq_on_sys(struct spa_source *source) const snd_seq_addr_t *addr; if (state->ump) { -#if ALSA_UMP +#ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ump_ev; res = snd_seq_ump_event_input(state->sys.hndl, &ump_ev); @@ -583,7 +583,7 @@ static int process_read(struct seq_state *state) struct seq_port *port; uint64_t ev_time, diff; uint32_t offset; -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ump_ev; #endif uint8_t *midi1_ptr; @@ -591,7 +591,7 @@ static int process_read(struct seq_state *state) uint64_t ump_state = 0; if (state->ump) { -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP res = snd_seq_ump_event_input(state->event.hndl, &ump_ev); if (res <= 0) break; @@ -627,7 +627,7 @@ static int process_read(struct seq_state *state) } if (state->ump) { -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP data = (uint32_t*)&ump_ev->ump[0]; size = spa_ump_message_size(snd_ump_msg_hdr_type(ump_ev->ump[0])) * 4; #else @@ -783,7 +783,7 @@ static int process_write(struct seq_state *state) SPA_POD_SEQUENCE_FOREACH(pod, c) { snd_seq_event_t *ev; snd_seq_event_t midi1_ev; -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP snd_seq_ump_event_t ump_ev; #endif size_t body_size; @@ -796,7 +796,7 @@ static int process_write(struct seq_state *state) body_size = SPA_POD_BODY_SIZE(&c->value); if (state->ump) { -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP spa_zero(ump_ev); memcpy(ump_ev.ump, body, SPA_MIN(sizeof(ump_ev.ump), (size_t)body_size)); ev = (snd_seq_event_t *)&ump_ev; @@ -835,7 +835,7 @@ static int process_write(struct seq_state *state) ev->type, out_time, c->offset, body_size, port->addr.client, port->addr.port); if (state->ump) { -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP if ((err = snd_seq_ump_event_output(state->event.hndl, &ump_ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 0546459db..274311c70 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -12,8 +12,10 @@ extern "C" { #include #include +#include "config.h" + #include -#ifdef ALSA_UMP +#ifdef HAVE_ALSA_UMP #include #endif From a503244c100e82314bc1b0e2ed0e4c339395281b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 9 Mar 2025 21:24:47 +0200 Subject: [PATCH 0014/1014] spa: avoid casting between snd_seq_event_t & snd_seq_ump_event_t Although the two structs have same initial sequence, it's not really correct to cast between their pointers. Alsa-lib also does this only internally, but not in API. --- spa/plugins/alsa/alsa-seq.c | 209 +++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 87 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index fcf6dd09a..3e6f5c29f 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -213,43 +213,74 @@ static void debug_event(struct seq_state *state, snd_seq_event_t *ev) ev->queue); } +#ifdef HAVE_ALSA_UMP +static void debug_ump_event(struct seq_state *state, snd_seq_ump_event_t *ev) +{ + if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) + return; + + spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags); + switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { + case SND_SEQ_TIME_STAMP_TICK: + spa_log_trace(state->log, " time: %d ticks", ev->time.tick); + break; + case SND_SEQ_TIME_STAMP_REAL: + spa_log_trace(state->log, " time = %d.%09d", + (int)ev->time.time.tv_sec, + (int)ev->time.time.tv_nsec); + break; + } + spa_log_trace(state->log, " source:%d.%d dest:%d.%d queue:%d", + ev->source.client, + ev->source.port, + ev->dest.client, + ev->dest.port, + ev->queue); +} +#endif + static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; - snd_seq_event_t *ev; + const bool ump = state->ump; int res; while (1) { const snd_seq_addr_t *addr; + snd_seq_event_type_t type; - if (state->ump) { + if (ump) { #ifdef HAVE_ALSA_UMP - snd_seq_ump_event_t *ump_ev; + snd_seq_ump_event_t *ev; - res = snd_seq_ump_event_input(state->sys.hndl, &ump_ev); + res = snd_seq_ump_event_input(state->sys.hndl, &ev); if (res <= 0) break; - /* Structures are layout-compatible */ - ev = (snd_seq_event_t *)ump_ev; - addr = &ump_ev->data.addr; + debug_ump_event(state, ev); + + addr = &ev->data.addr; + type = ev->type; #else spa_assert_not_reached(); #endif } else { + snd_seq_event_t *ev; + res = snd_seq_event_input(state->sys.hndl, &ev); if (res <= 0) break; + debug_event(state, ev); + addr = &ev->data.addr; + type = ev->type; } if (addr->client == state->event.addr.client) continue; - debug_event(state, ev); - - switch (ev->type) { + switch (type) { case SND_SEQ_EVENT_CLIENT_START: case SND_SEQ_EVENT_CLIENT_CHANGE: spa_log_info(state->log, "client add/change %d", addr->client); @@ -283,7 +314,7 @@ static void alsa_seq_on_sys(struct spa_source *source) break; default: spa_log_info(state->log, "unhandled event %d: %d:%d", - ev->type, addr->client, addr->port); + type, addr->client, addr->port); break; } @@ -568,8 +599,8 @@ static int process_recycle(struct seq_state *state) static int process_read(struct seq_state *state) { - snd_seq_event_t *ev; struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; + const bool ump = state->ump; uint32_t i; uint32_t *data; uint8_t midi1_data[MAX_EVENT_SIZE]; @@ -583,34 +614,43 @@ static int process_read(struct seq_state *state) struct seq_port *port; uint64_t ev_time, diff; uint32_t offset; -#ifdef HAVE_ALSA_UMP - snd_seq_ump_event_t *ump_ev; -#endif + void *event; uint8_t *midi1_ptr; size_t midi1_size; uint64_t ump_state = 0; + snd_seq_event_type_t SPA_UNUSED type; - if (state->ump) { + if (ump) { #ifdef HAVE_ALSA_UMP - res = snd_seq_ump_event_input(state->event.hndl, &ump_ev); + snd_seq_ump_event_t *ev; + + res = snd_seq_ump_event_input(state->event.hndl, &ev); if (res <= 0) break; - /* Structures are layout-compatible */ - ev = (snd_seq_event_t *)ump_ev; - addr = &ump_ev->source; + debug_ump_event(state, ev); + + event = ev; + addr = &ev->source; + ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); + type = ev->type; #else spa_assert_not_reached(); #endif } else { + snd_seq_event_t *ev; + res = snd_seq_event_input(state->event.hndl, &ev); if (res <= 0) break; - addr = &ev->source; - } + debug_event(state, ev); - debug_event(state, ev); + event = ev; + addr = &ev->source; + ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); + type = ev->type; + } if ((port = find_port(state, stream, addr)) == NULL) { spa_log_debug(state->log, "unknown port %d.%d", @@ -626,27 +666,8 @@ static int process_read(struct seq_state *state) continue; } - if (state->ump) { -#ifdef HAVE_ALSA_UMP - data = (uint32_t*)&ump_ev->ump[0]; - size = spa_ump_message_size(snd_ump_msg_hdr_type(ump_ev->ump[0])) * 4; -#else - spa_assert_not_reached(); -#endif - } else { - snd_midi_event_reset_decode(stream->codec); - if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) { - spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); - continue; - } - - midi1_ptr = midi1_data; - midi1_size = size; - } - /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ - ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); if (state->queue_time > ev_time) diff = state->queue_time - ev_time; else @@ -659,8 +680,30 @@ static int process_read(struct seq_state *state) else offset = 0; + if (ump) { +#ifdef HAVE_ALSA_UMP + snd_seq_ump_event_t *ev = event; + + data = (uint32_t*)&ev->ump[0]; + size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; +#else + spa_assert_not_reached(); +#endif + } else { + snd_seq_event_t *ev = event; + + snd_midi_event_reset_decode(stream->codec); + if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) { + spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); + continue; + } + + midi1_ptr = midi1_data; + midi1_size = size; + } + do { - if (!state->ump) { + if (!ump) { data = ump_data; size = spa_ump_from_midi(&midi1_ptr, &midi1_size, ump_data, sizeof(ump_data), 0, &ump_state); @@ -669,7 +712,7 @@ static int process_read(struct seq_state *state) } spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", - ev->type, ev_time, offset, size, addr->client, addr->port); + type, ev_time, offset, size, addr->client, addr->port); spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&port->builder, data, size); @@ -681,7 +724,7 @@ static int process_read(struct seq_state *state) MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) goto done; - } while (!state->ump); + } while (!ump); } done: @@ -746,6 +789,7 @@ done: static int process_write(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_INPUT]; + const bool ump = state->ump; uint32_t i; int err, res = 0; @@ -781,11 +825,6 @@ static int process_write(struct seq_state *state) } SPA_POD_SEQUENCE_FOREACH(pod, c) { - snd_seq_event_t *ev; - snd_seq_event_t midi1_ev; -#ifdef HAVE_ALSA_UMP - snd_seq_ump_event_t ump_ev; -#endif size_t body_size; uint8_t *body; @@ -795,48 +834,25 @@ static int process_write(struct seq_state *state) body = SPA_POD_BODY(&c->value); body_size = SPA_POD_BODY_SIZE(&c->value); - if (state->ump) { -#ifdef HAVE_ALSA_UMP - spa_zero(ump_ev); - memcpy(ump_ev.ump, body, SPA_MIN(sizeof(ump_ev.ump), (size_t)body_size)); - ev = (snd_seq_event_t *)&ump_ev; -#else - spa_assert_not_reached(); -#endif - } else { - uint8_t data[MAX_EVENT_SIZE]; - int size; - - if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) - continue; - - snd_seq_ev_clear(&midi1_ev); - - snd_midi_event_reset_encode(stream->codec); - if ((size = snd_midi_event_encode(stream->codec, data, size, &midi1_ev)) <= 0) { - spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); - continue; - } - - ev = &midi1_ev; - body_size = size; - } - - snd_seq_ev_set_source(ev, state->event.addr.port); - snd_seq_ev_set_dest(ev, port->addr.client, port->addr.port); - out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); - out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; - snd_seq_ev_schedule_real(ev, state->event.queue_id, 0, &out_rt); - spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d", - ev->type, out_time, c->offset, body_size, port->addr.client, port->addr.port); + spa_log_trace_fp(state->log, "event time:%"PRIu64" offset:%d size:%zd port:%d.%d", + out_time, c->offset, body_size, port->addr.client, port->addr.port); - if (state->ump) { + if (ump) { #ifdef HAVE_ALSA_UMP - if ((err = snd_seq_ump_event_output(state->event.hndl, &ump_ev)) < 0) { + snd_seq_ump_event_t ev; + + spa_zero(ev); + memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); + + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } @@ -844,7 +860,26 @@ static int process_write(struct seq_state *state) spa_assert_not_reached(); #endif } else { - if ((err = snd_seq_event_output(state->event.hndl, ev)) < 0) { + snd_seq_event_t ev; + uint8_t data[MAX_EVENT_SIZE]; + int size; + + if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) + continue; + + snd_seq_ev_clear(&ev); + + snd_midi_event_reset_encode(stream->codec); + if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) <= 0) { + spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); + continue; + } + + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } From d43fb09ea1177df1dced04b6bafdca96dd73bf30 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 11:50:48 +0100 Subject: [PATCH 0015/1014] audioconvert: configure resample channels correctly Depending on the direction of the conversion, we run the resampler before or after the channelmix. This means we need to use the channel count before or after the channelmixer instead of always using the channels after channelmixing. Fixes #4595 --- spa/plugins/audioconvert/audioconvert.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 47859e654..9726842cd 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1971,13 +1971,19 @@ static int setup_resample(struct impl *this) struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; int res; + uint32_t channels; + + if (this->direction == SPA_DIRECTION_INPUT) + channels = in->format.info.raw.channels; + else + channels = out->format.info.raw.channels; spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), - out->format.info.raw.channels, + channels, in->format.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), - out->format.info.raw.channels, + channels, out->format.info.raw.rate); if (this->props.resample_disabled && !this->resample_peaks && @@ -1987,7 +1993,7 @@ static int setup_resample(struct impl *this) if (this->resample.free) resample_free(&this->resample); - this->resample.channels = out->format.info.raw.channels; + this->resample.channels = channels; this->resample.i_rate = in->format.info.raw.rate; this->resample.o_rate = out->format.info.raw.rate; this->resample.log = this->log; From badd8691bd3c216f8151868530e3f2da46f6f9be Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 11:52:52 +0100 Subject: [PATCH 0016/1014] audioconvert: remove some unused fields --- spa/plugins/audioconvert/audioconvert.c | 31 +++++-------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 9726842cd..a244294e6 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -210,7 +210,6 @@ struct stage_context { uint32_t src_idx; uint32_t dst_idx; uint32_t final_idx; - uint32_t n_datas; struct port *ctrlport; }; @@ -219,8 +218,6 @@ struct stage { bool passthrough; uint32_t in_idx; uint32_t out_idx; - uint32_t n_in; - uint32_t n_out; void *data; void (*run) (struct stage *stage, struct stage_context *c); }; @@ -3223,8 +3220,6 @@ static void add_wav_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->src_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_wav_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3236,7 +3231,7 @@ static void run_dst_remap_stage(struct stage *s, struct stage_context *c) struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; uint32_t i; - for (i = 0; i < s->n_in; i++) { + for (i = 0; i < dir->conv.n_channels; i++) { c->datas[s->out_idx][i] = c->datas[s->in_idx][dir->remap[i]]; spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); } @@ -3248,8 +3243,6 @@ static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->dst_idx; s->out_idx = CTX_DATA_REMAP_DST; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_dst_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3275,8 +3268,6 @@ static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = CTX_DATA_REMAP_SRC; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_src_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3310,8 +3301,6 @@ static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_src_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3340,8 +3329,6 @@ static void add_resample_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_resample_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3389,8 +3376,6 @@ static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = fg; s->run = run_filter_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3405,8 +3390,6 @@ static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_channelmix_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3440,8 +3423,6 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->final_idx; - s->n_in = ctx->n_datas; - s->n_out = ctx->n_datas; s->data = NULL; s->run = run_dst_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); @@ -3834,14 +3815,14 @@ static int impl_node_process(void *object) ctx.in_samples = n_samples; ctx.n_samples = n_samples; ctx.n_out = n_out; - ctx.src_idx = CTX_DATA_SRC; - ctx.dst_idx = CTX_DATA_DST; - ctx.final_idx = CTX_DATA_DST; - ctx.n_datas = dir->conv.n_channels; ctx.ctrlport = ctrlport; - if (this->recalc) + if (SPA_UNLIKELY(this->recalc)) { + ctx.src_idx = CTX_DATA_SRC; + ctx.dst_idx = CTX_DATA_DST; + ctx.final_idx = CTX_DATA_DST; recalc_stages(this, &ctx); + } for (i = 0; i < this->n_stages; i++) { struct stage *s = &this->stages[i]; From ff08c28b62a876a1d698ce355defe7748687bfaf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 12:32:28 +0100 Subject: [PATCH 0017/1014] alsa-seq: avoid uninitialized warning --- spa/plugins/alsa/alsa-seq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 3e6f5c29f..42b59cc00 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -616,7 +616,7 @@ static int process_read(struct seq_state *state) uint32_t offset; void *event; uint8_t *midi1_ptr; - size_t midi1_size; + size_t midi1_size = 0; uint64_t ump_state = 0; snd_seq_event_type_t SPA_UNUSED type; From bf3c7aa6e115db08687627891f8111adca702e0d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 13:29:38 +0100 Subject: [PATCH 0018/1014] alsa-seq: fix UMP output We need to set the UMP flag, which is done with snd_seq_ev_set_ump_data() to make it output the midi data. --- spa/plugins/alsa/alsa-seq.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 42b59cc00..4f64ee5ba 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -845,9 +845,8 @@ static int process_write(struct seq_state *state) #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t ev; - spa_zero(ev); - memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); - + snd_seq_ump_ev_clear(&ev); + snd_seq_ev_set_ump_data(&ev, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); snd_seq_ev_set_source(&ev, state->event.addr.port); snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); From b997ff8ac18f06deb9780b416bc8f196c0396ecc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 15:14:03 +0100 Subject: [PATCH 0019/1014] devolpment is 1.5.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7afffda19..9950d5f3f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.4.0', + version : '1.5.0', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', From 8e8306cad87cae61677ab63ebd79f117ffeb6d9a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 16:26:06 +0100 Subject: [PATCH 0020/1014] doc: link to the protocol message documentation --- doc/dox/internals/design.dox | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/dox/internals/design.dox b/doc/dox/internals/design.dox index 147336083..9a186954d 100644 --- a/doc/dox/internals/design.dox +++ b/doc/dox/internals/design.dox @@ -44,6 +44,8 @@ The native protocol and object model is similar to serialization/deserialization of messages. This is because the data structures in the messages are more complicated and not easily expressible in XML. See \ref page_module_protocol_native for details. +See also \ref page_native_protocol for the documentation of the protocol +messages. # Extensibility From 6598938f1241081d886db8fdb9f2abe60e120d7e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 16:31:08 +0100 Subject: [PATCH 0021/1014] doc: update MIDI doc --- doc/dox/internals/midi.dox | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/dox/internals/midi.dox b/doc/dox/internals/midi.dox index 12283004d..5a266b2b2 100644 --- a/doc/dox/internals/midi.dox +++ b/doc/dox/internals/midi.dox @@ -92,6 +92,9 @@ The media session will check the permissions on `/dev/snd/seq` before attempting to create this node. It will also use inotify to wait until the sequencer device node is accessible. +Currently, the session manager does not try to link control messages +automatically. + ## JACK JACK assumes all `"application/control"` ports are MIDI ports. @@ -102,11 +105,12 @@ filtering out the \ref SPA_CONTROL_Midi, \ref SPA_CONTROL_OSC and converted to control messages in a similar way. Normally, all MIDI and UMP messages are converted to MIDI1 jack events unless -the JACK port was created with an explcit "32 bits raw UMP" format, in which -case the raw UMP is passed to the JACK application directly. For output ports, +the JACK port was created with an explcit "32 bits raw UMP" format or with +the JackPortIsMIDI2 flag, in which case the raw UMP is passed to the JACK +application directly. For output ports, the JACK events are assumed to be MIDI1 and converted to UMP unless the port -has the "32 bit raw UMP" format, in which case the UMP messages are simply -passed on. +has the "32 bit raw UMP" formati or the JackPortIsMIDI2 flag, in which case +the UMP messages are simply passed on. There is a 1 to 1 mapping between the JACK events and control messages so there is no information loss or need for complicated From 48a32e4ced5504d2832b425048b353a441dc9e26 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 11 Mar 2025 16:34:28 +0100 Subject: [PATCH 0022/1014] loop: remove return from function returning void Fixes #4603 --- src/pipewire/loop.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index 9de1cbf3f..8bb762170 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -141,7 +141,7 @@ pw_loop_add_signal(struct pw_loop *object, int signal_number, PW_API_LOOP_IMPL void pw_loop_destroy_source(struct pw_loop *object, struct spa_source *source) { - return spa_loop_utils_destroy_source(object->utils, source); + spa_loop_utils_destroy_source(object->utils, source); } /** From 1af1fc846c5d21f46c648d8a02cc1390ba95bc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 6 Mar 2025 08:29:06 +0100 Subject: [PATCH 0023/1014] bluez5: backend-native: Add volume support to HFP HF The volume synchronization could be done even if there's no audio link and so no transport opened. This patch allows to send the Speaker (AT+VGS) and Microphone (AT+VGM) commands at the end of the SLC. And to exchange volume updates using the telephony DBus interface, even without a transport. --- spa/plugins/bluez5/backend-native.c | 159 +++++++++++++++++++++++----- spa/plugins/bluez5/telephony.c | 128 +++++++++++++++++++++- spa/plugins/bluez5/telephony.h | 5 + 3 files changed, 261 insertions(+), 31 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 8d5354d31..906e5268d 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -48,6 +48,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #define PROP_KEY_ROLES "bluez5.roles" #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" #define PROP_KEY_HFP_DISABLE_NREC "bluez5.hfp-hf.disable-nrec" +#define PROP_KEY_HFP_DEFAULT_MIC_VOL "bluez5.hfp-hf.default-mic-volume" +#define PROP_KEY_HFP_DEFAULT_SPEAKER_VOL "bluez5.hfp-hf.default-speaker-volume" #define HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC 5000 #define HFP_CODEC_SWITCH_TIMEOUT_MSEC 20000 @@ -100,6 +102,8 @@ struct impl { #define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; bool hfp_disable_nrec; + int hfp_default_mic_volume; + int hfp_default_speaker_volume; struct spa_source sco; @@ -460,7 +464,7 @@ static void rfcomm_send_error(const struct rfcomm *rfcomm, enum cmee_error error rfcomm_send_reply(rfcomm, "ERROR"); } -static bool rfcomm_volume_enabled(struct rfcomm *rfcomm) +static bool rfcomm_hw_volume_enabled(struct rfcomm *rfcomm) { return rfcomm->device != NULL && (rfcomm->device->hw_volume_profiles & rfcomm->profile); @@ -470,9 +474,6 @@ static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_vol { struct spa_bt_transport_volume *t_volume; - if (!rfcomm_volume_enabled(rfcomm)) - return; - if ((id == SPA_BT_VOLUME_ID_RX || id == SPA_BT_VOLUME_ID_TX) && hw_volume >= 0) { rfcomm->volumes[id].active = true; rfcomm->volumes[id].hw_volume = hw_volume; @@ -480,17 +481,24 @@ static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_vol spa_log_debug(rfcomm->backend->log, "volume changed %d", hw_volume); - if (rfcomm->transport == NULL || !rfcomm->has_volume) - return; + if (rfcomm_hw_volume_enabled(rfcomm)) { + if (rfcomm->transport == NULL || !rfcomm->has_volume) + return; - for (int i = 0; i < SPA_BT_VOLUME_ID_TERM ; ++i) { - t_volume = &rfcomm->transport->volumes[i]; - t_volume->active = rfcomm->volumes[i].active; - t_volume->volume = (float) - spa_bt_volume_hw_to_linear(rfcomm->volumes[i].hw_volume, t_volume->hw_volume_max); + for (int i = 0; i < SPA_BT_VOLUME_ID_TERM ; ++i) { + t_volume = &rfcomm->transport->volumes[i]; + t_volume->active = rfcomm->volumes[i].active; + t_volume->volume = (float) + spa_bt_volume_hw_to_linear(rfcomm->volumes[i].hw_volume, t_volume->hw_volume_max); + } + + spa_bt_transport_emit_volume_changed(rfcomm->transport); } - spa_bt_transport_emit_volume_changed(rfcomm->transport); + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->volume[id] = hw_volume; + telephony_ag_notify_updated_props(rfcomm->telephony_ag); + } } #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE @@ -534,18 +542,21 @@ static bool rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) { struct spa_bt_transport_volume *t_volume; const char *format; - int hw_volume; + int hw_volume = rfcomm->volumes[id].hw_volume; - if (!rfcomm_volume_enabled(rfcomm)) - return false; + if (rfcomm_hw_volume_enabled(rfcomm)) { + t_volume = rfcomm->transport ? &rfcomm->transport->volumes[id] : NULL; - t_volume = rfcomm->transport ? &rfcomm->transport->volumes[id] : NULL; + if (t_volume && t_volume->active) { + hw_volume = spa_bt_volume_linear_to_hw(t_volume->volume, t_volume->hw_volume_max); + rfcomm->volumes[id].hw_volume = hw_volume; + } + } - if (!(t_volume && t_volume->active)) - return false; - - hw_volume = spa_bt_volume_linear_to_hw(t_volume->volume, t_volume->hw_volume_max); - rfcomm->volumes[id].hw_volume = hw_volume; + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->volume[id] = hw_volume; + telephony_ag_notify_updated_props(rfcomm->telephony_ag); + } if (id == SPA_BT_VOLUME_ID_TX) format = "AT+VGM"; @@ -1898,6 +1909,70 @@ static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *e *err = BT_TELEPHONY_ERROR_NONE; } +static void hfp_hf_set_speaker_volume(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_transport_volume *t_volume; + char reply[20]; + bool res; + + rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = volume; + if (rfcomm_hw_volume_enabled(rfcomm)) { + t_volume = rfcomm->transport ? &rfcomm->transport->volumes[SPA_BT_VOLUME_ID_RX] : NULL; + + if (t_volume && t_volume->active) { + t_volume->volume = (float) spa_bt_volume_hw_to_linear(volume, t_volume->hw_volume_max); + spa_bt_transport_emit_volume_changed(rfcomm->transport); + } + } + + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to send AT+VGS"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_set_microphone_volume(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_transport_volume *t_volume; + char reply[20]; + bool res; + + rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = volume; + if (rfcomm_hw_volume_enabled(rfcomm)) { + t_volume = rfcomm->transport ? &rfcomm->transport->volumes[SPA_BT_VOLUME_ID_TX] : NULL; + + if (t_volume && t_volume->active) { + t_volume->volume = (float) spa_bt_volume_hw_to_linear(volume, t_volume->hw_volume_max); + spa_bt_transport_emit_volume_changed(rfcomm->transport); + } + } + + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to send AT+VGM"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS, .dial = hfp_hf_dial, @@ -1909,6 +1984,8 @@ static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { .create_multiparty = hfp_hf_create_multiparty, .send_tones = hfp_hf_send_tones, .transport_activate = hfp_hf_transport_activate, + .set_speaker_volume = hfp_hf_set_speaker_volume, + .set_microphone_volume = hfp_hf_set_microphone_volume, }; static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) @@ -2342,6 +2419,8 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); rfcomm->telephony_ag->address = strdup(rfcomm->device->address); + rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_RX] = rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = backend->hfp_default_speaker_volume; + rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_TX] = rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = backend->hfp_default_mic_volume; telephony_ag_set_callbacks(rfcomm->telephony_ag, &telephony_ag_callbacks, rfcomm); if (rfcomm->transport) { @@ -2979,7 +3058,7 @@ static int rfcomm_ag_set_volume(struct spa_bt_transport *t, int id) const char *format; int value; - if (!rfcomm_volume_enabled(rfcomm) + if (!rfcomm_hw_volume_enabled(rfcomm) || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; @@ -3013,7 +3092,7 @@ static int sco_set_volume_cb(void *data, int id, float volume) struct rfcomm *rfcomm = td->rfcomm; int value; - if (!rfcomm_volume_enabled(rfcomm) + if (!rfcomm_hw_volume_enabled(rfcomm) || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; @@ -3350,7 +3429,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) goto fail_need_memory; - rfcomm->has_volume = rfcomm_volume_enabled(rfcomm); + rfcomm->has_volume = rfcomm_hw_volume_enabled(rfcomm); if (profile == SPA_BT_PROFILE_HSP_AG) { rfcomm->hs_state = hsp_hs_init1; @@ -3383,10 +3462,8 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->codec_negotiation_supported = false; } - if (rfcomm_volume_enabled(rfcomm)) { - rfcomm->has_volume = true; - hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; - } + rfcomm->has_volume = true; + hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; /* send command to AG with the features supported by Hands-Free */ rfcomm_send_cmd(rfcomm, "AT+BRSF=%u", hf_features); @@ -3394,7 +3471,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->hf_state = hfp_hf_brsf; } - if (rfcomm_volume_enabled(rfcomm) && (profile == SPA_BT_PROFILE_HFP_HF || profile == SPA_BT_PROFILE_HSP_HS)) { + if (rfcomm_hw_volume_enabled(rfcomm) && (profile == SPA_BT_PROFILE_HFP_HF || profile == SPA_BT_PROFILE_HSP_HS)) { uint32_t device_features; if (spa_bt_quirks_get_features(backend->quirks, d->adapter, d, &device_features) == 0) { rfcomm->broken_mic_hw_volume = !(device_features & SPA_BT_FEATURE_HW_VOLUME_MIC); @@ -3944,6 +4021,29 @@ static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict * backend->hfp_disable_nrec = false; } +static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info) +{ + const char *str; + int vol = -1; + + if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DEFAULT_MIC_VOL)) != NULL) + spa_atoi32(str, &vol, 10); + + if (vol >= 0 && vol <= 15) + backend->hfp_default_mic_volume = vol; + else + backend->hfp_default_mic_volume = SPA_BT_VOLUME_HS_MAX; + + vol = -1; + if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DEFAULT_SPEAKER_VOL)) != NULL) + spa_atoi32(str, &vol, 10); + + if (vol >= 0 && vol <= 15) + backend->hfp_default_speaker_volume = vol; + else + backend->hfp_default_speaker_volume = SPA_BT_VOLUME_HS_MAX; +} + static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_native_free, @@ -4002,6 +4102,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, goto fail; parse_hfp_disable_nrec(backend, info); + parse_hfp_default_volumes(backend, info); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (!dbus_connection_register_object_path(backend->conn, diff --git a/spa/plugins/bluez5/telephony.c b/spa/plugins/bluez5/telephony.c index c2f9d6741..f5887053d 100644 --- a/spa/plugins/bluez5/telephony.c +++ b/spa/plugins/bluez5/telephony.c @@ -117,6 +117,8 @@ " " \ " " \ " " \ + " " \ + " " \ " " \ " " \ " " \ @@ -205,6 +207,7 @@ struct agimpl { struct callimpl *dial_return; struct { + int volume[SPA_BT_VOLUME_ID_TERM]; struct spa_bt_telephony_ag_transport transport; } prev; }; @@ -235,6 +238,8 @@ struct callimpl { #define ag_emit_create_multiparty(s,e,cme) ag_emit(s,create_multiparty,0,e,cme) #define ag_emit_send_tones(s,t,e,cme) ag_emit(s,send_tones,0,t,e,cme) #define ag_emit_transport_activate(s,e,cme) ag_emit(s,transport_activate,0,e,cme) +#define ag_emit_set_speaker_volume(s,v,e,cme) ag_emit(s,set_speaker_volume,0,v,e,cme) +#define ag_emit_set_microphone_volume(s,v,e,cme) ag_emit(s,set_microphone_volume,0,v,e,cme) #define call_emit(c,m,v,...) spa_callbacks_call(&c->callbacks, struct spa_bt_telephony_call_callbacks, m, v, ##__VA_ARGS__) #define call_emit_answer(s,e,cme) call_emit(s,answer,0,e,cme) @@ -516,6 +521,14 @@ void telephony_free(struct spa_bt_telephony *telephony) free(impl); } +static void telephony_ag_commit_properties(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { + agimpl->prev.volume[i] = ag->volume[i]; + } +} + static void telephony_ag_transport_commit_properties(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); @@ -538,6 +551,7 @@ static const char * const * transport_state_to_string(int state) static bool dbus_iter_append_ag_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) { + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); DBusMessageIter dict, entry, variant; bool changed = false; @@ -558,6 +572,32 @@ dbus_iter_append_ag_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *a changed = true; } + if (all || ag->volume[SPA_BT_VOLUME_ID_RX] != agimpl->prev.volume[SPA_BT_VOLUME_ID_RX]) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "SpeakerVolume"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->volume[SPA_BT_VOLUME_ID_RX]); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + if (all || ag->volume[SPA_BT_VOLUME_ID_TX] != agimpl->prev.volume[SPA_BT_VOLUME_ID_TX]) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "MicrophoneVolume"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->volume[SPA_BT_VOLUME_ID_TX]); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + dbus_message_iter_close_container(i, &dict); return changed; } @@ -709,6 +749,28 @@ static DBusMessage *ag_properties_get(struct agimpl *agimpl, DBusMessage *m) &agimpl->this.address); dbus_message_iter_close_container(&i, &v); return r; + } else if (spa_streq(name, "SpeakerVolume")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, + &agimpl->this.volume[SPA_BT_VOLUME_ID_RX]); + dbus_message_iter_close_container(&i, &v); + return r; + } else if (spa_streq(name, "MicrophoneVolume")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, + &agimpl->this.volume[SPA_BT_VOLUME_ID_TX]); + dbus_message_iter_close_container(&i, &v); + return r; } } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { if (spa_streq(name, "Codec")) { @@ -796,7 +858,38 @@ static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) DBUS_TYPE_INVALID)) return NULL; - if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { + if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (spa_streq(name, "SpeakerVolume")) { + dbus_message_iter_init(m, &i); + dbus_message_iter_next(&i); /* skip iface */ + dbus_message_iter_next(&i); /* skip name */ + dbus_message_iter_recurse(&i, &variant); /* value */ + dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_RX]); + + if (ag_emit_set_speaker_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_RX], &err, &cme_error) && + err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); + } else if (spa_streq(name, "MicrophoneVolume")) { + dbus_message_iter_init(m, &i); + dbus_message_iter_next(&i); /* skip iface */ + dbus_message_iter_next(&i); /* skip name */ + dbus_message_iter_recurse(&i, &variant); /* value */ + dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_TX]); + + if (ag_emit_set_microphone_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_TX], &err, &cme_error) && + err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); + } + } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { if (spa_streq(name, "RejectSCO")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ @@ -1219,7 +1312,38 @@ void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) agimpl->path = NULL; } -/* send message to notify about property changes */ +/* send message to notify about volume property changes */ +void telephony_ag_notify_updated_props(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + spa_autoptr(DBusMessage) msg = NULL; + const char *interface = PW_TELEPHONY_AG_IFACE; + DBusMessageIter i, a; + + msg = dbus_message_new_signal(agimpl->path, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + + dbus_message_iter_init_append(msg, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); + + if (!dbus_iter_append_ag_properties(&i, ag, false)) + return; + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &a); + dbus_message_iter_close_container(&i, &a); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertiesChanged failed"); + } + + telephony_ag_commit_properties(ag); +} + +/* send message to notify about transport property changes */ void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); diff --git a/spa/plugins/bluez5/telephony.h b/spa/plugins/bluez5/telephony.h index 8ba85ec47..13961b7db 100644 --- a/spa/plugins/bluez5/telephony.h +++ b/spa/plugins/bluez5/telephony.h @@ -45,6 +45,7 @@ struct spa_bt_telephony_ag { /* D-Bus properties */ char *address; + int volume[SPA_BT_VOLUME_ID_TERM]; struct spa_bt_telephony_ag_transport transport; }; @@ -76,6 +77,9 @@ struct spa_bt_telephony_ag_callbacks { void (*send_tones)(void *data, const char *tones, enum spa_bt_telephony_error *err, uint8_t *cme_error); void (*transport_activate)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + + void (*set_speaker_volume)(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*set_microphone_volume)(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error); }; struct spa_bt_telephony_call_callbacks { @@ -103,6 +107,7 @@ void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, const struct spa_bt_telephony_ag_callbacks *cbs, void *data); +void telephony_ag_notify_updated_props(struct spa_bt_telephony_ag *ag); void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag); /* register/unregister AudioGateway object on the bus */ From 60981494d6b270af4b40db166d7b5749eaacc572 Mon Sep 17 00:00:00 2001 From: sunyuechi Date: Thu, 13 Mar 2025 01:01:19 +0800 Subject: [PATCH 0024/1014] Add x86 CPU check in meson.build for clang build fix have_avx = cc.has_argument(avx_args) gcc generates an error when AVX is unsupported, whereas clang only produces a warning. Thus, using Clang on the RISC-V platform incorrectly enables AVX file compilation, leading to build failures. --- meson.build | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/meson.build b/meson.build index 9950d5f3f..42775e2ea 100644 --- a/meson.build +++ b/meson.build @@ -127,21 +127,30 @@ if have_cpp add_project_arguments(cxx.get_supported_arguments(cxx_flags), language: 'cpp') endif -sse_args = '-msse' -sse2_args = '-msse2' -ssse3_args = '-mssse3' -sse41_args = '-msse4.1' -fma_args = '-mfma' -avx_args = '-mavx' -avx2_args = '-mavx2' +have_sse = false +have_sse2 = false +have_ssse3 = false +have_sse41 = false +have_fma = false +have_avx = false +have_avx2 = false +if host_machine.cpu_family() in ['x86', 'x86_64'] + sse_args = '-msse' + sse2_args = '-msse2' + ssse3_args = '-mssse3' + sse41_args = '-msse4.1' + fma_args = '-mfma' + avx_args = '-mavx' + avx2_args = '-mavx2' -have_sse = cc.has_argument(sse_args) -have_sse2 = cc.has_argument(sse2_args) -have_ssse3 = cc.has_argument(ssse3_args) -have_sse41 = cc.has_argument(sse41_args) -have_fma = cc.has_argument(fma_args) -have_avx = cc.has_argument(avx_args) -have_avx2 = cc.has_argument(avx2_args) + have_sse = cc.has_argument(sse_args) + have_sse2 = cc.has_argument(sse2_args) + have_ssse3 = cc.has_argument(ssse3_args) + have_sse41 = cc.has_argument(sse41_args) + have_fma = cc.has_argument(fma_args) + have_avx = cc.has_argument(avx_args) + have_avx2 = cc.has_argument(avx2_args) +endif have_neon = false if host_machine.cpu_family() == 'aarch64' From 6c9ada270b7714ad6df1b5b6f33234d66350ae44 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 12 Mar 2025 17:34:19 +0530 Subject: [PATCH 0025/1014] gst: Fix handling of video planar formats Tiled formats are not tested and supported yet. --- src/gst/gstpipewirepool.c | 10 +++++++++- src/gst/gstpipewiresink.c | 21 ++++++++++++++++++--- src/gst/gstpipewiresrc.c | 14 +++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 78869e163..f00612fc8 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -94,13 +94,21 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) } if (pool->add_metavideo) { - gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, + GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, + GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_INFO_FORMAT (&pool->video_info), GST_VIDEO_INFO_WIDTH (&pool->video_info), GST_VIDEO_INFO_HEIGHT (&pool->video_info), GST_VIDEO_INFO_N_PLANES (&pool->video_info), pool->video_info.offset, pool->video_info.stride); + + /* + * We need to set the video meta as pooled, else gst_buffer_pool_release_buffer + * will call reset_buffer and the default_reset_buffer implementation for + * GstBufferPool removes all metadata without the POOLED flag. + */ + GST_META_FLAG_SET (meta, GST_META_FLAG_POOLED); } data->pool = gst_object_ref (pool); diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index aa9d94b99..7f32f0fa8 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -314,6 +314,10 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX), 0); + /* MUST have n_datas == n_planes */ + spa_pod_builder_add (&b, + SPA_PARAM_BUFFERS_blocks, + SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info))); spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), @@ -630,6 +634,9 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) } } data->b->size = 0; + + spa_assert(b->n_datas == gst_buffer_n_memory(buffer)); + for (i = 0; i < b->n_datas; i++) { struct spa_data *d = &b->datas[i]; GstMemory *mem = gst_buffer_peek_memory (buffer, i); @@ -643,16 +650,24 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) GstVideoMeta *meta = gst_buffer_get_video_meta (buffer); if (meta) { if (meta->n_planes == b->n_datas) { + uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (&data->pool->video_info); + gboolean is_planar = n_planes > 1; gsize video_size = 0; - for (i = 0; i < meta->n_planes; i++) { + + for (i = 0; i < n_planes; i++) { struct spa_data *d = &b->datas[i]; - d->chunk->offset += meta->offset[i] - video_size; + d->chunk->stride = meta->stride[i]; + if (is_planar) + d->chunk->offset = meta->offset[i]; + else + d->chunk->offset += meta->offset[i] - video_size; video_size += d->chunk->size; } } else { - GST_ERROR_OBJECT (pwsink, "plane num not matching, meta:%u buffer:%u", meta->n_planes, b->n_datas); + GST_ERROR_OBJECT (pwsink, "plane num not matching, meta:%u buffer:%u", + meta->n_planes, b->n_datas); } } diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 1cd049418..c9a5d43cd 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -661,8 +661,12 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) } if (pwsrc->is_video) { - gsize video_size = 0; GstVideoInfo *info = &pwsrc->video_info; + uint32_t n_datas = b->buffer->n_datas; + uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (info); + gboolean is_planar = n_planes > 1; + gsize video_size = 0; + GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), @@ -671,15 +675,19 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) info->offset, info->stride); - for (i = 0; i < MIN (b->buffer->n_datas, GST_VIDEO_MAX_PLANES); i++) { + for (i = 0; i < MIN (n_datas, n_planes); i++) { struct spa_data *d = &b->buffer->datas[i]; - meta->offset[i] = video_size; + meta->offset[i] = is_planar ? d->chunk->offset : video_size; meta->stride[i] = d->chunk->stride; video_size += d->chunk->size; } } + if (b->buffer->n_datas != gst_buffer_n_memory(data->buf)) { + GST_ERROR_OBJECT(pwsrc, "n_datas != n_memory"); + } + for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; From 4338189f76b28fef53c56ab30f418e29a2a0c411 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 12 Mar 2025 18:40:14 +0200 Subject: [PATCH 0026/1014] spa: acp: allow also too few channels in SplitPCM configs GoXLR Mini has different numbers of channels actually available (21, 23, or 25) depending on its firmware/etc, but its UCM profile specifies always 23. The count can then be bigger or smaller than what is actually available. Fail a bit more gracefully in the case of too few channels: create all the split devices specified by the profile. The channels that aren't actually available in HW just won't get routed anywhere. ALSA upstream IIUC is saying that the channel counts should be fixed, so spew warnings that say the UCM profiles are wrong if they look wrong. --- spa/plugins/alsa/acp/alsa-ucm.c | 45 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index f64c573a4..41e0a1874 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -353,6 +353,15 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd const char *device_name; int i; uint32_t hw_channels; + const char *pcm_name; + const char *rule_name; + + if (spa_streq(prefix, "Playback")) + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); + else + pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); + if (!pcm_name) + pcm_name = ""; device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); if (!device_name) @@ -373,15 +382,20 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd break; if (idx >= hw_channels) - goto fail; + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d", + pcm_name, device_name, prefix, i, idx, prefix, hw_channels); value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); - if (!value) + if (!value) { + rule_name = "ChannelPos"; goto fail; + } map = snd_pcm_chmap_parse_string(value); - if (!map) + if (!map) { + rule_name = "ChannelPos value"; goto fail; + } if (map->channels == 1) { pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)", @@ -391,6 +405,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd free(map); } else { free(map); + rule_name = "channel map parsing"; goto fail; } } @@ -405,7 +420,7 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd return split; fail: - pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name); + pa_log_warn("Invalid SplitPCM ALSA UCM %s for device %s (%s)", rule_name, pcm_name, device_name); pa_xfree(split); return NULL; } @@ -2422,21 +2437,25 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, if (pcm) { if (m->split) { - if (try_map.channels < m->split->hw_channels) { - pa_alsa_close(&pcm); + const char *mode_name = mode == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"; - pa_logl((max_channels ? PA_LOG_WARN : PA_LOG_DEBUG), - "Too few channels in %s for ALSA UCM SplitPCM: avail %d < required %d", - m->device_strings[0], try_map.channels, m->split->hw_channels); + if (try_map.channels < m->split->hw_channels) { + pa_logl((max_channels ? PA_LOG_NOTICE : PA_LOG_DEBUG), + "Error in ALSA UCM profile for %s (%s): %sChannels=%d > avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); /* Retry with max channel count, in case ALSA rounded down */ - if (!max_channels) + if (!max_channels) { + pa_alsa_close(&pcm); return mapping_open_pcm(ucm, m, mode, true); + } - return NULL; + /* Just accept whatever we got... Some of the routings won't get connected + * anywhere */ + m->split->hw_channels = try_map.channels; } else if (try_map.channels > m->split->hw_channels) { - pa_log_debug("Update split PCM channel count for %s: %d -> %d", - m->device_strings[0], m->split->hw_channels, try_map.channels); + pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d", + m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); m->split->hw_channels = try_map.channels; } } else if (!exact_channels) { From bd3a8b594f686bc5ab644bb904cce99876ac1d95 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 13 Mar 2025 19:23:32 +0200 Subject: [PATCH 0027/1014] spa: acp: indicate ALSA UCM profile errors in UIs Add "[ALSA UCM error]" to the end of card description, to indicate something is wrong. --- spa/plugins/alsa/acp/acp.c | 23 +++++++++++++++++++++++ spa/plugins/alsa/acp/alsa-ucm.c | 6 +++++- spa/plugins/alsa/acp/alsa-ucm.h | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 7b49c8be6..63a32eafb 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -498,6 +498,7 @@ static void add_profiles(pa_card *impl) int n_profiles, n_ports, n_devices; uint32_t idx; const char *arr; + bool broken_ucm = false; n_devices = 0; pa_dynarray_init(&impl->out.devices, device_free); @@ -541,6 +542,9 @@ static void add_profiles(pa_card *impl) dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); + + if (m->split && m->split->broken) + broken_ucm = true; } } @@ -564,6 +568,9 @@ static void add_profiles(pa_card *impl) dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); + + if (m->split && m->split->broken) + broken_ucm = true; } } cp->n_devices = pa_dynarray_size(&ap->out.devices); @@ -571,6 +578,22 @@ static void add_profiles(pa_card *impl) pa_hashmap_put(impl->profiles, ap->name, cp); } + + /* Add a conspicuous notice if there are errors in the UCM profile */ + if (broken_ucm) { + const char *desc; + char *new_desc = NULL; + + desc = pa_proplist_gets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION); + if (!desc) + desc = ""; + new_desc = spa_aprintf(_("%s [ALSA UCM error]"), desc); + pa_log_notice("Errors in ALSA UCM profile for card %s", desc); + if (new_desc) + pa_proplist_sets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION, new_desc); + free(new_desc); + } + pa_dynarray_init(&impl->out.ports, NULL); n_ports = 0; PA_HASHMAP_FOREACH(dp, impl->ports, state) { diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 41e0a1874..73466f004 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -381,9 +381,11 @@ static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd if (pa_atou(value, &idx) < 0) break; - if (idx >= hw_channels) + if (idx >= hw_channels) { pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d", pcm_name, device_name, prefix, i, idx, prefix, hw_channels); + split->broken = true; + } value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); if (!value) { @@ -2453,10 +2455,12 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, /* Just accept whatever we got... Some of the routings won't get connected * anywhere */ m->split->hw_channels = try_map.channels; + m->split->broken = true; } else if (try_map.channels > m->split->hw_channels) { pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d", m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); m->split->hw_channels = try_map.channels; + m->split->broken = true; } } else if (!exact_channels) { m->channel_map = try_map; diff --git a/spa/plugins/alsa/acp/alsa-ucm.h b/spa/plugins/alsa/acp/alsa-ucm.h index 74dbe8219..8e4c1a7d1 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.h +++ b/spa/plugins/alsa/acp/alsa-ucm.h @@ -185,6 +185,7 @@ struct pa_alsa_ucm_split { int channels; int idx[PA_CHANNELS_MAX]; enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX]; + bool broken; }; struct pa_alsa_ucm_device { From 76619eaa1d5f8830d483c92f55f07737b71aaaea Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 10:03:44 +0100 Subject: [PATCH 0028/1014] audioconvert: fix the filter-graph samplerate The filter graph runs before or after the resampler depending on the direction of the conversion. --- spa/plugins/audioconvert/audioconvert.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index a244294e6..a18e08629 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1000,13 +1000,13 @@ static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) { int res; char rate_str[64]; - struct dir *in; + struct dir *dir; if (graph == NULL) return 0; - in = &this->dir[SPA_DIRECTION_INPUT]; - snprintf(rate_str, sizeof(rate_str), "%d", in->format.info.raw.rate); + dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; + snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); spa_filter_graph_deactivate(graph); res = spa_filter_graph_activate(graph, From f0e09ae36376c43f505b4fdb6b0e2ec94e9a71da Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 10:10:18 +0100 Subject: [PATCH 0029/1014] filter-graph: make the filter-graph ports dynamic When parsing the graph, parse the input and output port names into a separate string array. This was we can keep them around when setting up the graph. Instead of setting up the graph right after loading it, do the graph setup when we activate the graph. This makes it possible to pass the input channels to the filter-graph and let it create the right amount of plugins and ouput channels. When setting up the graphs in the audioconverter, pass the current number of channels as the input to the graph and keep track of the channels that each filter produces. This way we can also load a custom upmix or downmix graph, for example. --- spa/include/spa/filter-graph/filter-graph.h | 1 + spa/plugins/audioconvert/audioconvert.c | 36 ++++-- spa/plugins/filter-graph/filter-graph.c | 124 +++++++++++++++----- 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h index 05904c7f3..b9c5ea426 100644 --- a/spa/include/spa/filter-graph/filter-graph.h +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -46,6 +46,7 @@ struct spa_filter_graph_info { #define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) #define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) +#define SPA_FILTER_GRAPH_CHANGE_MASK_PORTS (1u<<2) uint64_t change_mask; uint64_t flags; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index a18e08629..6148858f0 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -996,10 +996,10 @@ struct spa_filter_graph_events graph_events = { .props_changed = graph_props_changed, }; -static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) +static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph, uint32_t channels) { int res; - char rate_str[64]; + char rate_str[64], in_ports[64]; struct dir *dir; if (graph == NULL) @@ -1007,11 +1007,15 @@ static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); + if (channels) + snprintf(in_ports, sizeof(in_ports), "%d", channels); spa_filter_graph_deactivate(graph); res = spa_filter_graph_activate(graph, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str))); + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), + SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); + return res; } @@ -1105,7 +1109,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) goto error; /* prepare new filter and swap it */ - res = setup_filter_graph(impl, iface); + res = setup_filter_graph(impl, iface, 0); if (res < 0) goto error; pending->graph = iface; @@ -1906,7 +1910,7 @@ static char *format_position(char *str, size_t len, uint32_t channels, uint32_t return str; } -static int setup_channelmix(struct impl *this) +static int setup_channelmix(struct impl *this, uint32_t channels) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -1915,7 +1919,7 @@ static int setup_channelmix(struct impl *this) char str[1024]; int res; - src_chan = in->format.info.raw.channels; + src_chan = channels; dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { @@ -1926,6 +1930,13 @@ static int setup_channelmix(struct impl *this) p = out->format.info.raw.position[i]; dst_mask |= 1ULL << (p < 64 ? p : 0); } + + /* if we needed a channel conversion but we already did one before this + * stage, assume we are now with the dst layout */ + if ((out->format.info.raw.channels != in->format.info.raw.channels) && + channels != in->format.info.raw.channels) + src_mask = dst_mask; + spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, in->format.info.raw.position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), @@ -2219,7 +2230,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration; + uint32_t i, rate, maxsize, maxports, duration, channels; struct port *p; int res; @@ -2266,16 +2277,19 @@ static int setup_convert(struct impl *this) if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) return -EINVAL; + channels = in->format.info.raw.channels; + if ((res = setup_in_convert(this)) < 0) return res; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; + for (i = 0; i < this->n_graph; i++) { + struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; if (!g->active) continue; - if ((res = setup_filter_graph(this, g->graph)) < 0) + if ((res = setup_filter_graph(this, g->graph, channels)) < 0) return res; + channels = g->n_outputs; } - if ((res = setup_channelmix(this)) < 0) + if ((res = setup_channelmix(this, channels)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index bc9ff154a..75e3a00f6 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -175,9 +175,16 @@ struct graph { uint32_t n_control; struct port **control_port; + uint32_t n_input_names; + char **input_names; + + uint32_t n_output_names; + char **output_names; + struct volume volume[2]; unsigned activated:1; + unsigned setup:1; }; struct impl { @@ -1423,6 +1430,8 @@ static int impl_deactivate(void *object) return 0; } +static int setup_graph(struct graph *graph); + static int impl_activate(void *object, const struct spa_dict *props) { struct impl *impl = object; @@ -1433,10 +1442,10 @@ static int impl_activate(void *object, const struct spa_dict *props) struct descriptor *desc; const struct spa_fga_descriptor *d; const struct spa_fga_plugin *p; - uint32_t i, j, max_samples = impl->quantum_limit; + uint32_t i, j, max_samples = impl->quantum_limit, n_ports; int res; float *sd, *dd, *data; - const char *rate; + const char *rate, *str; if (graph->activated) return 0; @@ -1446,6 +1455,31 @@ static int impl_activate(void *object, const struct spa_dict *props) rate = spa_dict_lookup(props, SPA_KEY_AUDIO_RATE); impl->rate = rate ? atoi(rate) : DEFAULT_RATE; + if ((str = spa_dict_lookup(props, "filter-graph.n_inputs")) != NULL) { + if (spa_atou32(str, &n_ports, 0) && + n_ports != impl->info.n_inputs) { + impl->info.n_inputs = n_ports; + impl->info.n_outputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + graph->setup = false; + } + } + if ((str = spa_dict_lookup(props, "filter-graph.n_outputs")) != NULL) { + if (spa_atou32(str, &n_ports, 0) && + n_ports != impl->info.n_outputs) { + impl->info.n_outputs = n_ports; + impl->info.n_inputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + graph->setup = false; + } + } + if (!graph->setup) { + if ((res = setup_graph(graph)) < 0) + return res; + graph->setup = true; + spa_filter_graph_emit_info(&impl->hooks, &impl->info); + } + /* first make instances */ spa_list_for_each(node, &graph->node_list, link) { node_cleanup(node); @@ -1570,7 +1604,19 @@ static struct node *find_next_node(struct graph *graph) return NULL; } -static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) +static void unsetup_graph(struct graph *graph) +{ + free(graph->input); + graph->input = NULL; + free(graph->output); + graph->output = NULL; + free(graph->hndl); + graph->hndl = NULL; + free(graph->control_port); + graph->control_port = NULL; + +} +static int setup_graph(struct graph *graph) { struct impl *impl = graph->impl; struct node *node, *first, *last; @@ -1578,33 +1624,35 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ struct link *link; struct graph_port *gp; struct graph_hndl *gh; - uint32_t i, j, n_nodes, n_input, n_output, n_control, n_hndl = 0; + uint32_t i, j, n, n_nodes, n_input, n_output, n_control, n_hndl = 0; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; - char v[256]; + char *pname; bool allow_unused; + unsetup_graph(graph); + first = spa_list_first(&graph->node_list, struct node, link); last = spa_list_last(&graph->node_list, struct node, link); /* calculate the number of inputs and outputs into the graph. - * If we have a list of inputs/outputs, just count them. Otherwise + * If we have a list of inputs/outputs, just use them. Otherwise * we count all input ports of the first node and all output * ports of the last node */ - if (inputs != NULL) - n_input = count_array(inputs); + if (graph->n_input_names != 0) + n_input = graph->n_input_names; else n_input = first->desc->n_input; - if (outputs != NULL) - n_output = count_array(outputs); + if (graph->n_output_names != 0) + n_output = graph->n_output_names; else n_output = last->desc->n_output; /* we allow unconnected ports when not explicitly given and the nodes support * NULL data */ - allow_unused = inputs == NULL && outputs == NULL && + allow_unused = graph->n_input_names == 0 && graph->n_output_names == 0 && SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); @@ -1676,7 +1724,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ /* now collect all input and output ports for all the handles. */ for (i = 0; i < n_hndl; i++) { - if (inputs == NULL) { + if (graph->n_input_names == 0) { desc = first->desc; d = desc->desc; for (j = 0; j < desc->n_input; j++) { @@ -1688,15 +1736,15 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ gp->port = desc->input[j]; } } else { - struct spa_json it = *inputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { - if (spa_streq(v, "null")) { + for (n = 0; n < graph->n_input_names; n++) { + pname = graph->input_names[n]; + if (spa_streq(pname, "null")) { gp = &graph->input[graph->n_input++]; gp->desc = NULL; spa_log_info(impl->log, "ignore input port %d", graph->n_input); - } else if ((port = find_port(first, v, SPA_FGA_PORT_INPUT)) == NULL) { + } else if ((port = find_port(first, pname, SPA_FGA_PORT_INPUT)) == NULL) { res = -ENOENT; - spa_log_error(impl->log, "input port %s not found", v); + spa_log_error(impl->log, "input port %s not found", pname); goto error; } else { bool disabled = false; @@ -1755,7 +1803,7 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ } } } - if (outputs == NULL) { + if (graph->n_output_names == 0) { desc = last->desc; d = desc->desc; for (j = 0; j < desc->n_output; j++) { @@ -1767,15 +1815,15 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_ gp->port = desc->output[j]; } } else { - struct spa_json it = *outputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { + for (n = 0; n < graph->n_output_names; n++) { + pname = graph->output_names[n]; gp = &graph->output[graph->n_output]; - if (spa_streq(v, "null")) { + if (spa_streq(pname, "null")) { gp->desc = NULL; spa_log_info(impl->log, "silence output port %d", graph->n_output); - } else if ((port = find_port(last, v, SPA_FGA_PORT_OUTPUT)) == NULL) { + } else if ((port = find_port(last, pname, SPA_FGA_PORT_OUTPUT)) == NULL) { res = -ENOENT; - spa_log_error(impl->log, "output port %s not found", v); + spa_log_error(impl->log, "output port %s not found", pname); goto error; } else { desc = port->node->desc; @@ -1980,21 +2028,41 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) return res; } } - return setup_graph(graph, pinputs, poutputs); + if (pinputs != NULL) { + graph->n_input_names = count_array(pinputs); + graph->input_names = calloc(graph->n_input_names, sizeof(char *)); + graph->n_input_names = 0; + while (spa_json_get_string(pinputs, key, sizeof(key)) > 0) + graph->input_names[graph->n_input_names++] = strdup(key); + } + if (poutputs != NULL) { + graph->n_output_names = count_array(poutputs); + graph->output_names = calloc(graph->n_output_names, sizeof(char *)); + graph->n_output_names = 0; + while (spa_json_get_string(poutputs, key, sizeof(key)) > 0) + graph->output_names[graph->n_output_names++] = strdup(key); + } + return 0; } static void graph_free(struct graph *graph) { struct link *link; struct node *node; + uint32_t i; + + unsetup_graph(graph); + spa_list_consume(link, &graph->link_list, link) link_free(link); spa_list_consume(node, &graph->node_list, link) node_free(node); - free(graph->input); - free(graph->output); - free(graph->hndl); - free(graph->control_port); + for (i = 0; i < graph->n_input_names; i++) + free(graph->input_names[i]); + free(graph->input_names); + for (i = 0; i < graph->n_output_names; i++) + free(graph->output_names[i]); + free(graph->output_names); } static const struct spa_filter_graph_methods impl_filter_graph = { From f1ed12fc8d9cc032051ef1c3eaf211f8bdedc3ef Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 13:25:45 +0100 Subject: [PATCH 0030/1014] audioconvert: PortConfig only needs channels and position --- spa/plugins/audioconvert/audioconvert.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 6148858f0..c691ad676 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1698,9 +1698,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; - if (info.info.raw.format == 0 || - info.info.raw.rate == 0 || - info.info.raw.channels == 0 || + if (info.info.raw.channels == 0 || info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) return -EINVAL; From 4e421e0012202911013f7e3a2ebbe04962baa0a0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 13:26:21 +0100 Subject: [PATCH 0031/1014] audioadapter: return error code from set_param --- spa/plugins/audioconvert/audioadapter.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 6e393bd0d..d03807e7c 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -2014,9 +2014,8 @@ static int do_auto_port_config(struct impl *this, const char *str) SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - impl_node_set_param(this, SPA_PARAM_PortConfig, 0, param); - return 0; + return impl_node_set_param(this, SPA_PARAM_PortConfig, 0, param); } static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) From 4ffa82b5902ab1b180465cab565d01d0d190e967 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 14 Mar 2025 13:28:48 +0100 Subject: [PATCH 0032/1014] jack: add another node name fallback Use the object-path as a fallback name for the node. --- pipewire-jack/src/pipewire-jack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 839a9fc24..40e6c1b8e 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -3746,6 +3746,8 @@ static void registry_event_global(void *data, uint32_t id, } if (str == NULL) str = node_name; + if (str == NULL) + str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH); if (str == NULL) str = "node"; From d329dac6ba7d32dacb13fd358b5360a59f7aac27 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Fri, 14 Mar 2025 16:07:28 +0530 Subject: [PATCH 0033/1014] gst: Do not use video only info for SPA_PARAM_BUFFERS_blocks We mistakenly used video only info for setting SPA_PARAM_BUFFERS_blocks, which would be completely incorrect for audio. Fixes 6c9ada270. --- src/gst/gstpipewiresink.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 7f32f0fa8..f0aa0c2c0 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -314,10 +314,12 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX), 0); - /* MUST have n_datas == n_planes */ - spa_pod_builder_add (&b, - SPA_PARAM_BUFFERS_blocks, - SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info))); + if (sink->is_video) { + /* MUST have n_datas == n_planes */ + spa_pod_builder_add (&b, + SPA_PARAM_BUFFERS_blocks, + SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info)), 0); + } spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), From 21e9fdf8add1b39ec99ef544df64b43584bee78c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Mar 2025 09:55:06 +0100 Subject: [PATCH 0034/1014] conf: add a jack-tunnel config snippet The minimal config might want to enable JACK as a backend so add an example config for this. --- src/daemon/minimal.conf.in | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index cfaab1c1c..5bd527fa0 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -47,6 +47,9 @@ context.properties = { # Load the pulseaudio emulation daemon minimal.use-pulse = true + + # Load the jack-tunnel as a backend + minimal.use-jack-tunnel = false } context.properties.rules = [ @@ -153,6 +156,36 @@ context.modules = [ { name = libpipewire-module-protocol-pulse condition = [ { minimal.use-pulse = true } ] } + + { name = libpipewire-module-jack-tunnel + args = { + #jack.library = libjack.so.0 + #jack.server = null + #jack.client-name = PipeWire + #jack.connect = true + #tunnel.mode = duplex + #midi.ports = 0 + #audio.channels = 2 + #audio.position = [ FL FR ] + source.props = { + # extra sink properties + # jack-tunnel needs a PortConfig from the + # session manager so do this here. + node.param.PortConfig = { + direction = Output + mode = dsp + } + } + sink.props = { + # extra sink properties + node.param.PortConfig = { + direction = Input + mode = dsp + } + } + } + condition = [ { minimal.use-jack-tunnel = true } ] + } ] pulse.properties = { From 74035f0a340aafb1cfb308056c75fb651add723e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Mar 2025 11:02:35 +0100 Subject: [PATCH 0035/1014] rtp-sdp: format ptime in the C locale Don't place locale dependent strings in the SDP. Fixes #4615 --- src/modules/module-rtp-sap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 9eeb85024..dc01cb987 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -660,7 +660,7 @@ static void update_ts_refclk(struct impl *impl) static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_t buffer_size) { - char src_addr[64], dst_addr[64], dst_ttl[8]; + char src_addr[64], dst_addr[64], dst_ttl[8], ptime[32]; struct sdp_info *sdp = &sess->info; bool src_ip4, dst_ip4; bool multicast; @@ -738,7 +738,7 @@ static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_ if (sdp->ptime > 0) spa_strbuf_append(&buf, - "a=ptime:%.6g\n", sdp->ptime); + "a=ptime:%s\n", spa_dtoa(ptime, sizeof(ptime), sdp->ptime)); if (sdp->framecount > 0) spa_strbuf_append(&buf, From a241495eafb26761aa699dbd665a46dafb90b4b2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Mar 2025 12:07:42 +0100 Subject: [PATCH 0036/1014] jack-tunnel: support passing port names to link Add jack.connect-audio and jack.connect-midi to specify an array of port names to link to instead of the default phyisical ports. Also actually fixes linking the midi ports correctly. --- src/daemon/minimal.conf.in | 10 +++- src/modules/module-jack-tunnel.c | 95 +++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 5bd527fa0..82647e9ca 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -49,7 +49,7 @@ context.properties = { minimal.use-pulse = true # Load the jack-tunnel as a backend - minimal.use-jack-tunnel = false + minimal.use-jack-tunnel = true } context.properties.rules = [ @@ -163,12 +163,16 @@ context.modules = [ #jack.server = null #jack.client-name = PipeWire #jack.connect = true + #jack.connect-audio = [ ] + #jack.connect-midi = [ ] #tunnel.mode = duplex - #midi.ports = 0 + #midi.ports = 1 #audio.channels = 2 #audio.position = [ FL FR ] source.props = { # extra sink properties + #jack.connect-audio = [ "system:capture_1" ] + #jack.connect-midi = [ "system:midi_capture_2" ] # jack-tunnel needs a PortConfig from the # session manager so do this here. node.param.PortConfig = { @@ -178,6 +182,8 @@ context.modules = [ } sink.props = { # extra sink properties + #jack.connect-audio = [ "system:playback_1" ] + #jack.connect-midi = [ "system:midi_playback_2" ] node.param.PortConfig = { direction = Input mode = dsp diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 48e8c6f8e..8bb2b5c0e 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -55,6 +55,12 @@ * - `jack.client-name`: the name of the JACK client. * - `jack.connect`: if jack ports should be connected automatically. Can also be * placed per stream. + * - `jack.connect-audio`: An array of audio ports to connect to. Can also be placed per + * stream. An empty array will not connect anything, even when + * jack.connect is true. + * - `jack.connect-midi`: An array of midi ports to connect to. Can also be placed per + * stream. An empty array will not connect anything, even when + * jack.connect is true. * - `tunnel.mode`: the tunnel mode, sink|source|duplex, default duplex * - `midi.ports`: the number of midi ports. Can also be added to the stream props. * - `source.props`: Extra properties for the source filter. @@ -86,6 +92,8 @@ * #jack.server = null * #jack.client-name = PipeWire * #jack.connect = true + * #jack.connect-audio = [ playback_1 playback_2 ] + * #jack.connect-midi = [ midi_playback_1 ] * #tunnel.mode = duplex * #midi.ports = 0 * #audio.channels = 2 @@ -118,6 +126,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( jack.server= ) " \ "( jack.client-name= ] " \ "( jack.connect= ] " \ + "( jack.connect-audio= ] "\ + "( jack.connect-midi= ] " \ "( tunnel.mode= ] " \ "( midi.ports= ] " \ "( audio.channels= ] " \ @@ -164,7 +174,6 @@ struct stream { struct volume volume; unsigned int running:1; - unsigned int connect:1; }; struct impl { @@ -450,10 +459,10 @@ static void make_stream_ports(struct stream *s) struct pw_properties *props; const char *str, *prefix, *type; char name[256]; - const char **audio_ports = NULL, **link_ports = NULL; - const char **midi_ports = NULL; + char **audio_ports = NULL, **midi_ports = NULL; unsigned long jack_peer, jack_flags; - bool is_midi; + bool do_connect, is_midi, strv_audio = false, strv_midi = false; + int res, n_audio_ports = 0, n_midi_ports = 0; if (s->direction == PW_DIRECTION_INPUT) { /* sink */ @@ -467,14 +476,28 @@ static void make_stream_ports(struct stream *s) prefix = "capture"; } - if (s->connect) { - audio_ports = jack.get_ports(impl->client, NULL, JACK_DEFAULT_AUDIO_TYPE, + do_connect = pw_properties_get_bool(s->props, "jack.connect", true); + + str = pw_properties_get(s->props, "jack.connect-audio"); + if (str != NULL) { + audio_ports = pw_strv_parse(str, strlen(str), INT_MAX, NULL); + strv_audio = true; + } else if (do_connect) { + audio_ports = (char**)jack.get_ports(impl->client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|jack_peer); - midi_ports = jack.get_ports(impl->client, NULL, JACK_DEFAULT_MIDI_TYPE, + } + str = pw_properties_get(s->props, "jack.connect-midi"); + if (str != NULL) { + midi_ports = pw_strv_parse(str, strlen(str), INT_MAX, NULL); + strv_midi = true; + } else if (do_connect) { + midi_ports = (char**)jack.get_ports(impl->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsPhysical|jack_peer); } for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; + char *link_port = NULL; + if (port != NULL) { s->ports[i] = NULL; if (port->jack_port) @@ -498,7 +521,9 @@ static void make_stream_ports(struct stream *s) NULL); type = JACK_DEFAULT_AUDIO_TYPE; - link_ports = audio_ports; + if (audio_ports && audio_ports[n_audio_ports]) + link_port = audio_ports[n_audio_ports++]; + is_midi = false; } else { snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); @@ -509,7 +534,8 @@ static void make_stream_ports(struct stream *s) NULL); type = JACK_DEFAULT_MIDI_TYPE; - link_ports = midi_ports; + if (midi_ports && midi_ports[n_midi_ports]) + link_port = midi_ports[n_midi_ports++]; is_midi = true; } @@ -522,21 +548,35 @@ static void make_stream_ports(struct stream *s) port->is_midi = is_midi; port->jack_port = jack.port_register (impl->client, name, type, jack_flags, 0); - if (link_ports != NULL && link_ports[i] != NULL) { + if (link_port != NULL) { if (jack_flags & JackPortIsOutput) { - if (jack.connect(impl->client, jack.port_name(port->jack_port), link_ports[i])) - pw_log_warn("cannot connect ports"); + pw_log_info("connecting ports '%s' to '%s'", + jack.port_name(port->jack_port), link_port); + if ((res = jack.connect(impl->client, jack.port_name(port->jack_port), link_port))) + pw_log_warn("cannot connect ports '%s' to '%s': %s", + jack.port_name(port->jack_port), link_port, strerror(res)); } else { - if (jack.connect(impl->client, link_ports[i], jack.port_name(port->jack_port))) - pw_log_warn("cannot connect ports"); + pw_log_info("connecting ports '%s' to '%s'", + link_port, jack.port_name(port->jack_port)); + if ((res = jack.connect(impl->client, link_port, jack.port_name(port->jack_port)))) + pw_log_warn("cannot connect ports '%s' to '%s': %s", + link_port, jack.port_name(port->jack_port), strerror(res)); } } s->ports[i] = port; } - if (audio_ports) - jack.free(audio_ports); - if (midi_ports) - jack.free(midi_ports); + if (audio_ports) { + if (strv_audio) + pw_free_strv(audio_ports); + else + jack.free(audio_ports); + } + if (midi_ports) { + if (strv_midi) + pw_free_strv(midi_ports); + else + jack.free(midi_ports); + } } static struct spa_pod *make_props_param(struct spa_pod_builder *b, @@ -640,8 +680,7 @@ static int make_stream(struct stream *s, const char *name) n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); - s->filter = pw_filter_new(impl->core, name, s->props); - s->props = NULL; + s->filter = pw_filter_new(impl->core, name, pw_properties_copy(s->props)); if (s->filter == NULL) return -errno; @@ -930,12 +969,8 @@ static int create_jack_client(struct impl *impl) impl->source.info.rate = impl->samplerate; impl->sink.info.rate = impl->samplerate; - return 0; -} - -static int start_jack_clients(struct impl *impl) -{ jack.activate(impl->client); + return 0; } @@ -1121,6 +1156,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, "jack.connect"); + copy_props(impl, props, "jack.connect-audio"); + copy_props(impl, props, "jack.connect-midi"); parse_audio_info(impl->source.props, &impl->source.info); parse_audio_info(impl->sink.props, &impl->sink.info); @@ -1138,11 +1175,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) goto error; } - impl->source.connect = pw_properties_get_bool(impl->source.props, - "jack.connect", true); - impl->sink.connect = pw_properties_get_bool(impl->sink.props, - "jack.connect", true); - impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); @@ -1172,9 +1204,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((res = create_filters(impl)) < 0) goto error; - if ((res = start_jack_clients(impl)) < 0) - goto error; - pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); From ada214669295a5b2e1a585d730c6fa48064785f7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Mar 2025 12:26:39 +0100 Subject: [PATCH 0037/1014] jack-tunnel: improve port names Prefix midi port names with midi_ and number ports from 1 instead of 0. --- src/modules/module-jack-tunnel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 8bb2b5c0e..9d09596cc 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -511,7 +511,7 @@ static void make_stream_ports(struct stream *s) if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else - snprintf(name, sizeof(name), "%s_%d", prefix, i); + snprintf(name, sizeof(name), "%s_%d", prefix, i+1); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", @@ -526,7 +526,7 @@ static void make_stream_ports(struct stream *s) is_midi = false; } else { - snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); + snprintf(name, sizeof(name), "midi_%s_%d", prefix, i - s->info.channels + 1); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, name, From 33584dae1d2168da9d599f9582dbf65fe9f67939 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 17 Mar 2025 18:05:06 +0100 Subject: [PATCH 0038/1014] ump: handle sysex from UMP to MIDI1 better SysEx in UMP can span multiple packets. In MIDI1 we can't split them up into multiple events so we need to collect the complete sysex and then write out the event. Fixes SysEx writes to ALSA seq by running the event encoder until a valid packet is completed. Also fixes split MIDI1 packets in the JACK API when going through the tunnel or via netjack. --- pipewire-jack/src/pipewire-jack.c | 43 +++++++++++++- spa/plugins/alsa/alsa-seq.c | 16 ++++- src/modules/module-jack-tunnel.c | 24 ++++++-- src/modules/module-netjack2/peer.c | 94 +++++++++++++++++++++++------- 4 files changed, 143 insertions(+), 34 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 40e6c1b8e..6d6e52b2f 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1546,14 +1546,37 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer, return res; } +static inline int midi_event_append(void *port_buffer, const jack_midi_data_t *data, size_t data_size) +{ + struct midi_buffer *mb = port_buffer; + struct midi_event *events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); + struct midi_event *ev; + size_t old_size; + uint8_t *old, *buf; + + ev = &events[--mb->event_count]; + mb->write_pos -= ev->size; + old_size = ev->size; + if (old_size <= MIDI_INLINE_MAX) + old = ev->inline_data; + else + old = SPA_PTROFF(mb, ev->byte_offset, uint8_t); + buf = midi_event_reserve(port_buffer, ev->time, old_size + data_size); + if (SPA_UNLIKELY(buf == NULL)) + return -ENOBUFS; + memmove(buf, old, old_size); + memcpy(buf+old_size, data, data_size); + return 0; +} + static inline int midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size, bool fix) { jack_midi_data_t *retbuf = midi_event_reserve (port_buffer, time, data_size); - if (SPA_UNLIKELY(retbuf == NULL)) - return -ENOBUFS; + if (SPA_UNLIKELY(retbuf == NULL)) + return -ENOBUFS; memcpy (retbuf, data, data_size); if (fix) fix_midi_event(retbuf, data_size); @@ -1566,6 +1589,7 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void uint64_t state = 0; uint32_t i; int res = 0; + bool in_sysex = false; for (i = 0; i < n_seq; i++) c[i] = spa_pod_control_first(&seq[i]->body); @@ -1620,17 +1644,30 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void void *data = SPA_POD_BODY(&next->value); size_t size = SPA_POD_BODY_SIZE(&next->value); uint8_t ev[32]; + bool was_sysex = in_sysex; if (type == TYPE_ID_MIDI) { int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev)); if (ev_size <= 0) break; + size = ev_size; data = ev; + + if (!in_sysex && ev[0] == 0xf0) + in_sysex = true; + if (in_sysex && ev[ev_size-1] == 0xf7) + in_sysex = false; + } else if (type != TYPE_ID_UMP) break; - if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0) + if (was_sysex) + res = midi_event_append(midi, data, size); + else + res = midi_event_write(midi, next->offset, data, size, fix); + + if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); } diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 4f64ee5ba..2548b0954 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -60,6 +60,7 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state); state->ump = true; } + state->ump = false; return 0; } @@ -802,6 +803,7 @@ static int process_write(struct seq_state *state) struct spa_pod_control *c; uint64_t out_time; snd_seq_real_time_t out_rt; + bool first = true; if (!port->valid || io == NULL) continue; @@ -866,13 +868,20 @@ static int process_write(struct seq_state *state) if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) continue; - snd_seq_ev_clear(&ev); + if (first) + snd_seq_ev_clear(&ev); - snd_midi_event_reset_encode(stream->codec); - if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) <= 0) { + if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) { spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); + snd_midi_event_reset_encode(stream->codec); + first = true; continue; } + first = false; + if (ev.type == SND_SEQ_EVENT_NONE) + /* this can happen when the event is not complete yet, like + * a sysex message and we need to encode some more data. */ + continue; snd_seq_ev_set_source(&ev, state->event.addr.port); snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); @@ -882,6 +891,7 @@ static int process_write(struct seq_state *state) spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } + first = true; } } } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 9d09596cc..c8dabf04e 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -256,6 +256,9 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s struct spa_pod_sequence *seq; struct spa_pod_control *c; int res; + bool in_sysex = false; + uint8_t tmp[n_samples * 4]; + size_t tmp_size = 0; jack.midi_clear_buffer(dst); if (src == NULL) @@ -269,23 +272,32 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s seq = (struct spa_pod_sequence*)pod; SPA_POD_SEQUENCE_FOREACH(seq, c) { - uint8_t data[16]; int size; if (c->type != SPA_CONTROL_UMP) continue; size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + SPA_POD_BODY_SIZE(&c->value), &tmp[tmp_size], sizeof(tmp) - tmp_size); if (size <= 0) continue; if (impl->fix_midi) - fix_midi_event(data, size); + fix_midi_event(&tmp[tmp_size], size); - if ((res = jack.midi_event_write(dst, c->offset, data, size)) < 0) - pw_log_warn("midi %p: can't write event: %s", dst, - spa_strerror(res)); + if (!in_sysex && tmp[tmp_size] == 0xf0) + in_sysex = true; + + tmp_size += size; + if (in_sysex && tmp[tmp_size-1] == 0xf7) + in_sysex = false; + + if (!in_sysex) { + if ((res = jack.midi_event_write(dst, c->offset, tmp, tmp_size)) < 0) + pw_log_warn("midi %p: can't write event: %s", dst, + spa_strerror(res)); + tmp_size = 0; + } } } diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index bac6f1322..8016a048a 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -242,14 +242,71 @@ static inline void fix_midi_event(uint8_t *data, size_t size) } } +static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf, + uint32_t offset, uint32_t size) +{ + struct nj2_midi_event *ev; + void *ptr; + + if (size <= 0) + return NULL; + + size_t used_size = sizeof(*buf) + buf->write_pos + + ((buf->event_count + 1) * sizeof(struct nj2_midi_event)); + + ev = &buf->event[buf->event_count]; + ev->time = offset; + ev->size = size; + if (size <= MIDI_INLINE_MAX) { + ptr = ev->buffer; + } else { + if (used_size + size > buf->buffer_size) + return NULL; + buf->write_pos += size; + ev->offset = buf->buffer_size - 1 - buf->write_pos; + ptr = SPA_PTROFF(buf, ev->offset, void); + } + buf->event_count++; + return ptr; +} + +static inline void n2j_midi_buffer_write(struct nj2_midi_buffer *buf, + uint32_t offset, void *data, uint32_t size) +{ + void *ptr = n2j_midi_buffer_reserve(buf, offset, size); + if (ptr != NULL) + memcpy(ptr, data, size); + else + buf->lost_events++; +} + +static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf, + void *data, uint32_t size) +{ + struct nj2_midi_event *ev; + uint32_t old_size; + uint8_t *old_ptr, *new_ptr; + + ev = &buf->event[--buf->event_count]; + old_size = ev->size; + if (old_size <= MIDI_INLINE_MAX) { + old_ptr = ev->buffer; + } else { + buf->write_pos -= old_size; + old_ptr = SPA_PTROFF(buf, ev->offset, void); + } + new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size); + memmove(new_ptr, old_ptr, old_size); + memcpy(new_ptr+old_size, data, size); +} + static void midi_to_netjack2(struct netjack2_peer *peer, struct nj2_midi_buffer *buf, float *src, uint32_t n_samples) { struct spa_pod *pod; struct spa_pod_sequence *seq; struct spa_pod_control *c; - struct nj2_midi_event *ev; - int free_size; + bool in_sysex = false; buf->magic = MIDI_BUFFER_MAGIC; buf->buffer_size = peer->quantum_limit * sizeof(float); @@ -269,12 +326,10 @@ static void midi_to_netjack2(struct netjack2_peer *peer, seq = (struct spa_pod_sequence*)pod; - free_size = buf->buffer_size - sizeof(*buf); - SPA_POD_SEQUENCE_FOREACH(seq, c) { int size; uint8_t data[16]; - void *ptr; + bool was_sysex = in_sysex; if (c->type != SPA_CONTROL_UMP) continue; @@ -284,29 +339,24 @@ static void midi_to_netjack2(struct netjack2_peer *peer, if (size <= 0) continue; - if (c->offset >= n_samples || - size >= free_size) { + if (c->offset >= n_samples) { buf->lost_events++; continue; } - if (peer->fix_midi) + if (!in_sysex && data[0] == 0xf0) + in_sysex = true; + + if (!in_sysex && peer->fix_midi) fix_midi_event(data, size); - ev = &buf->event[buf->event_count]; - ev->time = c->offset; - ev->size = size; - if (size <= MIDI_INLINE_MAX) { - ptr = ev->buffer; - } else { - buf->write_pos += size; - ev->offset = buf->buffer_size - 1 - buf->write_pos; - free_size -= size; - ptr = SPA_PTROFF(buf, ev->offset, void); - } - memcpy(ptr, data, size); - buf->event_count++; - free_size -= sizeof(*ev); + if (in_sysex && data[size-1] == 0xf7) + in_sysex = false; + + if (was_sysex) + n2j_midi_buffer_append(buf, data, size); + else + n2j_midi_buffer_write(buf, c->offset, data, size); } if (buf->write_pos > 0) memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), From 91806ff747acd36a2231644bdd1f3c9bc6541f2b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 09:38:55 +0100 Subject: [PATCH 0039/1014] netjack: handle overflow in midi buffer append --- src/modules/module-netjack2/peer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 8016a048a..a3a20f294 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -296,8 +296,12 @@ static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf, old_ptr = SPA_PTROFF(buf, ev->offset, void); } new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size); - memmove(new_ptr, old_ptr, old_size); - memcpy(new_ptr+old_size, data, size); + if (new_ptr == NULL) { + buf->lost_events++; + } else { + memmove(new_ptr, old_ptr, old_size); + memcpy(new_ptr+old_size, data, size); + } } static void midi_to_netjack2(struct netjack2_peer *peer, From 9b2b420cf5fe59debcbaf91d023877d35cee8ff1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 09:48:08 +0100 Subject: [PATCH 0040/1014] spa: we need ALSA >= 1.2.11 for snd_seq_ump_ev_clear() --- spa/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/meson.build b/spa/meson.build index 9faaae48a..1b07117fa 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -46,7 +46,7 @@ if get_option('spa-plugins').allowed() alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') - if alsa_dep.version().version_compare('>=1.2.10') + if alsa_dep.version().version_compare('>=1.2.11') cdata.set('HAVE_ALSA_UMP', true) endif From de2ab7cac9155848debff68f409cd709ce0493d4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 12:39:15 +0100 Subject: [PATCH 0041/1014] filter-graph: add support for channel positions Make it possible to define a channel position in filter-graph. Use the channel positions to perform the final channelmix. --- spa/include/spa/filter-graph/filter-graph.h | 1 - spa/plugins/audioconvert/audioconvert.c | 62 ++++++++--- spa/plugins/filter-graph/filter-graph.c | 115 ++++++++++++++++---- 3 files changed, 139 insertions(+), 39 deletions(-) diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h index b9c5ea426..05904c7f3 100644 --- a/spa/include/spa/filter-graph/filter-graph.h +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -46,7 +46,6 @@ struct spa_filter_graph_info { #define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) #define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) -#define SPA_FILTER_GRAPH_CHANGE_MASK_PORTS (1u<<2) uint64_t change_mask; uint64_t flags; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index c691ad676..8df8b528a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -229,7 +229,9 @@ struct filter_graph { struct spa_filter_graph *graph; struct spa_hook listener; uint32_t n_inputs; + uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; + uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; bool active; }; @@ -961,10 +963,28 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct filter_graph *g = object; + struct spa_dict *props = info->props; + uint32_t i; + if (!g->active) return; + g->n_inputs = info->n_inputs; g->n_outputs = info->n_outputs; + for (i = 0; props && i < props->n_items; i++) { + const char *k = props->items[i].key; + const char *s = props->items[i].value; + if (spa_streq(k, "n_inputs")) + spa_atou32(s, &g->n_inputs, 0); + else if (spa_streq(k, "n_outputs")) + spa_atou32(s, &g->n_outputs, 0); + else if (spa_streq(k, "inputs.audio.position")) + spa_audio_parse_position(s, strlen(s), + g->inputs_position, &g->n_inputs); + else if (spa_streq(k, "outputs.audio.position")) + spa_audio_parse_position(s, strlen(s), + g->outputs_position, &g->n_outputs); + } } static int apply_props(struct impl *impl, const struct spa_pod *props); @@ -996,22 +1016,29 @@ struct spa_filter_graph_events graph_events = { .props_changed = graph_props_changed, }; -static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph, uint32_t channels) +static int setup_filter_graph(struct impl *this, struct filter_graph *g, + uint32_t channels, uint32_t *position) { int res; char rate_str[64], in_ports[64]; struct dir *dir; - if (graph == NULL) + if (g == NULL || g->graph == NULL) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); - if (channels) + if (channels) { snprintf(in_ports, sizeof(in_ports), "%d", channels); + g->n_inputs = channels; + if (position) { + memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); + memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); + } + } - spa_filter_graph_deactivate(graph); - res = spa_filter_graph_activate(graph, + spa_filter_graph_deactivate(g->graph); + res = spa_filter_graph_activate(g->graph, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); @@ -1109,10 +1136,12 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) goto error; /* prepare new filter and swap it */ - res = setup_filter_graph(impl, iface, 0); - if (res < 0) - goto error; pending->graph = iface; + res = setup_filter_graph(impl, pending, 0, NULL); + if (res < 0) { + pending->graph = NULL; + goto error; + } pending->active = true; spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d", order, idx, n_graph + 1); @@ -1908,7 +1937,7 @@ static char *format_position(char *str, size_t len, uint32_t channels, uint32_t return str; } -static int setup_channelmix(struct impl *this, uint32_t channels) +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -1921,7 +1950,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels) dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { - p = in->format.info.raw.position[i]; + p = position[i]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { @@ -1936,7 +1965,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels) src_mask = dst_mask; spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), - src_chan, in->format.info.raw.position), src_mask); + src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), dst_chan, out->format.info.raw.position), dst_mask); @@ -2228,7 +2257,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration, channels; + uint32_t i, rate, maxsize, maxports, duration, channels, *position; struct port *p; int res; @@ -2276,6 +2305,8 @@ static int setup_convert(struct impl *this) return -EINVAL; channels = in->format.info.raw.channels; + position = in->format.info.raw.position; + maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); if ((res = setup_in_convert(this)) < 0) return res; @@ -2283,11 +2314,13 @@ static int setup_convert(struct impl *this) struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; if (!g->active) continue; - if ((res = setup_filter_graph(this, g->graph, channels)) < 0) + if ((res = setup_filter_graph(this, g, channels, position)) < 0) return res; channels = g->n_outputs; + position = g->outputs_position; + maxports = SPA_MAX(maxports, channels); } - if ((res = setup_channelmix(this, channels)) < 0) + if ((res = setup_channelmix(this, channels, position)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; @@ -2303,7 +2336,6 @@ static int setup_convert(struct impl *this) p = GET_OUT_PORT(this, i); maxsize = SPA_MAX(maxsize, p->maxsize); } - maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); if ((res = ensure_tmp(this, maxsize, maxports)) < 0) return res; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 75e3a00f6..9c5edc116 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -183,6 +183,13 @@ struct graph { struct volume volume[2]; + uint32_t n_inputs; + uint32_t n_outputs; + uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_inputs_position; + uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_outputs_position; + unsigned activated:1; unsigned setup:1; }; @@ -212,14 +219,52 @@ struct impl { float *discard_data; }; +static inline void print_channels(char *buffer, size_t max_size, uint32_t n_channels, uint32_t *positions) +{ + uint32_t i; + struct spa_strbuf buf; + + spa_strbuf_init(&buf, buffer, max_size); + spa_strbuf_append(&buf, "["); + for (i = 0; i < n_channels; i++) { + spa_strbuf_append(&buf, "%s%s", i ? "," : "", + spa_type_audio_channel_to_short_name(positions[i])); + } + spa_strbuf_append(&buf, "]"); +} + static void emit_filter_graph_info(struct impl *impl, bool full) { uint64_t old = full ? impl->info.change_mask : 0; + struct graph *graph = &impl->graph; if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask || full) { + char n_inputs[64], n_outputs[64]; + struct spa_dict_item items[6]; + struct spa_dict dict = SPA_DICT(items, 0); + char in_pos[SPA_AUDIO_MAX_CHANNELS * 8]; + char out_pos[SPA_AUDIO_MAX_CHANNELS * 8]; + + snprintf(n_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs); + snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); + + items[dict.n_items++] = SPA_DICT_ITEM("n_inputs", n_inputs); + items[dict.n_items++] = SPA_DICT_ITEM("n_outputs", n_outputs); + if (graph->n_inputs_position) { + print_channels(in_pos, sizeof(in_pos), + graph->n_inputs_position, graph->inputs_position); + items[dict.n_items++] = SPA_DICT_ITEM("inputs.audio.position", in_pos); + } + if (graph->n_outputs_position) { + print_channels(out_pos, sizeof(out_pos), + graph->n_outputs_position, graph->outputs_position); + items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos); + } + impl->info.props = &dict; spa_filter_graph_emit_info(&impl->hooks, &impl->info); + impl->info.props = NULL; impl->info.change_mask = old; } } @@ -250,7 +295,7 @@ static int impl_process(void *object, uint32_t i, j, n_hndl = graph->n_hndl; struct graph_port *port; - for (i = 0, j = 0; i < impl->info.n_inputs; i++) { + for (i = 0, j = 0; i < graph->n_inputs; i++) { while (j < graph->n_input) { port = &graph->input[j++]; if (port->desc && in[i]) @@ -259,7 +304,7 @@ static int impl_process(void *object, break; } } - for (i = 0; i < impl->info.n_outputs; i++) { + for (i = 0; i < graph->n_outputs; i++) { if (out[i] == NULL) continue; @@ -1457,19 +1502,19 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((str = spa_dict_lookup(props, "filter-graph.n_inputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && - n_ports != impl->info.n_inputs) { - impl->info.n_inputs = n_ports; - impl->info.n_outputs = 0; - impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + n_ports != graph->n_inputs) { + graph->n_inputs = n_ports; + graph->n_outputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } if ((str = spa_dict_lookup(props, "filter-graph.n_outputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && - n_ports != impl->info.n_outputs) { - impl->info.n_outputs = n_ports; - impl->info.n_inputs = 0; - impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PORTS; + n_ports != graph->n_outputs) { + graph->n_outputs = n_ports; + graph->n_inputs = 0; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } @@ -1477,7 +1522,7 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((res = setup_graph(graph)) < 0) return res; graph->setup = true; - spa_filter_graph_emit_info(&impl->hooks, &impl->info); + emit_filter_graph_info(impl, false); } /* first make instances */ @@ -1666,24 +1711,28 @@ static int setup_graph(struct graph *graph) res = -EINVAL; goto error; } + if (graph->n_inputs == 0) + graph->n_inputs = impl->info.n_inputs; + if (graph->n_inputs == 0) + graph->n_inputs = n_input; - if (impl->info.n_inputs == 0) - impl->info.n_inputs = n_input; + if (graph->n_outputs == 0) + graph->n_outputs = impl->info.n_outputs; /* compare to the requested number of inputs and duplicate the * graph n_hndl times when needed. */ - n_hndl = impl->info.n_inputs / n_input; + n_hndl = graph->n_inputs / n_input; - if (impl->info.n_outputs == 0) - impl->info.n_outputs = n_output * n_hndl; + if (graph->n_outputs == 0) + graph->n_outputs = n_output * n_hndl; - if (n_hndl != impl->info.n_outputs / n_output) { + if (n_hndl != graph->n_outputs / n_output) { spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. input:%1$d / input:%2$d != " "output:%3$d / output:%4$d. Check inputs and outputs objects.", - impl->info.n_inputs, n_input, - impl->info.n_outputs, n_output); + graph->n_inputs, n_input, + graph->n_outputs, n_output); res = -EINVAL; goto error; } @@ -1699,11 +1748,11 @@ static int setup_graph(struct graph *graph) "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. Some filter ports will be " "unconnected..", - impl->info.n_inputs, n_input, - impl->info.n_outputs, n_output); + graph->n_inputs, n_input, + graph->n_outputs, n_output); - if (impl->info.n_outputs == 0) - impl->info.n_outputs = n_output * n_hndl; + if (graph->n_outputs == 0) + graph->n_outputs = n_output * n_hndl; } spa_log_info(impl->log, "using %d instances %d %d", n_hndl, n_input, n_output); @@ -1949,6 +1998,26 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) } impl->info.n_outputs = res; } + else if (spa_streq("inputs.audio.position", key)) { + if (!spa_json_is_array(val, len) || + (len = spa_json_container_len(&it[0], val, len)) < 0) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_audio_parse_position(val, len, graph->inputs_position, + &graph->n_inputs_position); + impl->info.n_inputs = graph->n_inputs_position; + } + else if (spa_streq("outputs.audio.position", key)) { + if (!spa_json_is_array(val, len) || + (len = spa_json_container_len(&it[0], val, len)) < 0) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_audio_parse_position(val, len, graph->outputs_position, + &graph->n_outputs_position); + impl->info.n_outputs = graph->n_outputs_position; + } else if (spa_streq("nodes", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); From bcde5cbd8a9e66ecdfb122b1c473578fa15a1ac0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 18 Mar 2025 16:30:25 +0100 Subject: [PATCH 0042/1014] audioconvert: rework the filter-graphs a little Use a simple free/active linked list for the filter-graphs and insert the new filters in the right position in the list. Then simply copy the list to an array for the processing thread. when reconfiguring, set up all the filters again because the number of channels might have changed. --- spa/plugins/audioconvert/audioconvert.c | 244 +++++++++++++----------- 1 file changed, 130 insertions(+), 114 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 8df8b528a..bf5a7593a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -224,6 +224,7 @@ struct stage { struct filter_graph { struct impl *impl; + struct spa_list link; int order; struct spa_handle *handle; struct spa_filter_graph *graph; @@ -232,7 +233,8 @@ struct filter_graph { uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; - bool active; + bool removing; + bool setup; }; struct impl { @@ -245,9 +247,12 @@ struct impl { struct spa_plugin_loader *loader; uint32_t n_graph; - uint32_t graph_index[MAX_GRAPH]; + struct filter_graph *filter_graph[MAX_GRAPH]; + + struct spa_list free_graphs; + struct spa_list active_graphs; + struct filter_graph graphs[MAX_GRAPH]; - struct filter_graph filter_graph[MAX_GRAPH]; int in_filter_props; int filter_props_count; @@ -305,6 +310,8 @@ struct impl { char group_name[128]; + uint32_t maxsize; + uint32_t maxports; uint32_t scratch_size; uint32_t scratch_ports; float *empty; @@ -818,8 +825,8 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: - if (this->filter_graph[0].graph) { - res = spa_filter_graph_enum_prop_info(this->filter_graph[0].graph, + if (this->filter_graph[0] && this->filter_graph[0]->graph) { + res = spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, result.index - 30, &b, ¶m); if (res <= 0) return res; @@ -911,13 +918,13 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_pop(&b, &f[0]); break; default: - if (result.index > MAX_GRAPH) + if (result.index-1 >= this->n_graph) return 0; - if (this->filter_graph[result.index-1].graph == NULL) + if (this->filter_graph[result.index-1]->graph == NULL) goto next; - res = spa_filter_graph_get_props(this->filter_graph[result.index-1].graph, + res = spa_filter_graph_get_props(this->filter_graph[result.index-1]->graph, &b, ¶m); if (res < 0) return res; @@ -966,7 +973,7 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) struct spa_dict *props = info->props; uint32_t i; - if (!g->active) + if (g->removing) return; g->n_inputs = info->n_inputs; @@ -993,7 +1000,7 @@ static void graph_apply_props(void *object, enum spa_direction direction, const { struct filter_graph *g = object; struct impl *impl = g->impl; - if (!g->active) + if (g->removing) return; if (apply_props(impl, props) > 0) emit_node_info(impl, false); @@ -1003,7 +1010,7 @@ static void graph_props_changed(void *object, enum spa_direction direction) { struct filter_graph *g = object; struct impl *impl = g->impl; - if (!g->active) + if (g->removing) return; impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[IDX_Props].user++; @@ -1023,7 +1030,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, char rate_str[64], in_ports[64]; struct dir *dir; - if (g == NULL || g->graph == NULL) + if (g == NULL || g->graph == NULL || g->setup) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; @@ -1043,82 +1050,118 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); + g->setup = res >= 0; + return res; } +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); + +static int setup_filter_graphs(struct impl *impl) +{ + int res; + uint32_t channels, *position; + struct dir *in, *out; + struct filter_graph *g, *t; + + in = &impl->dir[SPA_DIRECTION_INPUT]; + out = &impl->dir[SPA_DIRECTION_OUTPUT]; + + channels = in->format.info.raw.channels; + position = in->format.info.raw.position; + impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); + + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (g->removing) + continue; + if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { + g->removing = true; + spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, + spa_strerror(res)); + } else { + channels = g->n_outputs; + position = g->outputs_position; + impl->maxports = SPA_MAX(impl->maxports, channels); + } + } + if ((res = setup_channelmix(impl, channels, position)) < 0) + return res; + + return 0; +} + static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = user_data; - uint32_t i, j; - impl->n_graph = 0; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &impl->filter_graph[i]; - if (g->graph == NULL || !g->active) - continue; - impl->graph_index[impl->n_graph++] = i; + struct filter_graph *g; + + impl->n_graph = 0; + spa_list_for_each(g, &impl->active_graphs, link) + if (g->setup && !g->removing) + impl->filter_graph[impl->n_graph++] = g; - for (j = impl->n_graph-1; j > 0; j--) { - if (impl->filter_graph[impl->graph_index[j]].order >= - impl->filter_graph[impl->graph_index[j-1]].order) - break; - SPA_SWAP(impl->graph_index[j], impl->graph_index[j-1]); - } - } impl->recalc = true; return 0; } static void clean_filter_handles(struct impl *impl, bool force) { - uint32_t i; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &impl->filter_graph[i]; - if (!g->active || force) { - if (g->graph) - spa_hook_remove(&g->listener); - if (g->handle) - spa_plugin_loader_unload(impl->loader, g->handle); - spa_zero(*g); - } + struct filter_graph *g, *t; + + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (!g->removing) + continue; + spa_list_remove(&g->link); + if (g->graph) + spa_hook_remove(&g->listener); + if (g->handle) + spa_plugin_loader_unload(impl->loader, g->handle); + spa_zero(*g); + spa_list_append(&impl->free_graphs, &g->link); } } +static inline void insert_graph(struct spa_list *graphs, struct filter_graph *pending) +{ + struct filter_graph *g; + + spa_list_for_each(g, graphs, link) { + if (g->order < pending->order) + break; + } + spa_list_append(&g->link, &pending->link); +} + static int load_filter_graph(struct impl *impl, const char *graph, int order) { char qlimit[64]; int res; void *iface; struct spa_handle *new_handle = NULL; - uint32_t i, idx, n_graph; - struct filter_graph *pending, *old_active = NULL; + struct filter_graph *pending, *g, *t; if (impl->props.filter_graph_disabled) return -EPERM; /* find graph spot */ - idx = SPA_ID_INVALID; - n_graph = 0; - for (i = 0; i < MAX_GRAPH; i++) { - pending = &impl->filter_graph[i]; - /* find the first free spot for our new filter */ - if (!pending->active && idx == SPA_ID_INVALID) - idx = i; - /* deactivate an existing filter of the same order */ - if (pending->active) { - if (pending->order == order) - old_active = pending; - else - n_graph++; - } - } - /* we can at most have MAX_GRAPH-1 active filters */ - if (n_graph >= MAX_GRAPH-1) + if (spa_list_is_empty(&impl->free_graphs)) return -ENOSPC; - pending = &impl->filter_graph[idx]; + /* find free graph for our new filter */ + pending = spa_list_first(&impl->free_graphs, struct filter_graph, link); + pending->impl = impl; pending->order = order; + pending->removing = false; + + /* move active graphs with same order to inactive list */ + spa_list_for_each_safe(g, t, &impl->active_graphs, link) { + if (g->order == order) { + g->removing = true; + spa_log_info(impl->log, "removing filter-graph order:%d", order); + } + } if (graph != NULL && graph[0] != '\0') { snprintf(qlimit, sizeof(qlimit), "%u", impl->quantum_limit); @@ -1137,33 +1180,18 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) /* prepare new filter and swap it */ pending->graph = iface; - res = setup_filter_graph(impl, pending, 0, NULL); - if (res < 0) { - pending->graph = NULL; - goto error; - } - pending->active = true; - spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d", - order, idx, n_graph + 1); - } else { - pending->active = false; - spa_log_info(impl->log, "removing filter-graph order:%d active:%d", - order, n_graph); - } - if (old_active) - old_active->active = false; - - /* we call this here on the pending_graph so that the n_input/n_output is updated - * before we switch */ - if (pending->active) + pending->handle = new_handle; spa_filter_graph_add_listener(pending->graph, &pending->listener, &graph_events, pending); + spa_list_remove(&pending->link); + insert_graph(&impl->active_graphs, pending); + + spa_log_info(impl->log, "loading filter-graph order:%d", order); + } + res = setup_filter_graphs(impl); spa_loop_invoke(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, true, impl); - if (pending->active) - pending->handle = new_handle; - if (impl->in_filter_props == 0) clean_filter_handles(impl, false); @@ -1742,16 +1770,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, } case SPA_PARAM_Props: { - uint32_t i; bool have_graph = false; + struct filter_graph *g, *t; this->filter_props_count = 0; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; - if (!g->active) + + spa_list_for_each_safe(g, t, &this->active_graphs, link) { + if (g->removing) continue; have_graph = true; - this->in_filter_props++; spa_filter_graph_set_props(g->graph, SPA_DIRECTION_INPUT, param); @@ -1958,12 +1985,6 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi dst_mask |= 1ULL << (p < 64 ? p : 0); } - /* if we needed a channel conversion but we already did one before this - * stage, assume we are now with the dst layout */ - if ((out->format.info.raw.channels != in->format.info.raw.channels) && - channels != in->format.info.raw.channels) - src_mask = dst_mask; - spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), @@ -2160,8 +2181,9 @@ static void free_tmp(struct impl *this) } } -static int ensure_tmp(struct impl *this, uint32_t maxsize, uint32_t maxports) +static int ensure_tmp(struct impl *this) { + uint32_t maxsize = this->maxsize, maxports = this->maxports; if (maxsize > this->scratch_size || maxports > this->scratch_ports) { float *empty, *scratch, *tmp[2]; uint32_t i; @@ -2257,7 +2279,7 @@ static inline bool resample_is_passthrough(struct impl *this) static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t i, rate, maxsize, maxports, duration, channels, *position; + uint32_t i, rate, duration; struct port *p; int res; @@ -2304,39 +2326,25 @@ static int setup_convert(struct impl *this) if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) return -EINVAL; - channels = in->format.info.raw.channels; - position = in->format.info.raw.position; - maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); - if ((res = setup_in_convert(this)) < 0) return res; - for (i = 0; i < this->n_graph; i++) { - struct filter_graph *g = &this->filter_graph[this->graph_index[i]]; - if (!g->active) - continue; - if ((res = setup_filter_graph(this, g, channels, position)) < 0) - return res; - channels = g->n_outputs; - position = g->outputs_position; - maxports = SPA_MAX(maxports, channels); - } - if ((res = setup_channelmix(this, channels, position)) < 0) + if ((res = setup_filter_graphs(this)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; if ((res = setup_out_convert(this)) < 0) return res; - maxsize = this->quantum_limit * sizeof(float); + this->maxsize = this->quantum_limit * sizeof(float); for (i = 0; i < in->n_ports; i++) { p = GET_IN_PORT(this, i); - maxsize = SPA_MAX(maxsize, p->maxsize); + this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } for (i = 0; i < out->n_ports; i++) { p = GET_OUT_PORT(this, i); - maxsize = SPA_MAX(maxsize, p->maxsize); + this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } - if ((res = ensure_tmp(this, maxsize, maxports)) < 0) + if ((res = ensure_tmp(this)) < 0) return res; resample_update_rate_match(this, resample_is_passthrough(this), duration, 0); @@ -2351,11 +2359,12 @@ static int setup_convert(struct impl *this) static void reset_node(struct impl *this) { - uint32_t i; - for (i = 0; i < MAX_GRAPH; i++) { - struct filter_graph *g = &this->filter_graph[i]; + struct filter_graph *g; + + spa_list_for_each(g, &this->active_graphs, link) { if (g->graph) spa_filter_graph_deactivate(g->graph); + g->setup = false; } if (this->resample.reset) resample_reset(&this->resample); @@ -3535,7 +3544,7 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx) } if (!filter_passthrough) { for (i = 0; i < this->n_graph; i++) { - struct filter_graph *fg = &this->filter_graph[this->graph_index[i]]; + struct filter_graph *fg = this->filter_graph[i]; if (mix_passthrough && resample_passthrough && out_passthrough && i + 1 == this->n_graph) @@ -4066,6 +4075,13 @@ impl_init(const struct spa_handle_factory *factory, props_reset(&this->props); filter_graph_disabled = this->props.filter_graph_disabled; + spa_list_init(&this->active_graphs); + spa_list_init(&this->free_graphs); + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &this->graphs[i]; + g->impl = this; + spa_list_append(&this->free_graphs, &g->link); + } this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1; From 795e9177160ccca0e7504e4809a4048010d62987 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Mar 2025 12:57:44 +0100 Subject: [PATCH 0043/1014] jack: write midi events to end of buffer There is no need to keep one extra byte at the end of the buffer, we can write the event up to the last byte. --- pipewire-jack/src/pipewire-jack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 6d6e52b2f..3ba2d7e3c 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1538,7 +1538,7 @@ static inline jack_midi_data_t* midi_event_reserve(void *port_buffer, res = ev->inline_data; } else { mb->write_pos += data_size; - ev->byte_offset = mb->buffer_size - 1 - mb->write_pos; + ev->byte_offset = mb->buffer_size - mb->write_pos; res = SPA_PTROFF(mb, ev->byte_offset, uint8_t); } mb->event_count += 1; @@ -5793,8 +5793,8 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id); memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); - if (mb->write_pos) { - size_t offs = mb->buffer_size - 1 - mb->write_pos; + if (mb->write_pos > 0) { + size_t offs = mb->buffer_size - mb->write_pos; memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos); } return ptr; From 3be88eacb83a2d6c443c2e45b4b320923c99a9cc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Mar 2025 13:06:15 +0100 Subject: [PATCH 0044/1014] netjack2: copy large midi events to the end of the buffer There is no need to keep an extra free byte at the end and it will cause us to lose a byte when we copy the large midi events down. --- src/modules/module-netjack2/peer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index a3a20f294..d0e819847 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -263,7 +263,7 @@ static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf, if (used_size + size > buf->buffer_size) return NULL; buf->write_pos += size; - ev->offset = buf->buffer_size - 1 - buf->write_pos; + ev->offset = buf->buffer_size - buf->write_pos; ptr = SPA_PTROFF(buf, ev->offset, void); } buf->event_count++; From e3a068de7d4a8d183811ed5518d1bbb270e66116 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Mar 2025 13:09:07 +0100 Subject: [PATCH 0045/1014] netjack2: set correct max midi buffer size It depends on the negotiated period size, not the graph quantum. --- src/modules/module-netjack2/peer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index d0e819847..319ae164c 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -313,7 +313,7 @@ static void midi_to_netjack2(struct netjack2_peer *peer, bool in_sysex = false; buf->magic = MIDI_BUFFER_MAGIC; - buf->buffer_size = peer->quantum_limit * sizeof(float); + buf->buffer_size = peer->params.period_size * sizeof(float); buf->nframes = n_samples; buf->write_pos = 0; buf->event_count = 0; From a460842769ab737688485a9c8655304a1d553854 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Mar 2025 13:10:15 +0100 Subject: [PATCH 0046/1014] netjack2: fix the large midi events offset The midi events have their large data offsets relative to the start of the buffer and the large data is at the end of the buffer. Because we copied it down, right after the events, but we didn't adjust the offsets, calculate a correction offset when unpacking the events. --- src/modules/module-netjack2/peer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 319ae164c..411bd6abc 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -373,6 +373,8 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b struct spa_pod_builder b = { 0, }; uint32_t i; struct spa_pod_frame f; + size_t offset = size - buf->write_pos - + sizeof(*buf) - (buf->event_count * sizeof(struct nj2_midi_event)); spa_pod_builder_init(&b, dst, size); spa_pod_builder_push_sequence(&b, &f, 0); @@ -384,8 +386,8 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b if (ev->size <= MIDI_INLINE_MAX) data = ev->buffer; - else if (ev->offset > buf->write_pos) - data = SPA_PTROFF(buf, ev->offset - buf->write_pos, void); + else if (ev->offset > offset) + data = SPA_PTROFF(buf, ev->offset - offset, void); else continue; @@ -393,9 +395,10 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b while (s > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state); - if (ump_size <= 0) + if (ump_size <= 0) { + pw_log_warn("invalid MIDI received: %s", spa_strerror(ump_size)); break; - + } spa_pod_builder_control(&b, ev->time, SPA_CONTROL_UMP); spa_pod_builder_bytes(&b, ump, ump_size); } From 6f8a814f29cb749eaf8a2ed37fdd67cc9a9b90eb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 19 Mar 2025 17:04:38 +0100 Subject: [PATCH 0047/1014] pod: use default value of filter When using a filter, it makes more sense to use the default value of the filter as a first attempt. One case is in adapter when we try to find a passthrough format first. The audioconverter suggests a default rate of the graph rate but the follower filters this out for another unrelated default value and passthrough is not possible (altough it would be because the default value of the filter is in the supported follower range). Fixes #4619 --- spa/include/spa/pod/filter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6a47472e4..6c8a17d4a 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -177,7 +177,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f); /* default value */ - spa_pod_builder_primitive(b, v1); + spa_pod_builder_primitive(b, v2); if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || @@ -185,10 +185,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; /* copy all equal values but don't copy the default value again */ - for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1, size, void)) { - for (k = 0, a2 = alt2; k < nalt2; k++, a2 = SPA_PTROFF(a2,size,void)) { + for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) { + for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) { if (spa_pod_compare_value(type, a1, a2, size) == 0) { - if (p1c == SPA_CHOICE_Enum || j > 0) + if (p2c == SPA_CHOICE_Enum || j > 0) spa_pod_builder_raw(b, a1, size); n_copied++; } From 4e0545aa0439dc2b59b2de2a09bb02555e04fa22 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 20 Mar 2025 10:57:26 +0100 Subject: [PATCH 0048/1014] videoconvert: get the correct strides Use ffmpeg to get the right stride for the negotiated format instead of using a wrong hardcoded value. --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 955bfa93a..a46554417 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -1516,6 +1517,8 @@ static int port_set_format(void *object, else { struct dir *dir = &this->dir[direction]; struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + enum AVPixelFormat pix_fmt; + int linesizes[4]; if (info.media_type != SPA_MEDIA_TYPE_video) { spa_log_error(this->log, "unexpected types %d/%d", @@ -1526,8 +1529,9 @@ static int port_set_format(void *object, spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } - port->stride = 2; - port->stride *= info.info.raw.size.width; + pix_fmt = format_to_pix_fmt(info.info.raw.format); + av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); + port->stride = linesizes[0]; port->blocks = 1; dir->format = info; dir->have_format = true; From 7662a01f8572ecdfad1bbe41ca1564aab0559c4e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 20 Mar 2025 11:21:56 +0100 Subject: [PATCH 0049/1014] examples: warn when texture locking fails New SDL seems to fail when locking YUY2 now. --- src/examples/export-sink.c | 3 ++- src/examples/local-v4l2.c | 3 ++- src/examples/video-play-fixate.c | 5 ++++- src/examples/video-play-pull.c | 5 ++++- src/examples/video-play-reneg.c | 5 ++++- src/examples/video-play.c | 6 +++++- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/examples/export-sink.c b/src/examples/export-sink.c index 0a5644f8d..8ecad3e01 100644 --- a/src/examples/export-sink.c +++ b/src/examples/export-sink.c @@ -304,7 +304,8 @@ static int port_set_format(void *object, SDL_TEXTUREACCESS_STREAMING, d->format.size.width, d->format.size.height); - SDL_LockTexture(d->texture, NULL, &dest, &d->stride); + if (SDL_LockTexture(d->texture, NULL, &dest, &d->stride) < 0) + return -EINVAL; SDL_UnlockTexture(d->texture); } diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c index 2093a9b48..3a2ef35dc 100644 --- a/src/examples/local-v4l2.c +++ b/src/examples/local-v4l2.c @@ -210,7 +210,8 @@ static int port_set_format(void *object, enum spa_direction direction, uint32_t SDL_TEXTUREACCESS_STREAMING, d->format.size.width, d->format.size.height); - SDL_LockTexture(d->texture, NULL, &dest, &d->stride); + if (SDL_LockTexture(d->texture, NULL, &dest, &d->stride) < 0) + return -EINVAL; SDL_UnlockTexture(d->texture); } diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index b59be31b5..8503a6b3d 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -332,7 +332,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - SDL_LockTexture(data->texture, NULL, &d, &data->stride); + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + pw_stream_set_error(stream, -EINVAL, "invalid texture format"); + return; + } SDL_UnlockTexture(data->texture); /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index f9492edea..3ae69f55d 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -382,7 +382,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - SDL_LockTexture(data->texture, NULL, &d, &data->stride); + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + pw_stream_set_error(stream, -EINVAL, "invalid texture format"); + return; + } SDL_UnlockTexture(data->texture); switch(sdl_format) { diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index c93ed24de..b8e83d233 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -225,7 +225,10 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - SDL_LockTexture(data->texture, NULL, &d, &data->stride); + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + pw_stream_set_error(stream, -EINVAL, "invalid texture format"); + return; + } SDL_UnlockTexture(data->texture); /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 53041f284..6a12f2cc9 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -348,7 +348,11 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - SDL_LockTexture(data->texture, NULL, &d, &data->stride); + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + pw_stream_set_error(stream, -EINVAL, "invalid format"); + return; + } SDL_UnlockTexture(data->texture); switch(sdl_format) { From 923b8b48ec5a0e7973915189c4d5652fda3b74a8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 20 Mar 2025 12:55:09 +0100 Subject: [PATCH 0050/1014] alsa-seq; enable UMP again when we can It got accidentally disabled for debugging --- spa/plugins/alsa/alsa-seq.c | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 2548b0954..23b74d0a2 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -60,7 +60,6 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state); state->ump = true; } - state->ump = false; return 0; } From 2625983a236ca34e5ca18fb12ad4f52166a1b4bd Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Wed, 19 Mar 2025 08:25:34 +0100 Subject: [PATCH 0051/1014] systemd: Depend on dbus.service Solution suggested by Xi Ruoyao. The dbus user service is required for various features - the summary says: 'dbus (Bluetooth, rt, portal, pw-reserve)' On session logout the dbus service gets shut down while the Pipewire one relies on a timeout. If a user logs in again before PW timed out, the later stays alive but doesn't handle re-connecting to the dbus service of the new session, breaking the camera portal and potentially other features. Thus hard-depend on the dbus service (if enabled at build time) and thus shut down together with it. --- src/daemon/systemd/user/meson.build | 6 ++++++ src/daemon/systemd/user/pipewire.service.in | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build index 10227629d..a96409f2b 100644 --- a/src/daemon/systemd/user/meson.build +++ b/src/daemon/systemd/user/meson.build @@ -11,6 +11,12 @@ systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') +pw_service_reqs = '' +if get_option('dbus').enabled() + pw_service_reqs += 'dbus.service ' +endif +systemd_config.set('PW_SERVICE_REQS', pw_service_reqs) + configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in index 4236c6bd4..f0c8d9cd5 100644 --- a/src/daemon/systemd/user/pipewire.service.in +++ b/src/daemon/systemd/user/pipewire.service.in @@ -13,7 +13,7 @@ Description=PipeWire Multimedia Service # # After=pipewire.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). -Requires=pipewire.socket +Requires=pipewire.socket @PW_SERVICE_REQS@ ConditionUser=!root [Service] From e0938303e6e5aa863276eb95856007dd22331781 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 23 Nov 2024 13:36:39 +0200 Subject: [PATCH 0052/1014] acp: Sennheiser GSX stereo profile The card can output at higher sample rates in stereo profile, so add that. --- .../mixer/profile-sets/sennheiser-gsx.conf | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spa/plugins/alsa/mixer/profile-sets/sennheiser-gsx.conf b/spa/plugins/alsa/mixer/profile-sets/sennheiser-gsx.conf index 0ac157685..75eeffb2f 100644 --- a/spa/plugins/alsa/mixer/profile-sets/sennheiser-gsx.conf +++ b/spa/plugins/alsa/mixer/profile-sets/sennheiser-gsx.conf @@ -30,6 +30,7 @@ auto-profiles = no [Mapping analog-chat-output] +description-key = gaming-headset-chat device-strings = hw:%f,0 channel-map = mono paths-output = analog-chat-output @@ -38,13 +39,16 @@ priority = 4000 intended-roles = phone [Mapping analog-output-surround71] +description-key = analog-surround-71 device-strings = hw:%f,1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right +#channel-map = front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right # Swap channel fix that some devices require paths-output = virtual-surround-7.1 priority = 4100 direction = output [Mapping analog-chat-input] +description-key = gaming-headset-chat device-strings = hw:%f,0 channel-map = mono paths-input = analog-chat-input @@ -56,3 +60,17 @@ output-mappings = analog-output-surround71 analog-chat-output input-mappings = analog-chat-input priority = 5100 skip-probe = yes + +[Mapping stereo-output] +description = 2.0 HD +device-strings = hw:%f,1 +channel-map = stereo +priority = 3 +direction = output + +[Profile output:stereo-output+output:analog-chat-output+input:analog-chat-input] +description = 2.0 HD +output-mappings = stereo-output analog-chat-output +input-mappings = analog-chat-input +priority = 50 +skip-probe = yes From 6fe1c6d67bc8b7ae11514a71abd6f8e6f64a4e17 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 20 Mar 2025 20:35:46 +0200 Subject: [PATCH 0053/1014] alsa: seq: double-check midi client version Check midi client version after setting it, to see if it was really successfully set. Old kernels without UMP don't know about the midi version fields, so snd_seq_set_client_midi_version() appears to fail silently there. --- spa/plugins/alsa/alsa-seq.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 23b74d0a2..3b740f47e 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -44,9 +44,27 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu #ifdef HAVE_ALSA_UMP res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0); + if (!res) { + snd_seq_client_info_t *info = NULL; + + /* Double check client version */ + res = snd_seq_client_info_malloc(&info); + if (!res) + res = snd_seq_get_client_info(conn->hndl, info); + if (!res) { + res = snd_seq_client_info_get_midi_version(info); + if (res == SND_SEQ_CLIENT_UMP_MIDI_2_0) + res = 0; + else + res = -EIO; + } + if (info) + snd_seq_client_info_free(info); + } #else res = -EOPNOTSUPP; #endif + if (res < 0) { spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR), "%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res)); From b8d5334462aaba1b175b88cb166a6e49083dd626 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Thu, 20 Mar 2025 19:30:03 +0530 Subject: [PATCH 0054/1014] gst: pipewireformat: Do not use RANGE if values are equal This fixes assertion from the underlying gst_value_collect_int_range when using gst_caps_set_simple with range types when the values are equal. --- src/gst/gstpipewireformat.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 24115325e..903d216e2 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -1065,8 +1065,19 @@ handle_rect_prop (const struct spa_pod_prop *prop, const char *width, const char { if (n_items < 3) return; - gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, - height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL); + + if (rect[1].width == rect[2].width && + rect[1].height == rect[2].height) { + gst_caps_set_simple (res, + width, G_TYPE_INT, rect[1].width, + height, G_TYPE_INT, rect[1].height, + NULL); + } else { + gst_caps_set_simple (res, + width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, + height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, + NULL); + } break; } case SPA_CHOICE_Enum: @@ -1117,8 +1128,17 @@ handle_fraction_prop (const struct spa_pod_prop *prop, const char *key, GstCaps { if (n_items < 3) return; - gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom, - fract[2].num, fract[2].denom, NULL); + + if (fract[1].num == fract[2].num && + fract[1].denom == fract[2].denom) { + gst_caps_set_simple (res, key, GST_TYPE_FRACTION, + fract[1].num, fract[1].denom, NULL); + } else { + gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, + fract[1].num, fract[1].denom, + fract[2].num, fract[2].denom, + NULL); + } break; } case SPA_CHOICE_Enum: From de54cfc475d95a52897fd2737358b10140cc79f3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 21 Mar 2025 15:17:15 +0100 Subject: [PATCH 0055/1014] audioconvert: improve tmp buffer allocation Use per port allocated memory so that we can easily increase the size and add more buffers. This is necessary when we add filter-graphs that require more ports. --- spa/plugins/audioconvert/audioconvert.c | 139 ++++++++++++++---------- 1 file changed, 80 insertions(+), 59 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index bf5a7593a..4e52b6cef 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -316,7 +316,7 @@ struct impl { uint32_t scratch_ports; float *empty; float *scratch; - float *tmp[2]; + float *tmp[2][MAX_PORTS]; float *tmp_datas[2][MAX_PORTS]; struct wav_file *wav_file; @@ -1057,6 +1057,83 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); +static void free_tmp(struct impl *this) +{ + uint32_t i; + + spa_log_debug(this->log, "free tmp %d", this->scratch_size); + + free(this->empty); + this->empty = NULL; + this->scratch_size = 0; + this->scratch_ports = 0; + free(this->scratch); + this->scratch = NULL; + for (i = 0; i < MAX_PORTS; i++) { + free(this->tmp[0][i]); + this->tmp[0][i] = NULL; + free(this->tmp[1][i]); + this->tmp[1][i] = NULL; + this->tmp_datas[0][i] = NULL; + this->tmp_datas[1][i] = NULL; + } +} + + +static int ensure_tmp(struct impl *this) +{ + uint32_t maxsize = this->maxsize, maxports = this->maxports; + uint32_t i; + float *empty, *scratch, *tmp[2]; + + if (maxsize > this->scratch_size) { + spa_log_info(this->log, "resize tmp %d -> %d", this->scratch_size, maxsize); + + if ((empty = realloc(this->empty, maxsize + MAX_ALIGN)) != NULL) + this->empty = empty; + if ((scratch = realloc(this->scratch, maxsize + MAX_ALIGN)) != NULL) + this->scratch = scratch; + if (empty == NULL || scratch == NULL) { + free_tmp(this); + return -ENOMEM; + } + memset(this->empty, 0, maxsize + MAX_ALIGN); + + for (i = 0; i < this->scratch_ports; i++) { + if ((tmp[0] = realloc(this->tmp[0][i], maxsize + MAX_ALIGN)) != NULL) + this->tmp[0][i] = tmp[0]; + if ((tmp[1] = realloc(this->tmp[1][i], maxsize + MAX_ALIGN)) != NULL) + this->tmp[1][i] = tmp[1]; + if (tmp[0] == NULL || tmp[1] == NULL) { + free_tmp(this); + return -ENOMEM; + } + this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); + this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp[1][i], MAX_ALIGN, void); + } + this->scratch_size = maxsize; + } + if (maxports > this->scratch_ports) { + spa_log_info(this->log, "resize ports %d -> %d", this->scratch_ports, maxports); + + for (i = this->scratch_ports; i < maxports; i++) { + if ((tmp[0] = malloc(maxsize + MAX_ALIGN)) != NULL) + this->tmp[0][i] = tmp[0]; + if ((tmp[1] = malloc(maxsize + MAX_ALIGN)) != NULL) + this->tmp[1][i] = tmp[1]; + if (tmp[0] == NULL || tmp[1] == NULL) { + free_tmp(this); + return -ENOMEM; + } + this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); + this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); + } + this->scratch_ports = maxports; + } + return 0; +} + + static int setup_filter_graphs(struct impl *impl) { int res; @@ -1084,6 +1161,8 @@ static int setup_filter_graphs(struct impl *impl) impl->maxports = SPA_MAX(impl->maxports, channels); } } + if ((res = ensure_tmp(impl)) < 0) + return res; if ((res = setup_channelmix(impl, channels, position)) < 0) return res; @@ -2159,64 +2238,6 @@ static int setup_out_convert(struct impl *this) return 0; } -static void free_tmp(struct impl *this) -{ - uint32_t i; - - spa_log_debug(this->log, "free tmp %d", this->scratch_size); - - free(this->empty); - this->empty = NULL; - this->scratch_size = 0; - this->scratch_ports = 0; - free(this->scratch); - this->scratch = NULL; - free(this->tmp[0]); - this->tmp[0] = NULL; - free(this->tmp[1]); - this->tmp[1] = NULL; - for (i = 0; i < MAX_PORTS; i++) { - this->tmp_datas[0][i] = NULL; - this->tmp_datas[1][i] = NULL; - } -} - -static int ensure_tmp(struct impl *this) -{ - uint32_t maxsize = this->maxsize, maxports = this->maxports; - if (maxsize > this->scratch_size || maxports > this->scratch_ports) { - float *empty, *scratch, *tmp[2]; - uint32_t i; - - spa_log_debug(this->log, "resize tmp %d -> %d", this->scratch_size, maxsize); - - if ((empty = realloc(this->empty, maxsize + MAX_ALIGN)) != NULL) - this->empty = empty; - if ((scratch = realloc(this->scratch, maxsize + MAX_ALIGN)) != NULL) - this->scratch = scratch; - if ((tmp[0] = realloc(this->tmp[0], (maxsize + MAX_ALIGN) * maxports)) != NULL) - this->tmp[0] = tmp[0]; - if ((tmp[1] = realloc(this->tmp[1], (maxsize + MAX_ALIGN) * maxports)) != NULL) - this->tmp[1] = tmp[1]; - - if (empty == NULL || scratch == NULL || tmp[0] == NULL || tmp[1] == NULL) { - free_tmp(this); - return -ENOMEM; - } - memset(this->empty, 0, maxsize + MAX_ALIGN); - this->scratch_size = maxsize; - this->scratch_ports = maxports; - - for (i = 0; i < maxports; i++) { - this->tmp_datas[0][i] = SPA_PTROFF(tmp[0], maxsize * i, void); - this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp_datas[0][i], MAX_ALIGN, void); - this->tmp_datas[1][i] = SPA_PTROFF(tmp[1], maxsize * i, void); - this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp_datas[1][i], MAX_ALIGN, void); - } - } - return 0; -} - static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t size, uint32_t queued) { uint32_t delay, match_size; From 7e67daa2925b92a4c7dcde8669a31941f2880b8e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 24 Mar 2025 11:39:20 +0100 Subject: [PATCH 0056/1014] audioadapter: negotiate formats from output to input Try to avoid conversions by taking the output port format and using that as a filter for the input port format. Because filtering pods prefer the values of the filter, this will prefer the output format values and thus avoid conversions. --- spa/plugins/audioconvert/audioadapter.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index d03807e7c..28f1eee7e 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -924,6 +924,7 @@ static int negotiate_format(struct impl *this) uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; + struct spa_node *src, *dst; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); @@ -941,6 +942,15 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + if (this->direction == SPA_DIRECTION_INPUT) { + src = this->target; + dst = this->follower; + } else { + src = this->follower; + dst = this->target; + } + + /* first try the ideal converter format, which is likely passthrough */ tstate = 0; fres = node_port_enum_params_sync(this, this->target, @@ -960,8 +970,8 @@ static int negotiate_format(struct impl *this) /* then try something the follower can accept */ for (fstate = 0;;) { format = NULL; - res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, + res = node_port_enum_params_sync(this, src, + SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -971,8 +981,8 @@ static int negotiate_format(struct impl *this) break; tstate = 0; - fres = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + fres = node_port_enum_params_sync(this, dst, + SPA_DIRECTION_INPUT, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) From 824354f38efc8aa395c8e6434847e14cd1ec220f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 24 Mar 2025 11:45:11 +0100 Subject: [PATCH 0057/1014] videoadapter: sync with audioadapter --- spa/plugins/videoconvert/videoadapter.c | 273 +++++++++++++++--------- 1 file changed, 167 insertions(+), 106 deletions(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 8c1c8de77..772245347 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -97,16 +98,42 @@ struct impl { unsigned int started:1; unsigned int ready:1; unsigned int async:1; - unsigned int passthrough:1; + enum spa_param_port_config_mode mode; unsigned int follower_removing:1; unsigned int in_recalc; unsigned int warned:1; unsigned int driver:1; + + int in_enum_sync; }; /** \endcond */ +static int node_enum_params_sync(struct impl *impl, struct spa_node *node, + uint32_t id, uint32_t *index, const struct spa_pod *filter, + struct spa_pod **param, struct spa_pod_builder *builder) +{ + int res; + impl->in_enum_sync++; + res = spa_node_enum_params_sync(node, id, index, filter, param, builder); + impl->in_enum_sync--; + return res; +} + +static int node_port_enum_params_sync(struct impl *impl, struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t *index, const struct spa_pod *filter, + struct spa_pod **param, struct spa_pod_builder *builder) +{ + int res; + impl->in_enum_sync++; + res = spa_node_port_enum_params_sync(node, direction, port_id, id, index, + filter, param, builder); + impl->in_enum_sync--; + return res; +} + static int follower_enum_params(struct impl *this, uint32_t id, uint32_t idx, @@ -117,14 +144,14 @@ static int follower_enum_params(struct impl *this, int res; if (result->next < 0x100000 && this->follower != this->target) { - if ((res = spa_node_enum_params_sync(this->target, + if ((res = node_enum_params_sync(this, this->target, id, &result->next, filter, &result->param, builder)) == 1) return res; result->next = 0x100000; } if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { result->next &= 0xfffff; - if ((res = spa_node_enum_params_sync(this->follower, + if ((res = node_enum_params_sync(this, this->follower, id, &result->next, filter, &result->param, builder)) == 1) { result->next |= 0x100000; return res; @@ -188,7 +215,7 @@ next: switch (id) { case SPA_PARAM_EnumPortConfig: case SPA_PARAM_PortConfig: - if (this->passthrough) { + if (this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough) { switch (result.index) { case 0: result.param = spa_pod_builder_add_object(&b.b, @@ -222,7 +249,7 @@ next: case SPA_PARAM_Format: case SPA_PARAM_Latency: case SPA_PARAM_Tag: - res = spa_node_port_enum_params_sync(this->follower, + res = node_port_enum_params_sync(this, this->follower, this->direction, 0, id, &result.next, filter, &result.param, &b.b); break; @@ -373,7 +400,7 @@ static int debug_params(struct impl *this, struct spa_node *node, state = 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); - res = spa_node_port_enum_params_sync(node, + res = node_port_enum_params_sync(this, node, direction, port_id, id, &state, NULL, ¶m, &b); @@ -415,7 +442,7 @@ static int negotiate_buffers(struct impl *this) state = 0; param = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, + if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { @@ -429,7 +456,7 @@ static int negotiate_buffers(struct impl *this) } state = 0; - if ((res = spa_node_port_enum_params_sync(this->target, + if ((res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { @@ -544,7 +571,7 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ /* format was changed to nearest compatible format */ - if ((res = spa_node_port_enum_params_sync(this->follower, + if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Format, &state, NULL, &fmt, &b)) != 1) @@ -610,7 +637,7 @@ static int recalc_latency(struct impl *this, struct spa_node *src, enum spa_dire while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if ((res = spa_node_port_enum_params_sync(src, + if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Latency, &index, NULL, ¶m, &b)) != 1) { param = NULL; @@ -651,7 +678,7 @@ static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_directio while (true) { void *tag_state = NULL; spa_pod_builder_reset(&b.b, &state); - if ((res = spa_node_port_enum_params_sync(src, + if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Tag, &index, NULL, ¶m, &b.b)) != 1) { param = NULL; @@ -673,13 +700,14 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m int res = 0; struct spa_hook l; bool passthrough = mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; + bool old_passthrough = this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); if (!passthrough && this->convert == NULL) return -ENOTSUP; - if (this->passthrough != passthrough) { + if (old_passthrough != passthrough) { if (passthrough) { /* remove converter split/merge ports */ configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); @@ -699,8 +727,9 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) return res; - if (this->passthrough != passthrough) { - this->passthrough = passthrough; + this->mode = mode; + + if (old_passthrough != passthrough) { if (passthrough) { /* add follower ports */ spa_zero(l); @@ -888,6 +917,7 @@ static int negotiate_format(struct impl *this) uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; + struct spa_node *src, *dst; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); @@ -905,15 +935,24 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + if (this->direction == SPA_DIRECTION_INPUT) { + src = this->target; + dst = this->follower; + } else { + src = this->follower; + dst = this->target; + } + + /* first try the ideal converter format, which is likely passthrough */ tstate = 0; - fres = spa_node_port_enum_params_sync(this->target, + fres = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &tstate, NULL, &format, &b); if (fres == 1) { fstate = 0; - res = spa_node_port_enum_params_sync(this->follower, + res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, &fstate, format, &format, &b); @@ -924,8 +963,8 @@ static int negotiate_format(struct impl *this) /* then try something the follower can accept */ for (fstate = 0;;) { format = NULL; - res = spa_node_port_enum_params_sync(this->follower, - this->direction, 0, + res = node_port_enum_params_sync(this, src, + SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -935,8 +974,8 @@ static int negotiate_format(struct impl *this) break; tstate = 0; - fres = spa_node_port_enum_params_sync(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + fres = node_port_enum_params_sync(this, dst, + SPA_DIRECTION_INPUT, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) @@ -1194,7 +1233,7 @@ static void convert_result(void *data, int seq, int res, uint32_t type, const vo { struct impl *this = data; - if (this->target == this->follower) + if (this->target == this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); @@ -1223,7 +1262,7 @@ static void follower_info(void *data, const struct spa_node_info *info) if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; - else + else this->direction = SPA_DIRECTION_OUTPUT; if (this->direction == SPA_DIRECTION_INPUT) { @@ -1371,7 +1410,7 @@ static void follower_result(void *data, int seq, int res, uint32_t type, const v { struct impl *this = data; - if (this->target != this->follower) + if (this->target != this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); @@ -1404,6 +1443,20 @@ static const struct spa_node_events follower_node_events = { .event = follower_event, }; +static void follower_probe_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + if (info->max_input_ports > 0) + this->direction = SPA_DIRECTION_INPUT; + else + this->direction = SPA_DIRECTION_OUTPUT; +} + +static const struct spa_node_events follower_probe_events = { + SPA_VERSION_NODE_EVENTS, + .info = follower_probe_info, +}; + static int follower_ready(void *data, int status) { struct impl *this = data; @@ -1551,30 +1604,52 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return spa_node_remove_port(this->target, direction, port_id); } -static int follower_port_enum_params(struct impl *this, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t idx, - struct spa_result_node_params *result, - const struct spa_pod *filter, - struct spa_pod_builder *builder) +static int +port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction direction, + uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; int res; - if (result->next < 0x100000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { - if ((res = spa_node_port_enum_params_sync(this->follower, direction, port_id, - id, &result->next, filter, &result->param, builder)) == 1) - return res; - result->next = 0x100000; - } - if (result->next < 0x200000 && - this->follower != this->target) { - result->next &= 0xfffff; - if ((res = spa_node_port_enum_params_sync(this->target, direction, port_id, - id, &result->next, filter, &result->param, builder)) == 1) { - result->next |= 0x100000; - return res; + uint32_t count = 0; + struct spa_result_node_params result; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + result.id = id; + result.next = start; +next: + result.index = result.next; + + if (result.next < 0x100000) { + /* Enumerate follower formats first, until we have enough or we run out */ + if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, + &result.next, filter, &result.param, &b)) != 1) { + if (res == 0 || res == -ENOENT) { + result.next = 0x100000; + goto next; + } else { + spa_log_error(this->log, "could not enum follower format: %s", spa_strerror(res)); + return res; + } + } + } else if (result.next < 0x200000) { + /* Then enumerate converter formats */ + result.next &= 0xfffff; + if ((res = node_port_enum_params_sync(this, this->convert, direction, port_id, id, + &result.next, filter, &result.param, &b)) != 1) { + return res; + } else { + result.next |= 0x100000; } - result->next = 0x200000; } + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count < num) + goto next; + return 0; } @@ -1585,12 +1660,6 @@ impl_node_port_enum_params(void *object, int seq, const struct spa_pod *filter) { struct impl *this = object; - uint8_t buffer[4096]; - spa_auto(spa_pod_dynamic_builder) b = { 0 }; - struct spa_pod_builder_state state; - struct spa_result_node_params result; - uint32_t count = 0; - int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -1598,37 +1667,15 @@ impl_node_port_enum_params(void *object, int seq, if (direction != this->direction) port_id++; - spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); - spa_pod_builder_get_state(&b.b, &state); + spa_log_debug(this->log, "%p: %d %u %u %u", this, seq, id, start, num); - result.id = id; - result.next = start; -next: - result.index = result.next; - - spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); - - spa_pod_builder_reset(&b.b, &state); - - switch (id) { - case SPA_PARAM_EnumFormat: - res = follower_port_enum_params(this, direction, port_id, - id, IDX_EnumFormat, &result, filter, &b.b); - break; - default: + /* We only need special handling for EnumFormat in convert mode */ + if (id == SPA_PARAM_EnumFormat && this->mode == SPA_PARAM_PORT_CONFIG_MODE_convert) + return port_enum_formats_for_convert(this, seq, direction, port_id, id, + start, num, filter); + else return spa_node_port_enum_params(this->target, seq, direction, port_id, id, - start, num, filter); - } - if (res != 1) - return res; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - count++; - - if (count != num) - goto next; - - return 0; + start, num, filter); } static int @@ -1819,14 +1866,38 @@ static const struct spa_node_methods impl_node = { .process = impl_node_process, }; -static int load_plugin_from(struct impl *this, const struct spa_dict *info, - const char *convertname, struct spa_handle **handle, struct spa_node **iface) +static int load_converter(struct impl *this, const struct spa_dict *info, + const struct spa_support *support, uint32_t n_support) { + const char* factory_name = NULL; struct spa_handle *hnd_convert = NULL; void *iface_conv = NULL; - hnd_convert = spa_plugin_loader_load(this->ploader, convertname, info); - if (!hnd_convert) - return -EINVAL; + struct spa_dict_item *items; + struct spa_dict cinfo; + char direction[16]; + uint32_t i; + + items = alloca((info->n_items + 1) * sizeof(struct spa_dict_item)); + cinfo = SPA_DICT(items, 0); + for (i = 0; i < info->n_items; i++) + items[cinfo.n_items++] = info->items[i]; + + snprintf(direction, sizeof(direction), "%s", + SPA_DIRECTION_REVERSE(this->direction) == SPA_DIRECTION_INPUT ? + "input" : "output"); + items[cinfo.n_items++] = SPA_DICT_ITEM("convert.direction", direction); + + factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); + if (factory_name == NULL) + factory_name = SPA_NAME_VIDEO_CONVERT".ffmpeg"; + + if (this->ploader) { + hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); + if (!hnd_convert) + return -EINVAL; + } else { + return -ENOTSUP; + } spa_handle_get_interface(hnd_convert, SPA_TYPE_INTERFACE_Node, &iface_conv); if (iface_conv == NULL) { @@ -1834,30 +1905,13 @@ static int load_plugin_from(struct impl *this, const struct spa_dict *info, return -EINVAL; } - *handle = hnd_convert; - *iface = iface_conv; + this->hnd_convert = hnd_convert; + this->convert = iface_conv; + this->convertname = strdup(factory_name); return 0; } -static int load_converter(struct impl *this, const struct spa_dict *info) -{ - int ret; - if (!this->ploader || !info) - return -EINVAL; - - const char* factory_name = spa_dict_lookup(info, "video.adapt.converter"); - - if (factory_name) { - ret = load_plugin_from(this, info, factory_name, &this->hnd_convert, &this->convert); - if (ret >= 0) { - this->convertname = strdup(factory_name); - return ret; - } - } - return 0; -} - static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; @@ -1915,6 +1969,7 @@ impl_init(const struct spa_handle_factory *factory, struct impl *this; const char *str; int ret; + struct spa_hook probe_listener; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -1949,7 +2004,12 @@ impl_init(const struct spa_handle_factory *factory, SPA_VERSION_NODE, &impl_node, this); - ret = load_converter(this, info); + /* just probe the ports to get the direction */ + spa_zero(probe_listener); + spa_node_add_listener(this->follower, &probe_listener, &follower_probe_events, this); + spa_hook_remove(&probe_listener); + + ret = load_converter(this, info, support, n_support); spa_log_info(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, this->convertname, this->hnd_convert, this->convert); if (ret < 0) @@ -1957,10 +2017,11 @@ impl_init(const struct spa_handle_factory *factory, if (this->convert == NULL) { this->target = this->follower; - this->passthrough = true; + this->mode = SPA_PARAM_PORT_CONFIG_MODE_passthrough; } else { this->target = this->convert; - this->passthrough = false; + /* the actual mode is selected below */ + this->mode = SPA_PARAM_PORT_CONFIG_MODE_none; } this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | From 367e756ebefd3581c1d0b613c2cbe3b516ed6005 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 24 Mar 2025 11:45:29 +0100 Subject: [PATCH 0058/1014] videoconvert-ffmpeg: remove debug --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index a46554417..14249383f 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1479,7 +1479,6 @@ static int port_set_format(void *object, clear_buffers(this, port); } else { struct spa_video_info info = { 0 }; - spa_debug_format(2, NULL, format); if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); From dda60fb374d37fbce33d3b035fc1fe0a5dbe5416 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 24 Mar 2025 17:54:32 +0100 Subject: [PATCH 0059/1014] pw-cat: prefer AU format when using stdin/stdout WAV is actually not usable for streaming output by sndfile. See #4629 --- src/tools/pw-cat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index f485a08f1..c81ae0f8c 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1343,7 +1343,7 @@ static void format_from_filename(SF_INFO *info, const char *filename) } } if (format == -1) - format = SF_FORMAT_WAV; + format = spa_streq(filename, "-") ? SF_FORMAT_AU : SF_FORMAT_WAV; if (format == SF_FORMAT_WAV && info->channels > 2) format = SF_FORMAT_WAVEX; From b238c9d7a110e2095db5fbb2b574c93026166344 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 24 Mar 2025 18:44:21 +0100 Subject: [PATCH 0060/1014] pw-cat: improve sndfile file format debug info Print the endianness, container name and the sample format nicely instead of dumping the hex values. --- src/tools/pw-cat.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index c81ae0f8c..36a78981e 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1436,6 +1436,21 @@ static int setup_encodedfile(struct data *data) } #endif +static const char *endianness_to_name(int format) +{ + switch (format & SF_FORMAT_ENDMASK) { + case SF_ENDIAN_FILE: + return "Default Endian"; + case SF_ENDIAN_LITTLE: + return "Little Endian"; + case SF_ENDIAN_BIG: + return "Big Endian"; + case SF_ENDIAN_CPU: + return "CPU Endian"; + } + return "unknown"; +} + static int setup_sndfile(struct data *data) { const struct format_info *fi = NULL; @@ -1473,9 +1488,21 @@ static int setup_sndfile(struct data *data) return -EIO; } - if (data->verbose) - fprintf(stderr, "sndfile: opened file \"%s\" format %08x channels:%d rate:%d\n", - data->filename, info.format, info.channels, info.samplerate); + if (data->verbose) { + SF_FORMAT_INFO ti, sti; + spa_zero(ti); + ti.format = info.format & SF_FORMAT_TYPEMASK; + if (sf_command(NULL, SFC_GET_FORMAT_INFO, &ti, sizeof(ti)) != 0) + ti.name = "unknown"; + spa_zero(sti); + sti.format = info.format & SF_FORMAT_SUBMASK; + if (sf_command(NULL, SFC_GET_FORMAT_INFO, &sti, sizeof(sti)) != 0) + sti.name = "unknown"; + + fprintf(stderr, "sndfile: opened file \"%s\" format \"%s %s %s\" channels:%d rate:%d\n", + data->filename, endianness_to_name(info.format), + ti.name, sti.name, info.channels, info.samplerate); + } if (data->channels > 0 && info.channels != (int)data->channels) { fprintf(stderr, "sndfile: given channels (%u) don't match file channels (%d)\n", data->channels, info.channels); @@ -1511,7 +1538,6 @@ static int setup_sndfile(struct data *data) /* try native format first, else decode to float */ if ((fi = format_info_by_sf_format(info.format)) == NULL) fi = format_info_by_sf_format(SF_FORMAT_FLOAT); - } if (fi == NULL) return -EIO; From 4b3be9cc9b735b088e58f636fbd4c9e8ad22d70e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 11:47:28 +0100 Subject: [PATCH 0061/1014] spa: merge Range and Step filter together They share all of the code and the step just has an extra check for the step values. --- spa/include/spa/pod/filter.h | 69 +++++++++++------------------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6c8a17d4a..caf962aae 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -200,7 +200,9 @@ spa_pod_filter_prop(struct spa_pod_builder *b, } if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) || - (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Range)) { + (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Range) || + (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || + (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { int n_copied = 0; /* copy all values inside the range */ for (j = 0, a1 = alt1, a2 = alt2; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { @@ -208,29 +210,14 @@ spa_pod_filter_prop(struct spa_pod_builder *b, continue; if (spa_pod_compare_value(type, a1, SPA_PTROFF(a2,size,void), size) > 0) continue; - spa_pod_builder_raw(b, a1, size); - n_copied++; - } - if (n_copied == 0) - return -EINVAL; - nc->body.type = SPA_CHOICE_Enum; - } - if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || - (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { - int n_copied = 0; - for (j = 0, a1 = alt1, a2 = alt2; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { - int res; - if (spa_pod_compare_value(type, a1, a2, size) < 0) - continue; - if (spa_pod_compare_value(type, a1, SPA_PTROFF(a2,size,void), size) > 0) - continue; - - res = spa_pod_filter_is_step_of(type, a1, SPA_PTROFF(a2,size*2,void), size); - if (res == 0) - continue; - if (res == -ENOTSUP) - return -EINVAL; + if (p2c == SPA_CHOICE_Step) { + int res = spa_pod_filter_is_step_of(type, a1, SPA_PTROFF(a2,size*2,void), size); + if (res == 0) + continue; + if (res == -ENOTSUP) + return -EINVAL; + } spa_pod_builder_raw(b, a1, size); n_copied++; @@ -241,14 +228,23 @@ spa_pod_filter_prop(struct spa_pod_builder *b, } if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) || - (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Enum)) { + (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Enum) || + (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || + (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; /* copy all values inside the range */ - for (k = 0, a1 = alt1, a2 = alt2; k < nalt2; k++, a2 = SPA_PTROFF(a2,size,void)) { + for (j = 0, a1 = alt1, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { if (spa_pod_compare_value(type, a2, a1, size) < 0) continue; if (spa_pod_compare_value(type, a2, SPA_PTROFF(a1,size,void), size) > 0) continue; + if (p1c == SPA_CHOICE_Step) { + int res = spa_pod_filter_is_step_of(type, a2, SPA_PTROFF(a1,size*2,void), size); + if (res == 0) + continue; + if (res == -ENOTSUP) + return -EINVAL; + } spa_pod_builder_raw(b, a2, size); n_copied++; } @@ -291,29 +287,6 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if ((p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || - (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { - int n_copied = 0; - for (j = 0, a1 = alt1, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a1,size,void)) { - int res; - if (spa_pod_compare_value(type, a2, a1, size) < 0) - continue; - if (spa_pod_compare_value(type, a2, SPA_PTROFF(a1,size,void), size) > 0) - continue; - - res = spa_pod_filter_is_step_of(type, a2, SPA_PTROFF(a1,size*2,void), size); - if (res == 0) - continue; - if (res == -ENOTSUP) - return -EINVAL; - - spa_pod_builder_raw(b, a2, size); - n_copied++; - } - if (n_copied == 0) - return -EINVAL; - nc->body.type = SPA_CHOICE_Enum; - } if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags) return -ENOTSUP; From 77b23c619e2e503d621b9d8d80c703cc226c93ba Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 11:58:53 +0100 Subject: [PATCH 0062/1014] spa: Improve Range/Step code readability Use min,max,step variables to store the choice values for better readability. --- spa/include/spa/pod/filter.h | 46 ++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index caf962aae..e6192601d 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -204,15 +204,19 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { int n_copied = 0; + void *min = alt2; + void *max = SPA_PTROFF(min,size,void); + void *step = SPA_PTROFF(max,size,void); + /* copy all values inside the range */ - for (j = 0, a1 = alt1, a2 = alt2; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { - if (spa_pod_compare_value(type, a1, a2, size) < 0) + for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { + if (spa_pod_compare_value(type, a1, min, size) < 0) continue; - if (spa_pod_compare_value(type, a1, SPA_PTROFF(a2,size,void), size) > 0) + if (spa_pod_compare_value(type, a1, max, size) > 0) continue; if (p2c == SPA_CHOICE_Step) { - int res = spa_pod_filter_is_step_of(type, a1, SPA_PTROFF(a2,size*2,void), size); + int res = spa_pod_filter_is_step_of(type, a1, step, size); if (res == 0) continue; if (res == -ENOTSUP) @@ -232,14 +236,18 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; + void *min = alt1; + void *max = SPA_PTROFF(min,size,void); + void *step = SPA_PTROFF(max,size,void); + /* copy all values inside the range */ - for (j = 0, a1 = alt1, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { - if (spa_pod_compare_value(type, a2, a1, size) < 0) + for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { + if (spa_pod_compare_value(type, a2, min, size) < 0) continue; - if (spa_pod_compare_value(type, a2, SPA_PTROFF(a1,size,void), size) > 0) + if (spa_pod_compare_value(type, a2, max, size) > 0) continue; if (p1c == SPA_CHOICE_Step) { - int res = spa_pod_filter_is_step_of(type, a2, SPA_PTROFF(a1,size*2,void), size); + int res = spa_pod_filter_is_step_of(type, a2, step, size); if (res == 0) continue; if (res == -ENOTSUP) @@ -257,18 +265,20 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Step)) { - if (spa_pod_compare_value(type, alt1, alt2, size) < 0) - spa_pod_builder_raw(b, alt2, size); - else - spa_pod_builder_raw(b, alt1, size); + void *min1 = alt1; + void *max1 = SPA_PTROFF(min1,size,void); + void *min2 = alt2; + void *max2 = SPA_PTROFF(min2,size,void); - alt1 = SPA_PTROFF(alt1,size,void); - alt2 = SPA_PTROFF(alt2,size,void); + /* max of min */ + if (spa_pod_compare_value(type, min1, min2, size) < 0) + min1 = min2; + /* min of max */ + if (spa_pod_compare_value(type, max2, max1, size) < 0) + max1 = max2; - if (spa_pod_compare_value(type, alt1, alt2, size) < 0) - spa_pod_builder_raw(b, alt1, size); - else - spa_pod_builder_raw(b, alt2, size); + spa_pod_builder_raw(b, min1, size); + spa_pod_builder_raw(b, max1, size); nc->body.type = SPA_CHOICE_Range; } From 64b2792d6137cde8d3e6ff411ae37d21fa875989 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 12:41:44 +0100 Subject: [PATCH 0063/1014] spa: refactor range/step check --- spa/include/spa/pod/filter.h | 44 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index e6192601d..25e3efb94 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -133,6 +133,17 @@ SPA_API_POD_FILTER int spa_pod_filter_is_step_of(uint32_t type, const void *r1, return 0; } +SPA_API_POD_FILTER int spa_pod_filter_is_in_range(uint32_t type, const void *v, + const void *min, const void *max, const void *step, uint32_t size SPA_UNUSED) +{ + if (spa_pod_compare_value(type, v, min, size) < 0 || + spa_pod_compare_value(type, v, max, size) > 0) + return 0; + if (step != NULL) + return spa_pod_filter_is_step_of(type, v, step, size); + return 1; +} + SPA_API_POD_FILTER int spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p1, @@ -144,6 +155,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; struct spa_pod_frame f; + int res; v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c); alt1 = SPA_POD_BODY(v1); @@ -206,23 +218,14 @@ spa_pod_filter_prop(struct spa_pod_builder *b, int n_copied = 0; void *min = alt2; void *max = SPA_PTROFF(min,size,void); - void *step = SPA_PTROFF(max,size,void); + void *step = p2c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; /* copy all values inside the range */ for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { - if (spa_pod_compare_value(type, a1, min, size) < 0) + if ((res = spa_pod_filter_is_in_range(type, a1, min, max, step, size)) < 0) + return res; + if (res == 0) continue; - if (spa_pod_compare_value(type, a1, max, size) > 0) - continue; - - if (p2c == SPA_CHOICE_Step) { - int res = spa_pod_filter_is_step_of(type, a1, step, size); - if (res == 0) - continue; - if (res == -ENOTSUP) - return -EINVAL; - } - spa_pod_builder_raw(b, a1, size); n_copied++; } @@ -238,21 +241,14 @@ spa_pod_filter_prop(struct spa_pod_builder *b, int n_copied = 0; void *min = alt1; void *max = SPA_PTROFF(min,size,void); - void *step = SPA_PTROFF(max,size,void); + void *step = p1c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; /* copy all values inside the range */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { - if (spa_pod_compare_value(type, a2, min, size) < 0) + if ((res = spa_pod_filter_is_in_range(type, a2, min, max, step, size)) < 0) + return res; + if (res == 0) continue; - if (spa_pod_compare_value(type, a2, max, size) > 0) - continue; - if (p1c == SPA_CHOICE_Step) { - int res = spa_pod_filter_is_step_of(type, a2, step, size); - if (res == 0) - continue; - if (res == -ENOTSUP) - return -EINVAL; - } spa_pod_builder_raw(b, a2, size); n_copied++; } From fff8f63c0ec4a404f2e930db380861fb35fca340 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 12:50:30 +0100 Subject: [PATCH 0064/1014] spa: reject impossible ranges after filtering --- spa/include/spa/pod/filter.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 25e3efb94..100463ac1 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -273,6 +273,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (spa_pod_compare_value(type, max2, max1, size) < 0) max1 = max2; + /* reject impossible range */ + if (spa_pod_compare_value(type, max1, min1, size) < 0) + return -EINVAL; + spa_pod_builder_raw(b, min1, size); spa_pod_builder_raw(b, max1, size); From 7da66f8a5fb28b5b98ef92919bdf3976b8a9e5af Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 11:40:25 +0100 Subject: [PATCH 0065/1014] spa: simplify filtering --- spa/include/spa/pod/filter.h | 81 +++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 100463ac1..6d09afeed 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -169,46 +169,34 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (type != v2->type || size != v2->size || p1->key != p2->key) return -EINVAL; - if (p1c == SPA_CHOICE_None || p1c == SPA_CHOICE_Flags) { - nalt1 = 1; - } else { - alt1 = SPA_PTROFF(alt1, size, void); - nalt1--; - } - - if (p2c == SPA_CHOICE_None || p2c == SPA_CHOICE_Flags) { - nalt2 = 1; - } else { - alt2 = SPA_PTROFF(alt2, size, void); - nalt2--; - } - /* start with copying the property */ spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); spa_pod_builder_push_choice(b, &f, 0, 0); nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f); - /* default value */ - spa_pod_builder_primitive(b, v2); + /* we should prefer alt2 values but only if they are within the + * range, start with an empty child and we will select a good default + * below */ + spa_pod_builder_child(b, size, type); if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; - /* copy all equal values but don't copy the default value again */ + /* copy all equal values. Start with alt2 so that they are prefered. */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) { for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) { if (spa_pod_compare_value(type, a1, a2, size) == 0) { - if (p2c == SPA_CHOICE_Enum || j > 0) + if (n_copied++ == 0) spa_pod_builder_raw(b, a1, size); - n_copied++; + spa_pod_builder_raw(b, a1, size); } } } if (n_copied == 0) return -EINVAL; - nc->body.type = SPA_CHOICE_Enum; + nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) || @@ -216,22 +204,35 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { int n_copied = 0; - void *min = alt2; + void *min = SPA_PTROFF(alt2,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p2c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; + /* we should prefer the alt2 range default value but only if valid */ + for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { + if (spa_pod_compare_value(type, alt2, min, size) < 0 || + spa_pod_compare_value(type, alt2, max, size) > 0) + break; + if (spa_pod_compare_value(type, a1, alt2, size) == 0) { + /* it is in the enum, use as default then */ + spa_pod_builder_raw(b, a1, size); + n_copied++; + break; + } + } /* copy all values inside the range */ for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { if ((res = spa_pod_filter_is_in_range(type, a1, min, max, step, size)) < 0) return res; if (res == 0) continue; + if (n_copied++ == 0) + spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); - n_copied++; } if (n_copied == 0) return -EINVAL; - nc->body.type = SPA_CHOICE_Enum; + nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) || @@ -239,31 +240,33 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { int n_copied = 0; - void *min = alt1; + void *min = SPA_PTROFF(alt1,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p1c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; - /* copy all values inside the range */ + /* copy all values inside the range, this will automatically prefer + * a valid alt2 value */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { if ((res = spa_pod_filter_is_in_range(type, a2, min, max, step, size)) < 0) return res; if (res == 0) continue; + if (n_copied++ == 0) + spa_pod_builder_raw(b, a2, size); spa_pod_builder_raw(b, a2, size); - n_copied++; } if (n_copied == 0) return -EINVAL; - nc->body.type = SPA_CHOICE_Enum; + nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Step)) { - void *min1 = alt1; + void *min1 = SPA_PTROFF(alt1,size,void); void *max1 = SPA_PTROFF(min1,size,void); - void *min2 = alt2; + void *min2 = SPA_PTROFF(alt2,size,void); void *max2 = SPA_PTROFF(min2,size,void); /* max of min */ @@ -277,9 +280,23 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (spa_pod_compare_value(type, max1, min1, size) < 0) return -EINVAL; + /* prefer alt2 if in new range */ + a1 = alt2; + if ((res = spa_pod_filter_is_in_range(type, a1, min1, max1, NULL, size)) < 0) + return res; + if (res == 0) { + /* try alt1 otherwise */ + a1 = alt1; + if ((res = spa_pod_filter_is_in_range(type, a1, min1, max1, NULL, size)) < 0) + return res; + /* fall back to new min value then */ + if (res == 0) + a1 = min1; + } + + spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, min1, size); spa_pod_builder_raw(b, max1, size); - nc->body.type = SPA_CHOICE_Range; } @@ -293,13 +310,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Range) return -ENOTSUP; if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Step) @@ -308,7 +322,6 @@ spa_pod_filter_prop(struct spa_pod_builder *b, return -ENOTSUP; spa_pod_builder_pop(b, &f); - spa_pod_choice_fix_default(nc); return 0; } From 14b7b31bd9ef6c525b39bfa5e7cefbad40e27cfd Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 24 Mar 2025 15:43:37 -0400 Subject: [PATCH 0066/1014] videoadapter: Use dummy converter by default The ffmpeg converter doesn't quite work yet for planar formats at least, so let's leave the dummy as the default till that works. --- spa/plugins/videoconvert/videoadapter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 772245347..c3406eee0 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1889,7 +1889,7 @@ static int load_converter(struct impl *this, const struct spa_dict *info, factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); if (factory_name == NULL) - factory_name = SPA_NAME_VIDEO_CONVERT".ffmpeg"; + factory_name = SPA_NAME_VIDEO_CONVERT".dummy"; if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); From e584cee0666122cb3fab1209233ec873bf5cfeb2 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Tue, 25 Mar 2025 10:16:47 +0000 Subject: [PATCH 0067/1014] gst: src: Fix buffer pool handling in case of caps renegotiation In case negotiation is first attempted with unfixed caps, bufferpool support was unconditionally disabled. Then at a second caps negotiation attempt it wasn't restored according to the property value. --- src/gst/gstpipewiresrc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index c9a5d43cd..5c77b76e2 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1135,14 +1135,15 @@ handle_format_change (GstPipeWireSrc *pwsrc, #ifdef HAVE_GSTREAMER_DMA_DRM } #endif + } else { + /* Don't provide bufferpool for audio if not requested by the + * application/user */ + if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) + pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; } } else { pwsrc->negotiated = FALSE; pwsrc->is_video = FALSE; - - /* Don't provide bufferpool for audio if not requested by the application/user */ - if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) - pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; } if (pwsrc->caps) { From 4ddb17c4b5a6da5cc22dc1a3b64262c02bfcd893 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 15:14:03 +0100 Subject: [PATCH 0068/1014] videoconvert: don't load the dummy converter Just don't load any converter at all, the dummy converter has some issues. --- spa/plugins/videoconvert/videoadapter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index c3406eee0..615598e3e 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1889,7 +1889,7 @@ static int load_converter(struct impl *this, const struct spa_dict *info, factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); if (factory_name == NULL) - factory_name = SPA_NAME_VIDEO_CONVERT".dummy"; + return 0; if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); From 437e486d6e810a80f2dfa125651e33bcbdb95ca7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 16:17:38 +0100 Subject: [PATCH 0069/1014] spa: improve filter Enum and None results When we construct an Enum, check if we only added 1 value and remove the duplicate default value we added. If we added more values, promote the choice to an enum. --- spa/include/spa/pod/builder.h | 8 ++++++ spa/include/spa/pod/filter.h | 50 +++++++++++++++-------------------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 553f75512..231c1ebf2 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -156,6 +156,14 @@ SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, con return res; } +SPA_API_POD_BUILDER void spa_pod_builder_remove(struct spa_pod_builder *builder, uint32_t size) +{ + struct spa_pod_frame *f; + builder->state.offset -= size; + for (f = builder->state.frame; f ; f = f->parent) + f->pod.size -= size; +} + SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) { uint64_t zeroes = 0; diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6d09afeed..16e3b0b43 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -155,7 +155,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; struct spa_pod_frame f; - int res; + int res, n_copied = 0; v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c); alt1 = SPA_POD_BODY(v1); @@ -183,7 +183,6 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) { - int n_copied = 0; /* copy all equal values. Start with alt2 so that they are prefered. */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) { for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) { @@ -194,16 +193,11 @@ spa_pod_filter_prop(struct spa_pod_builder *b, } } } - if (n_copied == 0) - return -EINVAL; - nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } - - if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) || + else if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { - int n_copied = 0; void *min = SPA_PTROFF(alt2,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p2c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; @@ -230,16 +224,11 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); } - if (n_copied == 0) - return -EINVAL; - nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } - - if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) || + else if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { - int n_copied = 0; void *min = SPA_PTROFF(alt1,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p1c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; @@ -255,12 +244,8 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_raw(b, a2, size); spa_pod_builder_raw(b, a2, size); } - if (n_copied == 0) - return -EINVAL; - nc->body.type = n_copied == 1 ? SPA_CHOICE_None : SPA_CHOICE_Enum; } - - if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Range) || + else if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Step)) { @@ -299,28 +284,37 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_raw(b, max1, size); nc->body.type = SPA_CHOICE_Range; } - - if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Flags) || + else if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Flags) || (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Flags)) { if (spa_pod_filter_flags_value(b, type, alt1, alt2, size) != 1) return -EINVAL; nc->body.type = SPA_CHOICE_Flags; } - - if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags) + else if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags) + else if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags) + else if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags) return -ENOTSUP; - if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Range) + else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Range) return -ENOTSUP; - if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Step) + else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Step) return -ENOTSUP; - if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Enum) + else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Enum) return -ENOTSUP; + if (nc->body.type == SPA_CHOICE_None) { + if (n_copied == 0) { + return -EINVAL; + } else if (n_copied == 1) { + /* we always copy the default value twice, so remove it + * again when it was the only one added */ + spa_pod_builder_remove(b, size); + } else if (n_copied > 1) { + nc->body.type = SPA_CHOICE_Enum; + } + } spa_pod_builder_pop(b, &f); return 0; From 1515e46d50d4df14de297c5ffe968dffd79325f1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 25 Mar 2025 16:19:50 +0100 Subject: [PATCH 0070/1014] ffmeg: fix default number of buffers Make the MAX buffers different from the min and make sure the default value is between min and max. --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 14249383f..f255429bb 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -43,7 +43,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoconvert.ffmpeg"); #define MAX_ALIGN 64u -#define MAX_BUFFERS 32 +#define MAX_BUFFERS 32u #define MAX_DATAS 4 #define MAX_PORTS (1+1) @@ -1218,7 +1218,7 @@ impl_node_port_enum_params(void *object, int seq, break; case SPA_PARAM_Buffers: { - uint32_t size, min, max; + uint32_t size, min, max, def; if (!port->have_format) return -EIO; @@ -1233,15 +1233,16 @@ impl_node_port_enum_params(void *object, int seq, other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); if (other->n_buffers > 0) { - min = max = other->n_buffers; + min = other->n_buffers; } else { min = 2; - max = MAX_BUFFERS; } + max = MAX_BUFFERS; + def = SPA_CLAMP(8u, min, MAX_BUFFERS); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, min, max), + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(def, min, max), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( size * port->stride, From d37b0b4cd29b8c0becdeb20a028697a58d3ceb2a Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Sat, 22 Mar 2025 18:33:12 +0000 Subject: [PATCH 0071/1014] gst: deviceprovider: Fix a leak and a heap-use-after-free The device passed to gst_device_provider_device_add() is transfer:floating, so we need increase its ref, otherwise the pointer we keep internally will be a dangling ref. Also gst_device_provider_device_remove() doesn't actually release the device, so we have to do it ourselves. Fixes #4616 --- src/gst/gstpipewiredeviceprovider.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c index c9d0d7ad7..5bdd09b7d 100644 --- a/src/gst/gstpipewiredeviceprovider.c +++ b/src/gst/gstpipewiredeviceprovider.c @@ -327,6 +327,7 @@ static void do_add_nodes(GstPipeWireDeviceProvider *self) gst_object_ref_sink (device), compare_device_session_priority); } else { + gst_object_ref (device); gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device); } } @@ -484,7 +485,8 @@ destroy_node (void *data) } if (nd->dev != NULL) { - gst_device_provider_device_remove (provider, GST_DEVICE (nd->dev)); + gst_device_provider_device_remove (provider, nd->dev); + gst_clear_object (&nd->dev); } if (nd->caps) gst_caps_unref(nd->caps); From eb534b4515ceac27fa5598f13cd5da9dff334c3a Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 19 Mar 2025 19:54:46 +0530 Subject: [PATCH 0072/1014] gst: pipewiresrc: Fixate caps if intersect did not return fixated caps We might end up in a situation where depending on the pipeline, intersect might not give us fixated caps. Possible example of such a pipeline can be below. gst-launch-1.0 -e pipewiresrc target-object= ! audioconvert ! audio/x-raw,format=S16LE,rate=48000,channels=2 ! lamemp3enc ! filesink location=test.mp3 This results in non-fixated caps like below when intersecting caps from format param and possible_caps which depends on what we have downstream in the pipeline. audio/x-raw, layout=(string)interleaved, format=(string)S16LE, rate=(int)48000, channels=(int)2, channel-mask=(bitmask)0x0000000000000003; audio/x-raw, layout=(string)interleaved, format=(string)S16LE, rate=(int)48000, channels=(int)2 To fix this, fixate the caps explicitly. --- src/gst/gstpipewiresrc.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 5c77b76e2..1570c83e9 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1099,14 +1099,34 @@ handle_format_change (GstPipeWireSrc *pwsrc, } pw_peer_caps = gst_caps_from_format (param); + if (pw_peer_caps && pwsrc->possible_caps) { + GST_DEBUG_OBJECT (pwsrc, "peer caps %" GST_PTR_FORMAT, pw_peer_caps); + GST_DEBUG_OBJECT (pwsrc, "possible caps %" GST_PTR_FORMAT, pwsrc->possible_caps); + pwsrc->caps = gst_caps_intersect_full (pw_peer_caps, pwsrc->possible_caps, GST_CAPS_INTERSECT_FIRST); + + /* + * We expect pw_peer_caps to be fixed caps as we receive that from + * PipeWire. See pw_context_find_format() and SPA_PARAM_Format. + * possible_caps can be non-fixated caps based on what is downstream + * in the pipeline. + * + * The intersection result above might give us non-fixated caps. A + * possible scenario for this is the below pipeline. + * pipewiresrc ! audioconvert ! audio/x-raw,rate=44100,channels=2 ! .. + * + * So we fixate the caps explicitly here. + */ + pwsrc->caps = gst_caps_fixate (pwsrc->caps); gst_caps_maybe_fixate_dma_format (pwsrc->caps); } - if (pwsrc->caps && gst_caps_is_fixed (pwsrc->caps)) { + if (pwsrc->caps) { + g_return_if_fail (gst_caps_is_fixed (pwsrc->caps)); + pwsrc->negotiated = TRUE; structure = gst_caps_get_structure (pwsrc->caps, 0); From 4ae348254e98980fea4d0cc1b287740b275b388e Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 25 Mar 2025 11:58:27 -0400 Subject: [PATCH 0073/1014] ci: Bump to latest ci-templates --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3953445e5..194446609 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,8 +18,8 @@ stages: variables: FDO_UPSTREAM_REPO: 'pipewire/pipewire' -# ci-templates as of Jan 27th 2022 -.templates_sha: &templates_sha 0c312d9c7255f46e741d43bcd1930f09cd12efe7 +# ci-templates as of Mar 25th 2024 +.templates_sha: &templates_sha ef5e4669b7500834a17ffe9277e15fbb6d977fff include: - project: 'freedesktop/ci-templates' @@ -35,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2024-12-10.0' + FDO_DISTRIBUTION_TAG: '2025-03-25.0' FDO_DISTRIBUTION_VERSION: '40' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -101,7 +101,7 @@ include: .ubuntu: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2024-01-12.0' + FDO_DISTRIBUTION_TAG: '2025-03-25.0' FDO_DISTRIBUTION_VERSION: '22.04' FDO_DISTRIBUTION_PACKAGES: >- debhelper-compat @@ -141,7 +141,7 @@ include: .alpine: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2024-09-20.0' + FDO_DISTRIBUTION_TAG: '2025-03-25.0' FDO_DISTRIBUTION_VERSION: '3.20' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-dev From 3c62d29a55740ab9152b67323a5091876cb63f9b Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 25 Mar 2025 16:24:48 +0530 Subject: [PATCH 0074/1014] gstpipewiresrc: Fix re-linking When using PW source, one might want to dynamically link PW source to a different source. Setting possible_caps to NULL prevents the caps intersect from returning a successful result on format change. Do not set possible_caps to NULL as we get that from peer caps which should stay the same ideally for the duration of pipeline run. That allows re-linking PW source any number of times with a pipeline like below. gst-launch-1.0 pipewiresrc autoconnect=false ! queue ! video/x-raw,format=YUY2 ! videoconvert ! xvimagesink The above pipeline can be made to switch between a camera source and a screen capture source like wf-recorder. Note that this fix only improves the status quo and won't work if the peer caps change due to a re-negotiation. --- src/gst/gstpipewiresrc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 1570c83e9..f8e8669d6 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -990,7 +990,7 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) GST_DEBUG_OBJECT (basesrc, "connect capture with path %s, target-object %s", pwsrc->stream->path, pwsrc->stream->target_object); - pwsrc->possible_caps = possible_caps; + pwsrc->possible_caps = gst_caps_ref (possible_caps); pwsrc->negotiated = FALSE; enum pw_stream_flags flags; @@ -1027,7 +1027,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) } negotiated_caps = g_steal_pointer (&pwsrc->caps); - pwsrc->possible_caps = NULL; pw_thread_loop_unlock (pwsrc->stream->core->loop); if (negotiated_caps == NULL) @@ -1472,6 +1471,7 @@ gst_pipewire_src_stop (GstBaseSrc * basesrc) pwsrc->eos = false; gst_buffer_replace (&pwsrc->last_buffer, NULL); gst_caps_replace(&pwsrc->caps, NULL); + gst_caps_replace(&pwsrc->possible_caps, NULL); pwsrc->transform_value = UINT32_MAX; pw_thread_loop_unlock (pwsrc->stream->core->loop); From 439d5d04feeb3c22d1ce660185300f2e4c32c38a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 24 Mar 2025 22:11:36 -0400 Subject: [PATCH 0075/1014] gst: sink: Don't provide clock in provide mode Counter-intuitive as it seems, when we are driving the clock, we can't also provide a clock from PipeWire to the pipeline -- we need the pipeline to drive the graph. So we make the mode control whether we provide a clock or not. --- src/gst/gstpipewiresink.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index f0aa0c2c0..fc067a275 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -242,7 +242,8 @@ gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass) GST_TYPE_PIPEWIRE_SINK_MODE, DEFAULT_PROP_MODE, G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); + G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); g_object_class_install_property (gobject_class, PROP_FD, @@ -1037,6 +1038,12 @@ gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) goto open_failed; break; case GST_STATE_CHANGE_READY_TO_PAUSED: + /* If we are a driver, we shouldn't try to also provide the clock, as we + * _are_ the clock for the graph. For that case, we rely on the pipeline + * clock to drive the pipeline (and thus the graph). */ + if (this->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE) + GST_OBJECT_FLAG_UNSET (this, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + /* the initial stream state is active, which is needed for linking and * negotiation to happen and the bufferpool to be set up. We don't know * if we'll go to plaing, so we deactivate the stream until that From aea77dc05571f8fc629a60f7281352c350778342 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 09:41:45 +0100 Subject: [PATCH 0076/1014] spa: avoid duplicate default value When filtering an enum/none against a range/step, make sure we don't add the default value twice. --- spa/include/spa/pod/filter.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 16e3b0b43..4160a7e0f 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -201,6 +201,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, void *min = SPA_PTROFF(alt2,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p2c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; + bool found_def = false; /* we should prefer the alt2 range default value but only if valid */ for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { @@ -210,7 +211,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (spa_pod_compare_value(type, a1, alt2, size) == 0) { /* it is in the enum, use as default then */ spa_pod_builder_raw(b, a1, size); - n_copied++; + found_def = true; break; } } @@ -220,7 +221,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, return res; if (res == 0) continue; - if (n_copied++ == 0) + if (n_copied++ == 0 && !found_def) spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); } From 6015fa353a494866ff6049e0bb12db06fa03ac70 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 09:43:58 +0100 Subject: [PATCH 0077/1014] adapter: call reconfigure_mode instead of configure_convert configure_convert does not set up the current mode, so call the more complete reconfigure_mode to set the initial converter mode. --- spa/plugins/audioconvert/audioadapter.c | 5 +++-- spa/plugins/videoconvert/videoadapter.c | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 28f1eee7e..651b842a0 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -2172,8 +2172,9 @@ impl_init(const struct spa_handle_factory *factory, &this->convert_listener, &convert_node_events, this); if (info && (str = spa_dict_lookup(info, "adapter.auto-port-config")) != NULL) do_auto_port_config(this, str); - else - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + else { + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_dsp, this->direction, NULL); + } } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 615598e3e..63502feb4 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -2053,10 +2053,9 @@ impl_init(const struct spa_handle_factory *factory, &this->convert_listener, &convert_node_events, this); if (strcmp(this->convertname, "video.convert.dummy") == 0) { - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } else { - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_convert); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, this->direction, NULL); } } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); From beb075c5a6357b7e3f5b17075fec47fb386ce0b6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 09:51:09 +0100 Subject: [PATCH 0078/1014] adapter: only use DYNAMIC buffer data when supported --- spa/plugins/audioconvert/audioadapter.c | 10 ++++++++-- spa/plugins/videoconvert/videoadapter.c | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 651b842a0..92474a636 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -428,7 +428,7 @@ static int negotiate_buffers(struct impl *this) int res; bool follower_alloc, conv_alloc; uint32_t i, size, buffers, blocks, align, flags, stride = 0; - uint32_t *aligns; + uint32_t *aligns, data_flags; struct spa_data *datas; uint64_t follower_flags, conv_flags; @@ -505,9 +505,15 @@ static int negotiate_buffers(struct impl *this) datas = alloca(sizeof(struct spa_data) * blocks); memset(datas, 0, sizeof(struct spa_data) * blocks); aligns = alloca(sizeof(uint32_t) * blocks); + + data_flags = SPA_DATA_FLAG_READWRITE; + if (SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_DYNAMIC_DATA) && + SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) + data_flags |= SPA_DATA_FLAG_DYNAMIC; + for (i = 0; i < blocks; i++) { datas[i].type = SPA_DATA_MemPtr; - datas[i].flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_DYNAMIC; + datas[i].flags = data_flags; datas[i].maxsize = size; aligns[i] = align; } diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 63502feb4..b23193a34 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -428,7 +428,7 @@ static int negotiate_buffers(struct impl *this) int res; bool follower_alloc, conv_alloc; uint32_t i, size, buffers, blocks, align, flags, stride = 0; - uint32_t *aligns; + uint32_t *aligns, data_flags; struct spa_data *datas; uint64_t follower_flags, conv_flags; @@ -505,9 +505,15 @@ static int negotiate_buffers(struct impl *this) datas = alloca(sizeof(struct spa_data) * blocks); memset(datas, 0, sizeof(struct spa_data) * blocks); aligns = alloca(sizeof(uint32_t) * blocks); + + data_flags = SPA_DATA_FLAG_READWRITE; + if (SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_DYNAMIC_DATA) && + SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) + data_flags |= SPA_DATA_FLAG_DYNAMIC; + for (i = 0; i < blocks; i++) { datas[i].type = SPA_DATA_MemPtr; - datas[i].flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_DYNAMIC; + datas[i].flags = data_flags; datas[i].maxsize = size; aligns[i] = align; } From 5fb9716ce72b680cb622766d38a1f4d057a4ed06 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 09:52:22 +0100 Subject: [PATCH 0079/1014] gst: require a buffer size of at least 1 Setting the default size to 0 and outside of the min/max range now means that there is no suggestion for the size and it should use the suggestion of the peer. --- src/gst/gstpipewiresrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index f8e8669d6..94d715570 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1186,7 +1186,7 @@ handle_format_change (GstPipeWireSrc *pwsrc, pwsrc->min_buffers, pwsrc->max_buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes)); From 636e123fddc633eff1fbbf4dd72f77bc39985fe7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 09:54:12 +0100 Subject: [PATCH 0080/1014] videoconvert-ffmpeg: set better size suggestion Use a better suggestion for the buffer size. --- .../videoconvert/videoconvert-ffmpeg.c | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index f255429bb..eab5d5389 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -98,6 +98,7 @@ struct port { uint32_t blocks; uint32_t stride; + uint32_t size; uint32_t maxsize; struct spa_list queue; @@ -1228,7 +1229,7 @@ impl_node_port_enum_params(void *object, int seq, if (PORT_IS_DSP(this, direction, port_id)) { size = 1024 * 1024 * 16; } else { - size = 1024 * 1024 * 4; + size = port->size; } other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); @@ -1245,10 +1246,9 @@ impl_node_port_enum_params(void *object, int seq, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(def, min, max), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + size, 16, INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int( + port->stride, 0, INT32_MAX)); break; } case SPA_PARAM_Meta: @@ -1529,9 +1529,21 @@ static int port_set_format(void *object, spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } - pix_fmt = format_to_pix_fmt(info.info.raw.format); - av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); - port->stride = linesizes[0]; + switch (info.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + pix_fmt = format_to_pix_fmt(info.info.raw.format); + av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); + port->stride = linesizes[0]; + port->size = port->stride * info.info.raw.size.height; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + port->stride = 0; + port->size = info.info.mjpg.size.width * info.info.mjpg.size.height; + break; + default: + spa_log_error(this->log, "unsupported subtype %d", info.media_subtype); + return -ENOTSUP; + } port->blocks = 1; dir->format = info; dir->have_format = true; @@ -1678,8 +1690,12 @@ impl_node_port_use_buffers(void *object, if (other->n_buffers <= 0) return -EIO; + + for (j = 0; j < n_datas; j++) { + b->datas[j] = other->buffers[i % other->n_buffers].datas[j]; + maxsize = SPA_MAX(maxsize, d[j].maxsize); + } *b->buf = *other->buffers[i % other->n_buffers].buf; - b->datas[0] = other->buffers[i % other->n_buffers].datas[0]; } else { for (j = 0; j < n_datas; j++) { void *data = d[j].data; @@ -1882,7 +1898,6 @@ static int impl_node_process(void *object) datas[0] = this->encoder.packet->data; sizes[0] = this->encoder.packet->size; strides[0] = 1; - } else { datas[0] = f->data[0]; strides[0] = f->linesize[0]; From ea7cfb9e94d03376fa8e8965c9be8cc45fa50907 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 10:26:41 +0100 Subject: [PATCH 0081/1014] audioconvert: make sure the converter is in None mode The audioconverter starts in Convert mode, so make sure it goes to the None mode before we attempt to reconfigure ourselves. Also remove the ports on audioconvert when going to None mode. This used to somewhat work because we configured it in DSP mode without any params, which is like None without ports. --- spa/plugins/audioconvert/audioadapter.c | 3 ++- spa/plugins/audioconvert/audioconvert.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 92474a636..631c0bff1 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -2149,6 +2149,7 @@ impl_init(const struct spa_handle_factory *factory, this->target = this->convert; /* the actual mode is selected below */ this->mode = SPA_PARAM_PORT_CONFIG_MODE_none; + configure_convert(this, this->mode); } this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | @@ -2179,7 +2180,7 @@ impl_init(const struct spa_handle_factory *factory, if (info && (str = spa_dict_lookup(info, "adapter.auto-port-config")) != NULL) do_auto_port_config(this, str); else { - reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_dsp, this->direction, NULL); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, this->direction, NULL); } } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 4e52b6cef..84813e12b 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1775,6 +1775,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m break; } case SPA_PARAM_PORT_CONFIG_MODE_none: + dir->n_ports = 0; break; default: return -ENOTSUP; From 241ec04d88927254dd55da8211b0b5fbc1d641da Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 12:29:26 +0100 Subject: [PATCH 0082/1014] videoconvert: support planar formats Keep track of the per-place strides and sizes and use that to fill the output buffer. --- .../videoconvert/videoconvert-ffmpeg.c | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index eab5d5389..122ab7db8 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -119,6 +119,9 @@ struct dir { int width; int height; + ptrdiff_t linesizes[4]; + size_t size[4]; + unsigned int control:1; }; @@ -1518,7 +1521,6 @@ static int port_set_format(void *object, struct dir *dir = &this->dir[direction]; struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; enum AVPixelFormat pix_fmt; - int linesizes[4]; if (info.media_type != SPA_MEDIA_TYPE_video) { spa_log_error(this->log, "unexpected types %d/%d", @@ -1531,20 +1533,36 @@ static int port_set_format(void *object, } switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: + { + int linesizes[4]; + pix_fmt = format_to_pix_fmt(info.info.raw.format); av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); port->stride = linesizes[0]; - port->size = port->stride * info.info.raw.size.height; + port->blocks = 0; + for (int i = 0; i < 4; i++) { + dir->linesizes[i] = linesizes[i]; + if (linesizes[i]) + port->blocks++; + } + av_image_fill_plane_sizes(dir->size, + pix_fmt, info.info.raw.size.height, + dir->linesizes); + port->size = av_image_get_buffer_size(pix_fmt, + info.info.raw.size.width, + info.info.raw.size.height, this->max_align); + break; + } case SPA_MEDIA_SUBTYPE_mjpg: port->stride = 0; port->size = info.info.mjpg.size.width * info.info.mjpg.size.height; + port->blocks = 1; break; default: spa_log_error(this->log, "unsupported subtype %d", info.media_subtype); return -ENOTSUP; } - port->blocks = 1; dir->format = info; dir->have_format = true; if (odir->have_format) { @@ -1868,8 +1886,10 @@ static int impl_node_process(void *object) f->format = in->pix_fmt; f->width = in->width; f->height = in->height; - f->data[0] = sbuf->datas[0]; - f->linesize[0] = sbuf->buf->datas[0].chunk->stride; + for (uint_fast32_t i = 0; i < sbuf->buf->n_datas; ++i) { + f->data[i] = sbuf->datas[i]; + f->linesize[i] = sbuf->buf->datas[i].chunk->stride; + } } /* do conversion */ @@ -1899,9 +1919,11 @@ static int impl_node_process(void *object) sizes[0] = this->encoder.packet->size; strides[0] = 1; } else { - datas[0] = f->data[0]; - strides[0] = f->linesize[0]; - sizes[0] = strides[0] * out->height; + for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { + datas[i] = f->data[i]; + strides[i] = f->linesize[i]; + sizes[i] = out->size[i]; + } } /* write to output */ From cc6081b70dfd30e5a609075edda1b4ff568e7c59 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 12:30:41 +0100 Subject: [PATCH 0083/1014] gst: fix video metadata offsets The offsets in GStreamer are always offsets into the buffer memory where the plane starts so set this to the accumulated plane sizes. --- src/gst/gstpipewiresrc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 94d715570..68a9e0452 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -664,7 +664,6 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) GstVideoInfo *info = &pwsrc->video_info; uint32_t n_datas = b->buffer->n_datas; uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (info); - gboolean is_planar = n_planes > 1; gsize video_size = 0; GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, @@ -677,7 +676,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) for (i = 0; i < MIN (n_datas, n_planes); i++) { struct spa_data *d = &b->buffer->datas[i]; - meta->offset[i] = is_planar ? d->chunk->offset : video_size; + meta->offset[i] = video_size; meta->stride[i] = d->chunk->stride; video_size += d->chunk->size; From 4c200183b97a7a97e6dfbdc55eacd36dc382986a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 26 Mar 2025 09:26:38 -0400 Subject: [PATCH 0084/1014] gst: src: Minor fix for offsets I don't see any actual usage of left/top padding (yet), but we should account for chunk offset in addition to the overall size. --- src/gst/gstpipewiresrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 68a9e0452..963c6863f 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -676,7 +676,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) for (i = 0; i < MIN (n_datas, n_planes); i++) { struct spa_data *d = &b->buffer->datas[i]; - meta->offset[i] = video_size; + meta->offset[i] = d->chunk->offset + video_size; meta->stride[i] = d->chunk->stride; video_size += d->chunk->size; From dfdc3e333a4015f889a4738fd0c224c7378e087c Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 26 Mar 2025 09:51:53 -0400 Subject: [PATCH 0085/1014] gst: sink: Minor style consistency fixup --- src/gst/gstpipewiresink.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index fc067a275..c0d7df8a6 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -944,15 +944,12 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) gboolean copied = FALSE; buf_size = 0; // to break from the loop - /* - splitting of buffers in the case of video might break the frame layout - and that seems to be causing issues while retrieving the buffers on the receiver - side. Hence use the video_frame_map to copy the buffer of bigger size into the - pipewirepool's buffer - */ + /* splitting of buffers in the case of video might break the frame layout + * and that seems to be causing issues while retrieving the buffers on the receiver + * side. Hence use the video_frame_map to copy the buffer of bigger size into the + * pipewirepool's buffer */ - if (!gst_video_frame_map (&dst, &pwsink->stream->pool->video_info, b, - GST_MAP_WRITE)) { + if (!gst_video_frame_map (&dst, &pwsink->stream->pool->video_info, b, GST_MAP_WRITE)) { GST_ERROR_OBJECT(pwsink, "Failed to map dest buffer"); return GST_FLOW_ERROR; } From 5ef13489dbc3cb498021c2c154760f372936ad62 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 26 Mar 2025 11:49:02 -0400 Subject: [PATCH 0086/1014] gst: sink: Correctly set size and offset on planar data We need to make sure the memory sizes are correctly initialised so the meta makes sense, and we don't copy the meta from the input buffer as that doesn't make sense given we have our own meta already. --- src/gst/gstpipewirepool.c | 12 ++++++++++++ src/gst/gstpipewiresink.c | 8 +------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index f00612fc8..6c6e2dcab 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -102,6 +102,18 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) GST_VIDEO_INFO_N_PLANES (&pool->video_info), pool->video_info.offset, pool->video_info.stride); + gsize plane_sizes[GST_VIDEO_MAX_PLANES]; + + if (!gst_video_meta_get_plane_size (meta, plane_sizes)) { + GST_ERROR_OBJECT (pool, "could not compute plane sizes"); + } else { + /* Set memory sizes to expected plane sizes, so we know the valid size, + * and the offsets in the meta make sense */ + for (i = 0; i < gst_buffer_n_memory (buf); i++) { + GstMemory *mem = gst_buffer_peek_memory (buf, i); + gst_memory_resize (mem, 0, plane_sizes[i]); + } + } /* * We need to set the video meta as pooled, else gst_buffer_pool_release_buffer diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index c0d7df8a6..4457e2eac 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -654,17 +654,13 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) if (meta) { if (meta->n_planes == b->n_datas) { uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (&data->pool->video_info); - gboolean is_planar = n_planes > 1; gsize video_size = 0; for (i = 0; i < n_planes; i++) { struct spa_data *d = &b->datas[i]; d->chunk->stride = meta->stride[i]; - if (is_planar) - d->chunk->offset = meta->offset[i]; - else - d->chunk->offset += meta->offset[i] - video_size; + d->chunk->offset = meta->offset[i] - video_size; video_size += d->chunk->size; } @@ -969,8 +965,6 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) GST_ERROR_OBJECT(pwsink, "Failed to copy the frame"); return GST_FLOW_ERROR; } - - gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1); } else { gst_buffer_map (b, &info, GST_MAP_WRITE); gsize extract_size = (buf_size <= info.maxsize) ? buf_size: info.maxsize; From d7cb68bfc72925d136699270ae0fec911e46bc66 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 26 Mar 2025 11:55:13 -0400 Subject: [PATCH 0087/1014] gst: pool: Some refinements to min/max handling A number of changes for correctness. 1) We expose the actualy min and max values we support in the allocation query. 2) We don't support max_buffers as 0, as unlimited buffers is not an option 3) In ParamBuffers, we request the max_buffers from bufferpool config, as we cannot dynamically allocate buffers --- src/gst/gstpipewirepool.c | 10 ++++++++++ src/gst/gstpipewirepool.h | 3 +++ src/gst/gstpipewiresink.c | 17 +++++++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 6c6e2dcab..ec0b9bc59 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -255,6 +255,16 @@ set_config (GstBufferPool * pool, GstStructure * config) return FALSE; } + /* We don't support unlimited buffers */ + if (max_buffers == 0) + max_buffers = PIPEWIRE_POOL_MAX_BUFFERS; + /* Pick a sensible min to avoid starvation */ + if (min_buffers == 0) + min_buffers = PIPEWIRE_POOL_MIN_BUFFERS; + + if (min_buffers < PIPEWIRE_POOL_MIN_BUFFERS || max_buffers > PIPEWIRE_POOL_MAX_BUFFERS) + return FALSE; + structure = gst_caps_get_structure (caps, 0); if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index fb00a100c..15b7f0d59 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -18,6 +18,9 @@ G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_POOL (gst_pipewire_pool_get_type()) G_DECLARE_FINAL_TYPE (GstPipeWirePool, gst_pipewire_pool, GST, PIPEWIRE_POOL, GstBufferPool) +#define PIPEWIRE_POOL_MIN_BUFFERS 2u +#define PIPEWIRE_POOL_MAX_BUFFERS 16u + typedef struct _GstPipeWirePoolData GstPipeWirePoolData; struct _GstPipeWirePoolData { GstPipeWirePool *pool; diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 4457e2eac..2afcbbd78 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -40,8 +40,6 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); #define DEFAULT_PROP_SLAVE_METHOD GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE #define DEFAULT_PROP_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO -#define MIN_BUFFERS 8u - enum { PROP_0, @@ -167,7 +165,8 @@ gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink); if (pwsink->use_bufferpool != USE_BUFFERPOOL_NO) - gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->stream->pool), 0, 0, 0); + gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->stream->pool), 0, + PIPEWIRE_POOL_MIN_BUFFERS, PIPEWIRE_POOL_MAX_BUFFERS); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); return TRUE; @@ -310,6 +309,12 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool)); gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); + /* We cannot dynamically grow the pool */ + if (max_buffers == 0) { + GST_WARNING_OBJECT (sink, "cannot support unlimited buffers in pool"); + max_buffers = PIPEWIRE_POOL_MAX_BUFFERS; + } + spa_pod_builder_init (&b, buffer, sizeof (buffer)); spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add (&b, @@ -324,10 +329,10 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), + /* At this stage, we will request as many buffers as we _might_ need as + * the default, since we can't grow the pool once this is set */ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int( - SPA_MAX(MIN_BUFFERS, min_buffers), - SPA_MAX(MIN_BUFFERS, min_buffers), - max_buffers ? max_buffers : INT32_MAX), + max_buffers, min_buffers, max_buffers), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int( (1< Date: Wed, 26 Mar 2025 17:22:11 +0100 Subject: [PATCH 0088/1014] adapter: add Header metadata by default Firefox needs this but we should eventually check the Meta Params to decide on this. --- spa/plugins/videoconvert/videoadapter.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index b23193a34..3c841a051 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -430,6 +430,7 @@ static int negotiate_buffers(struct impl *this) uint32_t i, size, buffers, blocks, align, flags, stride = 0; uint32_t *aligns, data_flags; struct spa_data *datas; + struct spa_meta metas[1]; uint64_t follower_flags, conv_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); @@ -517,9 +518,11 @@ static int negotiate_buffers(struct impl *this) datas[i].maxsize = size; aligns[i] = align; } + metas[0].type = SPA_META_Header; + metas[0].size = sizeof(struct spa_meta_header); free(this->buffers); - this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); + this->buffers = spa_buffer_alloc_array(buffers, flags, 1, metas, blocks, datas, aligns); if (this->buffers == NULL) return -errno; this->n_buffers = buffers; From f8dcf32c8d7217697800fe6106276ce24ec3ffa9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 17:24:27 +0100 Subject: [PATCH 0089/1014] videoconvert-ffmpeg: fix passthrough mode Keep the passthrough flag up to date when we unset a port format or when it changes. We should only fill in the buffer data/fd when the ALLOC flag is set. We should only take the passthrough input buffer as output when we are in passthrough mode. Copy the header metadata. --- .../videoconvert/videoconvert-ffmpeg.c | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 122ab7db8..6df054ca7 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -63,6 +63,7 @@ struct buffer { struct spa_list link; struct spa_buffer *buf; void *datas[MAX_DATAS]; + struct spa_meta_header *h; }; struct port { @@ -1480,6 +1481,7 @@ static int port_set_format(void *object, if (format == NULL) { port->have_format = false; + this->fmt_passthrough = false; clear_buffers(this, port); } else { struct spa_video_info info = { 0 }; @@ -1566,8 +1568,8 @@ static int port_set_format(void *object, dir->format = info; dir->have_format = true; if (odir->have_format) { - if (memcmp(&odir->format, &dir->format, sizeof(dir->format)) == 0) - this->fmt_passthrough = true; + this->fmt_passthrough = + (memcmp(&odir->format, &dir->format, sizeof(dir->format)) == 0); } this->setup = false; } @@ -1676,8 +1678,8 @@ impl_node_port_use_buffers(void *object, port = GET_PORT(this, direction, port_id); - spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", - this, n_buffers, direction, port_id); + spa_log_debug(this->log, "%p: use buffers %d on port %d:%d flags %08x", + this, n_buffers, direction, port_id, flags); clear_buffers(this, port); @@ -1697,6 +1699,8 @@ impl_node_port_use_buffers(void *object, b->id = i; b->flags = 0; b->buf = buffers[i]; + b->h = spa_buffer_find_meta_data(b->buf, + SPA_META_Header, sizeof(struct spa_meta_header)); if (n_datas != port->blocks) { spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", @@ -1712,8 +1716,11 @@ impl_node_port_use_buffers(void *object, for (j = 0; j < n_datas; j++) { b->datas[j] = other->buffers[i % other->n_buffers].datas[j]; maxsize = SPA_MAX(maxsize, d[j].maxsize); + spa_log_debug(this->log, "buffer %d: mem:%d passthrough:%p maxsize:%d", + i, j, b->datas[j], d[j].maxsize); + b->buf->datas[j].data = b->datas[j]; } - *b->buf = *other->buffers[i % other->n_buffers].buf; + } else { for (j = 0; j < n_datas; j++) { void *data = d[j].data; @@ -1732,6 +1739,8 @@ impl_node_port_use_buffers(void *object, this, j, i); } b->datas[j] = data; + spa_log_debug(this->log, "buffer %d: mem:%d mapped:%p maxsize:%d", + i, j, data, d[j].maxsize); maxsize = SPA_MAX(maxsize, d[j].maxsize); } } @@ -1851,11 +1860,12 @@ static int impl_node_process(void *object) sbuf = &in_port->buffers[input->buffer_id]; - if ((dbuf = peek_buffer(this, out_port)) == NULL) { - spa_log_error(this->log, "%p: out of buffers", this); + if (this->fmt_passthrough) { + dbuf = &out_port->buffers[input->buffer_id]; + } else if ((dbuf = peek_buffer(this, out_port)) == NULL) { + spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } - dbuf = &out_port->buffers[input->buffer_id]; spa_log_trace(this->log, "%d %p:%p %d %d %d", input->buffer_id, sbuf->buf->datas[0].chunk, dbuf->buf->datas[0].chunk, sbuf->buf->datas[0].chunk->size, @@ -1866,6 +1876,9 @@ static int impl_node_process(void *object) this->decoder.packet->data = sbuf->datas[0]; this->decoder.packet->size = sbuf->buf->datas[0].chunk->size; + spa_log_trace(this->log, "decode %p:%d", this->decoder.packet->data, + this->decoder.packet->size); + if ((res = avcodec_send_packet(this->decoder.context, this->decoder.packet)) < 0) { spa_log_error(this->log, "failed to send frame to codec: %d %p:%d", res, this->decoder.packet->data, this->decoder.packet->size); @@ -1887,8 +1900,11 @@ static int impl_node_process(void *object) f->width = in->width; f->height = in->height; for (uint_fast32_t i = 0; i < sbuf->buf->n_datas; ++i) { - f->data[i] = sbuf->datas[i]; - f->linesize[i] = sbuf->buf->datas[i].chunk->stride; + spa_log_trace(this->log, "in %d %ld %p %d", sbuf->id, i, + sbuf->datas[i], sbuf->buf->datas[i].chunk->size); + datas[i] = f->data[i] = sbuf->datas[i]; + strides[i] = f->linesize[i] = sbuf->buf->datas[i].chunk->stride; + sizes[i] = sbuf->buf->datas[i].chunk->size; } } @@ -1902,8 +1918,14 @@ static int impl_node_process(void *object) out->width, out->height, out->pix_fmt, 0, NULL, NULL, NULL); } + spa_log_trace(this->log, "convert"); sws_scale_frame(this->convert.context, this->convert.frame, f); f = this->convert.frame; + for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { + datas[i] = f->data[i]; + strides[i] = f->linesize[i]; + sizes[i] = out->size[i]; + } } /* do encoding */ if (this->encoder.codec) { @@ -1918,12 +1940,7 @@ static int impl_node_process(void *object) datas[0] = this->encoder.packet->data; sizes[0] = this->encoder.packet->size; strides[0] = 1; - } else { - for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { - datas[i] = f->data[i]; - strides[i] = f->linesize[i]; - sizes[i] = out->size[i]; - } + spa_log_trace(this->log, "encode %p %d", datas[0], sizes[0]); } /* write to output */ @@ -1937,9 +1954,15 @@ static int impl_node_process(void *object) dbuf->buf->datas[i].chunk->stride = strides[i]; dbuf->buf->datas[i].chunk->size = sizes[i]; } + spa_log_trace(this->log, "out %d %ld %p %d", dbuf->id, i, + dbuf->buf->datas[i].data, + dbuf->buf->datas[i].chunk->size); } - dequeue_buffer(this, out_port, dbuf); + + if (sbuf->h && dbuf->h) + *dbuf->h = *sbuf->h; + output->buffer_id = dbuf->id; output->status = SPA_STATUS_HAVE_DATA; From 479ba3368537220a4d6d7e0b71caab616b207edb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 17:50:54 +0100 Subject: [PATCH 0090/1014] videoconvert-ffmpeg: support transcoding When the input and output is an encoded format but the dimensions are different, do a decode, scale and encode. --- .../videoconvert/videoconvert-ffmpeg.c | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 6df054ca7..4f05c5cd5 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -863,6 +863,7 @@ static int setup_convert(struct impl *this) { struct dir *in, *out; uint32_t format; + int decoder_id = 0, encoder_id = 0; in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -876,6 +877,9 @@ static int setup_convert(struct impl *this) if (!in->have_format || !out->have_format) return -EIO; + get_format(in, &in->width, &in->height, &format); + get_format(out, &out->width, &out->height, &format); + switch (in->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: in->pix_fmt = format_to_pix_fmt(in->format.info.raw.format); @@ -884,20 +888,14 @@ static int setup_convert(struct impl *this) out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); break; case SPA_MEDIA_SUBTYPE_mjpg: - if ((this->encoder.codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG)) == NULL) { - spa_log_error(this->log, "failed to find MJPEG encoder"); - return -ENOTSUP; - } + encoder_id = AV_CODEC_ID_MJPEG; out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; out->format.info.raw.size = in->format.info.raw.size; out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; case SPA_MEDIA_SUBTYPE_h264: - if ((this->encoder.codec = avcodec_find_encoder(AV_CODEC_ID_H264)) == NULL) { - spa_log_error(this->log, "failed to find H264 encoder"); - return -ENOTSUP; - } + encoder_id = AV_CODEC_ID_H264; break; default: return -ENOTSUP; @@ -906,14 +904,16 @@ static int setup_convert(struct impl *this) case SPA_MEDIA_SUBTYPE_mjpg: switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_mjpg: - /* passthrough */ + /* passthrough if same dimensions or else reencode */ + if (in->width != out->width || + in->height != out->height) { + encoder_id = decoder_id = AV_CODEC_ID_MJPEG; + out->pix_fmt = AV_PIX_FMT_YUVJ420P; + } break; case SPA_MEDIA_SUBTYPE_raw: out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); - if ((this->decoder.codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG)) == NULL) { - spa_log_error(this->log, "failed to find MJPEG decoder"); - return -ENOTSUP; - } + decoder_id = AV_CODEC_ID_MJPEG; break; default: return -ENOTSUP; @@ -922,14 +922,14 @@ static int setup_convert(struct impl *this) case SPA_MEDIA_SUBTYPE_h264: switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_h264: - /* passthrough */ + /* passthrough if same dimensions or else reencode */ + if (in->width != out->width || + in->height != out->height) + encoder_id = decoder_id = AV_CODEC_ID_H264; break; case SPA_MEDIA_SUBTYPE_raw: out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); - if ((this->decoder.codec = avcodec_find_decoder(AV_CODEC_ID_H264)) == NULL) { - spa_log_error(this->log, "failed to find H264 decoder"); - return -ENOTSUP; - } + decoder_id = AV_CODEC_ID_H264; break; default: return -ENOTSUP; @@ -939,10 +939,11 @@ static int setup_convert(struct impl *this) return -ENOTSUP; } - get_format(in, &in->width, &in->height, &format); - get_format(out, &out->width, &out->height, &format); - - if (this->decoder.codec) { + if (decoder_id) { + if ((this->decoder.codec = avcodec_find_decoder(decoder_id)) == NULL) { + spa_log_error(this->log, "failed to find %d decoder", decoder_id); + return -ENOTSUP; + } if ((this->decoder.context = avcodec_alloc_context3(this->decoder.codec)) == NULL) return -EIO; @@ -958,7 +959,12 @@ static int setup_convert(struct impl *this) } if ((this->decoder.frame = av_frame_alloc()) == NULL) return -EIO; - if (this->encoder.codec) { + + if (encoder_id) { + if ((this->encoder.codec = avcodec_find_encoder(encoder_id)) == NULL) { + spa_log_error(this->log, "failed to find %d encoder", encoder_id); + return -ENOTSUP; + } if ((this->encoder.context = avcodec_alloc_context3(this->encoder.codec)) == NULL) return -EIO; @@ -981,7 +987,6 @@ static int setup_convert(struct impl *this) if ((this->convert.frame = av_frame_alloc()) == NULL) return -EIO; - this->setup = true; emit_node_info(this, false); From afb4a2f49c601de0b7b93611d0f0beb6fac7fe78 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 26 Mar 2025 17:54:37 +0100 Subject: [PATCH 0091/1014] Revert "gst: src: Minor fix for offsets" This reverts commit 4c200183b97a7a97e6dfbdc55eacd36dc382986a. The offset is already applied when we share/copy the memory in the target buffer. --- src/gst/gstpipewiresrc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 963c6863f..01f73c9c7 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -676,7 +676,9 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) for (i = 0; i < MIN (n_datas, n_planes); i++) { struct spa_data *d = &b->buffer->datas[i]; - meta->offset[i] = d->chunk->offset + video_size; + /* don't add the chunk offset here, this is done below when we + * share/copy the memory in the target buffer below */ + meta->offset[i] = video_size; meta->stride[i] = d->chunk->stride; video_size += d->chunk->size; From abc03ec81043e65f844191cd9ebf9c4ee788c5e2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 27 Mar 2025 09:29:52 +0100 Subject: [PATCH 0092/1014] adapter: fix buffer alloc order Prefer to let the follower allocate buffers. If we are allocating buffers, first do use_buffers on the allocating node or else the non-allocating node just ends up with NULL buffers. --- spa/plugins/audioconvert/audioadapter.c | 31 ++++++++++++++++++------- spa/plugins/videoconvert/videoadapter.c | 31 ++++++++++++++++++------- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 631c0bff1..350132278 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -431,6 +431,9 @@ static int negotiate_buffers(struct impl *this) uint32_t *aligns, data_flags; struct spa_data *datas; uint64_t follower_flags, conv_flags; + struct spa_node *alloc_node; + enum spa_direction alloc_direction; + uint32_t alloc_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); @@ -476,11 +479,10 @@ static int negotiate_buffers(struct impl *this) follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - flags = 0; + flags = alloc_flags = 0; if (conv_alloc || follower_alloc) { flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; - if (conv_alloc) - follower_alloc = false; + alloc_flags = SPA_NODE_BUFFERS_FLAG_ALLOC; } align = DEFAULT_ALIGN; @@ -524,15 +526,26 @@ static int negotiate_buffers(struct impl *this) return -errno; this->n_buffers = buffers; - if ((res = spa_node_port_use_buffers(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + /* prefer to let the follower alloc */ + if (follower_alloc) { + alloc_node = this->follower; + alloc_direction = this->direction; + } else { + alloc_node = this->target; + alloc_direction = SPA_DIRECTION_REVERSE(this->direction); + } + + if ((res = spa_node_port_use_buffers(alloc_node, + alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; - if ((res = spa_node_port_use_buffers(this->follower, - this->direction, 0, - follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + alloc_node = alloc_node == this->follower ? this->target : this->follower; + alloc_direction = SPA_DIRECTION_REVERSE(alloc_direction); + alloc_flags = 0; + + if ((res = spa_node_port_use_buffers(alloc_node, + alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 3c841a051..033670a89 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -432,6 +432,9 @@ static int negotiate_buffers(struct impl *this) struct spa_data *datas; struct spa_meta metas[1]; uint64_t follower_flags, conv_flags; + struct spa_node *alloc_node; + enum spa_direction alloc_direction; + uint32_t alloc_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); @@ -477,11 +480,10 @@ static int negotiate_buffers(struct impl *this) follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); - flags = 0; + flags = alloc_flags = 0; if (conv_alloc || follower_alloc) { flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; - if (conv_alloc) - follower_alloc = false; + alloc_flags = SPA_NODE_BUFFERS_FLAG_ALLOC; } align = DEFAULT_ALIGN; @@ -527,15 +529,26 @@ static int negotiate_buffers(struct impl *this) return -errno; this->n_buffers = buffers; - if ((res = spa_node_port_use_buffers(this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + /* prefer to let the follower alloc */ + if (follower_alloc) { + alloc_node = this->follower; + alloc_direction = this->direction; + } else { + alloc_node = this->target; + alloc_direction = SPA_DIRECTION_REVERSE(this->direction); + } + + if ((res = spa_node_port_use_buffers(alloc_node, + alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; - if ((res = spa_node_port_use_buffers(this->follower, - this->direction, 0, - follower_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, + alloc_node = alloc_node == this->follower ? this->target : this->follower; + alloc_direction = SPA_DIRECTION_REVERSE(alloc_direction); + alloc_flags = 0; + + if ((res = spa_node_port_use_buffers(alloc_node, + alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; From 9ceb4310f81398e671f54f1984ad39a574abc6c5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 27 Mar 2025 15:21:12 +0100 Subject: [PATCH 0093/1014] videoconvert: unmap buffer memory as well Set a flag when we mmaped the buffer memory so that we can unmap it again when clearing the buffers. --- .../videoconvert/videoconvert-ffmpeg.c | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 4f05c5cd5..194c24824 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -59,6 +59,7 @@ static void props_reset(struct props *props) struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1<<0) +#define BUFFER_FLAG_MAPPED (1<<1) uint32_t flags; struct spa_list link; struct spa_buffer *buf; @@ -1329,11 +1330,26 @@ impl_node_port_enum_params(void *object, int seq, static int clear_buffers(struct impl *this, struct port *port) { - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); + uint32_t i, j; + + spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + spa_log_debug(this->log, "%p: %d %p %d", this, i, b, b->flags); + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + for (j = 0; j < b->buf->n_datas; j++) { + if (b->datas[j]) { + spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", + this, i, j, b->datas[j]); + munmap(b->datas[j], b->buf->datas[j].maxsize); + b->datas[j] = NULL; + } + } + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); + } } + port->n_buffers = 0; + spa_list_init(&port->queue); return 0; } @@ -1738,13 +1754,16 @@ impl_node_port_use_buffers(void *object, this, j, i, d[j].type, data); return -EINVAL; } + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", + this, j, i, d[j].type, data, b); } if (data != NULL && !SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } b->datas[j] = data; - spa_log_debug(this->log, "buffer %d: mem:%d mapped:%p maxsize:%d", + spa_log_debug(this->log, "buffer %d: mem:%d data:%p maxsize:%d", i, j, data, d[j].maxsize); maxsize = SPA_MAX(maxsize, d[j].maxsize); } From 328718f9587c2d5e51dc5e3272e1449275743d15 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 27 Mar 2025 15:37:19 +0100 Subject: [PATCH 0094/1014] audioconvert: add support for buffer data mmap When there is no data and the buffer is mmapable, try to mmap it. Unmap again when clearing the buffers. Use the mmaped data pointer of the buffer when processing. --- spa/plugins/audioconvert/audioconvert.c | 57 ++++++++++++++----- .../videoconvert/videoconvert-ffmpeg.c | 1 - 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 84813e12b..eb93ed27a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -127,6 +128,7 @@ static void props_reset(struct props *props) struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1<<0) +#define BUFFER_FLAG_MAPPED (1<<1) uint32_t flags; struct spa_list link; struct spa_buffer *buf; @@ -2724,11 +2726,25 @@ impl_node_port_enum_params(void *object, int seq, static int clear_buffers(struct impl *this, struct port *port) { - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); + uint32_t i, j; + + spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + for (j = 0; j < b->buf->n_datas; j++) { + if (b->datas[j]) { + spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", + this, i, j, b->datas[j]); + munmap(b->datas[j], b->buf->datas[j].maxsize); + b->datas[j] = NULL; + } + } + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); + } } + port->n_buffers = 0; + spa_list_init(&port->queue); return 0; } @@ -3080,12 +3096,25 @@ impl_node_port_use_buffers(void *object, } for (j = 0; j < n_datas; j++) { - if (d[j].data == NULL) { - spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", - this, j, i, d[j].type, d[j].data); - return -EINVAL; + void *data = d[j].data; + if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { + data = mmap(NULL, d[j].maxsize, + PROT_READ, MAP_SHARED, d[j].fd, + d[j].mapoffset); + if (data == MAP_FAILED) { + spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", + this, j, i, d[j].type, data); + return -EINVAL; + } + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", + this, j, i, d[j].type, data, b); } - if (!SPA_IS_ALIGNED(d[j].data, this->max_align)) { + if (data == NULL) { + spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", + this, j, i, d[j].type, data); + return -EINVAL; + } else if (!SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } @@ -3093,7 +3122,7 @@ impl_node_port_use_buffers(void *object, !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) this->is_passthrough = false; - b->datas[j] = d[j].data; + b->datas[j] = data; maxsize = SPA_MAX(maxsize, d[j].maxsize); } @@ -3721,7 +3750,7 @@ static int impl_node_process(void *object) spa_log_trace_fp(this->log, "%p: control %d", this, i * port->blocks + j); ctrlport = port; - ctrl = spa_pod_from_data(bd->data, bd->maxsize, + ctrl = spa_pod_from_data(buf->datas[j], bd->maxsize, bd->chunk->offset, bd->chunk->size); if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) ctrl = NULL; @@ -3735,7 +3764,7 @@ static int impl_node_process(void *object) remap = n_src_datas++; offs += this->in_offset * port->stride; - src_datas[remap] = SPA_PTROFF(bd->data, offs, void); + src_datas[remap] = SPA_PTROFF(buf->datas[j], offs, void); spa_log_trace_fp(this->log, "%p: input %d:%d:%d %d %d %d->%d", this, offs, size, port->stride, this->in_offset, max_in, @@ -3829,7 +3858,7 @@ static int impl_node_process(void *object) mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); - volume_process(&this->volume, bd->data, src_datas[remap], + volume_process(&this->volume, buf->datas[j], src_datas[remap], volume, mon_max); bd->chunk->size = mon_max * port->stride; @@ -3846,7 +3875,7 @@ static int impl_node_process(void *object) spa_log_trace_fp(this->log, "%p: control %d", this, j); } else { remap = n_dst_datas++; - dst_datas[remap] = SPA_PTROFF(bd->data, + dst_datas[remap] = SPA_PTROFF(buf->datas[j], this->out_offset * port->stride, void); max_out = SPA_MIN(max_out, bd->maxsize / port->stride); diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 194c24824..f071fd7e4 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1335,7 +1335,6 @@ static int clear_buffers(struct impl *this, struct port *port) spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; - spa_log_debug(this->log, "%p: %d %p %d", this, i, b, b->flags); if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { for (j = 0; j < b->buf->n_datas; j++) { if (b->datas[j]) { From b501290bd5457bde4db3b8ab2d3bb3959f378082 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 27 Mar 2025 16:59:03 +0100 Subject: [PATCH 0095/1014] audioconvert: support DYNAMIC data again Because we advertize on out ports that we support DYNAMIC data, we need to read the data pointer directly from the buffer and only fall back to our cache (mmaped) pointer when it is NULL. With DYNAMIC data, the peer element (mixer-dsp) directly copies the input data pointer into the buffer data in the processing loop in order to avoid a memcpy when there is no mixing needed. --- spa/plugins/audioconvert/audioconvert.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index eb93ed27a..cd0529087 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -3638,7 +3638,7 @@ static int impl_node_process(void *object) { struct impl *this = object; const void *src_datas[MAX_PORTS]; - void *dst_datas[MAX_PORTS], *remap_src_datas[MAX_PORTS], *remap_dst_datas[MAX_PORTS]; + void *dst_datas[MAX_PORTS], *remap_src_datas[MAX_PORTS], *remap_dst_datas[MAX_PORTS], *data; uint32_t i, j, n_src_datas = 0, n_dst_datas = 0, n_mon_datas = 0, remap; uint32_t n_samples, max_in, n_out, max_out, quant_samples; struct port *port, *ctrlport = NULL; @@ -3740,6 +3740,7 @@ static int impl_node_process(void *object) uint32_t offs, size; bd = &buf->buf->datas[j]; + data = bd->data ? bd->data : buf->datas[j]; offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); @@ -3750,7 +3751,7 @@ static int impl_node_process(void *object) spa_log_trace_fp(this->log, "%p: control %d", this, i * port->blocks + j); ctrlport = port; - ctrl = spa_pod_from_data(buf->datas[j], bd->maxsize, + ctrl = spa_pod_from_data(data, bd->maxsize, bd->chunk->offset, bd->chunk->size); if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) ctrl = NULL; @@ -3764,7 +3765,7 @@ static int impl_node_process(void *object) remap = n_src_datas++; offs += this->in_offset * port->stride; - src_datas[remap] = SPA_PTROFF(buf->datas[j], offs, void); + src_datas[remap] = SPA_PTROFF(data, offs, void); spa_log_trace_fp(this->log, "%p: input %d:%d:%d %d %d %d->%d", this, offs, size, port->stride, this->in_offset, max_in, @@ -3839,6 +3840,7 @@ static int impl_node_process(void *object) } else { for (j = 0; j < port->blocks; j++) { bd = &buf->buf->datas[j]; + data = bd->data ? bd->data : buf->datas[j]; bd->chunk->offset = 0; bd->chunk->size = 0; @@ -3858,7 +3860,7 @@ static int impl_node_process(void *object) mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); - volume_process(&this->volume, buf->datas[j], src_datas[remap], + volume_process(&this->volume, data, src_datas[remap], volume, mon_max); bd->chunk->size = mon_max * port->stride; @@ -3875,7 +3877,7 @@ static int impl_node_process(void *object) spa_log_trace_fp(this->log, "%p: control %d", this, j); } else { remap = n_dst_datas++; - dst_datas[remap] = SPA_PTROFF(buf->datas[j], + dst_datas[remap] = SPA_PTROFF(data, this->out_offset * port->stride, void); max_out = SPA_MIN(max_out, bd->maxsize / port->stride); From d0c4b491da53b1f2395fce4dabff7ac3f2876f41 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:00:51 +0100 Subject: [PATCH 0096/1014] v4l2: handle nearest set_format The set_format function can return 1 when the format was adjusted to the nearest supported format so return this from the port_set_param function. This instructs the adapter to recheck the configured format so that it can store the adjuted format on the converter. --- spa/plugins/v4l2/v4l2-source.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index f19dffde8..b06dda994 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -675,7 +675,7 @@ static int port_set_format(struct impl *this, struct port *port, const struct spa_pod *format) { struct spa_video_info info; - int res; + int res = 0; spa_zero(info); @@ -755,7 +755,7 @@ static int port_set_format(struct impl *this, struct port *port, emit_port_info(this, port, false); emit_node_info(this, false); - return 0; + return res; } static int impl_node_port_set_param(void *object, From dba31d7e2ae5f224e8cffae0e01a65f30a834cdc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:04:54 +0100 Subject: [PATCH 0097/1014] videoadapter: use the supported dataType in Buffer param Get the dataType field from the Buffer param. This is a mask of the supported data types for the buffers. Pass this to the allocating node if there is one, otherwise use MemPtr as the allocated format. --- spa/plugins/videoconvert/videoadapter.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 033670a89..060855ab6 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -427,7 +427,7 @@ static int negotiate_buffers(struct impl *this) struct spa_pod *param; int res; bool follower_alloc, conv_alloc; - uint32_t i, size, buffers, blocks, align, flags, stride = 0; + uint32_t i, size, buffers, blocks, align, flags, stride = 0, types; uint32_t *aligns, data_flags; struct spa_data *datas; struct spa_meta metas[1]; @@ -487,14 +487,16 @@ static int negotiate_buffers(struct impl *this) } align = DEFAULT_ALIGN; + types = SPA_ID_INVALID; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, - SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), - SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), + SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align), + SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&types))) < 0) return res; if (this->async) @@ -514,8 +516,12 @@ static int negotiate_buffers(struct impl *this) SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) data_flags |= SPA_DATA_FLAG_DYNAMIC; + /* if we allocate, we allocate MemPtr memory */ + if (!SPA_FLAG_IS_SET(alloc_flags, SPA_NODE_BUFFERS_FLAG_ALLOC)) + types = SPA_DATA_MemPtr; + for (i = 0; i < blocks; i++) { - datas[i].type = SPA_DATA_MemPtr; + datas[i].type = types; datas[i].flags = data_flags; datas[i].maxsize = size; aligns[i] = align; From ae1bbc93d8365e1145cc260259dc6ad176263b33 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:08:19 +0100 Subject: [PATCH 0098/1014] videoconvert: return error on invalid formats --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index f071fd7e4..e9d52e6ef 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1559,6 +1559,8 @@ static int port_set_format(void *object, int linesizes[4]; pix_fmt = format_to_pix_fmt(info.info.raw.format); + if (pix_fmt == AV_PIX_FMT_NONE) + return -EINVAL; av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); port->stride = linesizes[0]; port->blocks = 0; From 5f4c0cdd1e2d889b597dd4f242d250b25b501086 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:08:57 +0100 Subject: [PATCH 0099/1014] improve debug and error reporting a little --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 4 ++-- src/gst/gstpipewirepool.c | 6 +++++- src/gst/gstpipewiresrc.c | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index e9d52e6ef..9c96e5b34 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1725,8 +1725,8 @@ impl_node_port_use_buffers(void *object, SPA_META_Header, sizeof(struct spa_meta_header)); if (n_datas != port->blocks) { - spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", - this, n_datas, i); + spa_log_error(this->log, "%p: invalid blocks %d on buffer %d, expected %d", + this, n_datas, i, port->blocks); return -EINVAL; } if (SPA_FLAG_IS_SET(flags, SPA_NODE_BUFFERS_FLAG_ALLOC)) { diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index ec0b9bc59..f2a925c94 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -62,7 +62,7 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) uint32_t i; GstPipeWirePoolData *data; - GST_DEBUG_OBJECT (pool, "wrap buffer"); + GST_DEBUG_OBJECT (pool, "wrap buffer, datas:%d", b->buffer->n_datas); data = g_slice_new (GstPipeWirePoolData); @@ -89,6 +89,10 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, 0, d->maxsize, NULL, NULL); } + else { + GST_WARNING_OBJECT (pool, "unknown data type (%s %d)", + spa_debug_type_find_short_name(spa_type_data_type, d->type), d->type); + } if (gmem) gst_buffer_insert_memory (buf, i, gmem); } diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 01f73c9c7..86c39f2b7 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -686,7 +686,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) } if (b->buffer->n_datas != gst_buffer_n_memory(data->buf)) { - GST_ERROR_OBJECT(pwsrc, "n_datas != n_memory"); + GST_ERROR_OBJECT(pwsrc, "n_datas != n_memory, (%d != %d)", b->buffer->n_datas, gst_buffer_n_memory(data->buf)); } for (i = 0; i < b->buffer->n_datas; i++) { From b23161d7721176730af1e71eb106a7cce75ae66c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:09:36 +0100 Subject: [PATCH 0100/1014] videoconvert: always put frame size on Format param We can now set the 0x0 frame size as the default as a wildcard. --- .../videoconvert/videoconvert-ffmpeg.c | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 9c96e5b34..b37ae8c4d 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -850,11 +850,13 @@ static int get_format(struct dir *dir, int *width, int *height, uint32_t *format *height = dir->format.info.h264.size.height; break; default: - *width = *height = 0; + *width = 640; + *height = 480; break; } } else { - *width = *height = 0; + *width = 640; + *height = 480; } return 0; } @@ -1127,22 +1129,18 @@ static int port_enum_formats(void *object, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, - format, + format ? format : SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_UYVY, SPA_VIDEO_FORMAT_YVYU, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); - if (width != 0 && height != 0) { - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(width, height), - &SPA_RECTANGLE(1, 1), - &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), - 0); - } *param = spa_pod_builder_pop(builder, &f[0]); break; case 2: @@ -1155,15 +1153,11 @@ static int port_enum_formats(void *object, spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); - if (width != 0 && height != 0) { - spa_pod_builder_add(builder, - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(width, height), - &SPA_RECTANGLE(1, 1), - &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), - 0); - } *param = spa_pod_builder_pop(builder, &f[0]); break; default: From 5c5075f4d610af0efd2539ba0417f76afe492a65 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 28 Mar 2025 16:10:42 +0100 Subject: [PATCH 0101/1014] videoconvert-ffmpeg: copy complete passthrough buffer data We also need to copy the data type, flags and sizes. --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index b37ae8c4d..688d6d92c 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1730,11 +1730,11 @@ impl_node_port_use_buffers(void *object, return -EIO; for (j = 0; j < n_datas; j++) { + b->buf->datas[j] = other->buffers[i % other->n_buffers].buf->datas[j]; b->datas[j] = other->buffers[i % other->n_buffers].datas[j]; maxsize = SPA_MAX(maxsize, d[j].maxsize); spa_log_debug(this->log, "buffer %d: mem:%d passthrough:%p maxsize:%d", i, j, b->datas[j], d[j].maxsize); - b->buf->datas[j].data = b->datas[j]; } } else { From 94910f77c90c0a33ceb60675a9e69198d844f831 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Apr 2025 10:18:05 +0200 Subject: [PATCH 0102/1014] pod: handle builder overflows When the builder is overflowed, we might get a NULL pod. This is a valid situation that we need to handle because it can be used to get the required builder buffer size. --- spa/include/spa/pod/filter.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 4160a7e0f..f795d0cb5 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -150,7 +150,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p2) { const struct spa_pod *v1, *v2; - struct spa_pod_choice *nc; + struct spa_pod_choice *nc, dummy; uint32_t j, k, nalt1, nalt2; void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; @@ -173,6 +173,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); spa_pod_builder_push_choice(b, &f, 0, 0); nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f); + /* write to dummy value when builder overflows. We don't want to error + * because overflowing is a way to determine the required buffer size. */ + if (nc == NULL) + nc = &dummy; /* we should prefer alt2 values but only if they are within the * range, start with an empty child and we will select a good default From aca673b3ab22747d9cd65ec66b93d15a3b3a67ae Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Apr 2025 10:19:40 +0200 Subject: [PATCH 0103/1014] impl-link: improve debug log Log the format after we patch it up and log some context lines. Move some info log to debug. --- src/pipewire/impl-link.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 3df092ad8..a9de783e3 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -388,11 +388,10 @@ static int do_negotiate(struct pw_impl_link *this) } } - pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); - SPA_POD_OBJECT_ID(format) = SPA_PARAM_Format; pw_log_debug("%p: doing set format %p fixated:%d", this, format, spa_pod_is_fixated(format)); + pw_log_pod(SPA_LOG_LEVEL_INFO, format); if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) { pw_log_debug("%p: doing set format on output", this); @@ -405,7 +404,7 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res)) { - pw_log_info("output set format %d", res); + pw_log_debug("output set format %d", res); busy_id = pw_work_queue_add(impl->work, &this->output_link, spa_node_sync(output->node->node, res), complete_ready, this); @@ -425,7 +424,7 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res2)) { - pw_log_info("input set format %d", res2); + pw_log_debug("input set format %d", res2); busy_id = pw_work_queue_add(impl->work, &this->input_link, spa_node_sync(input->node->node, res2), complete_ready, this); From 1adc9e5445b464a0aa3e6f94813cec3ed73585f2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Apr 2025 16:23:34 +0200 Subject: [PATCH 0104/1014] videoadapter: init the builder for each param Or else we keep on adding items until we overflow. --- spa/plugins/audioconvert/audioadapter.c | 4 ++-- spa/plugins/videoconvert/videoadapter.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 350132278..efea34e7d 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1641,13 +1641,13 @@ port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction dir uint32_t count = 0; struct spa_result_node_params result; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - result.id = id; result.next = start; next: result.index = result.next; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (result.next < 0x100000) { /* Enumerate follower formats first, until we have enough or we run out */ if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 060855ab6..b1f604f59 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1643,13 +1643,13 @@ port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction dir uint32_t count = 0; struct spa_result_node_params result; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - result.id = id; result.next = start; next: result.index = result.next; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (result.next < 0x100000) { /* Enumerate follower formats first, until we have enough or we run out */ if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, From da69bddb953c47feeb8e7d2e3e0537ed49201df5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Apr 2025 16:31:25 +0200 Subject: [PATCH 0105/1014] adapter: Improve convert setup Always do configure_convert, even when the passthrough state didn't change, for example when going from none to convert. --- spa/plugins/audioconvert/audioadapter.c | 21 ++++++++++----------- spa/plugins/videoconvert/videoadapter.c | 20 +++++++++----------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index efea34e7d..6db0a39ae 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -750,18 +750,17 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->mode = mode; - if (old_passthrough != passthrough) { - if (passthrough) { - /* add follower ports */ - spa_zero(l); - spa_node_add_listener(this->follower, &l, &follower_node_events, this); - spa_hook_remove(&l); - } else { - /* add converter ports */ - configure_convert(this, mode); - } - link_io(this); + if (old_passthrough != passthrough && passthrough) { + /* add follower ports */ + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + } else { + /* add converter ports */ + configure_convert(this, mode); } + link_io(this); + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; SPA_FLAG_CLEAR(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index b1f604f59..101e6d9b9 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -757,18 +757,16 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->mode = mode; - if (old_passthrough != passthrough) { - if (passthrough) { - /* add follower ports */ - spa_zero(l); - spa_node_add_listener(this->follower, &l, &follower_node_events, this); - spa_hook_remove(&l); - } else { - /* add converter ports */ - configure_convert(this, mode); - } - link_io(this); + if (old_passthrough != passthrough && passthrough) { + /* add follower ports */ + spa_zero(l); + spa_node_add_listener(this->follower, &l, &follower_node_events, this); + spa_hook_remove(&l); + } else { + configure_convert(this, mode); } + link_io(this); + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; SPA_FLAG_CLEAR(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, From bf952a6e4b32539b36a9fda2ed667843fd7065e3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Apr 2025 16:34:47 +0200 Subject: [PATCH 0106/1014] videoconvert: improve setup of DSP mode Parse and use DSP formats. Redo the conversion setup when the formats changed. We usually do this when starting the node but the formats can change while running as well. --- .../videoconvert/videoconvert-ffmpeg.c | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 688d6d92c..7632e5b0c 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -563,14 +563,10 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_dsp: { - if (info) { - dir->n_ports = 1; - dir->format = *info; - dir->format.info.dsp.format = SPA_VIDEO_FORMAT_DSP_F32; - dir->have_format = true; - } else { - dir->n_ports = 0; - } + dir->n_ports = 1; + dir->format.info.dsp = SPA_VIDEO_INFO_DSP_INIT( + .format = SPA_VIDEO_FORMAT_DSP_F32); + dir->have_format = true; if (this->monitor && direction == SPA_DIRECTION_INPUT) this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; @@ -836,6 +832,11 @@ static int get_format(struct dir *dir, int *width, int *height, uint32_t *format { if (dir->have_format) { switch (dir->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_dsp: + *format = dir->format.info.dsp.format; + *width = 640; + *height = 480; + break; case SPA_MEDIA_SUBTYPE_raw: *width = dir->format.info.raw.size.width; *height = dir->format.info.raw.size.height; @@ -865,7 +866,7 @@ static int get_format(struct dir *dir, int *width, int *height, uint32_t *format static int setup_convert(struct impl *this) { struct dir *in, *out; - uint32_t format; + uint32_t in_format = 0, out_format = 0; int decoder_id = 0, encoder_id = 0; in = &this->dir[SPA_DIRECTION_INPUT]; @@ -880,27 +881,31 @@ static int setup_convert(struct impl *this) if (!in->have_format || !out->have_format) return -EIO; - get_format(in, &in->width, &in->height, &format); - get_format(out, &out->width, &out->height, &format); + get_format(in, &in->width, &in->height, &in_format); + get_format(out, &out->width, &out->height, &out_format); switch (in->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_dsp: case SPA_MEDIA_SUBTYPE_raw: - in->pix_fmt = format_to_pix_fmt(in->format.info.raw.format); + in->pix_fmt = format_to_pix_fmt(in_format); switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + case SPA_MEDIA_SUBTYPE_dsp: + out->pix_fmt = format_to_pix_fmt(out_format); break; case SPA_MEDIA_SUBTYPE_mjpg: encoder_id = AV_CODEC_ID_MJPEG; out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; - out->format.info.raw.size = in->format.info.raw.size; + out->format.info.raw.size = SPA_RECTANGLE(in->width, in->height); out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; case SPA_MEDIA_SUBTYPE_h264: encoder_id = AV_CODEC_ID_H264; break; default: + spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, + in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; @@ -915,10 +920,13 @@ static int setup_convert(struct impl *this) } break; case SPA_MEDIA_SUBTYPE_raw: - out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + case SPA_MEDIA_SUBTYPE_dsp: + out->pix_fmt = format_to_pix_fmt(out_format); decoder_id = AV_CODEC_ID_MJPEG; break; default: + spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, + in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; @@ -931,14 +939,19 @@ static int setup_convert(struct impl *this) encoder_id = decoder_id = AV_CODEC_ID_H264; break; case SPA_MEDIA_SUBTYPE_raw: - out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + case SPA_MEDIA_SUBTYPE_dsp: + out->pix_fmt = format_to_pix_fmt(out_format); decoder_id = AV_CODEC_ID_H264; break; default: + spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, + in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; default: + spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, + in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } @@ -1099,8 +1112,8 @@ static int port_enum_formats(void *object, switch (index) { case 0: if (PORT_IS_DSP(this, direction, port_id)) { - struct spa_video_info_dsp info; - info.format = SPA_VIDEO_FORMAT_DSP_F32; + struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( + .format = SPA_VIDEO_FORMAT_DSP_F32); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &info); } else if (PORT_IS_CONTROL(this, direction, port_id)) { @@ -1495,9 +1508,12 @@ static int port_set_format(void *object, if (format == NULL) { port->have_format = false; + this->setup = false; this->fmt_passthrough = false; clear_buffers(this, port); } else { + struct dir *dir = &this->dir[direction]; + struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { @@ -1522,6 +1538,7 @@ static int port_set_format(void *object, } port->blocks = 1; port->stride = 16; + dir->have_format = true; } else if (PORT_IS_CONTROL(this, direction, port_id)) { if (info.media_type != SPA_MEDIA_TYPE_application || @@ -1532,10 +1549,9 @@ static int port_set_format(void *object, } port->blocks = 1; port->stride = 1; + dir->have_format = true; } else { - struct dir *dir = &this->dir[direction]; - struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; enum AVPixelFormat pix_fmt; if (info.media_type != SPA_MEDIA_TYPE_video) { @@ -1587,13 +1603,17 @@ static int port_set_format(void *object, this->fmt_passthrough = (memcmp(&odir->format, &dir->format, sizeof(dir->format)) == 0); } - this->setup = false; } port->format = info; port->have_format = true; + this->setup = false; spa_log_debug(this->log, "%p: %d %d %d", this, port_id, port->stride, port->blocks); + + if (dir->have_format && odir->have_format) + if ((res = setup_convert(this)) < 0) + return res; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; From 774f9cbb803d6b4146bc2c4dde3e0c82a5ddeec6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 2 Apr 2025 10:15:37 +0200 Subject: [PATCH 0107/1014] videconvert: fix compilation on 32 bits --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 7632e5b0c..d5cb2ee7b 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1938,8 +1938,8 @@ static int impl_node_process(void *object) f->format = in->pix_fmt; f->width = in->width; f->height = in->height; - for (uint_fast32_t i = 0; i < sbuf->buf->n_datas; ++i) { - spa_log_trace(this->log, "in %d %ld %p %d", sbuf->id, i, + for (uint32_t i = 0; i < sbuf->buf->n_datas; ++i) { + spa_log_trace(this->log, "in %u %u %p %d", sbuf->id, i, sbuf->datas[i], sbuf->buf->datas[i].chunk->size); datas[i] = f->data[i] = sbuf->datas[i]; strides[i] = f->linesize[i] = sbuf->buf->datas[i].chunk->stride; @@ -1960,7 +1960,7 @@ static int impl_node_process(void *object) spa_log_trace(this->log, "convert"); sws_scale_frame(this->convert.context, this->convert.frame, f); f = this->convert.frame; - for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { + for (uint32_t i = 0; i < dbuf->buf->n_datas; ++i) { datas[i] = f->data[i]; strides[i] = f->linesize[i]; sizes[i] = out->size[i]; @@ -1983,7 +1983,7 @@ static int impl_node_process(void *object) } /* write to output */ - for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { + for (uint32_t i = 0; i < dbuf->buf->n_datas; ++i) { if (SPA_FLAG_IS_SET(dbuf->buf->datas[i].flags, SPA_DATA_FLAG_DYNAMIC)) dbuf->buf->datas[i].data = datas[i]; else if (datas[i] && dbuf->datas[i] && dbuf->datas[i] != datas[i]) @@ -1993,7 +1993,7 @@ static int impl_node_process(void *object) dbuf->buf->datas[i].chunk->stride = strides[i]; dbuf->buf->datas[i].chunk->size = sizes[i]; } - spa_log_trace(this->log, "out %d %ld %p %d", dbuf->id, i, + spa_log_trace(this->log, "out %u %u %p %d", dbuf->id, i, dbuf->buf->datas[i].data, dbuf->buf->datas[i].chunk->size); } From 1b258f4ecc2a8148c550c65b03de71071ad44fcc Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 28 Mar 2025 09:48:35 -0400 Subject: [PATCH 0108/1014] gst: sink: Only add VideoCrop meta for video --- src/gst/gstpipewiresink.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 2afcbbd78..c40fd32ec 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -305,6 +305,7 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) struct spa_pod_builder b = { NULL }; uint8_t buffer[1024]; struct spa_pod_frame f; + guint n_params = 0; config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool)); gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); @@ -337,20 +338,22 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) (1<is_video) { + port_params[n_params++] = spa_pod_builder_add_object (&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), + SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); + } pw_thread_loop_lock (sink->stream->core->loop); - pw_stream_update_params (sink->stream->pwstream, port_params, 3); + pw_stream_update_params (sink->stream->pwstream, port_params, n_params); pw_thread_loop_unlock (sink->stream->core->loop); } From ee2c6eb41ebab02c91faeb567cba86ece44dd996 Mon Sep 17 00:00:00 2001 From: Taruntej Kanakamalla Date: Thu, 20 Mar 2025 21:13:03 -0400 Subject: [PATCH 0109/1014] gst: sink: Manage buffer pool memory manually Let's make sure we own the memory in buffers, so that we can be resilient to the PW link going away. This currently maintains the status quo of copying data into the pipewirepool for sending to the remote end, but moves the allocation of buffers so that ownership is maintained by the sink in all cases. There are some tricky corners, especially with bufferpool vs. buffers param negotiation -- bufferpool parameters can be negotiated in GStreamer before the link even comes up, so we try to adapt the buffers param to use the negotiated value. For now, that is more brittle than tying those two aspects together. We can revisit this if we can find a way to tie pipeline state and link state more closely. Co-authored-by: Arun Raghavan --- meson.build | 5 ++ src/gst/gstpipewirepool.c | 151 ++++++++++++++++++++++++++++++-------- src/gst/gstpipewirepool.h | 6 ++ src/gst/gstpipewiresink.c | 26 +++++-- 4 files changed, 151 insertions(+), 37 deletions(-) diff --git a/meson.build b/meson.build index 42775e2ea..c7a15bda0 100644 --- a/meson.build +++ b/meson.build @@ -410,6 +410,7 @@ gst_deps_def = { gst_dep = [] gst_dma_drm_found = false +gst_shm_allocator_found = false foreach depname, kwargs: gst_deps_def dep = dependency(depname, required: gst_option, kwargs: kwargs) summary({depname: dep.found()}, bool_yn: true, section: 'GStreamer modules') @@ -425,9 +426,13 @@ foreach depname, kwargs: gst_deps_def if depname == 'gstreamer-allocators-1.0' and dep.version().version_compare('>= 1.23.1') gst_dma_drm_found = true + gst_shm_allocator_found = true endif endforeach +summary({'gstreamer SHM allocator': gst_shm_allocator_found}, bool_yn: true, section: 'Backend') +cdata.set('HAVE_GSTREAMER_SHM_ALLOCATOR', gst_shm_allocator_found) + # This code relies on the array being empty if any dependency was not found gst_dp_found = gst_dep.length() > 0 summary({'gstreamer-device-provider': gst_dp_found}, bool_yn: true, section: 'Backend') diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index f2a925c94..3e061a0fd 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -10,8 +10,12 @@ #include #include +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR +#include +#endif #include +#include #include "gstpipewirepool.h" @@ -61,6 +65,8 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) GstBuffer *buf; uint32_t i; GstPipeWirePoolData *data; + /* Default to a large enough value */ + gsize plane_sizes[GST_VIDEO_MAX_PLANES] = { pool->video_info.size, }; GST_DEBUG_OBJECT (pool, "wrap buffer, datas:%d", b->buffer->n_datas); @@ -68,6 +74,30 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) buf = gst_buffer_new (); + if (pool->add_metavideo) { + GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, + GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (&pool->video_info), + GST_VIDEO_INFO_WIDTH (&pool->video_info), + GST_VIDEO_INFO_HEIGHT (&pool->video_info), + GST_VIDEO_INFO_N_PLANES (&pool->video_info), + pool->video_info.offset, + pool->video_info.stride); + + gst_video_meta_set_alignment (meta, pool->video_align); + + if (!gst_video_meta_get_plane_size (meta, plane_sizes)) { + GST_ERROR_OBJECT (pool, "could not compute plane sizes"); + } + + /* + * We need to set the video meta as pooled, else gst_buffer_pool_release_buffer + * will call reset_buffer and the default_reset_buffer implementation for + * GstBufferPool removes all metadata without the POOLED flag. + */ + GST_META_FLAG_SET (meta, GST_META_FLAG_POOLED); + } + for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; GstMemory *gmem = NULL; @@ -75,7 +105,51 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) GST_DEBUG_OBJECT (pool, "wrap data (%s %d) %d %d", spa_debug_type_find_short_name(spa_type_data_type, d->type), d->type, d->mapoffset, d->maxsize); - if (d->type == SPA_DATA_MemFd) { + + if (pool->allocate_memory) { +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + gsize block_size = d->maxsize; + + if (pool->has_video) { + /* For video, we know block sizes from the video info already */ + block_size = plane_sizes[i]; + } else { + /* For audio, reserve space based on the quantum limit and channel count */ + g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&pool->stream); + + struct pw_context *context = pw_core_get_context(pw_stream_get_core(s->pwstream)); + const struct pw_properties *props = pw_context_get_properties(context); + uint32_t quantum_limit = 8192; /* "reasonable" default */ + + const char *quantum = spa_dict_lookup(&props->dict, "clock.quantum-limit"); + if (!quantum) { + quantum = spa_dict_lookup(&props->dict, "default.clock.quantum-limit"); + GST_DEBUG_OBJECT (pool, "using default quantum limit %s", quantum); + } + + if (quantum) + spa_atou32(quantum, &quantum_limit, 0); + GST_DEBUG_OBJECT (pool, "quantum limit %s", quantum); + + block_size = quantum_limit * pool->audio_info.bpf; + } + + GST_DEBUG_OBJECT (pool, "setting block size %lu", block_size); + + if (!pool->shm_allocator) + pool->shm_allocator = gst_shm_allocator_get(); + + /* use MemFd only. That is the only supported data type when memory is remote i.e. allocated by the client */ + gmem = gst_allocator_alloc (pool->shm_allocator, block_size, NULL); + d->fd = gst_fd_memory_get_fd (gmem); + d->mapoffset = 0; + d->flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_MAPPABLE; + + d->type = SPA_DATA_MemFd; + d->maxsize = block_size; + d->data = NULL; +#endif + } else if (d->type == SPA_DATA_MemFd) { gmem = gst_fd_allocator_alloc (pool->fd_allocator, dup(d->fd), d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE); gst_memory_resize (gmem, d->mapoffset, d->maxsize); @@ -97,34 +171,13 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) gst_buffer_insert_memory (buf, i, gmem); } - if (pool->add_metavideo) { - GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, - GST_VIDEO_FRAME_FLAG_NONE, - GST_VIDEO_INFO_FORMAT (&pool->video_info), - GST_VIDEO_INFO_WIDTH (&pool->video_info), - GST_VIDEO_INFO_HEIGHT (&pool->video_info), - GST_VIDEO_INFO_N_PLANES (&pool->video_info), - pool->video_info.offset, - pool->video_info.stride); - gsize plane_sizes[GST_VIDEO_MAX_PLANES]; - - if (!gst_video_meta_get_plane_size (meta, plane_sizes)) { - GST_ERROR_OBJECT (pool, "could not compute plane sizes"); - } else { + if (pool->add_metavideo && !pool->allocate_memory) { /* Set memory sizes to expected plane sizes, so we know the valid size, * and the offsets in the meta make sense */ for (i = 0; i < gst_buffer_n_memory (buf); i++) { GstMemory *mem = gst_buffer_peek_memory (buf, i); gst_memory_resize (mem, 0, plane_sizes[i]); - } } - - /* - * We need to set the video meta as pooled, else gst_buffer_pool_release_buffer - * will call reset_buffer and the default_reset_buffer implementation for - * GstBufferPool removes all metadata without the POOLED flag. - */ - GST_META_FLAG_SET (meta, GST_META_FLAG_POOLED); } data->pool = gst_object_ref (pool); @@ -157,7 +210,8 @@ void gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, struct pw_buffer *b data->crop = NULL; data->videotransform = NULL; - gst_buffer_remove_all_memory (data->buf); + if (!pool->allocate_memory) + gst_buffer_remove_all_memory (data->buf); /* this will also destroy the pool data, if this is the last reference */ gst_clear_buffer (&data->buf); @@ -236,7 +290,13 @@ no_more_buffers: static const gchar ** get_options (GstBufferPool * pool) { - static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, NULL }; + static const gchar *options[] = { + GST_BUFFER_POOL_OPTION_VIDEO_META, +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT, +#endif + NULL + }; return options; } @@ -247,7 +307,7 @@ set_config (GstBufferPool * pool, GstStructure * config) GstCaps *caps; GstStructure *structure; guint size, min_buffers, max_buffers; - gboolean has_video; + gboolean has_videoalign; if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers)) { GST_WARNING_OBJECT (pool, "invalid config"); @@ -272,15 +332,41 @@ set_config (GstBufferPool * pool, GstStructure * config) structure = gst_caps_get_structure (caps, 0); if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { - has_video = TRUE; + p->has_video = TRUE; gst_video_info_from_caps (&p->video_info, caps); + +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (p->video_info.finfo) && + GST_VIDEO_FORMAT_INFO_FORMAT (p->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM) { + gst_video_alignment_reset (&p->video_align); + gst_video_info_align (&p->video_info, &p->video_align); + } +#endif + } else if (g_str_has_prefix(gst_structure_get_name(structure), "audio/")) { + p->has_video = FALSE; + gst_audio_info_from_caps(&p->audio_info, caps); } else { - has_video = FALSE; + g_assert_not_reached (); } - p->add_metavideo = has_video && gst_buffer_pool_config_has_option (config, + p->add_metavideo = p->has_video && gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + has_videoalign = p->has_video && gst_buffer_pool_config_has_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + + if (has_videoalign) { + gst_buffer_pool_config_get_video_alignment (config, &p->video_align); + gst_video_info_align (&p->video_info, &p->video_align); + gst_buffer_pool_config_set_video_alignment (config, &p->video_align); + + GST_LOG_OBJECT (pool, "Set alignment: %u-%ux%u-%u", + p->video_align.padding_left, p->video_align.padding_right, + p->video_align.padding_top, p->video_align.padding_bottom); + } +#endif + if (p->video_info.size != 0) size = p->video_info.size; @@ -355,6 +441,10 @@ gst_pipewire_pool_finalize (GObject * object) g_weak_ref_set (&pool->stream, NULL); g_object_unref (pool->fd_allocator); g_object_unref (pool->dmabuf_allocator); +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + if (pool->shm_allocator) + g_object_unref (pool->shm_allocator); +#endif G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object); } @@ -389,5 +479,8 @@ gst_pipewire_pool_init (GstPipeWirePool * pool) { pool->fd_allocator = gst_fd_allocator_new (); pool->dmabuf_allocator = gst_dmabuf_allocator_new (); +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + gst_shm_allocator_init_once(); +#endif g_cond_init (&pool->cond); } diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index 15b7f0d59..c62d79e7d 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -9,6 +9,7 @@ #include +#include #include #include @@ -40,14 +41,19 @@ struct _GstPipeWirePool { GWeakRef stream; guint n_buffers; + gboolean has_video; gboolean add_metavideo; + GstAudioInfo audio_info; GstVideoInfo video_info; + GstVideoAlignment video_align; GstAllocator *fd_allocator; GstAllocator *dmabuf_allocator; + GstAllocator *shm_allocator; GCond cond; gboolean paused; + gboolean allocate_memory; }; enum GstPipeWirePoolMode { diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index c40fd32ec..981058bb1 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -334,9 +334,7 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) * the default, since we can't grow the pool once this is set */ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int( max_buffers, min_buffers, max_buffers), - SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int( - (1<stream->pool, b); if (!gst_pipewire_pool_has_buffers (pwsink->stream->pool) && !GST_BUFFER_POOL_IS_FLUSHING (GST_BUFFER_POOL_CAST (pwsink->stream->pool))) { - GST_ELEMENT_ERROR (pwsink, RESOURCE, NOT_FOUND, - ("all buffers have been removed"), - ("PipeWire link to remote node was destroyed")); + if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE) { + GST_ELEMENT_ERROR (pwsink, RESOURCE, NOT_FOUND, + ("all buffers have been removed"), + ("PipeWire link to remote node was destroyed")); + } } } @@ -807,6 +808,11 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) else flags |= PW_STREAM_FLAG_DRIVER; +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + flags |= PW_STREAM_FLAG_ALLOC_BUFFERS; + pwsink->stream->pool->allocate_memory = true; +#endif + target_id = pwsink->stream->path ? (uint32_t)atoi(pwsink->stream->path) : PW_ID_ANY; if (pwsink->stream->target_object) { @@ -860,8 +866,12 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool)); gst_buffer_pool_config_get_params (config, NULL, &size, &min_buffers, &max_buffers); gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); - if(pwsink->is_video) - gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); + if (pwsink->is_video) { + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); +#ifdef HAVE_GSTREAMER_SHM_ALLOCATOR + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); +#endif + } gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool), config); pw_thread_loop_unlock (pwsink->stream->core->loop); From 5c547d58d1bea70401abeaa9929973a500cd1c75 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 2 Apr 2025 10:39:34 +0200 Subject: [PATCH 0110/1014] adapter: handle builder overflow --- spa/plugins/audioconvert/audioadapter.c | 2 ++ spa/plugins/videoconvert/videoadapter.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 6db0a39ae..2fd36f45b 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1025,6 +1025,8 @@ found: format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); + if (format == NULL) + return -ENOSPC; spa_pod_fixate(format); diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 101e6d9b9..3cb464501 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1026,6 +1026,8 @@ found: format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); + if (format == NULL) + return -ENOSPC; spa_pod_fixate(format); From 832c5a6d2597885c9de1d868e22ee461412e9460 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 2 Apr 2025 13:25:31 +0200 Subject: [PATCH 0111/1014] fix printf modifier for gsize Fixes #4641 --- src/gst/gstpipewirepool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 3e061a0fd..3ac1b42d0 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -134,7 +134,7 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) block_size = quantum_limit * pool->audio_info.bpf; } - GST_DEBUG_OBJECT (pool, "setting block size %lu", block_size); + GST_DEBUG_OBJECT (pool, "setting block size %zu", block_size); if (!pool->shm_allocator) pool->shm_allocator = gst_shm_allocator_get(); From dd683176c23d4d7e16201306e63c02e0cea5937b Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Apr 2025 06:59:31 -0400 Subject: [PATCH 0112/1014] gst: sink: Set provide clock flag if not in provide mode Handle a theoretical corner case of an element that is first started with mode=provide, and then restarted without mode=provide. --- src/gst/gstpipewiresink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 981058bb1..26be00195 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -1052,6 +1052,8 @@ gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) * clock to drive the pipeline (and thus the graph). */ if (this->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE) GST_OBJECT_FLAG_UNSET (this, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + else + GST_OBJECT_FLAG_SET (this, GST_ELEMENT_FLAG_PROVIDE_CLOCK); /* the initial stream state is active, which is needed for linking and * negotiation to happen and the bufferpool to be set up. We don't know From 77143e54d96070f963a8a78cf58322d1afe537b9 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 1 Apr 2025 17:34:15 +0530 Subject: [PATCH 0113/1014] gstpipewiresrc: Fix re-linking for audio For a pipeline like below, we might want to dynamically switch the audio source. gst-launch-1.0 -e pipewiresrc autoconnect=false ! queue ! audioconvert ! autoaudiosink On switching to a different audio source, any one of driver, quantum or clock rate might change which changes the return `result` value of gst_pipewire_clock_get_internal_time. This can result in the basesrc create function incorrectly waiting in gst_clock_id_wait. We post clock lost message to fix this. In the case of gst-launch, it will set the pipeline to PAUSED and then PLAYING to to force a new clock and a new base_time distribution. Without the clock lost message, the following can be seen before re-linking to a different source 0:00:30.887602864 79499 0x7fffe8000d40 DEBUG GST_CLOCK gstsystemclock.c:1158:gst_system_clock_id_wait_jitter_unlocked: entry 0x7fffd803fad0 time 0:00:17.024565416 now 0:00:17.024109144 diff (time-now) 456272 after re-linking to a different source 0:00:45.790843245 79499 0x7fffe8000d40 DEBUG GST_CLOCK gstsystemclock.c:1158:gst_system_clock_id_wait_jitter_unlocked: entry 0x7fffd803fad0 time 0:00:31.927694059 now 0:00:17.066883864 diff (time-now) 14860810195 With the clock lost message, the following can be seen before re-linking to a different source 0:01:09.336533552 89461 0x7fffe8000d40 DEBUG GST_CLOCK gstsystemclock.c:1158:gst_system_clock_id_wait_jitter_unlocked: entry 0x7fffd803fad0 time 0:00:58.198536772 now 0:00:58.197444926 diff (time-now) 1091846 after re-linking to a different source 0:01:21.659827958 89461 0x7fffe8000d40 DEBUG GST_CLOCK gstsystemclock.c:1158:gst_system_clock_id_wait_jitter_unlocked: entry 0x7fffd803fad0 time 0:28:24.853517646 now 0:28:24.853527204 diff (time-now) -9558 Note the difference in `time` and `now` fields of the above log message. This is easy to reproduce by using a pipewiresink as the audio source with a pipeline like below, as one of the sources during switching. gst-launch-1.0 -e audiotestsrc wave=ticks ! audioconvert ! audio/x-raw,format=F32LE,rate=48000,channels=1 ! pipewiresink stream-properties="props,media.class=Audio/Source,node.description=pwsink" client-name=pwsink Applications need to handle the GST_MESSAGE_CLOCK_LOST message in their bus handlers. --- src/gst/gstpipewiresrc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 86c39f2b7..a2213ec86 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -739,13 +739,29 @@ on_state_changed (void *data, enum pw_stream_state state, const char *error) { GstPipeWireSrc *pwsrc = data; + GstState current_state = GST_ELEMENT_CAST (pwsrc)->current_state; GST_DEBUG ("got stream state %s", pw_stream_state_as_string (state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: + break; case PW_STREAM_STATE_PAUSED: + /* + * We may see a driver/quantum/clock rate change on switching audio + * sources. The same is not applicable for video. + * + * We post the clock lost message here to take care of a possible + * jump or shift in base_time/clock for the pipeline. Application + * must handle the clock lost message in it's bus handler by pausing + * the pipeline and then setting it back to playing. + */ + if (current_state == GST_STATE_PLAYING && !pwsrc->is_video) + gst_element_post_message (GST_ELEMENT_CAST (pwsrc), + gst_message_new_clock_lost (GST_OBJECT_CAST (pwsrc), + GST_CLOCK_CAST (pwsrc->stream->clock))); + break; case PW_STREAM_STATE_STREAMING: break; case PW_STREAM_STATE_ERROR: From 9d6098f8c639348b29566bec1f51b4edf4282244 Mon Sep 17 00:00:00 2001 From: Quentin Date: Thu, 3 Apr 2025 21:49:05 +0000 Subject: [PATCH 0114/1014] Small changes, more natural sounding --- po/oc.po | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/po/oc.po b/po/oc.po index f2cfad815..e71993297 100644 --- a/po/oc.po +++ b/po/oc.po @@ -272,7 +272,7 @@ msgstr "Contraròtle automatic del ganh" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" -msgstr "Pas de contraròtle automatic del ganh" +msgstr "Cap de contraròtle automatic del ganh" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" @@ -288,7 +288,7 @@ msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" -msgstr "Pas d'amplificador" +msgstr "Cap d'amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" @@ -296,7 +296,7 @@ msgstr "Amplificacion bassas" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" -msgstr "Pas d'amplificacion de las bassas" +msgstr "Cap d'amplificacion de las bassas" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1995 @@ -531,12 +531,12 @@ msgstr "Messatjariá mono + Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" -msgstr "%s Sortida" +msgstr "Sortida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" -msgstr "%s Entrada" +msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format From 5cfd461ba2a6a31e8a42025912accc9ea6b28576 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 15:00:04 +0200 Subject: [PATCH 0115/1014] alsa: Use the configured max channels Don't hardcode the max channels to 64 but use the globally configured SPA_AUDIO_MAX_CHANNELS value. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index bd6836e53..fdfce3bd4 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -30,7 +30,6 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define MIN_BUFFERS 2u #define MAX_BUFFERS 64u -#define MAX_CHANNELS 64 #define MAX_RATE (48000*8) #define MIN_PERIOD 64 @@ -1097,7 +1096,7 @@ struct param_info infos[] = { { "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX, { 1, MAX_RATE }, 2, collect_int }, { "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX, - { 1, MAX_CHANNELS }, 2, collect_int }, + { 1, SPA_AUDIO_MAX_CHANNELS }, 2, collect_int }, { "alsa.buffer-bytes", SND_PCM_IOPLUG_HW_BUFFER_BYTES, TYPE_MIN_MAX, { MIN_BUFFER_BYTES, MAX_BUFFER_BYTES }, 2, collect_int }, { "alsa.period-bytes", SND_PCM_IOPLUG_HW_PERIOD_BYTES, TYPE_MIN_MAX, From 722776cf65e3a902cceb9f8b0601dc4851c76fee Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 15:04:09 +0200 Subject: [PATCH 0116/1014] Remove some hardcoded channel number values Mostly related to the number of channels. --- spa/plugins/alsa/acp/acp.c | 2 +- spa/plugins/audiomixer/audiomixer.c | 1 - spa/plugins/bluez5/defs.h | 3 ++- src/modules/module-rtp/opus.c | 2 +- src/pipewire/stream.c | 6 ++++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 63a32eafb..aadb1277d 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -1777,7 +1777,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->auto_port = true; impl->ignore_dB = false; impl->rate = DEFAULT_RATE; - impl->pro_channels = 64; + impl->pro_channels = ACP_MAX_CHANNELS; if (props) { if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index bab300713..ad1e8fdba 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -31,7 +31,6 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audiomixer"); #define MAX_BUFFERS 64 #define MAX_PORTS 512 -#define MAX_CHANNELS 64 #define MAX_ALIGN MIX_OPS_MAX_ALIGN #define PORT_DEFAULT_VOLUME 1.0 diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 230945808..bbf814b3c 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -17,6 +17,7 @@ extern "C" { #include #include #include +#include #include @@ -655,7 +656,7 @@ struct spa_bt_transport { struct spa_list bap_transport_linked; uint32_t n_channels; - uint32_t channels[64]; + uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; struct spa_bt_transport_volume volumes[SPA_BT_VOLUME_ID_TERM]; diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 83857c80b..bc0150386 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -320,7 +320,7 @@ static void rtp_opus_process_capture(void *data) static int rtp_opus_init(struct impl *impl, enum spa_direction direction) { int err; - unsigned char mapping[64]; + unsigned char mapping[SPA_AUDIO_MAX_CHANNELS]; uint32_t i; if (impl->psamples >= 2880) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 4af3a3afa..29aa9afe8 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -28,7 +28,8 @@ PW_LOG_TOPIC_EXTERN(log_stream); #define PW_LOG_TOPIC_DEFAULT log_stream -#define MAX_BUFFERS 64 +#define MAX_BUFFERS 64u +#define MAX_VALUES 256u #define MASK_BUFFERS (MAX_BUFFERS-1) @@ -72,7 +73,7 @@ struct control { struct pw_stream_control control; struct spa_pod *info; unsigned int emitted:1; - float values[64]; + float values[MAX_VALUES]; }; struct stream { @@ -1354,6 +1355,7 @@ static int node_event_param(void *object, int seq, if ((values = spa_pod_get_array(&prop->value, &n_values)) == NULL || !spa_pod_is_float(SPA_POD_ARRAY_CHILD(&prop->value))) continue; + n_values = SPA_MIN(n_values, MAX_VALUES); break; default: continue; From a9f12537d1bd331ef438f5e210220f56e7613d48 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 15:13:47 +0200 Subject: [PATCH 0117/1014] pulse: clamp channel numbers to right values When converting between pipewire and pulse channelmaps, make sure we clamp the channel numbers to the the right limit. --- src/modules/module-protocol-pulse/format.c | 17 +++++++++-------- src/modules/module-protocol-pulse/module.c | 4 ++-- src/modules/module-protocol-pulse/volume.c | 6 +++--- src/modules/module-pulse-tunnel.c | 11 ++++++----- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 7ce9757c5..3143e830b 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -348,14 +348,15 @@ uint32_t channel_paname2id(const char *name, size_t size) void channel_map_to_positions(const struct channel_map *map, uint32_t *pos) { - int i; - for (i = 0; i < map->channels; i++) + uint32_t i, channels = SPA_MIN(map->channels, SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < channels; i++) pos[i] = map->map[i]; } void positions_to_channel_map(const uint32_t *pos, uint32_t channels, struct channel_map *map) { uint32_t i; + channels = SPA_MIN(channels, CHANNELS_MAX); for (i = 0; i < channels; i++) map->map[i] = pos[i]; map->channels = channels; @@ -430,7 +431,7 @@ void channel_map_parse(const char *str, struct channel_map *map) }; } else { channels = map->channels = 0; - while (*p && channels < SPA_AUDIO_MAX_CHANNELS) { + while (*p && channels < CHANNELS_MAX) { uint32_t chname; if ((len = strcspn(p, ",")) == 0) @@ -576,11 +577,11 @@ int format_parse_param(const struct spa_pod *param, bool collect, if (info.info.raw.rate) ss->rate = info.info.raw.rate; if (info.info.raw.channels) - ss->channels = info.info.raw.channels; + ss->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); } if (map) { if (info.info.raw.channels) { - map->channels = info.info.raw.channels; + map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); for (i = 0; i < map->channels; i++) map->map[i] = info.info.raw.position[i]; } @@ -654,13 +655,13 @@ int format_info_from_spec(struct format_info *info, const struct sample_spec *ss pw_properties_setf(info->props, "format.channels", "%d", ss->channels); if (map && map->channels == ss->channels) { char chmap[1024] = ""; - int i, o, r; - uint32_t aux = 0; + int r; + uint32_t aux = 0, i, o; for (i = 0, o = 0; i < map->channels; i++) { r = snprintf(chmap+o, sizeof(chmap)-o, "%s%s", i == 0 ? "" : ",", channel_id2paname(map->map[i], &aux)); - if (r < 0 || o + r >= (int)sizeof(chmap)) + if (r < 0 || o + r >= sizeof(chmap)) return -ENOSPC; o += r; } diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index 0d45399c3..fe4b346d9 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -204,7 +204,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props } if (key_channels && (str = pw_properties_get(props, key_channels)) != NULL) { info->channels = pw_properties_parse_int(str); - if (info->channels == 0 || info->channels > SPA_AUDIO_MAX_CHANNELS) { + if (info->channels == 0 || info->channels > CHANNELS_MAX) { pw_log_error("invalid %s '%s'", key_channels, str); return -EINVAL; } @@ -214,7 +214,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props struct channel_map map; channel_map_parse(str, &map); - if (map.channels == 0 || map.channels > SPA_AUDIO_MAX_CHANNELS) { + if (map.channels == 0 || map.channels > CHANNELS_MAX) { pw_log_error("invalid %s '%s'", key_channel_map, str); return -EINVAL; } diff --git a/src/modules/module-protocol-pulse/volume.c b/src/modules/module-protocol-pulse/volume.c index b47a1d026..de71baa53 100644 --- a/src/modules/module-protocol-pulse/volume.c +++ b/src/modules/module-protocol-pulse/volume.c @@ -53,7 +53,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo if (monitor) continue; info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - info->volume.values, SPA_AUDIO_MAX_CHANNELS); + info->volume.values, CHANNELS_MAX); SPA_FLAG_UPDATE(info->flags, VOLUME_HW_VOLUME, prop->flags & SPA_POD_PROP_FLAG_HARDWARE); break; @@ -68,7 +68,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo if (!monitor) continue; info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - info->volume.values, SPA_AUDIO_MAX_CHANNELS); + info->volume.values, CHANNELS_MAX); SPA_FLAG_CLEAR(info->flags, VOLUME_HW_VOLUME); break; case SPA_PROP_volumeBase: @@ -84,7 +84,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo } case SPA_PROP_channelMap: info->map.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - info->map.map, SPA_AUDIO_MAX_CHANNELS); + info->map.map, CHANNELS_MAX); break; default: break; diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 32d01122a..4999a199d 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -298,7 +298,7 @@ static void stream_param_changed(void *d, uint32_t id, const struct spa_pod *par if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { - volume.channels = n; + volume.channels = SPA_MIN(PA_CHANNELS_MAX, n); for (n = 0; n < volume.channels; n++) volume.values[n] = pa_sw_volume_from_linear(vols[n]); @@ -834,11 +834,12 @@ do_stream_sync_volumes(struct spa_loop *loop, struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[1]; struct spa_pod *param; - uint32_t i; + uint32_t i, channels; float vols[SPA_AUDIO_MAX_CHANNELS]; float soft_vols[SPA_AUDIO_MAX_CHANNELS]; - for (i = 0; i < impl->volume.channels; i++) { + channels = SPA_MIN(impl->volume.channels, SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < channels; i++) { vols[i] = (float)pa_sw_volume_to_linear(impl->volume.values[i]); soft_vols[i] = 1.0f; } @@ -851,10 +852,10 @@ do_stream_sync_volumes(struct spa_loop *loop, spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0); spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, - impl->volume.channels, vols); + channels, vols); spa_pod_builder_prop(&b, SPA_PROP_softVolumes, 0); spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float, - impl->volume.channels, soft_vols); + channels, soft_vols); param = spa_pod_builder_pop(&b, &f[0]); pw_stream_set_param(impl->stream, SPA_PARAM_Props, param); From 8205038ffb5ecb6f227f6a45c9295128f6f8c4ba Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 15:18:43 +0200 Subject: [PATCH 0118/1014] buffers: increase max datas limit Increase the max amount of buffer datas allowed. --- src/modules/module-client-node/client-node.c | 2 +- src/modules/module-client-node/protocol-native.c | 2 +- src/pipewire/buffers.c | 4 ++-- src/pipewire/filter.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 3c6d6e4d8..134511b35 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -30,7 +30,7 @@ PW_LOG_TOPIC_EXTERN(mod_topic); #define MAX_BUFFERS 64 #define MAX_METAS 16u -#define MAX_DATAS 64u +#define MAX_DATAS 256u #define AREA_SLOT (sizeof(struct spa_io_async_buffers)) #define AREA_SIZE (4096u / AREA_SLOT) #define MAX_AREAS 32 diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c index 3051703af..17f8e2da1 100644 --- a/src/modules/module-client-node/protocol-native.c +++ b/src/modules/module-client-node/protocol-native.c @@ -18,7 +18,7 @@ #define MAX_PARAM_INFO 128 #define MAX_BUFFERS 64 #define MAX_METAS 16u -#define MAX_DATAS 64u +#define MAX_DATAS 256u PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index e73adaaec..bef597a06 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -16,8 +16,8 @@ PW_LOG_TOPIC_EXTERN(log_buffers); #define PW_LOG_TOPIC_DEFAULT log_buffers -#define MAX_ALIGN 32 -#define MAX_BLOCKS 64u +#define MAX_ALIGN 32u +#define MAX_BLOCKS 256u struct port { struct spa_node *node; diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 519c1e2f7..c4130322a 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -26,7 +26,7 @@ PW_LOG_TOPIC_EXTERN(log_filter); #define PW_LOG_TOPIC_DEFAULT log_filter -#define MAX_BUFFERS 64 +#define MAX_BUFFERS 64u #define MASK_BUFFERS (MAX_BUFFERS-1) From 031014bbabcb9f092504dd17420c4b4f2bfa26c1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 15:34:39 +0200 Subject: [PATCH 0119/1014] acp: remove channel limit from API Internally bump the channel limit and allocate the channel map dynamically. --- spa/plugins/alsa/acp/acp.c | 9 ++++++--- spa/plugins/alsa/acp/acp.h | 3 +-- spa/plugins/alsa/alsa-acp-device.c | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index aadb1277d..c2ab555f0 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -16,7 +16,8 @@ void *_acp_log_data; struct spa_i18n *acp_i18n; -#define DEFAULT_RATE 48000 +#define DEFAULT_CHANNELS 256u +#define DEFAULT_RATE 48000u #define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ @@ -183,6 +184,7 @@ static void device_free(void *data) pa_dynarray_clear(&dev->port_array); pa_proplist_free(dev->proplist); pa_hashmap_free(dev->ports); + free(dev->device.format.map); } static inline void channelmap_to_acp(pa_channel_map *m, uint32_t *map) @@ -213,9 +215,10 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t dev->device.format.format_mask = m->sample_spec.format; dev->device.format.rate_mask = m->sample_spec.rate; dev->device.format.channels = m->channel_map.channels; + dev->device.format.map = calloc(m->channel_map.channels, sizeof(uint32_t)); + channelmap_to_acp(&m->channel_map, dev->device.format.map); pa_cvolume_reset(&dev->real_volume, dev->device.format.channels); pa_cvolume_reset(&dev->soft_volume, dev->device.format.channels); - channelmap_to_acp(&m->channel_map, dev->device.format.map); dev->direction = direction; dev->proplist = pa_proplist_new(); pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->proplist); @@ -1777,7 +1780,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->auto_port = true; impl->ignore_dB = false; impl->rate = DEFAULT_RATE; - impl->pro_channels = ACP_MAX_CHANNELS; + impl->pro_channels = DEFAULT_CHANNELS; if (props) { if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL) diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 4b9f2c495..01ad11806 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -24,7 +24,6 @@ extern "C" { #endif #define ACP_INVALID_INDEX ((uint32_t)-1) -#define ACP_MAX_CHANNELS 64 struct acp_dict_item { const char *key; @@ -93,7 +92,7 @@ struct acp_format { uint32_t format_mask; uint32_t rate_mask; uint32_t channels; - uint32_t map[ACP_MAX_CHANNELS]; + uint32_t *map; }; #define ACP_DICT_INIT(items,n_items) ((struct acp_dict) { 0, (n_items), (items) }) diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 5d46feed4..4d8a6d92d 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -671,8 +671,8 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) props; int changed = 0; - float volumes[ACP_MAX_CHANNELS]; - uint32_t channels[ACP_MAX_CHANNELS]; + float volumes[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_volumes = 0; if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) @@ -694,13 +694,13 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct break; case SPA_PROP_channelVolumes: if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - volumes, ACP_MAX_CHANNELS)) > 0) { + volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { changed++; } break; case SPA_PROP_channelMap: if (spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - channels, ACP_MAX_CHANNELS) > 0) { + channels, SPA_AUDIO_MAX_CHANNELS) > 0) { changed++; } break; From a99ed4da2afe2723948eea4889c9f1b554d27fd1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 11:24:39 +0200 Subject: [PATCH 0120/1014] raop: fix byte array initialization Initialize the byte array with bytes instead of a string because the 0 byte at the end of the string does not fit in the array and causes a compiler warning. --- src/modules/module-raop-sink.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 9d9406811..8eef429f0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1331,10 +1331,12 @@ static int rtsp_post_auth_setup_reply(void *data, int status, const struct spa_d static int rtsp_do_post_auth_setup(struct impl *impl) { - static const unsigned char content[33] = - "\x01" - "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07" - "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e"; + static const uint8_t content[33] = { + 0x01, + 0x59, 0x02, 0xed, 0xe9, 0x0d, 0x4e, 0xf2, 0xbd, + 0x4c, 0xb6, 0x8a, 0x63, 0x30, 0x03, 0x82, 0x07, + 0xa9, 0x4d, 0xbd, 0x50, 0xd8, 0xaa, 0x46, 0x5b, + 0x5d, 0x8c, 0x01, 0x2a, 0x0c, 0x7e, 0x1d, 0x4e }; return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict, "application/octet-stream", content, sizeof(content), From e355df3785e00c0e82f605ba195ea2e08ded60a0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 17:06:43 +0200 Subject: [PATCH 0121/1014] channelmix: fix channel_mask bits iteration Iterate the number of bits in the channel mask based on the number of bits in the mask, not the max amount of channel positions. --- spa/plugins/audioconvert/channelmix-ops.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index d774112bc..792cf7e09 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -129,6 +129,7 @@ static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uin #define STEREO (_MASK(FL)|_MASK(FR)) #define REAR (_MASK(RL)|_MASK(RR)) #define SIDE (_MASK(SL)|_MASK(SR)) +#define CHANNEL_BITS (64u) static uint32_t mask_to_ch(struct channelmix *mix, uint64_t mask) { @@ -168,7 +169,7 @@ static bool match_mix(struct channelmix *mix, { bool matched = false; uint32_t i; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < CHANNEL_BITS; i++) { if ((src_mask & dst_mask & (1ULL << i))) { spa_log_info(mix->log, "matched channel %u (%f)", i, 1.0f); matrix[i][i] = 1.0f; @@ -627,7 +628,7 @@ done: if (src_paired == 0) src_paired = ~0LU; - for (jc = 0, ic = 0, i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (jc = 0, ic = 0, i = 0; i < CHANNEL_BITS; i++) { float sum = 0.0f; char str1[1024], str2[1024]; struct spa_strbuf sb1, sb2; @@ -637,7 +638,7 @@ done: if ((dst_paired & (1UL << i)) == 0) continue; - for (jc = 0, j = 0; j < SPA_AUDIO_MAX_CHANNELS; j++) { + for (jc = 0, j = 0; j < CHANNEL_BITS; j++) { if ((src_paired & (1UL << j)) == 0) continue; if (ic >= dst_chan || jc >= src_chan) From b8ad9acbc71ed6d9dc83b460e30a2b7e2f6cb060 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 14 Mar 2025 22:19:21 +0200 Subject: [PATCH 0122/1014] CI: build also 1.4 docs --- .gitlab-ci.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 194446609..388c51233 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -318,11 +318,13 @@ build_on_fedora_html_docs: -Dsndfile=enabled -Dsession-managers=[] before_script: - - git fetch origin 1.0 1.2 master + - git fetch origin 1.0 1.2 1.4 master - git branch -f 1.0 origin/1.0 - git clone -b 1.0 . branch-1.0 - git branch -f 1.2 origin/1.2 - git clone -b 1.2 . branch-1.2 + - git branch -f 1.4 origin/1.4 + - git clone -b 1.4 . branch-1.4 - git branch -f master origin/master - git clone -b master . branch-master - !reference [.build, before_script] @@ -335,6 +337,10 @@ build_on_fedora_html_docs: - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs - cd .. + - cd branch-1.4 + - meson setup builddir $MESON_OPTIONS + - meson compile -C builddir doc/pipewire-docs + - cd .. - cd branch-master - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs @@ -576,11 +582,12 @@ pages: dependencies: - build_on_fedora_html_docs script: - - mkdir public public/1.0 public/1.2 public/devel + - mkdir public public/1.0 public/1.2 public/1.4 public/devel - cp -R branch-1.0/builddir/doc/html/* public/1.0/ - cp -R branch-1.2/builddir/doc/html/* public/1.2/ + - cp -R branch-1.4/builddir/doc/html/* public/1.4/ - cp -R branch-master/builddir/doc/html/* public/devel/ - - (cd public && ln -s 1.2/* .) + - (cd public && ln -s 1.4/* .) artifacts: paths: - public @@ -588,3 +595,4 @@ pages: - if: $CI_COMMIT_BRANCH == 'master' - if: $CI_COMMIT_BRANCH == '1.0' - if: $CI_COMMIT_BRANCH == '1.2' + - if: $CI_COMMIT_BRANCH == '1.4' From e3122a8555806ed39a380a2db81ff526bd1cce3c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 5 Apr 2025 14:35:58 +0300 Subject: [PATCH 0123/1014] doc: add links to docs of other versions --- doc/DoxygenLayout.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml index b5d353a6f..300c8400e 100644 --- a/doc/DoxygenLayout.xml +++ b/doc/DoxygenLayout.xml @@ -41,6 +41,11 @@ + + + + + From e9a24063142f531c991a320dcefb28140ba23a29 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 6 Apr 2025 12:05:10 +0300 Subject: [PATCH 0124/1014] CI: bump to Fedora 41 Fedora 40 EOL is next month, and newer Doxygen version is better for docs.pipewire.org. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 388c51233..0831d341c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,8 +35,8 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-03-25.0' - FDO_DISTRIBUTION_VERSION: '40' + FDO_DISTRIBUTION_TAG: '2025-04-06.1' + FDO_DISTRIBUTION_VERSION: '41' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel avahi-devel From bb1bb07f6cdb2a03019b10e9acd3dfb2a611696b Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 24 Mar 2025 15:50:22 +0530 Subject: [PATCH 0125/1014] gstpipewiresrc: Handle stream being disconnected When PW source is used with something like Camera and the camera is disconnected, all buffers are removed and stream will be paused. When using PW sink with source, the sink side pipeline can go to EOS. This again results in all the buffers being removed and stream being paused on the source side. PW source side pipeline can also crash if the sink was in the middle of frame copying a buffer to render which got removed. Handle this scenario by sending a flush-start event at the start of buffer removal and flush-stop at the end followed by an end of stream or pipeline error depending on user selection. --- src/gst/gstpipewiresrc.c | 133 ++++++++++++++++++++++++++++++++++++--- src/gst/gstpipewiresrc.h | 22 +++++++ 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index a2213ec86..4e716d2c1 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -46,7 +46,8 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define DEFAULT_RESEND_LAST false #define DEFAULT_KEEPALIVE_TIME 0 #define DEFAULT_AUTOCONNECT true -#define DEFAULT_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO +#define DEFAULT_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO +#define DEFAULT_ON_DISCONNECT GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE enum { @@ -64,8 +65,28 @@ enum PROP_KEEPALIVE_TIME, PROP_AUTOCONNECT, PROP_USE_BUFFERPOOL, + PROP_ON_DISCONNECT, }; +GType +gst_pipewire_src_on_disconnect_get_type (void) +{ + static gsize on_disconnect_type = 0; + static const GEnumValue on_disconnect[] = { + {GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE, "GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE", "none"}, + {GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS, "GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS", "eos"}, + {GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR, "GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR", "error"}, + {0, NULL, NULL}, + }; + + if (g_once_init_enter (&on_disconnect_type)) { + GType tmp = + g_enum_register_static ("GstPipeWireSrcOnDisconnect", on_disconnect); + g_once_init_leave (&on_disconnect_type, tmp); + } + + return (GType) on_disconnect_type; +} static GstStaticPadTemplate gst_pipewire_src_template = GST_STATIC_PAD_TEMPLATE ("src", @@ -170,6 +191,10 @@ gst_pipewire_src_set_property (GObject * object, guint prop_id, pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; break; + case PROP_ON_DISCONNECT: + pwsrc->on_disconnect = g_value_get_enum (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -235,6 +260,10 @@ gst_pipewire_src_get_property (GObject * object, guint prop_id, g_value_set_boolean (value, !!pwsrc->use_bufferpool); break; + case PROP_ON_DISCONNECT: + g_value_set_enum (value, pwsrc->on_disconnect); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -414,6 +443,16 @@ gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_ON_DISCONNECT, + g_param_spec_enum ("on-disconnect", + "On disconnect", + "Action to take on disconnect", + GST_TYPE_PIPEWIRE_SRC_ON_DISCONNECT, + DEFAULT_ON_DISCONNECT, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gstelement_class->provide_clock = gst_pipewire_src_provide_clock; gstelement_class->change_state = gst_pipewire_src_change_state; gstelement_class->send_event = gst_pipewire_src_send_event; @@ -462,6 +501,9 @@ gst_pipewire_src_init (GstPipeWireSrc * src) src->autoconnect = DEFAULT_AUTOCONNECT; src->min_latency = 0; src->max_latency = GST_CLOCK_TIME_NONE; + src->n_buffers = 0; + src->flushing_on_remove_buffer = FALSE; + src->on_disconnect = DEFAULT_ON_DISCONNECT; src->transform_value = UINT32_MAX; } @@ -469,11 +511,26 @@ gst_pipewire_src_init (GstPipeWireSrc * src) static gboolean buffer_recycle (GstMiniObject *obj) { - GstPipeWireSrc *src; - GstPipeWirePoolData *data; + GstPipeWirePoolData *data = gst_pipewire_pool_get_data (GST_BUFFER_CAST(obj)); + GstPipeWireSrc *src = data->owner; int res; - data = gst_pipewire_pool_get_data (GST_BUFFER_CAST(obj)); + if (src->flushing_on_remove_buffer) { + /* + * If a flush-start was initiated, this might be called by elements like + * queues downstream purging buffers from their internal queues. This can + * deadlock if queues use min-threshold-buffers/bytes/time with src_create + * trying to take the loop lock and buffer_recycle trying to take the loop + * lock down below. We return from here, to prevent deadlock with streaming + * thread in a queue thread. + * + * We will take care of queueing the buffer in on_remove_buffer. + */ + GstBuffer *buffer = GST_BUFFER_CAST(obj); + GST_DEBUG_OBJECT (src, + "flush-start initiated, skipping buffer recycle %p", buffer); + return TRUE; + } GST_OBJECT_LOCK (data->pool); if (!obj->dispose) { @@ -482,7 +539,6 @@ buffer_recycle (GstMiniObject *obj) } GST_BUFFER_FLAGS (obj) = data->flags; - src = data->owner; pw_thread_loop_lock (src->stream->core->loop); if (!obj->dispose) { @@ -519,6 +575,8 @@ on_add_buffer (void *_data, struct pw_buffer *b) data->owner = pwsrc; data->queued = TRUE; GST_MINI_OBJECT_CAST (data->buf)->dispose = buffer_recycle; + + pwsrc->n_buffers++; } static void @@ -527,17 +585,76 @@ on_remove_buffer (void *_data, struct pw_buffer *b) GstPipeWireSrc *pwsrc = _data; GstPipeWirePoolData *data = b->user_data; GstBuffer *buf = data->buf; + gboolean flush_on_remove; int res; - GST_DEBUG_OBJECT (pwsrc, "remove buffer %p", buf); + GST_DEBUG_OBJECT (pwsrc, "remove buffer %p, queued: %d", + buf, data->queued); GST_MINI_OBJECT_CAST (buf)->dispose = NULL; + flush_on_remove = + pwsrc->on_disconnect == GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR || + pwsrc->on_disconnect == GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS; + + if (flush_on_remove && !pwsrc->flushing_on_remove_buffer) { + pwsrc->flushing_on_remove_buffer = TRUE; + + GST_DEBUG_OBJECT (pwsrc, "flush-start on remove buffer"); + /* + * It is possible that when buffers are being removed, a downstream + * element can be holding on to a buffer or in the middle of rendering + * the same. Former is possible with queues min-threshold-buffers or + * similar. Latter can result in a crash during gst_video_frame_copy. + * + * We send a flush-start event downstream to make elements discard + * any buffers they may be holding on to as well as return from their + * chain function ASAP. + */ + gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), + gst_event_new_flush_start ()); + } + if (data->queued) { gst_buffer_unref (buf); } else { if ((res = pw_stream_queue_buffer (pwsrc->stream->pwstream, b)) < 0) - GST_WARNING_OBJECT (pwsrc, "can't queue removed buffer %p, %s", buf, spa_strerror(res)); + GST_WARNING_OBJECT (pwsrc, "can't queue removed buffer %p, %s", + buf, spa_strerror(res)); + else + GST_DEBUG_OBJECT (pwsrc, "queued buffer %p", buf); + } + + pwsrc->n_buffers--; + + if (pwsrc->n_buffers == 0) { + GST_DEBUG_OBJECT (pwsrc, "removed all buffers"); + + pwsrc->flushing_on_remove_buffer = FALSE; + + switch (pwsrc->on_disconnect) { + case GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR: + GST_DEBUG_OBJECT (pwsrc, "flush-stop on removing all buffers"); + gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), + gst_event_new_flush_stop (FALSE)); + + GST_ELEMENT_ERROR (pwsrc, RESOURCE, NOT_FOUND, + ("all buffers have been removed"), + ("PipeWire link to remote node was destroyed")); + break; + case GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS: + GST_DEBUG_OBJECT (pwsrc, "flush-stop on removing all buffers"); + gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), + gst_event_new_flush_stop (FALSE)); + + GST_DEBUG_OBJECT (pwsrc, "sending eos downstream"); + gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), + gst_event_new_eos()); + break; + case GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE: + GST_DEBUG_OBJECT (pwsrc, "stream closed or removed"); + break; + } } } @@ -741,7 +858,7 @@ on_state_changed (void *data, GstPipeWireSrc *pwsrc = data; GstState current_state = GST_ELEMENT_CAST (pwsrc)->current_state; - GST_DEBUG ("got stream state %s", pw_stream_state_as_string (state)); + GST_DEBUG_OBJECT (pwsrc, "got stream state %s", pw_stream_state_as_string (state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index d5728cdc9..704ae682c 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -24,6 +24,22 @@ G_BEGIN_DECLS #define GST_PIPEWIRE_SRC_CAST(obj) ((GstPipeWireSrc *) (obj)) G_DECLARE_FINAL_TYPE (GstPipeWireSrc, gst_pipewire_src, GST, PIPEWIRE_SRC, GstPushSrc) +/** + * GstPipeWireSrcOnDisconnect: + * @GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS: send EoS downstream + * @GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR: raise pipeline error + * @GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE: no action + * + * Different actions on disconnect. + */ +typedef enum +{ + GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE, + GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS, + GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR, +} GstPipeWireSrcOnDisconnect; + +#define GST_TYPE_PIPEWIRE_SRC_ON_DISCONNECT (gst_pipewire_src_on_disconnect_get_type ()) /** * GstPipeWireSrc: @@ -36,6 +52,7 @@ struct _GstPipeWireSrc { GstPipeWireStream *stream; /*< private >*/ + gint n_buffers; gint use_bufferpool; gint min_buffers; gint max_buffers; @@ -56,6 +73,7 @@ struct _GstPipeWireSrc { gboolean flushing; gboolean started; gboolean eos; + gboolean flushing_on_remove_buffer; gboolean is_live; int64_t delay; @@ -65,8 +83,12 @@ struct _GstPipeWireSrc { GstBuffer *last_buffer; enum spa_meta_videotransform_value transform_value; + + GstPipeWireSrcOnDisconnect on_disconnect; }; +GType gst_pipewire_src_on_stream_disconnect_get_type (void); + G_END_DECLS #endif /* __GST_PIPEWIRE_SRC_H__ */ From 42b92a34872ac4497a5ee98def16c5972bffa036 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 6 Apr 2025 11:55:44 +0300 Subject: [PATCH 0126/1014] pw-loopback: add missing --channel-map long option --- src/tools/pw-loopback.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c index 65a613c15..1d76eb284 100644 --- a/src/tools/pw-loopback.c +++ b/src/tools/pw-loopback.c @@ -103,6 +103,7 @@ int main(int argc, char *argv[]) { "group", required_argument, NULL, 'g' }, { "name", required_argument, NULL, 'n' }, { "channels", required_argument, NULL, 'c' }, + { "channel-map", required_argument, NULL, 'm' }, { "latency", required_argument, NULL, 'l' }, { "delay", required_argument, NULL, 'd' }, { "capture", required_argument, NULL, 'C' }, From b15ec9bec9e4b77b24030b6e3e112eae6396ef1e Mon Sep 17 00:00:00 2001 From: fossdd Date: Sun, 6 Apr 2025 21:16:37 +0200 Subject: [PATCH 0127/1014] doc: fix typo in pw-link(1) --- doc/dox/programs/pw-link.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/dox/programs/pw-link.1.md b/doc/dox/programs/pw-link.1.md index aff53d600..83a1d8372 100644 --- a/doc/dox/programs/pw-link.1.md +++ b/doc/dox/programs/pw-link.1.md @@ -37,7 +37,7 @@ output ports and their links. List output ports \par -i | \--input -List output ports +List input ports \par -l | \--links List links From bbe1587f717d833c5c7e87ea07849d0d8548095b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 8 Apr 2025 01:04:42 +0300 Subject: [PATCH 0128/1014] bluez5: fix volume ids Node ids don't map directly to volume ids. Fix the indexing. --- spa/plugins/bluez5/bluez5-dbus.c | 6 +++++- spa/plugins/bluez5/bluez5-device.c | 20 ++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index e4495f0e8..98feef69f 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -3608,9 +3608,13 @@ fail: static int transport_set_volume(void *data, int id, float volume) { struct spa_bt_transport *transport = data; - struct spa_bt_transport_volume *t_volume = &transport->volumes[id]; + struct spa_bt_transport_volume *t_volume; uint16_t value; + spa_assert(id >= 0 && id < (int)SPA_N_ELEMENTS(transport->volumes)); + + t_volume = &transport->volumes[id]; + if (!t_volume->active || !spa_bt_transport_volume_enabled(transport)) return -ENOTSUP; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 2095ba82c..b6357e9cd 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -402,9 +402,15 @@ static void node_update_soft_volumes(struct node *node, float hw_volume) } } +static int get_volume_id(int node_id) +{ + return (node_id & SINK_ID_FLAG) ? SPA_BT_VOLUME_ID_TX : SPA_BT_VOLUME_ID_RX; +} + static bool node_update_volume_from_transport(struct node *node, bool reset) { struct impl *impl = node->impl; + int volume_id = get_volume_id(node->id); struct spa_bt_transport_volume *t_volume; float prev_hw_volume; @@ -417,7 +423,7 @@ static bool node_update_volume_from_transport(struct node *node, bool reset) && impl->profile != DEVICE_PROFILE_HSP_HFP) return false; - t_volume = &node->transport->volumes[node->id]; + t_volume = &node->transport->volumes[volume_id]; if (!t_volume->active) return false; @@ -2562,6 +2568,7 @@ static void device_set_update_volumes(struct node *node) struct device_set *dset = &impl->device_set; float hw_volume = node_get_hw_volume(node); bool sink = (node->id == DEVICE_ID_SINK_SET); + int volume_id = get_volume_id(node->id); struct device_set_member *members = sink ? dset->sink : dset->source; uint32_t n_members = sink ? dset->sinks : dset->sources; uint32_t i; @@ -2572,7 +2579,7 @@ static void device_set_update_volumes(struct node *node) for (i = 0; i < n_members; ++i) { struct spa_bt_transport *t = members[i].transport; - struct spa_bt_transport_volume *t_volume = t ? &t->volumes[members[i].id] : NULL; + struct spa_bt_transport_volume *t_volume = t ? &t->volumes[volume_id] : NULL; if (!t_volume || !t_volume->active) goto soft_volume; @@ -2580,13 +2587,13 @@ static void device_set_update_volumes(struct node *node) node_update_soft_volumes(node, hw_volume); for (i = 0; i < n_members; ++i) - spa_bt_transport_set_volume(members[i].transport, members[i].id, hw_volume); + spa_bt_transport_set_volume(members[i].transport, volume_id, hw_volume); return; soft_volume: /* Soft volume fallback */ for (i = 0; i < n_members; ++i) - spa_bt_transport_set_volume(members[i].transport, members[i].id, 1.0f); + spa_bt_transport_set_volume(members[i].transport, volume_id, 1.0f); node_update_soft_volumes(node, 1.0f); return; } @@ -2596,6 +2603,7 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[] uint32_t i; int changed = 0; struct spa_bt_transport_volume *t_volume; + int volume_id = get_volume_id(node->id); if (n_volumes == 0) return -EINVAL; @@ -2609,7 +2617,7 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[] node->volumes[i] = volumes[i % n_volumes]; } - t_volume = node->transport ? &node->transport->volumes[node->id]: NULL; + t_volume = node->transport ? &node->transport->volumes[volume_id]: NULL; if (t_volume && t_volume->active && spa_bt_transport_volume_enabled(node->transport)) { @@ -2617,7 +2625,7 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[] spa_log_debug(this->log, "node %d hardware volume %f", node->id, hw_volume); node_update_soft_volumes(node, hw_volume); - spa_bt_transport_set_volume(node->transport, node->id, hw_volume); + spa_bt_transport_set_volume(node->transport, volume_id, hw_volume); } else if (node->id == DEVICE_ID_SOURCE_SET || node->id == DEVICE_ID_SINK_SET) { device_set_update_volumes(node); } else { From 4c51e6518bb4547915afde3b2e791bb244e4d0b8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 7 Aug 2022 17:58:39 +0300 Subject: [PATCH 0129/1014] bluez5: ldac decoding support Add support for LDAC decoding, if libldac decoder is available. --- .gitlab-ci.yml | 1 + meson_options.txt | 4 + spa/meson.build | 17 +++ spa/plugins/bluez5/a2dp-codec-ldac.c | 182 ++++++++++++++++++++++++++- spa/plugins/bluez5/meson.build | 4 + 5 files changed, 207 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0831d341c..118b06887 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -375,6 +375,7 @@ build_all: -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled -Dbluez5-codec-lc3=disabled + -Dbluez5-codec-ldac-dec=disabled -Droc=disabled -Dlibcamera=disabled -Dsession-managers=[] diff --git a/meson_options.txt b/meson_options.txt index dc1b339f2..74ce3fa94 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -129,6 +129,10 @@ option('bluez5-codec-ldac', description: 'Enable LDAC Sony open source codec implementation', type: 'feature', value: 'auto') +option('bluez5-codec-ldac-dec', + description: 'Enable LDAC Sony open source codec decoding', + type: 'feature', + value: 'auto') option('bluez5-codec-aac', description: 'Enable Fraunhofer FDK AAC open source codec implementation', type: 'feature', diff --git a/spa/meson.build b/spa/meson.build index 1b07117fa..27e8ff049 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -70,6 +70,23 @@ if get_option('spa-plugins').allowed() summary({'LDAC': ldac_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') ldac_abr_dep = dependency('ldacBT-abr', required : get_option('bluez5-codec-ldac')) summary({'LDAC ABR': ldac_abr_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') + + if get_option('bluez5-codec-ldac-dec').allowed() + ldac_dec_dep = dependency('ldacBT-dec', required : false) + if not ldac_dec_dep.found() + dep = cc.find_library('ldacBT_dec', required : false) + if dep.found() and cc.has_function('ldacBT_decode', dependencies : dep) + ldac_dec_dep = dep + endif + endif + if not ldac_dec_dep.found() and get_option('bluez5-codec-ldac-dec').enabled() + error('LDAC decoder library not found') + endif + else + ldac_dec_dep = dependency('', required: false) + endif + summary({'LDAC DEC': ldac_dec_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') + aptx_dep = dependency('libfreeaptx', required : get_option('bluez5-codec-aptx')) summary({'aptX': aptx_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') fdk_aac_dep = dependency('fdk-aac', required : get_option('bluez5-codec-aac')) diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index f5d0b155f..3abc8e36c 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -19,6 +19,13 @@ #include #endif +#if defined(ENABLE_LDAC_DEC) +int ldacBT_decode(HANDLE_LDAC_BT handle, unsigned char *src, unsigned char *dst, + LDACBT_SMPL_FMT_T fmt, int src_size, int *consumed, int *dst_out); +int ldacBT_init_handle_decode(HANDLE_LDAC_BT handle, int channel_mode, int frequency, + int dummy1, int dummy2, int dummy3); +#endif + #include "rtp.h" #include "media-codecs.h" @@ -35,15 +42,25 @@ #define LDAC_ABR_SOCK_BUFFER_SIZE (LDAC_ABR_THRESHOLD_CRITICAL * LDAC_ABR_MAX_PACKET_NBYTES) +static struct spa_log *log_; + struct props { int eqmid; }; +struct dec_data { + int frames; + size_t max_frame_bytes; +}; + struct impl { HANDLE_LDAC_BT ldac; #ifdef ENABLE_LDAC_ABR HANDLE_LDAC_ABR ldac_abr; +#endif +#ifdef ENABLE_LDAC_DEC + HANDLE_LDAC_BT ldac_dec; #endif bool enable_abr; @@ -57,6 +74,8 @@ struct impl { int codesize; int frame_length; int frame_count; + + struct dec_data d; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, @@ -367,6 +386,66 @@ static int codec_set_props(void *props, const struct spa_pod *param) return prev_eqmid != p->eqmid; } +static const char *ldac_strerror(int ldac_error) +{ +#define LDAC_BT_ERROR_CASE(name) case name: return #name + switch (ldac_error) { + LDAC_BT_ERROR_CASE(LDACBT_ERR_NONE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_NON_FATAL); + LDAC_BT_ERROR_CASE(LDACBT_ERR_BIT_ALLOCATION); + LDAC_BT_ERROR_CASE(LDACBT_ERR_NOT_IMPLEMENTED); + LDAC_BT_ERROR_CASE(LDACBT_ERR_NON_FATAL_ENCODE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_FATAL); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_BAND); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_A); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_B); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_C); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_D); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_E); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_IDSF); + LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_SPEC); + LDAC_BT_ERROR_CASE(LDACBT_ERR_BIT_PACKING); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ALLOC_MEMORY); + LDAC_BT_ERROR_CASE(LDACBT_ERR_FATAL_HANDLE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SYNCWORD); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SMPL_FORMAT); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_PARAM); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SAMPLING_FREQ); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SUP_SAMPLING_FREQ); + LDAC_BT_ERROR_CASE(LDACBT_ERR_CHECK_SAMPLING_FREQ); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_CHANNEL_CONFIG); + LDAC_BT_ERROR_CASE(LDACBT_ERR_CHECK_CHANNEL_CONFIG); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_FRAME_LENGTH); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SUP_FRAME_LENGTH); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_FRAME_STATUS); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_NSHIFT); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_CHANNEL_MODE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_INIT_ALLOC); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADMODE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_A); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_B); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_C); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_D); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_NBANDS); + LDAC_BT_ERROR_CASE(LDACBT_ERR_PACK_BLOCK_FAILED); + LDAC_BT_ERROR_CASE(LDACBT_ERR_DEC_INIT_ALLOC); + LDAC_BT_ERROR_CASE(LDACBT_ERR_INPUT_BUFFER_SIZE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_BLOCK_FAILED); + LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_BLOCK_ALIGN); + LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_FRAME_ALIGN); + LDAC_BT_ERROR_CASE(LDACBT_ERR_FRAME_LENGTH_OVER); + LDAC_BT_ERROR_CASE(LDACBT_ERR_FRAME_ALIGN_OVER); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ALTER_EQMID_LIMITED); + LDAC_BT_ERROR_CASE(LDACBT_ERR_HANDLE_NOT_INIT); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_EQMID); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SAMPLING_FREQ); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_NUM_CHANNEL); + LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_MTU_SIZE); + LDAC_BT_ERROR_CASE(LDACBT_ERR_DEC_CONFIG_UPDATED); + default: return "other error"; + } +} + static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) @@ -384,6 +463,12 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if (this->ldac == NULL) goto error_errno; +#ifdef ENABLE_LDAC_DEC + this->ldac_dec = ldacBT_get_handle(); + if (this->ldac_dec == NULL) + goto error_errno; +#endif + #ifdef ENABLE_LDAC_ABR this->ldac_abr = ldac_ABR_get_handle(); if (this->ldac_abr == NULL) @@ -424,14 +509,34 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, goto error; } + this->d.max_frame_bytes = (size_t)this->codesize * LDACBT_MAX_LSU / LDACBT_ENC_LSU; + res = ldacBT_init_handle_encode(this->ldac, this->mtu, this->eqmid, conf->channel_mode, this->fmt, this->frequency); - if (res < 0) + if (res < 0) { + res = ldacBT_get_error_code(this->ldac); + spa_log_error(log_, "LDAC encoder initialization failed: %s (%d)", + ldac_strerror(LDACBT_API_ERR(res)), res); + res = -EIO; goto error; + } + +#ifdef ENABLE_LDAC_DEC + res = ldacBT_init_handle_decode(this->ldac_dec, + conf->channel_mode, + this->frequency, 0, 0, 0); + if (res < 0) { + res = ldacBT_get_error_code(this->ldac_dec); + spa_log_error(log_, "LDAC decoder initialization failed: %s (%d)", + ldac_strerror(LDACBT_API_ERR(res)), res); + res = -EIO; + goto error; + } +#endif #ifdef ENABLE_LDAC_ABR res = ldac_ABR_Init(this->ldac_abr, LDAC_ABR_INTERVAL_MS); @@ -552,6 +657,70 @@ static int codec_encode(void *data, return src_used; } +#ifdef ENABLE_LDAC_DEC + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + const struct rtp_header *header = src; + const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), struct rtp_payload); + size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + + spa_return_val_if_fail (src_size > header_size, -EINVAL); + + if (seqnum) + *seqnum = ntohs(header->sequence_number); + if (timestamp) + *timestamp = ntohl(header->timestamp); + + this->d.frames = payload->frame_count; + + return header_size; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + void *to = dst; + size_t avail = dst_size; + size_t processed = 0; + + *dst_out = 0; + + for (; this->d.frames > 0; --this->d.frames) { + int consumed; + int written; + + if (avail < this->d.max_frame_bytes) + return -EINVAL; + + if (ldacBT_decode(this->ldac_dec, (void *)src, to, this->fmt, src_size, + &consumed, &written) != 0) { + return -EINVAL; + } + if (consumed < 0 || (size_t)consumed > src_size) + return -EINVAL; + if (written < 0 || (size_t)written > avail) + return -EINVAL; + + src = SPA_PTROFF(src, consumed, const void); + src_size -= consumed; + processed += consumed; + + to = SPA_PTROFF(to, written, void); + avail -= written; + *dst_out += written; + } + + return processed; +} + +#endif + static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; @@ -571,6 +740,12 @@ static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) *decoder = 0; } +static void codec_set_log(struct spa_log *global_log) +{ + log_ = global_log; + spa_log_topic_init(log_, &codec_plugin_log_topic); +} + const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, .codec_id = A2DP_CODEC_VENDOR, @@ -595,9 +770,14 @@ const struct media_codec a2dp_codec_ldac = { .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, +#ifdef ENABLE_LDAC_DEC + .start_decode = codec_start_decode, + .decode = codec_decode, +#endif .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, + .set_log = codec_set_log, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index b0990eaf6..154dc9fe9 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -125,6 +125,10 @@ if ldac_dep.found() if ldac_abr_dep.found() ldac_args += [ '-DENABLE_LDAC_ABR' ] endif + if get_option('bluez5-codec-ldac-dec').allowed() and ldac_dec_dep.found() + ldac_args += [ '-DENABLE_LDAC_DEC' ] + ldac_dep = [ldac_dep, ldac_dec_dep] + endif bluez_codec_ldac = shared_library('spa-codec-bluez5-ldac', [ 'a2dp-codec-ldac.c', 'media-codecs.c' ], include_directories : [ configinc ], From 9e3f9607a6b39c7739b5abebf9d4b900c0dd055a Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Thu, 10 Apr 2025 23:13:46 +0200 Subject: [PATCH 0130/1014] raop: Add `fp_sap25` encryption type Add support for FairPlay SAP v2.5 (encryption type 5) type devices such as Apple Home Pod Minis. Apparently only these devices require the `POST /feedback` heartbeat, so fix that. --- src/modules/module-raop-discover.c | 8 +++++--- src/modules/module-raop-sink.c | 11 ++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index c794bc076..9a1079a4e 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -71,7 +71,7 @@ * #raop.domain = "" * #raop.device = "" * #raop.transport = "udp" | "tcp" - * #raop.encryption.type = "RSA" | "auth_setup" | "none" + * #raop.encryption.type = "none" | "RSA" | "auth_setup" | "fp_sap25" * #raop.audio.codec = "PCM" | "ALAC" | "AAC" | "AAC-ELD" * #audio.channels = 2 * #audio.format = "S16" | "S24" | "S32" @@ -244,10 +244,12 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, * 3 = FairPlay, * 4 = MFiSAP (/auth-setup), * 5 = FairPlay SAPv2.5 */ - if (str_in_list(value, ",", "1")) - value = "RSA"; + if (str_in_list(value, ",", "5")) + value = "fp_sap25"; else if (str_in_list(value, ",", "4")) value = "auth_setup"; + else if (str_in_list(value, ",", "1")) + value = "RSA"; else value = "none"; pw_properties_set(props, "raop.encryption.type", value); diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 8eef429f0..076c388d7 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -197,6 +197,7 @@ enum { CRYPTO_NONE, CRYPTO_RSA, CRYPTO_AUTH_SETUP, + CRYPTO_FP_SAP25, }; enum { CODEC_PCM, @@ -892,9 +893,9 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head interval.tv_nsec = 0; // feedback timer is only needed for auth_setup encryption - if (impl->encryption == CRYPTO_AUTH_SETUP && !impl->feedback_timer) { - - impl->feedback_timer = pw_loop_add_timer(impl->loop, rtsp_do_post_feedback, impl); + if (impl->encryption == CRYPTO_FP_SAP25) { + if (!impl->feedback_timer) + impl->feedback_timer = pw_loop_add_timer(impl->loop, rtsp_do_post_feedback, impl); pw_loop_update_timer(impl->loop, impl->feedback_timer, &timeout, &interval, false); } @@ -1239,6 +1240,7 @@ static int rtsp_do_announce(struct impl *impl) switch (impl->encryption) { case CRYPTO_NONE: + case CRYPTO_FP_SAP25: sdp = spa_aprintf("v=0\r\n" "o=iTunes %s 0 IN IP%d %s\r\n" "s=iTunes\r\n" @@ -1252,6 +1254,7 @@ static int rtsp_do_announce(struct impl *impl) if (!sdp) return -errno; break; + case CRYPTO_AUTH_SETUP: sdp = spa_aprintf("v=0\r\n" "o=iTunes %s 0 IN IP%d %s\r\n" @@ -1877,6 +1880,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->encryption = CRYPTO_RSA; else if (spa_streq(str, "auth_setup")) impl->encryption = CRYPTO_AUTH_SETUP; + else if (spa_streq(str, "fp_sap25")) + impl->encryption = CRYPTO_FP_SAP25; else { pw_log_error( "can't handle encryption type %s", str); res = -EINVAL; From 558721efc25e28c93a2e8c9e22c2c1180ba5f91c Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 11 Apr 2025 21:42:23 +0200 Subject: [PATCH 0131/1014] audioconvert: Increase param length limit to 4096 Parameter values read into a 512 byte long buffer, which is insufficient for medium to long filter-graph parameters. Increase the buffer to 4096 bytes to give some wiggle-room. --- spa/plugins/audioconvert/audioconvert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index cd0529087..b37e15613 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1356,7 +1356,7 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) while (true) { const char *name; struct spa_pod *pod; - char value[512]; + char value[4096]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; From 6dfb164cabf09efe646eb92c7e80d5e80a8d6863 Mon Sep 17 00:00:00 2001 From: Vinit Mehta Date: Mon, 3 Mar 2025 15:25:48 +0530 Subject: [PATCH 0132/1014] bluez5: Add support to configure & select BAP QoS config The current BAP unicast QoS configuration allows to register a sampling frequency based on the configuration done using wireplumber configuration. However, for a scenario were the user need to use a specific SDU framelength it cannot be done as the select_bap_qos function selects the QoS based on priority and hence it will use the best possible config rather than the user configured. This PR adds option to select the QoS set based on user configured value. If the remote device doesn't have the user configured capabilities it will always use the best priority config. Further, this change also allows the user to set RTN, Latency, Delay QoS config for certain use case to have controller use optimum bandwidth usage. Below is the example for the options that can be configured & selected in config file: bluez5.bap.set_name = "48_2_1" bluez5.bap.rtn = 5 bluez5.bap.latency = 20 bluez5.bap.delay = 40000 bluez5.framing = false --- spa/plugins/bluez5/bap-codec-lc3.c | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index e9b772325..93b241796 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -111,7 +111,7 @@ static const struct { .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ .delay = (delay_), .priority = (priority_) }) -static const struct bap_qos bap_qos_configs[] = { +static struct bap_qos bap_qos_configs[] = { /* Priority: low-latency > high-reliability, 7.5ms > 10ms, * bigger frequency and sdu better */ @@ -789,8 +789,10 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, uint32_t locations = 0; uint32_t channel_allocation = 0; bool sink = false, duplex = false; + uint32_t value = 0; struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); int i; + const char *str; if (caps == NULL) return -EINVAL; @@ -811,8 +813,41 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, /* Is remote endpoint duplex */ duplex = spa_atob(spa_dict_lookup(settings, "bluez5.bap.duplex")); + + + if ((str = spa_dict_lookup(settings, "bluez5.bap.set_name"))) { + + SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, qos_sets) { + + if (spa_streq(str, qos_sets->name)) { + + spa_log_debug(log_, "Found Forced QoS For Set: %s\n", qos_sets->name); + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.rtn"))) + if (spa_atou32(str, &value, 0)) + qos_sets->retransmission = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.latency"))) + if (spa_atou32(str, &value, 0)) + qos_sets->latency = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.delay"))) + if (spa_atou32(str, &value, 0)) + qos_sets->delay = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.framing"))) + qos_sets->framing = spa_atob(str); + + /* We set highest priority for the forced configuration. This allows the + * config to be used when LC3 QoS is selected at the time of CIS creation*/ + qos_sets->priority = 0xFF; + break; + } + } + } } + /* Select best conf from those possible */ npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx); if (npacs < 0) { From d22a4d60b37c7692af11e018e4dd41e3fa14007c Mon Sep 17 00:00:00 2001 From: Vinit Mehta Date: Mon, 3 Mar 2025 15:28:31 +0530 Subject: [PATCH 0133/1014] doc: Add BAP QoS config & select option This adds configuration options for BAP unicast QoS based on user config parameters --- doc/dox/config/pipewire-props.7.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index d7e6af820..461cb53d9 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -917,6 +917,22 @@ PipeWire Opus Pro audio profile duplex max bitrate. PipeWire Opus Pro audio profile duplex frame duration (1/10 ms). @PAR@ monitor-prop bluez5.bcast_source.config = [] # JSON + +@PAR@ monitor-prop bluez5.bap.set_name = "48_2_1" # string +BAP QoS set name that can be configured to select specific BAP QoS combination + +@PAR@ monitor-prop bluez5.bap.rtn = 5 # integer +BAP QoS retransmission number that needs to be configured for the set_name + +@PAR@ monitor-prop bluez5.bap.latency = 20 # integer +BAP QoS latency that needs to be configured for the set_name + +@PAR@ monitor-prop bluez5.bap.delay = 40000 # integer +BAP QoS delay that needs to be configured for the set_name + +@PAR@ monitor-prop bluez5.framing = false # boolean +BAP framing mode that needs to be configured for the set_name + \parblock Example: ``` From 55372a41b14936d605fd475ac4b471dd3031f086 Mon Sep 17 00:00:00 2001 From: Vinit Mehta Date: Fri, 14 Mar 2025 19:44:56 +0530 Subject: [PATCH 0134/1014] bluez5: Add support to select BAP QoS config The current BAP QoS configuration allows to register a sampling frequency based on the configuration done using wireplumber configuration. However, for a scenario were the user need to use a specific SDU framelength it cannot be done as the select_bap_qos function selects the QOS based on priority and hence it will use the best possible config rather than the user configured. This PR adds option to select the QoS set based on user configured value. If the remote device doesn't have the user configured capabilities it will always use the best priority config. Further, this change also allows the user to set RTN, Latency, Delay QoS config for certain use case to have controller use optimum bandwidth usage. Below are the example configuration on setting LC3 capabilities in config file: bluez5.bap.set_name = "48_2_1" bluez5.bap.rtn = 5 bluez5.bap.latency = 20 bluez5.bap.delay = 40000 bluez5.framing = false --- spa/plugins/bluez5/bap-codec-lc3.c | 192 ++++++++++++++++++----------- spa/plugins/bluez5/bluez5-dbus.c | 2 +- spa/plugins/bluez5/media-codecs.h | 2 +- 3 files changed, 123 insertions(+), 73 deletions(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 93b241796..652e051cb 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -111,7 +111,7 @@ static const struct { .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ .delay = (delay_), .priority = (priority_) }) -static struct bap_qos bap_qos_configs[] = { +static const struct bap_qos bap_qos_configs[] = { /* Priority: low-latency > high-reliability, 7.5ms > 10ms, * bigger frequency and sdu better */ @@ -470,28 +470,110 @@ static bool supports_channel_count(uint8_t mask, uint8_t count) return mask & (1u << (count - 1)); } -static const struct bap_qos *select_bap_qos(unsigned int rate_mask, unsigned int duration_mask, uint16_t framelen_min, uint16_t framelen_max) +static bool parse_qos_settings(struct bap_qos *qos_conf, + const struct spa_dict *settings, unsigned int rate_mask, + unsigned int duration_mask, uint16_t framelen_min, + uint16_t framelen_max) + { + bool found = false; + const char *str; + uint32_t value = 0; + + if ((str = spa_dict_lookup(settings, "bluez5.bap.set_name"))) { + spa_log_info(log_, "Parsing for vendor defined Configuration\n\n"); + + SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, c) + { + if (spa_streq(c->name, str)) { + /* We set the user defined preset configuration for vendor as + * default. This helps to avoid using invalid params in scenario + * were user has not configured or has missed to add any items. In + * case if user has added the config it will be overwritten if found */ + memcpy(qos_conf, c, sizeof(struct bap_qos)); + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.rtn"))) + if (spa_atou32(str, &value, 0)) + qos_conf->retransmission = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.latency"))) + if (spa_atou32(str, &value, 0)) + qos_conf->latency = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.delay"))) + if (spa_atou32(str, &value, 0)) + qos_conf->delay = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.framing"))) + qos_conf->framing = spa_atob(str); + + /*We check if the vendor defined config is compatible with the + * remote device. If not, we fallback to use default preset config*/ + if (!(get_rate_mask(c->rate) & rate_mask)) + break; + if (!(get_duration_mask(c->frame_duration) & duration_mask)) + break; + if (c->framing) + break; /* XXX: framing not supported */ + if (c->framelen < framelen_min || c->framelen > framelen_max) + break; + + /* At this point, the vendor configured settings is + * compatible with the remote device and hence we can + * use this configuration to establish transport*/ + found = true; + break; + } + } + } + + return found; +} + +static bool select_bap_qos(struct bap_qos *qos_conf, + const struct spa_dict *settings, unsigned int rate_mask, + unsigned int duration_mask, uint16_t framelen_min, + uint16_t framelen_max) { const struct bap_qos *best = NULL; unsigned int best_priority = 0; + bool found = false; - SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, c) { - if (c->priority < best_priority) - continue; - if (!(get_rate_mask(c->rate) & rate_mask)) - continue; - if (!(get_duration_mask(c->frame_duration) & duration_mask)) - continue; - if (c->framing) - continue; /* XXX: framing not supported */ - if (c->framelen < framelen_min || c->framelen > framelen_max) - continue; - - best = c; - best_priority = c->priority; + if(!qos_conf){ + spa_log_error(log_, "Invalid qos config\n"); + return found; + } + /* We will check if vendor defined QoS settings are configured. If so, we check if the + * configured settings are compatible with unicast server*/ + if (settings) { + found = parse_qos_settings(qos_conf, settings, rate_mask, + duration_mask, framelen_min, framelen_max); } - return best; + if (!found) { + SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, c) + { + if (c->priority < best_priority) + continue; + if (!(get_rate_mask(c->rate) & rate_mask)) + continue; + if (!(get_duration_mask(c->frame_duration) & duration_mask)) + continue; + if (c->framing) + continue; /* XXX: framing not supported */ + if (c->framelen < framelen_min || c->framelen > framelen_max) + continue; + + best = c; + best_priority = c->priority; + } + + if (best) { + memcpy(qos_conf, best, sizeof(struct bap_qos)); + found = true; + } + } + + return found; } static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t channel_allocation, @@ -550,7 +632,7 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t return 0; } -static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx) +static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx, const struct spa_dict *settings) { const uint8_t *data = pac->data; size_t data_size = pac->size; @@ -560,8 +642,9 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp uint8_t max_channels = 0; uint8_t duration_mask = 0; uint16_t rate_mask = 0; - const struct bap_qos *bap_qos = NULL; + struct bap_qos bap_qos; unsigned int i; + bool found = false; if (!data_size) return false; @@ -653,21 +736,21 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp * * Devices may list other values but not certain they will work properly. */ - bap_qos = select_bap_qos(rate_mask & LC3_FREQ_16KHZ, duration_mask, framelen_min, framelen_max); + found = select_bap_qos(&bap_qos, settings, rate_mask & LC3_FREQ_16KHZ, duration_mask, framelen_min, framelen_max); } - if (!bap_qos) - bap_qos = select_bap_qos(rate_mask, duration_mask, framelen_min, framelen_max); + if (!found) + found = select_bap_qos(&bap_qos, settings, rate_mask, duration_mask, framelen_min, framelen_max); - if (!bap_qos) { + if (!found) { spa_debugc(debug_ctx, "no compatible configuration found, rate:0x%08x, duration:0x%08x frame:%u-%u", rate_mask, duration_mask, framelen_min, framelen_max); return false; } - conf->rate = bap_qos->rate; - conf->frame_duration = bap_qos->frame_duration; - conf->framelen = bap_qos->framelen; - conf->priority = bap_qos->priority; + conf->rate = bap_qos.rate; + conf->frame_duration = bap_qos.frame_duration; + conf->framelen = bap_qos.framelen; + conf->priority = bap_qos.priority; return true; } @@ -771,8 +854,8 @@ static int pac_cmp(const void *p1, const void *p2) bap_lc3_t conf1, conf2; int res1, res2; - res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; - res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res1 = select_config(&conf1, pac1, &debug_ctx.ctx, NULL) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res2 = select_config(&conf2, pac2, &debug_ctx.ctx, NULL) ? (int)sizeof(bap_lc3_t) : -EINVAL; return conf_cmp(&conf1, res1, &conf2, res2); } @@ -789,10 +872,8 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, uint32_t locations = 0; uint32_t channel_allocation = 0; bool sink = false, duplex = false; - uint32_t value = 0; struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); int i; - const char *str; if (caps == NULL) return -EINVAL; @@ -813,38 +894,6 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, /* Is remote endpoint duplex */ duplex = spa_atob(spa_dict_lookup(settings, "bluez5.bap.duplex")); - - - if ((str = spa_dict_lookup(settings, "bluez5.bap.set_name"))) { - - SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, qos_sets) { - - if (spa_streq(str, qos_sets->name)) { - - spa_log_debug(log_, "Found Forced QoS For Set: %s\n", qos_sets->name); - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.rtn"))) - if (spa_atou32(str, &value, 0)) - qos_sets->retransmission = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.latency"))) - if (spa_atou32(str, &value, 0)) - qos_sets->latency = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.delay"))) - if (spa_atou32(str, &value, 0)) - qos_sets->delay = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.framing"))) - qos_sets->framing = spa_atob(str); - - /* We set highest priority for the forced configuration. This allows the - * config to be used when LC3 QoS is selected at the time of CIS creation*/ - qos_sets->priority = 0xFF; - break; - } - } - } } @@ -869,7 +918,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, spa_debugc(&debug_ctx.ctx, "selected PAC %d", pacs[0].index); - if (!select_config(&conf, &pacs[0], &debug_ctx.ctx)) + if (!select_config(&conf, &pacs[0], &debug_ctx.ctx, settings)) return -ENOTSUP; data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate); @@ -1052,19 +1101,20 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags static int codec_get_qos(const struct media_codec *codec, const void *config, size_t config_size, const struct bap_endpoint_qos *endpoint_qos, - struct bap_codec_qos *qos) + struct bap_codec_qos *qos, const struct spa_dict *settings) { - const struct bap_qos *bap_qos; + struct bap_qos bap_qos; bap_lc3_t conf; + bool found = false; spa_zero(*qos); if (!parse_conf(&conf, config, config_size)) return -EINVAL; - bap_qos = select_bap_qos(get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration), + found = select_bap_qos(&bap_qos, settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration), conf.framelen, conf.framelen); - if (!bap_qos) { + if (!found) { /* shouldn't happen: select_config should pick existing one */ spa_log_error(log_, "no QoS settings found"); return -EINVAL; @@ -1082,9 +1132,9 @@ static int codec_get_qos(const struct media_codec *codec, qos->interval = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000); qos->target_latency = BT_ISO_QOS_TARGET_LATENCY_BALANCED; - qos->delay = bap_qos->delay; - qos->latency = bap_qos->latency; - qos->retransmission = bap_qos->retransmission; + qos->delay = bap_qos.delay; + qos->latency = bap_qos.latency; + qos->retransmission = bap_qos.retransmission; /* Clamp to ASE values (if known) */ if (endpoint_qos->delay_min) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 98feef69f..3e6d4218d 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1025,7 +1025,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe spa_zero(qos); - res = codec->get_qos(codec, config, conf_size, &endpoint_qos, &qos); + res = codec->get_qos(codec, config, conf_size, &endpoint_qos, &qos, &settings); if (res < 0) { spa_log_error(monitor->log, "can't select QOS config: %d (%s)", res, spa_strerror(res)); diff --git a/spa/plugins/bluez5/media-codecs.h b/spa/plugins/bluez5/media-codecs.h index c9a00f7bf..47705683d 100644 --- a/spa/plugins/bluez5/media-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -103,7 +103,7 @@ struct media_codec { int (*get_qos)(const struct media_codec *codec, const void *config, size_t config_size, const struct bap_endpoint_qos *endpoint_qos, - struct bap_codec_qos *qos); + struct bap_codec_qos *qos, const struct spa_dict *settings); /** qsort comparison sorting caps in order of preference for the codec. * Used in codec switching to select best remote endpoints. From 6e57510d6bf9f06fb6d6cdc02834a0cfa3b24a90 Mon Sep 17 00:00:00 2001 From: Vinit Mehta Date: Fri, 14 Mar 2025 19:49:14 +0530 Subject: [PATCH 0135/1014] doc: Add BAP QoS config & select option This adds configuration options for BAP unicast QoS based on user config parameters --- doc/dox/config/pipewire-props.7.md | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 461cb53d9..05c9c3f35 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -917,22 +917,6 @@ PipeWire Opus Pro audio profile duplex max bitrate. PipeWire Opus Pro audio profile duplex frame duration (1/10 ms). @PAR@ monitor-prop bluez5.bcast_source.config = [] # JSON - -@PAR@ monitor-prop bluez5.bap.set_name = "48_2_1" # string -BAP QoS set name that can be configured to select specific BAP QoS combination - -@PAR@ monitor-prop bluez5.bap.rtn = 5 # integer -BAP QoS retransmission number that needs to be configured for the set_name - -@PAR@ monitor-prop bluez5.bap.latency = 20 # integer -BAP QoS latency that needs to be configured for the set_name - -@PAR@ monitor-prop bluez5.bap.delay = 40000 # integer -BAP QoS delay that needs to be configured for the set_name - -@PAR@ monitor-prop bluez5.framing = false # boolean -BAP framing mode that needs to be configured for the set_name - \parblock Example: ``` @@ -1013,6 +997,18 @@ PipeWire Opus Pro Audio duplex encoding mode: audio, voip, lowdelay @PAR@ device-prop bluez5.bap.cig = "auto" # integer, or 'auto' Set CIG ID for BAP unicast streams of the device. +@PAR@ device-prop bluez5.bap.rtn = "48_2_1" # string +BAP QoS preset name that needed to be used with vendor config + +@PAR@ device-prop bluez5.bap.latency = 20 # integer +BAP QoS latency that needs to be applied for vendor defined preset + +@PAR@ device-prop bluez5.bap.delay = 40000 # integer +BAP QoS delay that needs to be applied for vendor defined preset + +@PAR@ device-prop bluez5.framing = false # boolean +BAP QoS framing that needs to be applied for vendor defined preset + ## Node properties @PAR@ node-prop bluez5.media-source-role # string From e9dae61ccac05c74a4d3ccb019b9c381f9c5f4ef Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 7 Apr 2025 21:07:48 +0300 Subject: [PATCH 0136/1014] bluez5: simplify BAP settings parsing and use device settings for them Parse BAP settings in a single place, and simplify QoS customization a bit. Ensure the selected preset gets selected. For all the BAP codec settings, use device settings instead of global monitor ones. --- doc/dox/config/pipewire-props.7.md | 19 +- spa/plugins/bluez5/bap-codec-lc3.c | 268 ++++++++++++++--------------- spa/plugins/bluez5/bluez5-dbus.c | 11 +- spa/plugins/bluez5/media-codecs.h | 2 +- 4 files changed, 150 insertions(+), 150 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 05c9c3f35..ffa094cfc 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -997,17 +997,30 @@ PipeWire Opus Pro Audio duplex encoding mode: audio, voip, lowdelay @PAR@ device-prop bluez5.bap.cig = "auto" # integer, or 'auto' Set CIG ID for BAP unicast streams of the device. -@PAR@ device-prop bluez5.bap.rtn = "48_2_1" # string -BAP QoS preset name that needed to be used with vendor config +@PAR@ device-prop bluez5.bap.preset = "auto" # string +BAP QoS preset name that needed to be used with vendor config. +This property is experimental. +Available: "48_2_1", ... as in the BAP specification. -@PAR@ device-prop bluez5.bap.latency = 20 # integer +@PAR@ device-prop bluez5.bap.rtn # integer +BAP QoS preset name that needed to be used with vendor config. +This property is experimental. +Default: as per QoS preset. + +@PAR@ device-prop bluez5.bap.latency # integer BAP QoS latency that needs to be applied for vendor defined preset +This property is experimental. +Default: as QoS preset. @PAR@ device-prop bluez5.bap.delay = 40000 # integer BAP QoS delay that needs to be applied for vendor defined preset +This property is experimental. +Default: as per QoS preset. @PAR@ device-prop bluez5.framing = false # boolean BAP QoS framing that needs to be applied for vendor defined preset +This property is experimental. +Default: as per QoS preset. ## Node properties diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 652e051cb..d152b0a7b 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -38,14 +39,23 @@ struct impl { unsigned int codesize; }; -struct pac_data { - const uint8_t *data; - size_t size; - int index; +struct settings { uint32_t locations; uint32_t channel_allocation; bool sink; bool duplex; + const char *qos_name; + int retransmission; + int latency; + int64_t delay; + int framing; +}; + +struct pac_data { + const uint8_t *data; + size_t size; + int index; + const struct settings *settings; }; struct bap_qos { @@ -470,110 +480,47 @@ static bool supports_channel_count(uint8_t mask, uint8_t count) return mask & (1u << (count - 1)); } -static bool parse_qos_settings(struct bap_qos *qos_conf, - const struct spa_dict *settings, unsigned int rate_mask, - unsigned int duration_mask, uint16_t framelen_min, - uint16_t framelen_max) - { - bool found = false; - const char *str; - uint32_t value = 0; - - if ((str = spa_dict_lookup(settings, "bluez5.bap.set_name"))) { - spa_log_info(log_, "Parsing for vendor defined Configuration\n\n"); - - SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, c) - { - if (spa_streq(c->name, str)) { - /* We set the user defined preset configuration for vendor as - * default. This helps to avoid using invalid params in scenario - * were user has not configured or has missed to add any items. In - * case if user has added the config it will be overwritten if found */ - memcpy(qos_conf, c, sizeof(struct bap_qos)); - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.rtn"))) - if (spa_atou32(str, &value, 0)) - qos_conf->retransmission = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.latency"))) - if (spa_atou32(str, &value, 0)) - qos_conf->latency = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.bap.delay"))) - if (spa_atou32(str, &value, 0)) - qos_conf->delay = value; - - if (settings && (str = spa_dict_lookup(settings, "bluez5.framing"))) - qos_conf->framing = spa_atob(str); - - /*We check if the vendor defined config is compatible with the - * remote device. If not, we fallback to use default preset config*/ - if (!(get_rate_mask(c->rate) & rate_mask)) - break; - if (!(get_duration_mask(c->frame_duration) & duration_mask)) - break; - if (c->framing) - break; /* XXX: framing not supported */ - if (c->framelen < framelen_min || c->framelen > framelen_max) - break; - - /* At this point, the vendor configured settings is - * compatible with the remote device and hence we can - * use this configuration to establish transport*/ - found = true; - break; - } - } - } - - return found; -} - -static bool select_bap_qos(struct bap_qos *qos_conf, - const struct spa_dict *settings, unsigned int rate_mask, +static bool select_bap_qos(struct bap_qos *conf, + const struct settings *s, unsigned int rate_mask, unsigned int duration_mask, uint16_t framelen_min, uint16_t framelen_max) { - const struct bap_qos *best = NULL; - unsigned int best_priority = 0; - bool found = false; + conf->name = NULL; + conf->priority = 0; - if(!qos_conf){ - spa_log_error(log_, "Invalid qos config\n"); - return found; - } - /* We will check if vendor defined QoS settings are configured. If so, we check if the - * configured settings are compatible with unicast server*/ - if (settings) { - found = parse_qos_settings(qos_conf, settings, rate_mask, - duration_mask, framelen_min, framelen_max); + SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, cur_conf) { + struct bap_qos c = *cur_conf; + + /* Check if custom QoS settings are configured. If so, we check if + * the configured settings are compatible with unicast server + */ + if (spa_streq(c.name, s->qos_name)) + c.priority = UINT_MAX; + else if (c.priority < conf->priority) + continue; + + if (s->retransmission >= 0) + c.retransmission = s->retransmission; + if (s->latency >= 0) + c.latency = s->latency; + if (s->delay >= 0) + c.delay = s->delay; + if (s->framing >= 0) + c.framing = s->framing; + + if (!(get_rate_mask(c.rate) & rate_mask)) + continue; + if (!(get_duration_mask(c.frame_duration) & duration_mask)) + continue; + if (c.framing) + continue; /* XXX: framing not supported */ + if (c.framelen < framelen_min || c.framelen > framelen_max) + continue; + + *conf = c; } - if (!found) { - SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, c) - { - if (c->priority < best_priority) - continue; - if (!(get_rate_mask(c->rate) & rate_mask)) - continue; - if (!(get_duration_mask(c->frame_duration) & duration_mask)) - continue; - if (c->framing) - continue; /* XXX: framing not supported */ - if (c->framelen < framelen_min || c->framelen > framelen_max) - continue; - - best = c; - best_priority = c->priority; - } - - if (best) { - memcpy(qos_conf, best, sizeof(struct bap_qos)); - found = true; - } - } - - return found; + return conf->name; } static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t channel_allocation, @@ -632,7 +579,7 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t return 0; } -static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx, const struct spa_dict *settings) +static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx) { const uint8_t *data = pac->data; size_t data_size = pac->size; @@ -650,8 +597,8 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp return false; memset(conf, 0, sizeof(*conf)); - conf->sink = pac->sink; - conf->duplex = pac->duplex; + conf->sink = pac->settings->sink; + conf->duplex = pac->settings->duplex; /* XXX: we always use one frame block */ conf->n_blks = 1; @@ -713,7 +660,7 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp max_frames = max_channels; } - if (select_channels(channel_counts, pac->locations, pac->channel_allocation, &conf->channels) < 0) { + if (select_channels(channel_counts, pac->settings->locations, pac->settings->channel_allocation, &conf->channels) < 0) { spa_debugc(debug_ctx, "invalid channel configuration: 0x%02x %u", channel_counts, max_frames); return false; @@ -730,16 +677,16 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp * Frame length is not limited by ISO MTU, as kernel will fragment * and reassemble SDUs as needed. */ - if (pac->sink && pac->duplex) { + if (pac->settings->sink && pac->settings->duplex) { /* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, so prefer * it for now for input rate in duplex configuration. * * Devices may list other values but not certain they will work properly. */ - found = select_bap_qos(&bap_qos, settings, rate_mask & LC3_FREQ_16KHZ, duration_mask, framelen_min, framelen_max); + found = select_bap_qos(&bap_qos, pac->settings, rate_mask & LC3_FREQ_16KHZ, duration_mask, framelen_min, framelen_max); } if (!found) - found = select_bap_qos(&bap_qos, settings, rate_mask, duration_mask, framelen_min, framelen_max); + found = select_bap_qos(&bap_qos, pac->settings, rate_mask, duration_mask, framelen_min, framelen_max); if (!found) { spa_debugc(debug_ctx, "no compatible configuration found, rate:0x%08x, duration:0x%08x frame:%u-%u", @@ -832,6 +779,8 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in if (!a || !b) return b - a; + PREFER_EXPR(conf->priority == UINT_MAX); + PREFER_BOOL(conf->channels & LC3_CHAN_2); PREFER_BOOL(conf->channels & LC3_CHAN_1); @@ -854,12 +803,68 @@ static int pac_cmp(const void *p1, const void *p2) bap_lc3_t conf1, conf2; int res1, res2; - res1 = select_config(&conf1, pac1, &debug_ctx.ctx, NULL) ? (int)sizeof(bap_lc3_t) : -EINVAL; - res2 = select_config(&conf2, pac2, &debug_ctx.ctx, NULL) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; + res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; return conf_cmp(&conf1, res1, &conf2, res2); } +static void parse_settings(struct settings *s, const struct spa_dict *settings, + struct spa_debug_log_ctx *debug_ctx) +{ + const char *str; + uint32_t value; + + spa_zero(*s); + s->retransmission = -1; + s->latency = -1; + s->delay = -1; + s->framing = -1; + + if (!settings) + return; + + if ((str = spa_dict_lookup(settings, "bluez5.bap.preset"))) + s->qos_name = str; + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0)) + s->retransmission = value; + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.latency"), &value, 0)) + s->latency = value; + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.delay"), &value, 0)) + s->delay = value; + + if ((str = spa_dict_lookup(settings, "bluez5.bap.framing"))) + s->framing = spa_atob(str); + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.locations"), &value, 0)) + s->locations = value; + + if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.channel-allocation"), &value, 0)) + s->channel_allocation = value; + + if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug"))) + *debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG); + else + *debug_ctx = SPA_LOG_DEBUG_INIT(NULL, SPA_LOG_LEVEL_TRACE); + + /* Is remote endpoint sink or source */ + s->sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink")); + + /* Is remote endpoint duplex */ + s->duplex = spa_atob(spa_dict_lookup(settings, "bluez5.bap.duplex")); + + spa_debugc(&debug_ctx->ctx, + "BAP LC3 settings: preset:%s rtn:%d latency:%d delay:%d framing:%d " + "locations:%x chnalloc:%x sink:%d duplex:%d", + s->qos_name ? s->qos_name : "auto", + s->retransmission, s->latency, (int)s->delay, s->framing, + (unsigned int)s->locations, (unsigned int)s->channel_allocation, + (int)s->sink, (int)s->duplex); +} + static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, @@ -869,33 +874,14 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, int npacs; bap_lc3_t conf; uint8_t *data = config; - uint32_t locations = 0; - uint32_t channel_allocation = 0; - bool sink = false, duplex = false; - struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); + struct spa_debug_log_ctx debug_ctx; + struct settings s; int i; if (caps == NULL) return -EINVAL; - if (settings) { - for (i = 0; i < (int)settings->n_items; ++i) { - if (spa_streq(settings->items[i].key, "bluez5.bap.locations")) - sscanf(settings->items[i].value, "%"PRIu32, &locations); - if (spa_streq(settings->items[i].key, "bluez5.bap.channel-allocation")) - sscanf(settings->items[i].value, "%"PRIu32, &channel_allocation); - } - - if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug"))) - debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG); - - /* Is remote endpoint sink or source */ - sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink")); - - /* Is remote endpoint duplex */ - duplex = spa_atob(spa_dict_lookup(settings, "bluez5.bap.duplex")); - } - + parse_settings(&s, settings, &debug_ctx); /* Select best conf from those possible */ npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx); @@ -907,18 +893,14 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, return -EINVAL; } - for (i = 0; i < npacs; ++i) { - pacs[i].locations = locations; - pacs[i].channel_allocation = channel_allocation; - pacs[i].sink = sink; - pacs[i].duplex = duplex; - } + for (i = 0; i < npacs; ++i) + pacs[i].settings = &s; qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp); spa_debugc(&debug_ctx.ctx, "selected PAC %d", pacs[0].index); - if (!select_config(&conf, &pacs[0], &debug_ctx.ctx, settings)) + if (!select_config(&conf, &pacs[0], &debug_ctx.ctx)) return -ENOTSUP; data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate); @@ -1106,13 +1088,17 @@ static int codec_get_qos(const struct media_codec *codec, struct bap_qos bap_qos; bap_lc3_t conf; bool found = false; + struct settings s; + struct spa_debug_log_ctx debug_ctx; spa_zero(*qos); if (!parse_conf(&conf, config, config_size)) return -EINVAL; - found = select_bap_qos(&bap_qos, settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration), + parse_settings(&s, settings, &debug_ctx); + + found = select_bap_qos(&bap_qos, &s, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration), conf.framelen, conf.framelen); if (!found) { /* shouldn't happen: select_config should pick existing one */ diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 3e6d4218d..47fff8bc7 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -926,8 +926,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe bool sink, duplex; const char *err_msg = "Unknown error"; struct spa_dict settings; - struct spa_dict_item setting_items[SPA_N_ELEMENTS(monitor->global_setting_items) + 5]; - int i; + struct spa_dict_item setting_items[128]; + unsigned int i, j; const char *endpoint_path = NULL; uint8_t caps[A2DP_MAX_CAPS_SIZE]; @@ -986,15 +986,16 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe */ ep->acceptor = true; - for (i = 0; i < (int)monitor->global_settings.n_items; ++i) - setting_items[i] = monitor->global_settings.items[i]; + i = 0; setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.channel-allocation", channel_allocation); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.sink", sink ? "true" : "false"); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.duplex", duplex ? "true" : "false"); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true"); + if (ep->device->settings) + for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j) + setting_items[i] = ep->device->settings->items[j]; settings = SPA_DICT_INIT(setting_items, i); - spa_assert((size_t)i <= SPA_N_ELEMENTS(setting_items)); conf_size = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, &settings, config); if (conf_size < 0) { diff --git a/spa/plugins/bluez5/media-codecs.h b/spa/plugins/bluez5/media-codecs.h index 47705683d..ed62cd131 100644 --- a/spa/plugins/bluez5/media-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -26,7 +26,7 @@ #define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 12 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 13 struct spa_bluez5_codec_a2dp { struct spa_interface iface; From 3140ede326b36087e07e7b2217231f4115923b23 Mon Sep 17 00:00:00 2001 From: Vinit Mehta Date: Wed, 9 Apr 2025 18:47:23 +0530 Subject: [PATCH 0137/1014] bluez5: Fix return status for parse_qos_settings --- spa/plugins/bluez5/bap-codec-lc3.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index d152b0a7b..dd18585b3 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -485,6 +485,7 @@ static bool select_bap_qos(struct bap_qos *conf, unsigned int duration_mask, uint16_t framelen_min, uint16_t framelen_max) { + bool found = false; conf->name = NULL; conf->priority = 0; @@ -518,9 +519,10 @@ static bool select_bap_qos(struct bap_qos *conf, continue; *conf = c; + found = true; } - return conf->name; + return found; } static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t channel_allocation, From 57326d65d9c4c4e75587ed3e8cede1184ac559d0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 14 Apr 2025 11:19:44 +0200 Subject: [PATCH 0138/1014] ebur128: work around libebur128 bug Versions 1.2.5 and 1.2.6 don't scale the window in ms correctly, leading to excessive memory allocation, so work around this here. See https://github.com/jiixyj/libebur128/pull/132 Fixes #4646 --- spa/plugins/filter-graph/ebur128_plugin.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c index 22bf5f513..92904e102 100644 --- a/spa/plugins/filter-graph/ebur128_plugin.c +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -235,6 +235,8 @@ static void ebur128_cleanup(void * Instance) static void ebur128_activate(void * Instance) { struct ebur128_impl *impl = Instance; + unsigned long max_window; + int major, minor, patch; int mode = 0, i; int modes[] = { EBUR128_MODE_M, @@ -264,12 +266,17 @@ static void ebur128_activate(void * Instance) mode |= modes[i]; } + ebur128_get_version(&major, &minor, &patch); + max_window = impl->max_window; + if (major == 1 && minor == 2 && (patch == 5 || patch == 6)) + max_window = (max_window + 999) / 1000; + for (i = 0; i < 7; i++) { impl->st[i] = ebur128_init(1, impl->rate, mode); if (impl->st[i]) { ebur128_set_channel(impl->st[i], i, channels[i]); ebur128_set_max_history(impl->st[i], impl->max_history); - ebur128_set_max_window(impl->st[i], impl->max_window); + ebur128_set_max_window(impl->st[i], max_window); } } } From c4a88dfe6c3e9929dbb8bbf382a6471bb1818524 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Wed, 16 Apr 2025 17:57:44 +0200 Subject: [PATCH 0139/1014] midifile: decode UMP SysRT messages --- src/tools/midifile.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 80c5bbb1c..cd1b6ec52 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -940,7 +940,43 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev) dump_mem(out, "Utility", ev->data, ev->size); break; case 0x1: - dump_mem(out, "SysRT", ev->data, ev->size); + switch (ev->data[2]) { + case 0xf1: + fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", + ev->data[0] >> 4, ev->data[0] & 0xf); + break; + case 0xf2: + fprintf(out, "Song Position Pointer: value %d", + ((int)ev->data[1] << 7 | ev->data[0])); + break; + case 0xf3: + fprintf(out, "Song Select: value %d", (ev->data[0] & 0x7f)); + break; + case 0xf6: + fprintf(out, "Tune Request"); + break; + case 0xf8: + fprintf(out, "Timing Clock"); + break; + case 0xfa: + fprintf(out, "Start Sequence"); + break; + case 0xfb: + fprintf(out, "Continue Sequence"); + break; + case 0xfc: + fprintf(out, "Stop Sequence"); + break; + case 0xfe: + fprintf(out, "Active Sensing"); + break; + case 0xff: + fprintf(out, "System Reset"); + break; + default: + dump_mem(out, "SysRT", ev->data, ev->size); + break; + } break; case 0x2: { From 55f71520db5aac3314475d98e810305d13c9a40a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 16 Apr 2025 18:33:36 +0200 Subject: [PATCH 0140/1014] midifile: unpack the UMP SysRT bytes correctly They are packed in a native endian uint32_t so read it like that and then use shifts to get the right bytes. --- src/tools/midifile.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/tools/midifile.c b/src/tools/midifile.c index cd1b6ec52..a58a1bc97 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -940,17 +940,19 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev) dump_mem(out, "Utility", ev->data, ev->size); break; case 0x1: - switch (ev->data[2]) { + { + uint8_t b[3] = { (d[0] >> 16) & 0x7f, (d[0] >> 8) & 0x7f, d[0] & 0x7f }; + switch (b[0]) { case 0xf1: fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", - ev->data[0] >> 4, ev->data[0] & 0xf); + b[1] >> 4, b[1] & 0xf); break; case 0xf2: fprintf(out, "Song Position Pointer: value %d", - ((int)ev->data[1] << 7 | ev->data[0])); + ((int)b[2] << 7 | b[1])); break; case 0xf3: - fprintf(out, "Song Select: value %d", (ev->data[0] & 0x7f)); + fprintf(out, "Song Select: value %d", b[1]); break; case 0xf6: fprintf(out, "Tune Request"); @@ -978,20 +980,18 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev) break; } break; + } case 0x2: { struct midi_event ev1; - uint8_t msg[4]; + uint8_t b[3] = { d[0] >> 16, d[0] >> 8, d[0] }; ev1 = *ev; - msg[0] = (d[0] >> 16); - msg[1] = (d[0] >> 8); - msg[2] = (d[0]); - if (msg[0] >= 0xc0 && msg[0] <= 0xdf) + if (b[0] >= 0xc0 && b[0] <= 0xdf) ev1.size = 2; else ev1.size = 3; - ev1.data = msg; + ev1.data = b; dump_event_midi1(out, &ev1); break; } From ebe93812363279a44226da8aa1fdf0b416e36404 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 16 Apr 2025 19:47:40 -0400 Subject: [PATCH 0141/1014] gst: handle blocks and size allocation for encoded format In case of encoded video we get n_planes as 0 from the video info so passing that as n_datas is failing during the buffer negotiation. Make sure to use an appropriate value based on whether we have raw video or not. Co-authored-by: Taruntej Kanakamalla --- src/gst/gstpipewirepool.c | 22 +++++++++++++++++----- src/gst/gstpipewirepool.h | 7 +++++++ src/gst/gstpipewiresink.c | 35 +++++++++++++++++++++++++++-------- src/gst/gstpipewiresink.h | 2 +- src/gst/gstpipewiresrc.c | 13 ++++++++++++- src/gst/gstpipewiresrc.h | 1 + 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 3ac1b42d0..f82dfbf46 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -66,7 +66,10 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) uint32_t i; GstPipeWirePoolData *data; /* Default to a large enough value */ - gsize plane_sizes[GST_VIDEO_MAX_PLANES] = { pool->video_info.size, }; + gsize plane_0_size = pool->has_rawvideo ? + pool->video_info.size : + (gsize) pool->video_info.width * pool->video_info.height; + gsize plane_sizes[GST_VIDEO_MAX_PLANES] = { plane_0_size, }; GST_DEBUG_OBJECT (pool, "wrap buffer, datas:%d", b->buffer->n_datas); @@ -333,11 +336,20 @@ set_config (GstBufferPool * pool, GstStructure * config) if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { p->has_video = TRUE; + gst_video_info_from_caps (&p->video_info, caps); + if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (p->video_info.finfo) +#ifdef HAVE_GSTREAMER_DMA_DRM + && GST_VIDEO_FORMAT_INFO_FORMAT (p->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM +#endif + ) + p->has_rawvideo = TRUE; + else + p->has_rawvideo = FALSE; + #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR - if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (p->video_info.finfo) && - GST_VIDEO_FORMAT_INFO_FORMAT (p->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM) { + if (p->has_rawvideo) { gst_video_alignment_reset (&p->video_align); gst_video_info_align (&p->video_info, &p->video_align); } @@ -349,11 +361,11 @@ set_config (GstBufferPool * pool, GstStructure * config) g_assert_not_reached (); } - p->add_metavideo = p->has_video && gst_buffer_pool_config_has_option (config, + p->add_metavideo = p->has_rawvideo && gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR - has_videoalign = p->has_video && gst_buffer_pool_config_has_option (config, + has_videoalign = p->has_rawvideo && gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); if (has_videoalign) { diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index c62d79e7d..f71344354 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -22,6 +22,12 @@ G_DECLARE_FINAL_TYPE (GstPipeWirePool, gst_pipewire_pool, GST, PIPEWIRE_POOL, Gs #define PIPEWIRE_POOL_MIN_BUFFERS 2u #define PIPEWIRE_POOL_MAX_BUFFERS 16u +/* Only available in GStreamer 1.22+ */ +#ifndef GST_VIDEO_FORMAT_INFO_IS_VALID_RAW +#define GST_VIDEO_FORMAT_INFO_IS_VALID_RAW(info) \ + (info != NULL && (info)->format > GST_VIDEO_FORMAT_ENCODED) +#endif + typedef struct _GstPipeWirePoolData GstPipeWirePoolData; struct _GstPipeWirePoolData { GstPipeWirePool *pool; @@ -42,6 +48,7 @@ struct _GstPipeWirePool { guint n_buffers; gboolean has_video; + gboolean has_rawvideo; gboolean add_metavideo; GstAudioInfo audio_info; GstVideoInfo video_info; diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 26be00195..79b341e2c 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -321,11 +321,18 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX), 0); - if (sink->is_video) { + if (sink->is_rawvideo) { /* MUST have n_datas == n_planes */ spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_blocks, - SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info)), 0); + SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info)), + 0); + } else { + /* Non-planar data, get a single block */ + spa_pod_builder_add (&b, + SPA_PARAM_BUFFERS_blocks, + SPA_POD_Int(1), + 0); } spa_pod_builder_add (&b, @@ -343,7 +350,7 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header))); - if (sink->is_video) { + if (sink->is_rawvideo) { port_params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), @@ -369,7 +376,7 @@ gst_pipewire_sink_init (GstPipeWireSink * sink) sink->mode = DEFAULT_PROP_MODE; sink->use_bufferpool = DEFAULT_PROP_USE_BUFFERPOOL; - sink->is_video = false; + sink->is_rawvideo = false; GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK); @@ -387,7 +394,7 @@ gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps) structure = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (structure, "video/x-raw")) { - pwsink->is_video = true; + pwsink->is_rawvideo = true; gst_structure_fixate_field_nearest_int (structure, "width", 320); gst_structure_fixate_field_nearest_int (structure, "height", 240); gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); @@ -779,9 +786,21 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) if (pwsink->use_bufferpool != USE_BUFFERPOOL_YES) pwsink->use_bufferpool = USE_BUFFERPOOL_NO; } else { + GstVideoInfo video_info; + pwsink->rate = rate = 0; pwsink->rate_match = false; - pwsink->is_video = true; + + gst_video_info_from_caps (&video_info, caps); + + if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (video_info.finfo) +#ifdef HAVE_GSTREAMER_DMA_DRM + && GST_VIDEO_FORMAT_INFO_FORMAT (video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM +#endif + ) + pwsink->is_rawvideo = TRUE; + else + pwsink->is_rawvideo = FALSE; } spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, 4096, rate); @@ -866,7 +885,7 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool)); gst_buffer_pool_config_get_params (config, NULL, &size, &min_buffers, &max_buffers); gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); - if (pwsink->is_video) { + if (pwsink->is_rawvideo) { gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); @@ -953,7 +972,7 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) if (res != GST_FLOW_OK) goto done; - if (pwsink->is_video) { + if (pwsink->is_rawvideo) { GstVideoFrame src, dst; gboolean copied = FALSE; buf_size = 0; // to break from the loop diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h index 60eb3b79f..f4a961f9a 100644 --- a/src/gst/gstpipewiresink.h +++ b/src/gst/gstpipewiresink.h @@ -69,7 +69,7 @@ struct _GstPipeWireSink { gboolean negotiated; gboolean rate_match; gint rate; - gboolean is_video; + gboolean is_rawvideo; GstPipeWireSinkMode mode; GstPipeWireSinkSlaveMethod slave_method; diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 4e716d2c1..637608b48 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -777,7 +777,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) pwsrc->transform_value = transform_value; } - if (pwsrc->is_video) { + if (pwsrc->is_rawvideo) { GstVideoInfo *info = &pwsrc->video_info; uint32_t n_datas = b->buffer->n_datas; uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (info); @@ -1285,6 +1285,16 @@ handle_format_change (GstPipeWireSrc *pwsrc, gst_video_info_dma_drm_init (&pwsrc->drm_info); #endif gst_video_info_from_caps (&pwsrc->video_info, pwsrc->caps); + + if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (pwsrc->video_info.finfo) +#ifdef HAVE_GSTREAMER_DMA_DRM + && GST_VIDEO_FORMAT_INFO_FORMAT (pwsrc->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM +#endif + ) + pwsrc->is_rawvideo = TRUE; + else + pwsrc->is_rawvideo = FALSE; + #ifdef HAVE_GSTREAMER_DMA_DRM } #endif @@ -1297,6 +1307,7 @@ handle_format_change (GstPipeWireSrc *pwsrc, } else { pwsrc->negotiated = FALSE; pwsrc->is_video = FALSE; + pwsrc->is_rawvideo = FALSE; } if (pwsrc->caps) { diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index 704ae682c..3ac88b10b 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -64,6 +64,7 @@ struct _GstPipeWireSrc { GstCaps *possible_caps; gboolean is_video; + gboolean is_rawvideo; GstVideoInfo video_info; #ifdef HAVE_GSTREAMER_DMA_DRM GstVideoInfoDmaDrm drm_info; From 31069b262694e96afec1142926643f000c9e80ab Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 18 Apr 2025 11:47:34 -0400 Subject: [PATCH 0142/1014] Don't log to systemd during development Can get a bit spammy if we're doing verbose logging. --- Makefile.in | 2 ++ meson.build | 1 + pw-uninstalled.sh | 1 + 3 files changed, 4 insertions(+) diff --git a/Makefile.in b/Makefile.in index 104619316..8cb9bf291 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,6 +23,7 @@ run: all PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ + PIPEWIRE_LOG_SYSTEMD=false $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-uninstalled run-pulse: all @@ -32,6 +33,7 @@ run-pulse: all PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ + PIPEWIRE_LOG_SYSTEMD=false $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-pulse gdb: diff --git a/meson.build b/meson.build index c7a15bda0..1a97d9d98 100644 --- a/meson.build +++ b/meson.build @@ -626,6 +626,7 @@ devenv.prepend('GST_PLUGIN_PATH', builddir / 'src'/ 'gst') devenv.prepend('ALSA_PLUGIN_DIR', builddir / 'pipewire-alsa' / 'alsa-plugins') devenv.prepend('LD_LIBRARY_PATH', builddir / 'pipewire-jack' / 'src') +devenv.set('PIPEWIRE_LOG_SYSTEMD', 'false') devenv.set('PW_UNINSTALLED', '1') meson.add_devenv(devenv) diff --git a/pw-uninstalled.sh b/pw-uninstalled.sh index 1bb6c55c2..af6aa4d87 100755 --- a/pw-uninstalled.sh +++ b/pw-uninstalled.sh @@ -53,6 +53,7 @@ export ALSA_PLUGIN_DIR="${BUILDDIR}/pipewire-alsa/alsa-plugins" export PW_BUILDDIR=$BUILDDIR export PW_UNINSTALLED=1 export PKG_CONFIG_PATH="${BUILDDIR}/meson-uninstalled/:${PKG_CONFIG_PATH}" +export PIPEWIRE_LOG_SYSTEMD=false if [ -d "${BUILDDIR}/subprojects/wireplumber" ]; then # FIXME: find a nice, shell-neutral way to specify a prompt From a4148c80b4cae7b12ce899aee1079e51e12043e8 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 18 Apr 2025 14:05:12 -0400 Subject: [PATCH 0143/1014] Makefile: Fix up shell/run rules --- Makefile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.in b/Makefile.in index 8cb9bf291..31cd8eb9e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -23,7 +23,7 @@ run: all PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ - PIPEWIRE_LOG_SYSTEMD=false + PIPEWIRE_LOG_SYSTEMD=false \ $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-uninstalled run-pulse: all @@ -33,7 +33,7 @@ run-pulse: all PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ - PIPEWIRE_LOG_SYSTEMD=false + PIPEWIRE_LOG_SYSTEMD=false \ $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-pulse gdb: From 081116906dafa98538ab3a45f42d44aebd70c080 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 20 Apr 2025 20:09:16 +0300 Subject: [PATCH 0144/1014] bluez5: bap: allow configuring server locations/context Add configuration options for the BAP/PACS sink and source endpoint location (= channel positions) and context settings. Although BlueZ associates these with individual endpoints, in the PACS spec they are actually device-global, so configure directly in monitor settings. --- doc/dox/config/pipewire-props.7.md | 20 +++++++ spa/plugins/bluez5/bap-codec-caps.h | 37 ++++++++++++ spa/plugins/bluez5/bap-codec-lc3.c | 47 ++-------------- spa/plugins/bluez5/bluez5-dbus.c | 87 +++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 53 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index ffa094cfc..8d1cbd70c 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -963,6 +963,26 @@ Maximum number of octets supported per codec frame for the LC3 codec (default: 4 @PAR@ monitor-prop bluez5.bap-server-capabilities.max_frames # integer Maximum number of codec frames supported per SDU for the LC3 codec (default: 2). +@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.locations # JSON or integer +Sink audio locations of the server, as channel positions or PACS bitmask. +Example: `FL,FR` + +@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.contexts # integer +Available sink contexts PACS bitmask of the the server. + +@PAR@ monitor-prop bluez5.bap-server-capabilities.sink.supported-contexts # integer +Supported sink contexts PACS bitmask of the the server. + +@PAR@ monitor-prop bluez5.bap-server-capabilities.source.locations # JSON or integer +Source audio locations of the server, as channel positions or PACS bitmask. +Example: `FL,FR` + +@PAR@ monitor-prop bluez5.bap-server-capabilities.source.contexts # integer +Available source contexts PACS bitmask of the the server. + +@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer +Supported source contexts PACS bitmask of the the server. + ## Device properties @PAR@ device-prop bluez5.auto-connect # boolean diff --git a/spa/plugins/bluez5/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h index 9d1da42b4..6a862894c 100644 --- a/spa/plugins/bluez5/bap-codec-caps.h +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -5,6 +5,8 @@ #ifndef SPA_BLUEZ5_BAP_CODEC_CAPS_H_ #define SPA_BLUEZ5_BAP_CODEC_CAPS_H_ +#include + #define BAP_CODEC_LC3 0x06 #define LC3_TYPE_FREQ 0x01 @@ -175,4 +177,39 @@ struct bap_codec_qos_full { struct bap_codec_qos qos; }; +static const struct { + uint32_t bit; + enum spa_audio_channel channel; +} bap_channel_bits[] = { + { BAP_CHANNEL_MONO, SPA_AUDIO_CHANNEL_MONO }, + { BAP_CHANNEL_FL, SPA_AUDIO_CHANNEL_FL }, + { BAP_CHANNEL_FR, SPA_AUDIO_CHANNEL_FR }, + { BAP_CHANNEL_FC, SPA_AUDIO_CHANNEL_FC }, + { BAP_CHANNEL_LFE, SPA_AUDIO_CHANNEL_LFE }, + { BAP_CHANNEL_BL, SPA_AUDIO_CHANNEL_RL }, + { BAP_CHANNEL_BR, SPA_AUDIO_CHANNEL_RR }, + { BAP_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FLC }, + { BAP_CHANNEL_FRC, SPA_AUDIO_CHANNEL_FRC }, + { BAP_CHANNEL_BC, SPA_AUDIO_CHANNEL_BC }, + { BAP_CHANNEL_LFE2, SPA_AUDIO_CHANNEL_LFE2 }, + { BAP_CHANNEL_SL, SPA_AUDIO_CHANNEL_SL }, + { BAP_CHANNEL_SR, SPA_AUDIO_CHANNEL_SR }, + { BAP_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFL }, + { BAP_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TFR }, + { BAP_CHANNEL_TFC, SPA_AUDIO_CHANNEL_TFC }, + { BAP_CHANNEL_TC, SPA_AUDIO_CHANNEL_TC }, + { BAP_CHANNEL_TBL, SPA_AUDIO_CHANNEL_TRL }, + { BAP_CHANNEL_TBR, SPA_AUDIO_CHANNEL_TRR }, + { BAP_CHANNEL_TSL, SPA_AUDIO_CHANNEL_TSL }, + { BAP_CHANNEL_TSR, SPA_AUDIO_CHANNEL_TSR }, + { BAP_CHANNEL_TBC, SPA_AUDIO_CHANNEL_TRC }, + { BAP_CHANNEL_BFC, SPA_AUDIO_CHANNEL_BC }, + { BAP_CHANNEL_BFL, SPA_AUDIO_CHANNEL_BLC }, + { BAP_CHANNEL_BFR, SPA_AUDIO_CHANNEL_BRC }, + { BAP_CHANNEL_FLW, SPA_AUDIO_CHANNEL_FLW }, + { BAP_CHANNEL_FRW, SPA_AUDIO_CHANNEL_FRW }, + { BAP_CHANNEL_LS, SPA_AUDIO_CHANNEL_SL }, /* is it the right mapping? */ + { BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */ +}; + #endif diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index dd18585b3..2f1117ef8 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -81,41 +81,6 @@ typedef struct { unsigned int priority; } bap_lc3_t; -static const struct { - uint32_t bit; - enum spa_audio_channel channel; -} channel_bits[] = { - { BAP_CHANNEL_MONO, SPA_AUDIO_CHANNEL_MONO }, - { BAP_CHANNEL_FL, SPA_AUDIO_CHANNEL_FL }, - { BAP_CHANNEL_FR, SPA_AUDIO_CHANNEL_FR }, - { BAP_CHANNEL_FC, SPA_AUDIO_CHANNEL_FC }, - { BAP_CHANNEL_LFE, SPA_AUDIO_CHANNEL_LFE }, - { BAP_CHANNEL_BL, SPA_AUDIO_CHANNEL_RL }, - { BAP_CHANNEL_BR, SPA_AUDIO_CHANNEL_RR }, - { BAP_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FLC }, - { BAP_CHANNEL_FRC, SPA_AUDIO_CHANNEL_FRC }, - { BAP_CHANNEL_BC, SPA_AUDIO_CHANNEL_BC }, - { BAP_CHANNEL_LFE2, SPA_AUDIO_CHANNEL_LFE2 }, - { BAP_CHANNEL_SL, SPA_AUDIO_CHANNEL_SL }, - { BAP_CHANNEL_SR, SPA_AUDIO_CHANNEL_SR }, - { BAP_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFL }, - { BAP_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TFR }, - { BAP_CHANNEL_TFC, SPA_AUDIO_CHANNEL_TFC }, - { BAP_CHANNEL_TC, SPA_AUDIO_CHANNEL_TC }, - { BAP_CHANNEL_TBL, SPA_AUDIO_CHANNEL_TRL }, - { BAP_CHANNEL_TBR, SPA_AUDIO_CHANNEL_TRR }, - { BAP_CHANNEL_TSL, SPA_AUDIO_CHANNEL_TSL }, - { BAP_CHANNEL_TSR, SPA_AUDIO_CHANNEL_TSR }, - { BAP_CHANNEL_TBC, SPA_AUDIO_CHANNEL_TRC }, - { BAP_CHANNEL_BFC, SPA_AUDIO_CHANNEL_BC }, - { BAP_CHANNEL_BFL, SPA_AUDIO_CHANNEL_BLC }, - { BAP_CHANNEL_BFR, SPA_AUDIO_CHANNEL_BRC }, - { BAP_CHANNEL_FLW, SPA_AUDIO_CHANNEL_FLW }, - { BAP_CHANNEL_FRW, SPA_AUDIO_CHANNEL_FRW }, - { BAP_CHANNEL_LS, SPA_AUDIO_CHANNEL_SL }, /* is it the right mapping? */ - { BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */ -}; - #define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \ ((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \ .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ @@ -569,9 +534,9 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t return -1; *allocation = 0; - for (i = 0; i < SPA_N_ELEMENTS(channel_bits); ++i) { - if (locations & channel_bits[i].bit) { - *allocation |= channel_bits[i].bit; + for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i) { + if (locations & bap_channel_bits[i].bit) { + *allocation |= bap_channel_bits[i].bit; --num; if (num == 0) break; @@ -944,9 +909,9 @@ static uint8_t channels_to_positions(uint32_t channels, uint32_t *position) } else { unsigned int i; - for (i = 0; i < SPA_N_ELEMENTS(channel_bits); ++i) - if (channels & channel_bits[i].bit) - position[n_positions++] = channel_bits[i].channel; + for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i) + if (channels & bap_channel_bits[i].bit) + position[n_positions++] = bap_channel_bits[i].channel; } if (n_positions != n_channels) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 47fff8bc7..8b29086f7 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "config.h" #include "codec-loader.h" @@ -122,6 +123,13 @@ struct spa_bt_monitor { struct spa_list bcast_source_config_list; + uint32_t bap_sink_locations; + uint32_t bap_sink_contexts; + uint32_t bap_sink_supported_contexts; + uint32_t bap_source_locations; + uint32_t bap_source_contexts; + uint32_t bap_source_supported_contexts; + struct spa_bt_quirks *quirks; #define MAX_SETTINGS 128 @@ -5006,7 +5014,7 @@ out: return err; } -static void append_media_object(DBusMessageIter *iter, const char *endpoint, +static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size) { const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE; @@ -5033,19 +5041,24 @@ static void append_media_object(DBusMessageIter *iter, const char *endpoint, } if (spa_bt_profile_from_uuid(uuid) & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) { dbus_uint32_t locations; - dbus_uint16_t supported_context, context; + dbus_uint16_t supported_contexts, contexts; - locations = BAP_CHANNEL_ALL; if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_SINK) { - supported_context = context = BAP_CONTEXT_ALL; + locations = monitor->bap_sink_locations; + contexts = monitor->bap_sink_contexts; + supported_contexts = monitor->bap_sink_supported_contexts; } else { - supported_context = context = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL | - BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME); + locations = monitor->bap_source_locations; + contexts = monitor->bap_source_contexts; + supported_contexts = monitor->bap_source_supported_contexts; } + spa_log_debug(monitor->log, "BAP endpoint %s locations:0x%x contexts:0x%x supported-contexs:0x%x", + endpoint, locations, contexts, supported_contexts); + append_basic_variant_dict_entry(&dict, "Locations", DBUS_TYPE_UINT32, "u", &locations); - append_basic_variant_dict_entry(&dict, "Context", DBUS_TYPE_UINT16, "q", &context); - append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_context); + append_basic_variant_dict_entry(&dict, "Context", DBUS_TYPE_UINT16, "q", &contexts); + append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts); } dbus_message_iter_close_container(&entry, &dict); @@ -5127,7 +5140,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint); - append_media_object(&array, endpoint, + append_media_object(monitor, &array, endpoint, codec->bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, codec_id, caps, caps_size); } @@ -5142,7 +5155,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); - append_media_object(&array, endpoint, + append_media_object(monitor, &array, endpoint, codec->bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, codec_id, caps, caps_size); } @@ -5158,7 +5171,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE_BROADCAST, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); - append_media_object(&array, endpoint, + append_media_object(monitor, &array, endpoint, SPA_BT_UUID_BAP_BROADCAST_SOURCE, codec_id, caps, caps_size); } @@ -5173,7 +5186,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK_BROADCAST, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register broadcast media sink codec %s: %s", media_codecs[i]->name, endpoint); - append_media_object(&array, endpoint, + append_media_object(monitor, &array, endpoint, SPA_BT_UUID_BAP_BROADCAST_SINK, codec_id, caps, caps_size); } @@ -6549,6 +6562,55 @@ fallback: return 0; } +static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_dict *info, + const char *key, uint32_t *value) +{ + const char *str; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_channels; + uint32_t locations; + unsigned int i, j; + + if (!info || !(str = spa_dict_lookup(info, key))) + return; + + if (spa_atou32(str, value, 0)) + return; + + if (!spa_audio_parse_position(str, strlen(str), position, &n_channels)) { + spa_log_error(this->log, "property %s '%s' is not valid position array", key, str); + return; + } + + locations = 0; + for (i = 0; i < n_channels; ++i) + for (j = 0; j < SPA_N_ELEMENTS(bap_channel_bits); ++j) + if (bap_channel_bits[j].channel == position[i]) + locations |= bap_channel_bits[j].bit; + + *value = locations; +} + +static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info) +{ + this->bap_sink_locations = BAP_CHANNEL_ALL; + this->bap_source_locations = BAP_CHANNEL_ALL; + this->bap_sink_contexts = this->bap_sink_supported_contexts = BAP_CONTEXT_ALL; + this->bap_source_contexts = this->bap_source_supported_contexts = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL | + BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME); + + if (!info) + return; + + parse_bap_locations(this, info, "bluez5.bap-server-capabilities.sink.locations", &this->bap_sink_locations); + spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.contexts"), &this->bap_sink_contexts, 0); + spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.supported-contexts"), &this->bap_sink_supported_contexts, 0); + + parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations); + spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0); + spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0); +} + static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict) { uint32_t n_items = 0; @@ -6662,6 +6724,7 @@ impl_init(const struct spa_handle_factory *factory, parse_roles(this, info); parse_broadcast_source_config(this, info); + parse_bap_server(this, info); this->default_audio_info.rate = A2DP_CODEC_DEFAULT_RATE; this->default_audio_info.channels = A2DP_CODEC_DEFAULT_CHANNELS; From 0d61cc1b1dd3ba6a4e2e73dd940e12944f213cc6 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 22 Mar 2025 14:18:58 +0200 Subject: [PATCH 0145/1014] bluez5: use kernel-provided RX timestamps Use kernel-provided packet reception timestamps to get less jitter in packet timings. Mostly matters for ISO/SCO which have regular schedule. A2DP (L2CAP) doesn't currently do RX timestamps in kernel, but we can as well use the same mechanism for it. --- spa/plugins/bluez5/bluez5-dbus.c | 4 +- spa/plugins/bluez5/bt-latency.h | 1 + spa/plugins/bluez5/decode-buffer.h | 122 +++++++++++++++++++++++++++++ spa/plugins/bluez5/defs.h | 7 +- spa/plugins/bluez5/media-source.c | 25 +++--- spa/plugins/bluez5/sco-io.c | 20 +++-- spa/plugins/bluez5/sco-sink.c | 2 +- spa/plugins/bluez5/sco-source.c | 15 ++-- 8 files changed, 165 insertions(+), 31 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 8b29086f7..dc77ffed2 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -3269,10 +3269,10 @@ static int spa_bt_transport_stop_volume_timer(struct spa_bt_transport *transport } -int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop) +int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop, struct spa_system *data_system) { if (t->sco_io == NULL) { - t->sco_io = spa_bt_sco_io_create(t, data_loop, t->monitor->log); + t->sco_io = spa_bt_sco_io_create(t, data_loop, data_system, t->monitor->log); if (t->sco_io == NULL) return -ENOMEM; } diff --git a/spa/plugins/bluez5/bt-latency.h b/spa/plugins/bluez5/bt-latency.h index 6fba869aa..1df17f9a2 100644 --- a/spa/plugins/bluez5/bt-latency.h +++ b/spa/plugins/bluez5/bt-latency.h @@ -5,6 +5,7 @@ #ifndef SPA_BLUEZ5_BT_LATENCY_H #define SPA_BLUEZ5_BT_LATENCY_H +#include #include #include #include diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 0441f041c..83a7df2e8 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -31,6 +31,11 @@ #define SPA_BLUEZ5_DECODE_BUFFER_H #include +#include +#include +#include +#include + #include #include @@ -308,4 +313,121 @@ static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *thi } } +struct spa_bt_recvmsg_data { + struct spa_log *log; + struct spa_system *data_system; + int fd; + int64_t offset; + int64_t err; +}; + +static inline void spa_bt_recvmsg_update_clock(struct spa_bt_recvmsg_data *data, uint64_t *now) +{ + const int64_t max_resync = (50 * SPA_NSEC_PER_USEC); + const int64_t n_avg = 10; + struct timespec ts1, ts2, ts3; + int64_t t1, t2, t3, offset, err; + + spa_system_clock_gettime(data->data_system, CLOCK_MONOTONIC, &ts1); + spa_system_clock_gettime(data->data_system, CLOCK_REALTIME, &ts2); + spa_system_clock_gettime(data->data_system, CLOCK_MONOTONIC, &ts3); + + t1 = SPA_TIMESPEC_TO_NSEC(&ts1); + t2 = SPA_TIMESPEC_TO_NSEC(&ts2); + t3 = SPA_TIMESPEC_TO_NSEC(&ts3); + + if (now) + *now = t1; + + offset = t1 + (t3 - t1) / 2 - t2; + + /* Moving average smoothing, discarding outliers */ + err = offset - data->offset; + + if (SPA_ABS(err) > max_resync) { + /* Clock jump */ + spa_log_debug(data->log, "%p: nsec err %"PRIi64" > max_resync %"PRIi64", resetting", + data, err, max_resync); + data->offset = offset; + data->err = 0; + err = 0; + } else if (SPA_ABS(err) / 2 <= data->err) { + data->offset += err / n_avg; + } + + data->err += (SPA_ABS(err) - data->err) / n_avg; +} + +static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time) +{ + char control[1024]; + struct iovec data = { + .iov_base = buf, + .iov_len = max_size + }; + struct msghdr msg = { + .msg_iov = &data, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + uint64_t t = 0, now; + ssize_t res; + + res = recvmsg(r->fd, &msg, MSG_DONTWAIT); + if (res < 0 || !rx_time) + return res; + + spa_bt_recvmsg_update_clock(r, &now); + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + struct scm_timestamping *tss; + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_TIMESTAMPING) + continue; + + tss = (struct scm_timestamping *)CMSG_DATA(cmsg); + t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]); + break; + } + + if (!t) { + *rx_time = now; + return res; + } + + *rx_time = t + r->offset; + + /* CLOCK_REALTIME may jump, so sanity check */ + if (*rx_time > now || *rx_time + 20 * SPA_NSEC_PER_MSEC < now) + *rx_time = now; + + spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64, + r, *rx_time, now, now - *rx_time, r->offset); + + return res; +} + + +static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd, + struct spa_system *data_system, struct spa_log *log) +{ + int flags = 0; + socklen_t len = sizeof(flags); + + data->log = log; + data->data_system = data_system; + data->fd = fd; + data->offset = 0; + data->err = 0; + + if (getsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, &len) < 0) + spa_log_info(log, "failed to get SO_TIMESTAMPING"); + + flags |= SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)) < 0) + spa_log_info(log, "failed to set SO_TIMESTAMPING"); +} + #endif diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index bbf814b3c..c45bf3496 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -584,9 +584,10 @@ struct spa_bt_iso_io; struct spa_bt_sco_io; -struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_log *log); +struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, + struct spa_system *data_system, struct spa_log *log); void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io); -void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *userdata, uint8_t *data, int size), void *userdata); +void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time), void *userdata); void spa_bt_sco_io_set_sink_cb(struct spa_bt_sco_io *io, int (*sink_cb)(void *userdata), void *userdata); int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *data, int size); @@ -703,7 +704,7 @@ bool spa_bt_transport_volume_enabled(struct spa_bt_transport *transport); int spa_bt_transport_acquire(struct spa_bt_transport *t, bool optional); int spa_bt_transport_release(struct spa_bt_transport *t); int spa_bt_transport_keepalive(struct spa_bt_transport *t, bool keepalive); -int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop); +int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop, struct spa_system *data_system); #define spa_bt_transport_emit(t,m,v,...) spa_hook_list_call(&(t)->listener_list, \ struct spa_bt_transport_events, \ diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 005597490..d6ac64bc0 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -159,7 +159,7 @@ struct impl { struct spa_audio_info codec_format; uint8_t buffer_read[4096]; - struct timespec now; + uint64_t now; uint64_t sample_count; uint32_t errqueue_count; @@ -167,6 +167,8 @@ struct impl { struct delay_info delay; int64_t delay_sink; struct spa_source *update_delay_event; + + struct spa_bt_recvmsg_data recv; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) @@ -420,13 +422,14 @@ static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer } } -static int32_t read_data(struct impl *this) { +static int32_t read_data(struct impl *this, uint64_t *rx_time) +{ const ssize_t b_size = sizeof(this->buffer_read); int32_t size_read = 0; again: /* read data from socket */ - size_read = recv(this->fd, this->buffer_read, b_size, MSG_DONTWAIT); + size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time); if (size_read == 0) return 0; @@ -499,11 +502,11 @@ static void media_on_ready_read(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; - struct timespec now; void *buf; int32_t size_read, decoded; uint32_t avail; uint64_t dt; + uint64_t now; /* make sure the source is an input */ if ((source->rmask & SPA_IO_IN) == 0) { @@ -524,11 +527,8 @@ static void media_on_ready_read(struct spa_source *source) spa_log_trace(this->log, "socket poll"); - /* update the current pts */ - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - /* read */ - size_read = read_data (this); + size_read = read_data (this, &now); if (size_read == 0) return; if (size_read < 0) { @@ -559,11 +559,10 @@ static void media_on_ready_read(struct spa_source *source) if (!this->started) return; - spa_bt_decode_buffer_write_packet(&port->buffer, decoded, SPA_TIMESPEC_TO_NSEC(&now)); + spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); - dt = SPA_TIMESPEC_TO_NSEC(&this->now); + dt = now - this->now; this->now = now; - dt = SPA_TIMESPEC_TO_NSEC(&this->now) - dt; spa_log_trace(this->log, "decoded socket data size:%d frames:%d dt:%d dms", (int)decoded, (int)decoded/port->frame_size, @@ -766,6 +765,8 @@ static int transport_start(struct impl *this) if (setsockopt(this->fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) spa_log_warn(this->log, "SO_PRIORITY failed: %m"); + spa_bt_recvmsg_init(&this->recv, this->fd, this->data_system, this->log); + reset_buffers(port); spa_bt_decode_buffer_clear(&port->buffer); @@ -1549,7 +1550,7 @@ static void process_buffering(struct impl *this) if (buffer->h) { buffer->h->seq = this->sample_count; - buffer->h->pts = SPA_TIMESPEC_TO_NSEC(&this->now); + buffer->h->pts = this->now; buffer->h->dts_offset = 0; } diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 4b4b8f96d..bf41df645 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -34,6 +34,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sco-io"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic +#include "decode-buffer.h" + /* We'll use the read rx data size to find the correct packet size for writing, * since kernel might not report it as the socket MTU, see @@ -60,9 +62,12 @@ struct spa_bt_sco_io { struct spa_log *log; struct spa_loop *data_loop; + struct spa_system *data_system; struct spa_source source; - int (*source_cb)(void *userdata, uint8_t *data, int size); + struct spa_bt_recvmsg_data recv; + + int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time); void *source_userdata; int (*sink_cb)(void *userdata); @@ -92,9 +97,10 @@ static void sco_io_on_ready(struct spa_source *source) if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { int res; + uint64_t rx_time; read_again: - res = recv(io->fd, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), MSG_DONTWAIT); + res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time); if (res <= 0) { if (errno == EINTR) { /* retry if interrupted */ @@ -115,7 +121,7 @@ static void sco_io_on_ready(struct spa_source *source) if (io->source_cb) { int res; - res = io->source_cb(io->source_userdata, io->read_buffer, io->read_size); + res = io->source_cb(io->source_userdata, io->read_buffer, io->read_size, rx_time); if (res) { io->source_cb = NULL; } @@ -193,7 +199,8 @@ int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *buf, int size) } -struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_log *log) +struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, + struct spa_system *data_system, struct spa_log *log) { struct spa_bt_sco_io *io; @@ -207,6 +214,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s io->read_mtu = transport->read_mtu; io->write_mtu = transport->write_mtu; io->data_loop = data_loop; + io->data_system = data_system; io->log = log; if (transport->device->adapter->bus_type == BUS_TYPE_USB) { @@ -230,6 +238,8 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s spa_log_debug(io->log, "%p: initial packet size:%d", io, io->read_size); + spa_bt_recvmsg_init(&io->recv, io->fd, io->data_system, io->log); + /* Add the ready callback */ io->source.data = io; io->source.fd = io->fd; @@ -271,7 +281,7 @@ void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) * This function should only be called from the data thread. * Callback is called (in data loop) with data just read from the socket. */ -void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *, uint8_t *, int), void *userdata) +void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *, uint8_t *, int, uint64_t), void *userdata) { io->source_cb = source_cb; io->source_userdata = userdata; diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index fc377ae10..fff5bbbd2 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -823,7 +823,7 @@ static int transport_start(struct impl *this) (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); /* start socket i/o */ - if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop)) < 0) + if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) goto fail; this->flush_timer_source.data = this; diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index dc2e1f070..9a98f44f8 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -153,7 +153,7 @@ struct impl { void *lc3; #endif - struct timespec now; + uint64_t now; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) @@ -538,7 +538,7 @@ static uint32_t preprocess_and_decode_codec_data(void *userdata, uint8_t *read_d return decoded; } -static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) +static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read, uint64_t rx_time) { struct impl *this = userdata; struct port *port = &this->port; @@ -555,9 +555,8 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) } /* update the current pts */ - dt = SPA_TIMESPEC_TO_NSEC(&this->now); - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &this->now); - dt = SPA_TIMESPEC_TO_NSEC(&this->now) - dt; + dt = rx_time - this->now; + this->now = rx_time; /* handle data read from socket */ #if 0 @@ -566,7 +565,7 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) if (this->transport->codec == HFP_AUDIO_CODEC_MSBC || this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { - decoded = preprocess_and_decode_codec_data(userdata, read_data, size_read, SPA_TIMESPEC_TO_NSEC(&this->now)); + decoded = preprocess_and_decode_codec_data(userdata, read_data, size_read, this->now); } else { uint32_t avail; uint8_t *packet; @@ -591,7 +590,7 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) packet = spa_bt_decode_buffer_get_write(&port->buffer, &avail); avail = SPA_MIN(avail, (uint32_t)size_read); spa_memmove(packet, read_data, avail); - spa_bt_decode_buffer_write_packet(&port->buffer, avail, SPA_TIMESPEC_TO_NSEC(&this->now)); + spa_bt_decode_buffer_write_packet(&port->buffer, avail, this->now); decoded = avail; } @@ -763,7 +762,7 @@ static int transport_start(struct impl *this) this->io_error = false; /* Start socket i/o */ - if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop)) < 0) + if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) goto fail; spa_loop_invoke(this->data_loop, do_add_source, 0, NULL, 0, true, this); From 46c42619987e68b5f9005e3b9ce31ef19a45c6f1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 21 Apr 2025 14:22:21 +0300 Subject: [PATCH 0146/1014] bluez5: fix cmsg align in spa_bt_recmsg & smaller buffer --- spa/plugins/bluez5/decode-buffer.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 83a7df2e8..44bdc15d2 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -360,7 +360,10 @@ static inline void spa_bt_recvmsg_update_clock(struct spa_bt_recvmsg_data *data, static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time) { - char control[1024]; + union { + char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; + struct cmsghdr align; + } control; struct iovec data = { .iov_base = buf, .iov_len = max_size @@ -368,8 +371,8 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), + .msg_control = control.buf, + .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; uint64_t t = 0, now; From 2e409c0237e0ea973c27fe19c423c7cf94c9712c Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Tue, 22 Apr 2025 10:48:00 +0100 Subject: [PATCH 0147/1014] gst: src: Attach video meta when receiving DMABufs from PipeWire Fixes getDisplayMedia() in WebKitGTK after regression introduced by: https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/2330 --- src/gst/gstpipewiresrc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 637608b48..14592f0d5 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1281,6 +1281,7 @@ handle_format_change (GstPipeWireSrc *pwsrc, pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "internal error"); return; } + pwsrc->is_rawvideo = TRUE; } else { gst_video_info_dma_drm_init (&pwsrc->drm_info); #endif From 14f55663bf2f9ea7c1c323e8753ff9606f524815 Mon Sep 17 00:00:00 2001 From: Anders Jonsson Date: Tue, 22 Apr 2025 10:45:29 +0200 Subject: [PATCH 0148/1014] po: Update Swedish translation --- po/sv.po | 183 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 96 insertions(+), 87 deletions(-) diff --git a/po/sv.po b/po/sv.po index b867795ca..0bc2796a4 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,9 +1,9 @@ # Swedish translation for pipewire. -# Copyright © 2008-2024 Free Software Foundation, Inc. +# Copyright © 2008-2025 Free Software Foundation, Inc. # This file is distributed under the same license as the pipewire package. # Daniel Nylander , 2008, 2012. # Josef Andersson , 2014, 2017. -# Anders Jonsson , 2021, 2022, 2023, 2024. +# Anders Jonsson , 2021, 2022, 2023, 2024, 2025. # # Termer: # input/output: ingång/utgång (det handlar om ljud) @@ -19,8 +19,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2024-11-05 03:27+0000\n" -"PO-Revision-Date: 2024-11-07 21:52+0100\n" +"POT-Creation-Date: 2025-04-16 15:31+0000\n" +"PO-Revision-Date: 2025-04-22 10:43+0200\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\n" @@ -65,7 +65,7 @@ msgstr "Tunnel till %s%s%s" msgid "Dummy Output" msgstr "Attrapputgång" -#: src/modules/module-pulse-tunnel.c:777 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" @@ -184,7 +184,7 @@ msgstr "" " -o, --encoded Kodat läge\n" "\n" -#: src/tools/pw-cli.c:2318 +#: src/tools/pw-cli.c:2306 #, c-format msgid "" "%s [options] [command]\n" @@ -198,20 +198,25 @@ msgstr "" "%s [flaggor] [kommando]\n" " -h, --help Visa denna hjälp\n" " --version Show version\n" -" -d, --daemon Starta som demon (Standard false)\n" +" -d, --daemon Starta som demon (standard false)\n" " -r, --remote Fjärrdemonnamn\n" " -m, --monitor Övervaka aktivitet\n" "\n" -#: spa/plugins/alsa/acp/acp.c:327 +#: spa/plugins/alsa/acp/acp.c:350 msgid "Pro Audio" msgstr "Professionellt ljud" -#: spa/plugins/alsa/acp/acp.c:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 -#: spa/plugins/bluez5/bluez5-device.c:1705 +#: spa/plugins/alsa/acp/acp.c:511 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1802 msgid "Off" msgstr "Av" +#: spa/plugins/alsa/acp/acp.c:593 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [ALSA UCM-fel]" + #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Ingång" @@ -235,7 +240,7 @@ msgstr "Linje in" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2026 +#: spa/plugins/bluez5/bluez5-device.c:2146 msgid "Microphone" msgstr "Mikrofon" @@ -301,7 +306,7 @@ msgid "No Bass Boost" msgstr "Ingen basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:2032 +#: spa/plugins/bluez5/bluez5-device.c:2152 msgid "Speaker" msgstr "Högtalare" @@ -383,15 +388,15 @@ msgstr "Chatt-ingång" msgid "Virtual Surround 7.1" msgstr "Virtual surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4456 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Analog mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4457 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Analog mono (vänster)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4458 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Analog mono (höger)" @@ -400,147 +405,147 @@ msgstr "Analog mono (höger)" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4459 -#: spa/plugins/alsa/acp/alsa-mixer.c:4467 -#: spa/plugins/alsa/acp/alsa-mixer.c:4468 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Analog stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4460 +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4469 -#: spa/plugins/alsa/acp/alsa-mixer.c:4627 -#: spa/plugins/bluez5/bluez5-device.c:2014 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2134 msgid "Headset" msgstr "Headset" -#: spa/plugins/alsa/acp/alsa-mixer.c:4470 -#: spa/plugins/alsa/acp/alsa-mixer.c:4628 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Högtalartelefon" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Analog surround 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Analog surround 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Analog surround 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Analog surround 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4477 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Analog surround 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4478 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Analog surround 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4479 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Analog surround 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4480 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Analog surround 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4481 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Analog surround 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Analog surround 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Analog surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital surround 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital surround 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital surround 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital surround 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Chatt" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Spel" -#: spa/plugins/alsa/acp/alsa-mixer.c:4625 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Analog mono duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4626 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Analog stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo duplex (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4630 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Multikanalduplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4631 +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4632 +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chatt + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4733 +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "%s-utgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:4741 +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "%s-ingång" -#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -563,7 +568,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1297 +#: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -586,7 +591,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1344 +#: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -599,7 +604,7 @@ msgstr "" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1387 +#: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -634,100 +639,104 @@ msgstr "Inbyggt ljud" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1716 +#: spa/plugins/bluez5/bluez5-device.c:1813 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1764 +#: spa/plugins/bluez5/bluez5-device.c:1841 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "Ljudströmning för hörhjälpmedel (ASHA-utgång)" + +#: spa/plugins/bluez5/bluez5-device.c:1881 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High fidelity-uppspelning (A2DP-utgång, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1767 +#: spa/plugins/bluez5/bluez5-device.c:1884 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High fidelity duplex (A2DP-källa/utgång, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1775 +#: spa/plugins/bluez5/bluez5-device.c:1892 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgång)" -#: spa/plugins/bluez5/bluez5-device.c:1777 +#: spa/plugins/bluez5/bluez5-device.c:1894 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgång)" -#: spa/plugins/bluez5/bluez5-device.c:1827 +#: spa/plugins/bluez5/bluez5-device.c:1944 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High fidelity-uppspelning (BAP-utgång, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1832 +#: spa/plugins/bluez5/bluez5-device.c:1949 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High fidelity-ingång (BAP-källa, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1836 +#: spa/plugins/bluez5/bluez5-device.c:1953 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High fidelity duplex (BAP-källa/utgång, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1845 +#: spa/plugins/bluez5/bluez5-device.c:1962 msgid "High Fidelity Playback (BAP Sink)" msgstr "High fidelity-uppspelning (BAP-utgång)" -#: spa/plugins/bluez5/bluez5-device.c:1849 +#: spa/plugins/bluez5/bluez5-device.c:1966 msgid "High Fidelity Input (BAP Source)" msgstr "High fidelity-ingång (BAP-källa)" -#: spa/plugins/bluez5/bluez5-device.c:1852 +#: spa/plugins/bluez5/bluez5-device.c:1969 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High fidelity duplex (BAP-källa/utgång)" -#: spa/plugins/bluez5/bluez5-device.c:1901 +#: spa/plugins/bluez5/bluez5-device.c:2015 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2015 -#: spa/plugins/bluez5/bluez5-device.c:2020 -#: spa/plugins/bluez5/bluez5-device.c:2027 -#: spa/plugins/bluez5/bluez5-device.c:2033 -#: spa/plugins/bluez5/bluez5-device.c:2039 -#: spa/plugins/bluez5/bluez5-device.c:2045 -#: spa/plugins/bluez5/bluez5-device.c:2051 -#: spa/plugins/bluez5/bluez5-device.c:2057 -#: spa/plugins/bluez5/bluez5-device.c:2063 +#: spa/plugins/bluez5/bluez5-device.c:2135 +#: spa/plugins/bluez5/bluez5-device.c:2140 +#: spa/plugins/bluez5/bluez5-device.c:2147 +#: spa/plugins/bluez5/bluez5-device.c:2153 +#: spa/plugins/bluez5/bluez5-device.c:2159 +#: spa/plugins/bluez5/bluez5-device.c:2165 +#: spa/plugins/bluez5/bluez5-device.c:2171 +#: spa/plugins/bluez5/bluez5-device.c:2177 +#: spa/plugins/bluez5/bluez5-device.c:2183 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:2021 +#: spa/plugins/bluez5/bluez5-device.c:2141 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2038 +#: spa/plugins/bluez5/bluez5-device.c:2158 msgid "Headphone" msgstr "Hörlurar" -#: spa/plugins/bluez5/bluez5-device.c:2044 +#: spa/plugins/bluez5/bluez5-device.c:2164 msgid "Portable" msgstr "Bärbar" -#: spa/plugins/bluez5/bluez5-device.c:2050 +#: spa/plugins/bluez5/bluez5-device.c:2170 msgid "Car" msgstr "Bil" -#: spa/plugins/bluez5/bluez5-device.c:2056 +#: spa/plugins/bluez5/bluez5-device.c:2176 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2062 +#: spa/plugins/bluez5/bluez5-device.c:2182 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2069 +#: spa/plugins/bluez5/bluez5-device.c:2189 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2070 +#: spa/plugins/bluez5/bluez5-device.c:2190 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" From 336a5d1ded565298a5cc90b5e4157da5a4a464b3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Apr 2025 10:25:39 +0200 Subject: [PATCH 0149/1014] ump: fix program change 2.0 to 1.0 conversion The program change byte should not be shifted an extra bit, unlike all the other messages. Fixes #4664 --- spa/include/spa/control/ump-utils.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 9f57efb93..463b12d14 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -96,9 +96,17 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(uint32_t *ump, size_t ump_size, if (ump_size < 8) return 0; midi[size++] = (ump[0] >> 16) | 0x80; - if (midi[0] < 0xc0 || midi[0] > 0xdf) + switch (midi[0] & 0xf0) { + case 0xc0: + midi[size++] = (ump[1] >> 24); + break; + default: midi[size++] = (ump[0] >> 8) & 0x7f; - midi[size++] = (ump[1] >> 25); + SPA_FALLTHROUGH; + case 0xd0: + midi[size++] = (ump[1] >> 25); + break; + } break; case 0x0: /* Utility Messages */ From 205ee5b6b044a4db0af3d446e0d70cb23d193147 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 11:02:02 +0200 Subject: [PATCH 0150/1014] dynamic: add _continue The continue functions takes a builder as the argument and makes a new builder that starts from the old builder memory. If the old builder was dynamic, the new one will also be dynamic. Because it's a separate builder, the memory of the old builder will not be reallocated when extended. This makes it possible to freely read the memory from the old builder while we construct the result in a new builder without having to worry about reallocating the memory of the old builder. When the new object is completed, it can then be copied into the old builder. --- spa/include/spa/pod/dynamic.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/dynamic.h b/spa/include/spa/pod/dynamic.h index e9998cdb2..dad9c3d71 100644 --- a/spa/include/spa/pod/dynamic.h +++ b/spa/include/spa/pod/dynamic.h @@ -53,11 +53,21 @@ SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_init(struct spa_pod_dynamic_bui .overflow = spa_pod_dynamic_builder_overflow }; builder->b = SPA_POD_BUILDER_INIT(data, size); - spa_pod_builder_set_callbacks(&builder->b, &spa_pod_dynamic_builder_callbacks, builder); + if (extend > 0) + spa_pod_builder_set_callbacks(&builder->b, &spa_pod_dynamic_builder_callbacks, builder); builder->extend = extend; builder->data = data; } +SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_continue(struct spa_pod_dynamic_builder *builder, + struct spa_pod_builder *b) +{ + uint32_t remain = b->state.offset >= b->size ? 0 : b->size - b->state.offset; + spa_pod_dynamic_builder_init(builder, + remain ? SPA_PTROFF(b->data, b->state.offset, void) : NULL, + remain, b->callbacks.funcs == NULL ? 0 : 4096); +} + SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder) { if (builder->data != builder->b.data) From 2f21b273979f580f13b6ad4a5058ae301b65c54b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 11:06:02 +0200 Subject: [PATCH 0151/1014] filter: build result in separate builder and copy Build the filtered result into a new separate builder and copy it into the result builder afterwards. This ensures the memory of the old builder does not suddenly change when it gets reallocated. --- spa/include/spa/pod/filter.h | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index f795d0cb5..544e25f7b 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -19,6 +19,7 @@ extern "C" { #include #include #include +#include #ifndef SPA_API_POD_FILTER #ifdef SPA_API_IMPL @@ -438,14 +439,17 @@ spa_pod_filter(struct spa_pod_builder *b, spa_return_val_if_fail(b != NULL, -EINVAL); spa_pod_builder_get_state(b, &state); - if (filter == NULL) + if (filter == NULL) { res = spa_pod_builder_raw_padded(b, pod, SPA_POD_SIZE(pod)); - else - res = spa_pod_filter_part(b, pod, SPA_POD_SIZE(pod), filter, SPA_POD_SIZE(filter)); - - if (res < 0) { - spa_pod_builder_reset(b, &state); - } else if (result) { + } else { + struct spa_pod_dynamic_builder db; + spa_pod_dynamic_builder_continue(&db, b); + res = spa_pod_filter_part(&db.b, pod, SPA_POD_SIZE(pod), filter, SPA_POD_SIZE(filter)); + if (res >= 0) + res = spa_pod_builder_raw_padded(b, db.b.data, db.b.state.offset); + spa_pod_dynamic_builder_clean(&db); + } + if (res >= 0 && result) { *result = (struct spa_pod*)spa_pod_builder_deref(b, state.offset); if (*result == NULL) res = -ENOSPC; From 4774178c68b1558f97d6f81030fab55e0925af41 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 14 Apr 2025 17:13:54 +0200 Subject: [PATCH 0152/1014] pod: struct has spa_pod as the body --- spa/include/spa/pod/iter.h | 2 +- spa/include/spa/pod/pod.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 1bd569968..f4958251c 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -107,7 +107,7 @@ SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_p (iter) = (__typeof__(iter))spa_pod_next(iter)) #define SPA_POD_STRUCT_FOREACH(obj, iter) \ - SPA_POD_FOREACH(SPA_POD_BODY(obj), SPA_POD_BODY_SIZE(obj), iter) + SPA_POD_FOREACH(SPA_POD_STRUCT_BODY(obj), SPA_POD_BODY_SIZE(obj), iter) #define SPA_POD_OBJECT_BODY_FOREACH(body, size, iter) \ for ((iter) = spa_pod_prop_first(body); \ diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index f7627f48e..3703b4b06 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -138,6 +138,9 @@ struct spa_pod_choice { struct spa_pod_choice_body body; }; +#define SPA_POD_STRUCT_BODY(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),struct spa_pod) +#define SPA_POD_STRUCT_BODY_CONST(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),const struct spa_pod) + struct spa_pod_struct { struct spa_pod pod; /* one or more spa_pod follow */ From d1d580cafa4b598c37444753846f3e21ba341f62 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Apr 2025 18:33:29 +0200 Subject: [PATCH 0153/1014] pod: move common check outside of the loop --- spa/include/spa/pod/filter.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 544e25f7b..d32d9ad15 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -209,15 +209,15 @@ spa_pod_filter_prop(struct spa_pod_builder *b, bool found_def = false; /* we should prefer the alt2 range default value but only if valid */ - for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { - if (spa_pod_compare_value(type, alt2, min, size) < 0 || - spa_pod_compare_value(type, alt2, max, size) > 0) - break; - if (spa_pod_compare_value(type, a1, alt2, size) == 0) { - /* it is in the enum, use as default then */ - spa_pod_builder_raw(b, a1, size); - found_def = true; - break; + if (spa_pod_compare_value(type, alt2, min, size) >= 0 && + spa_pod_compare_value(type, alt2, max, size) <= 0) { + for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { + if (spa_pod_compare_value(type, a1, alt2, size) == 0) { + /* it is in the enum, use as default then */ + spa_pod_builder_raw(b, a1, size); + found_def = true; + break; + } } } /* copy all values inside the range */ From bcf0c0cf89f680078dcfdd70f3561855cfe880b7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Apr 2025 11:09:54 +0200 Subject: [PATCH 0154/1014] v4l2: only skip buffer for raw formats We don't want to skip the first buffer for encoded formats because it can contain the encoding parameters, in the h264 case, for example. --- spa/plugins/v4l2/v4l2-utils.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index be38ab32d..96ce68ff7 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1873,7 +1873,10 @@ static int spa_v4l2_stream_on(struct impl *this) spa_log_debug(this->log, "starting"); - port->first_buffer = true; + if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw) + port->first_buffer = true; + else + port->first_buffer = false; mmap_read(this); type = V4L2_BUF_TYPE_VIDEO_CAPTURE; From 1cca24c585c695d099ea92472176c06050d010eb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Apr 2025 11:14:34 +0200 Subject: [PATCH 0155/1014] videoconvert: consume input buffer in all cases Return the input buffer when we get some error so that the other side can send new data. --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index d5cb2ee7b..41e2da14b 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1898,6 +1898,7 @@ static int impl_node_process(void *object) } sbuf = &in_port->buffers[input->buffer_id]; + input->status = SPA_STATUS_NEED_DATA; if (this->fmt_passthrough) { dbuf = &out_port->buffers[input->buffer_id]; @@ -2005,8 +2006,6 @@ static int impl_node_process(void *object) output->buffer_id = dbuf->id; output->status = SPA_STATUS_HAVE_DATA; - input->status = SPA_STATUS_NEED_DATA; - return SPA_STATUS_HAVE_DATA; } From 19f6d83fa2991c5157f49dfeaedf81432a06bd9f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Apr 2025 11:16:54 +0200 Subject: [PATCH 0156/1014] pod: move some filter functions to compare --- spa/include/spa/pod/compare.h | 80 +++++++++++++++++++++++++++++++++++ spa/include/spa/pod/filter.h | 41 ++---------------- 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 508987561..05c061363 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -163,6 +163,86 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, return res; } +SPA_API_POD_COMPARE int spa_pod_compare_is_compatible_flags(uint32_t type, const void *r1, + const void *r2, uint32_t size SPA_UNUSED) +{ + switch (type) { + case SPA_TYPE_Int: + return ((*(int32_t *) r1) & (*(int32_t *) r2)) != 0; + case SPA_TYPE_Long: + return ((*(int64_t *) r1) & (*(int64_t *) r2)) != 0; + default: + return -ENOTSUP; + } + return 0; +} + + +SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1, + const void *r2, uint32_t size SPA_UNUSED) +{ + switch (type) { + case SPA_TYPE_Int: + return *(int32_t *) r1 % *(int32_t *) r2 == 0; + case SPA_TYPE_Long: + return *(int64_t *) r1 % *(int64_t *) r2 == 0; + case SPA_TYPE_Rectangle: + { + const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, + *rec2 = (struct spa_rectangle *) r2; + + return (rec1->width % rec2->width == 0 && + rec1->height % rec2->height == 0); + } + default: + return -ENOTSUP; + } + return 0; +} + +SPA_API_POD_COMPARE int spa_pod_compare_is_in_range(uint32_t type, const void *v, + const void *min, const void *max, const void *step, uint32_t size SPA_UNUSED) +{ + if (spa_pod_compare_value(type, v, min, size) < 0 || + spa_pod_compare_value(type, v, max, size) > 0) + return 0; + if (step != NULL) + return spa_pod_compare_is_step_of(type, v, step, size); + return 1; +} + +SPA_API_POD_COMPARE int spa_pod_compare_is_valid_choice(uint32_t type, uint32_t size, + const void *val, const void *vals, uint32_t n_vals, uint32_t choice) +{ + switch (choice) { + case SPA_CHOICE_None: + if (spa_pod_compare_value(type, val, vals, size) == 0) + return 1; + return 0; + case SPA_CHOICE_Enum: + { + const void *next = vals; + for (uint32_t i = 1; i < n_vals; i++) { + next = SPA_PTROFF(next, size, void); + if (spa_pod_compare_value(type, val, next, size) == 0) + return 1; + } + return 0; + } + case SPA_CHOICE_Range: + case SPA_CHOICE_Step: + { + void *min = SPA_PTROFF(vals,size,void); + void *max = SPA_PTROFF(min,size,void); + void *step = choice == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; + return spa_pod_compare_is_in_range(type, val, min, max, step, size); + } + case SPA_CHOICE_Flags: + return 1; + } + return 0; +} + /** * \} */ diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index d32d9ad15..3e5c6b37c 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -112,39 +112,6 @@ SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, return 1; } -SPA_API_POD_FILTER int spa_pod_filter_is_step_of(uint32_t type, const void *r1, - const void *r2, uint32_t size SPA_UNUSED) -{ - switch (type) { - case SPA_TYPE_Int: - return *(int32_t *) r1 % *(int32_t *) r2 == 0; - case SPA_TYPE_Long: - return *(int64_t *) r1 % *(int64_t *) r2 == 0; - case SPA_TYPE_Rectangle: - { - const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, - *rec2 = (struct spa_rectangle *) r2; - - return (rec1->width % rec2->width == 0 && - rec1->height % rec2->height == 0); - } - default: - return -ENOTSUP; - } - return 0; -} - -SPA_API_POD_FILTER int spa_pod_filter_is_in_range(uint32_t type, const void *v, - const void *min, const void *max, const void *step, uint32_t size SPA_UNUSED) -{ - if (spa_pod_compare_value(type, v, min, size) < 0 || - spa_pod_compare_value(type, v, max, size) > 0) - return 0; - if (step != NULL) - return spa_pod_filter_is_step_of(type, v, step, size); - return 1; -} - SPA_API_POD_FILTER int spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p1, @@ -222,7 +189,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, } /* copy all values inside the range */ for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { - if ((res = spa_pod_filter_is_in_range(type, a1, min, max, step, size)) < 0) + if ((res = spa_pod_compare_is_in_range(type, a1, min, max, step, size)) < 0) return res; if (res == 0) continue; @@ -242,7 +209,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, /* copy all values inside the range, this will automatically prefer * a valid alt2 value */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { - if ((res = spa_pod_filter_is_in_range(type, a2, min, max, step, size)) < 0) + if ((res = spa_pod_compare_is_in_range(type, a2, min, max, step, size)) < 0) return res; if (res == 0) continue; @@ -273,12 +240,12 @@ spa_pod_filter_prop(struct spa_pod_builder *b, /* prefer alt2 if in new range */ a1 = alt2; - if ((res = spa_pod_filter_is_in_range(type, a1, min1, max1, NULL, size)) < 0) + if ((res = spa_pod_compare_is_in_range(type, a1, min1, max1, NULL, size)) < 0) return res; if (res == 0) { /* try alt1 otherwise */ a1 = alt1; - if ((res = spa_pod_filter_is_in_range(type, a1, min1, max1, NULL, size)) < 0) + if ((res = spa_pod_compare_is_in_range(type, a1, min1, max1, NULL, size)) < 0) return res; /* fall back to new min value then */ if (res == 0) From 8b01fce2bca9cda6d77cbbb3e627ae5cbad1a8ce Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Apr 2025 11:17:57 +0200 Subject: [PATCH 0157/1014] filter: remove unused function --- spa/include/spa/pod/filter.h | 52 ------------------------------------ 1 file changed, 52 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 3e5c6b37c..66f9ae083 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -34,58 +34,6 @@ extern "C" { * \{ */ -SPA_API_POD_FILTER int spa_pod_choice_fix_default(struct spa_pod_choice *choice) -{ - void *val, *alt; - int i, nvals; - uint32_t type, size; - - nvals = SPA_POD_CHOICE_N_VALUES(choice); - type = SPA_POD_CHOICE_VALUE_TYPE(choice); - size = SPA_POD_CHOICE_VALUE_SIZE(choice); - alt = val = SPA_POD_CHOICE_VALUES(choice); - - switch (choice->body.type) { - case SPA_CHOICE_None: - break; - case SPA_CHOICE_Range: - case SPA_CHOICE_Step: - if (nvals > 1) { - alt = SPA_PTROFF(alt, size, void); - if (spa_pod_compare_value(type, val, alt, size) < 0) - memcpy(val, alt, size); - } - if (nvals > 2) { - alt = SPA_PTROFF(alt, size, void); - if (spa_pod_compare_value(type, val, alt, size) > 0) - memcpy(val, alt, size); - } - break; - case SPA_CHOICE_Flags: - case SPA_CHOICE_Enum: - { - void *best = NULL; - - for (i = 1; i < nvals; i++) { - alt = SPA_PTROFF(alt, size, void); - if (spa_pod_compare_value(type, val, alt, size) == 0) { - best = alt; - break; - } - if (best == NULL) - best = alt; - } - if (best) - memcpy(val, best, size); - - if (nvals <= 1) - choice->body.type = SPA_CHOICE_None; - break; - } - } - return 0; -} - SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) { From abfad78fb3ba97fe2eea2828029d20852f897287 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Apr 2025 18:33:48 +0200 Subject: [PATCH 0158/1014] pod: swap inputs for filter when invalid default We usualy want to prefer the filter default value. When this value is not within the valid range/alternatives, swap the logic and prefer the defaults of the other pod. This way we can have a filter with an invalid default that will then use the preference of the other pod but still enforce some bounds. --- spa/include/spa/pod/filter.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 66f9ae083..331999738 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -94,11 +94,18 @@ spa_pod_filter_prop(struct spa_pod_builder *b, if (nc == NULL) nc = &dummy; - /* we should prefer alt2 values but only if they are within the - * range, start with an empty child and we will select a good default + /* start with an empty child and we will select a good default * below */ spa_pod_builder_child(b, size, type); + /* we should prefer alt2 values but only if they are within the + * range. Swap the order otherwise. */ + if (!spa_pod_compare_is_valid_choice(type, size, alt2, alt2, nalt2, p2c)) { + SPA_SWAP(alt2, alt1); + SPA_SWAP(nalt2, nalt1); + SPA_SWAP(p2c, p1c); + } + if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_None) || From 7d6e2a641755690bd6e91205944367cc72cddc7c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 16 Apr 2025 10:21:08 +0200 Subject: [PATCH 0159/1014] impl-link: refactor format negotiation Make a new port_info structure that holds the link port information for input and output. We can use this in some places and remove some redundant code. We can also pass a reference to this port_info as the work-queue object, which makes it more natural to find the associated port info in the various work queue callbacks. Move the private pw_context_find_format to the link implementation, where is it actually used. Rework the format negotiation code to use an array of 2 port_info structures with the two ports to negotiate. The negiotiation will always use the first port_info as higher priority. Make sure a driver port has a lower priority than the other port. We want to negotiate the source/sink to something close that what is provided/requested by the client. The client can always adapt to the driver port format fields by giving a "don't care" value for the format property (either unspecified or with an out of range default value). --- src/pipewire/context.c | 188 ------------- src/pipewire/impl-link.c | 585 ++++++++++++++++++++++++--------------- src/pipewire/private.h | 13 - 3 files changed, 365 insertions(+), 421 deletions(-) diff --git a/src/pipewire/context.c b/src/pipewire/context.c index e81de9126..8f3b12f4f 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -886,194 +886,6 @@ SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this, return 0; } -/** Find a common format between two ports - * - * \param context a context object - * \param output an output port - * \param input an input port - * \param props extra properties - * \param n_format_filters number of format filters - * \param format_filters array of format filters - * \param[out] format the common format between the ports - * \param builder builder to use for processing - * \param[out] error an error when something is wrong - * \return a common format of NULL on error - * - * Find a common format between the given ports. The format will - * be restricted to a subset given with the format filters. - */ -int pw_context_find_format(struct pw_context *context, - struct pw_impl_port *output, - uint32_t output_mix, - struct pw_impl_port *input, - uint32_t input_mix, - struct pw_properties *props, - uint32_t n_format_filters, - struct spa_pod **format_filters, - struct spa_pod **format, - struct spa_pod_builder *builder, - char **error) -{ - uint32_t out_state, in_state; - int res; - uint32_t iidx = 0, oidx = 0; - struct spa_pod_builder fb = { 0 }; - uint8_t fbuf[4096]; - struct spa_pod *filter; - struct spa_node *in_node, *out_node; - uint32_t in_port, out_port; - - out_state = output->state; - in_state = input->state; - - if (output_mix == SPA_ID_INVALID) { - out_node = output->node->node; - out_port = output->port_id; - } else { - out_node = output->mix; - out_port = output_mix; - } - if (input_mix == SPA_ID_INVALID) { - in_node = input->node->node; - in_port = input->port_id; - } else { - in_node = input->mix; - in_port = input_mix; - } - - pw_log_debug("%p: finding best format %d %d", context, out_state, in_state); - - /* when a port is configured but the node is idle, we can reconfigure with a different format */ - if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) - out_state = PW_IMPL_PORT_STATE_CONFIGURE; - if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) - in_state = PW_IMPL_PORT_STATE_CONFIGURE; - - pw_log_debug("%p: states %d %d", context, out_state, in_state); - - if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state > PW_IMPL_PORT_STATE_CONFIGURE) { - /* only input needs format */ - spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); - if ((res = spa_node_port_enum_params_sync(out_node, - output->direction, out_port, - SPA_PARAM_Format, &oidx, - NULL, &filter, &fb)) != 1) { - if (res < 0) - *error = spa_aprintf("error get output format: %s", spa_strerror(res)); - else - *error = spa_aprintf("no output formats"); - goto error; - } - pw_log_debug("%p: Got output format:", context); - pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); - - if ((res = spa_node_port_enum_params_sync(in_node, - input->direction, in_port, - SPA_PARAM_EnumFormat, &iidx, - filter, format, builder)) <= 0) { - if (res == -ENOENT || res == 0) { - pw_log_debug("%p: no input format filter, using output format: %s", - context, spa_strerror(res)); - - uint32_t offset = builder->state.offset; - res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); - if (res < 0) { - *error = spa_aprintf("failed to add pod"); - goto error; - } - - *format = spa_pod_builder_deref(builder, offset); - } else { - *error = spa_aprintf("error input enum formats: %s", spa_strerror(res)); - goto error; - } - } - } else if (out_state >= PW_IMPL_PORT_STATE_CONFIGURE && in_state > PW_IMPL_PORT_STATE_CONFIGURE) { - /* only output needs format */ - spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); - if ((res = spa_node_port_enum_params_sync(in_node, - input->direction, in_port, - SPA_PARAM_Format, &iidx, - NULL, &filter, &fb)) != 1) { - if (res < 0) - *error = spa_aprintf("error get input format: %s", spa_strerror(res)); - else - *error = spa_aprintf("no input format"); - goto error; - } - pw_log_debug("%p: Got input format:", context); - pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); - - if ((res = spa_node_port_enum_params_sync(out_node, - output->direction, out_port, - SPA_PARAM_EnumFormat, &oidx, - filter, format, builder)) <= 0) { - if (res == -ENOENT || res == 0) { - pw_log_debug("%p: no output format filter, using input format: %s", - context, spa_strerror(res)); - - uint32_t offset = builder->state.offset; - res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); - if (res < 0) { - *error = spa_aprintf("failed to add pod"); - goto error; - } - - *format = spa_pod_builder_deref(builder, offset); - } else { - *error = spa_aprintf("error output enum formats: %s", spa_strerror(res)); - goto error; - } - } - } else if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state == PW_IMPL_PORT_STATE_CONFIGURE) { - again: - /* both ports need a format */ - pw_log_debug("%p: do enum input %d", context, iidx); - spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); - if ((res = spa_node_port_enum_params_sync(in_node, - input->direction, in_port, - SPA_PARAM_EnumFormat, &iidx, - NULL, &filter, &fb)) != 1) { - if (res == -ENOENT) { - pw_log_debug("%p: no input filter", context); - filter = NULL; - } else { - if (res < 0) - *error = spa_aprintf("error input enum formats: %s", spa_strerror(res)); - else - *error = spa_aprintf("no more input formats"); - goto error; - } - } - pw_log_debug("%p: enum output %d with filter: %p", context, oidx, filter); - pw_log_format(SPA_LOG_LEVEL_DEBUG, filter); - - if ((res = spa_node_port_enum_params_sync(out_node, - output->direction, out_port, - SPA_PARAM_EnumFormat, &oidx, - filter, format, builder)) != 1) { - if (res == 0 && filter != NULL) { - oidx = 0; - goto again; - } - *error = spa_aprintf("error output enum formats: %s", spa_strerror(res)); - goto error; - } - - pw_log_debug("%p: Got filtered:", context); - pw_log_format(SPA_LOG_LEVEL_DEBUG, *format); - } else { - res = -EBADF; - *error = spa_aprintf("error bad node state"); - goto error; - } - return res; -error: - if (res == 0) - res = -EINVAL; - return res; -} - static int ensure_state(struct pw_impl_node *node, bool running) { enum pw_node_state state = node->info.state; diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index a9de783e3..ce9a8689f 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -24,6 +24,18 @@ PW_LOG_TOPIC_EXTERN(log_link); #define pw_link_resource_info(r,...) pw_resource_call(r,struct pw_link_events,info,0,__VA_ARGS__) +struct port_info { + uint32_t busy_id; + int pending_seq; + int result; + struct spa_hook port_listener; + struct spa_hook node_listener; + struct spa_hook global_listener; + struct pw_impl_port *port; + struct pw_impl_node *node; + struct pw_impl_port_mix *mix; +}; + /** \cond */ struct impl { struct pw_impl_link this; @@ -32,26 +44,14 @@ struct impl { struct pw_work_queue *work; - uint32_t output_busy_id; - uint32_t input_busy_id; - int output_pending_seq; - int input_pending_seq; - int output_result; - int input_result; + struct port_info input; + struct port_info output; struct spa_pod *format_filter; struct pw_properties *properties; - struct spa_hook input_port_listener; - struct spa_hook input_node_listener; - struct spa_hook input_global_listener; - struct spa_hook output_port_listener; - struct spa_hook output_node_listener; - struct spa_hook output_global_listener; - struct spa_io_buffers io[2]; - struct pw_impl_node *inode, *onode; bool async; }; @@ -73,35 +73,18 @@ static void info_changed(struct pw_impl_link *link) link->info.change_mask = 0; } -static inline int input_set_busy_id(struct pw_impl_link *link, uint32_t id, int pending_seq) +static inline int port_set_busy_id(struct pw_impl_link *link, struct port_info *info, uint32_t id, int pending_seq) { - struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); - int res = impl->input_result; - if (impl->input_busy_id != SPA_ID_INVALID) - link->input->busy_count--; + int res = info->result; + if (info->busy_id != SPA_ID_INVALID) + info->port->busy_count--; if (id != SPA_ID_INVALID) - link->input->busy_count++; - impl->input_busy_id = id; - impl->input_pending_seq = SPA_RESULT_ASYNC_SEQ(pending_seq); - impl->input_result = 0; - if (link->input->busy_count < 0) - pw_log_error("%s: invalid busy count:%d", link->name, link->input->busy_count); - return res; -} - -static inline int output_set_busy_id(struct pw_impl_link *link, uint32_t id, int pending_seq) -{ - struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); - int res = impl->output_result; - if (impl->output_busy_id != SPA_ID_INVALID) - link->output->busy_count--; - if (id != SPA_ID_INVALID) - link->output->busy_count++; - impl->output_busy_id = id; - impl->output_pending_seq = SPA_RESULT_ASYNC_SEQ(pending_seq); - impl->output_result = 0; - if (link->output->busy_count < 0) - pw_log_error("%s: invalid busy count:%d", link->name, link->output->busy_count); + info->port->busy_count++; + info->busy_id = id; + info->pending_seq = SPA_RESULT_ASYNC_SEQ(pending_seq); + info->result = 0; + if (info->port->busy_count < 0) + pw_log_error("%s: invalid busy count:%d", link->name, info->port->busy_count); return res; } @@ -160,30 +143,23 @@ static void link_update_state(struct pw_impl_link *link, enum pw_link_state stat link->prepared = false; link->preparing = false; - output_set_busy_id(link, SPA_ID_INVALID, SPA_ID_INVALID); - pw_work_queue_cancel(impl->work, &link->output_link, SPA_ID_INVALID); + port_set_busy_id(link, &impl->output, SPA_ID_INVALID, SPA_ID_INVALID); + pw_work_queue_cancel(impl->work, &impl->output, SPA_ID_INVALID); - input_set_busy_id(link, SPA_ID_INVALID, SPA_ID_INVALID); - pw_work_queue_cancel(impl->work, &link->input_link, SPA_ID_INVALID); + port_set_busy_id(link, &impl->input, SPA_ID_INVALID, SPA_ID_INVALID); + pw_work_queue_cancel(impl->work, &impl->input, SPA_ID_INVALID); } } static void complete_ready(void *obj, void *data, int res, uint32_t id) { - struct pw_impl_port *port; + struct port_info *info = obj; + struct pw_impl_port *port = info->port; struct pw_impl_link *this = data; - struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); - - if (obj == &this->input_link) - port = this->input; - else - port = this->output; if (id != SPA_ID_INVALID) { - if (id == impl->input_busy_id) - res = input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); - else if (id == impl->output_busy_id) - res = output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + if (id == info->busy_id) + res = port_set_busy_id(this, info, SPA_ID_INVALID, SPA_ID_INVALID); else return; } @@ -205,24 +181,14 @@ static void complete_ready(void *obj, void *data, int res, uint32_t id) static void complete_paused(void *obj, void *data, int res, uint32_t id) { - struct pw_impl_port *port; + struct port_info *info = obj; + struct pw_impl_port *port = info->port; + struct pw_impl_port_mix *mix = info->mix; struct pw_impl_link *this = data; - struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); - struct pw_impl_port_mix *mix; - - if (obj == &this->input_link) { - port = this->input; - mix = &this->rt.in_mix; - } else { - port = this->output; - mix = &this->rt.out_mix; - } if (id != SPA_ID_INVALID) { - if (id == impl->input_busy_id) - res = input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); - else if (id == impl->output_busy_id) - res = output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + if (id == info->busy_id) + res = port_set_busy_id(this, info, SPA_ID_INVALID, SPA_ID_INVALID); else return; } @@ -246,18 +212,181 @@ static void complete_paused(void *obj, void *data, int res, uint32_t id) static void complete_sync(void *obj, void *data, int res, uint32_t id) { - struct pw_impl_port *port; + struct port_info *info = obj; + struct pw_impl_port *port = info->port; struct pw_impl_link *this = data; - if (obj == &this->input_link) - port = this->input; - else - port = this->output; - pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, port->state, spa_strerror(res)); } +/* find a common format. info[0] has the higher priority. + * Either the format contains a valid common format or error is set. */ +static int link_find_format(struct pw_impl_link *this, + struct port_info *info[2], + uint32_t port_id[2], + struct spa_pod **format, + struct spa_pod_builder *builder, + char **error) +{ + int res; + uint32_t state[2]; + uint32_t idx[2] = { 0, 0 }; + struct spa_pod_builder fb = { 0 }; + uint8_t fbuf[4096]; + struct spa_pod *filter; + struct spa_node *node[2]; + const char *dir[2]; + + state[0] = info[0]->port->state; + state[1] = info[1]->port->state; + node[0] = info[0]->node->node; + node[1] = info[1]->node->node; + port_id[0] = info[0]->port->port_id; + port_id[1] = info[1]->port->port_id; + dir[0] = pw_direction_as_string(info[0]->port->direction); + dir[1] = pw_direction_as_string(info[1]->port->direction); + + pw_log_debug("%p: finding best format %d %d", this, state[0], state[1]); + + /* when a port is configured but the node is idle, we can reconfigure with a different format */ + if (state[1] > PW_IMPL_PORT_STATE_CONFIGURE && info[1]->node->info.state == PW_NODE_STATE_IDLE) + state[1] = PW_IMPL_PORT_STATE_CONFIGURE; + if (state[0] > PW_IMPL_PORT_STATE_CONFIGURE && info[0]->node->info.state == PW_NODE_STATE_IDLE) + state[0] = PW_IMPL_PORT_STATE_CONFIGURE; + + pw_log_debug("%p: states %d %d", this, state[0], state[1]); + + if (state[0] == PW_IMPL_PORT_STATE_CONFIGURE && state[1] > PW_IMPL_PORT_STATE_CONFIGURE) { + /* only port 0 needs format, take format from port 1 and filter */ + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(node[1], + info[1]->port->direction, port_id[1], + SPA_PARAM_Format, &idx[1], + NULL, &filter, &fb)) != 1) { + if (res < 0) + *error = spa_aprintf("error get %s format: %s", dir[1], + spa_strerror(res)); + else + *error = spa_aprintf("no %s formats", dir[1]); + goto error; + } + pw_log_debug("%p: Got %s format:", this, dir[1]); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(node[0], + info[0]->port->direction, port_id[0], + SPA_PARAM_EnumFormat, &idx[0], + filter, format, builder)) <= 0) { + if (res == -ENOENT || res == 0) { + pw_log_debug("%p: no %s format filter, using %s format: %s", + this, dir[0], dir[1], spa_strerror(res)); + + uint32_t offset = builder->state.offset; + res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); + if (res < 0) { + *error = spa_aprintf("failed to add pod"); + goto error; + } + + *format = spa_pod_builder_deref(builder, offset); + } else { + *error = spa_aprintf("error %s enum formats: %s", dir[0], + spa_strerror(res)); + goto error; + } + } + } else if (state[1] >= PW_IMPL_PORT_STATE_CONFIGURE && state[0] > PW_IMPL_PORT_STATE_CONFIGURE) { + /* only port 1 needs format, take and filter format from port 0 */ + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(node[0], + info[0]->port->direction, port_id[0], + SPA_PARAM_Format, &idx[0], + NULL, &filter, &fb)) != 1) { + if (res < 0) + *error = spa_aprintf("error get %s format: %s", dir[0], + spa_strerror(res)); + else + *error = spa_aprintf("no %s format", dir[0]); + goto error; + } + pw_log_debug("%p: Got %s format:", this, dir[0]); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(node[1], + info[1]->port->direction, port_id[1], + SPA_PARAM_EnumFormat, &idx[1], + filter, format, builder)) <= 0) { + if (res == -ENOENT || res == 0) { + pw_log_debug("%p: no %s format filter, using %s format: %s", + this, dir[1], dir[0], spa_strerror(res)); + + uint32_t offset = builder->state.offset; + res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); + if (res < 0) { + *error = spa_aprintf("failed to add pod"); + goto error; + } + + *format = spa_pod_builder_deref(builder, offset); + } else { + *error = spa_aprintf("error %s enum formats: %s", dir[1], + spa_strerror(res)); + goto error; + } + } + } else if (state[0] == PW_IMPL_PORT_STATE_CONFIGURE && state[1] == PW_IMPL_PORT_STATE_CONFIGURE) { + again: + /* both ports need a format, we start with a format from port 0 and use that + * as a filter for port 1. Because the filter has higher priority, its + * defaults will be prefered. */ + pw_log_debug("%p: do enum %s %d", this, dir[0], idx[0]); + spa_pod_builder_init(&fb, fbuf, sizeof(fbuf)); + if ((res = spa_node_port_enum_params_sync(node[0], + info[0]->port->direction, port_id[0], + SPA_PARAM_EnumFormat, &idx[0], + NULL, &filter, &fb)) != 1) { + if (res == -ENOENT) { + pw_log_debug("%p: no %s filter", this, dir[0]); + filter = NULL; + } else { + if (res < 0) + *error = spa_aprintf("error %s enum formats: %s", dir[0], + spa_strerror(res)); + else + *error = spa_aprintf("no more %s formats", dir[0]); + goto error; + } + } + pw_log_debug("%p: enum %s %d with filter: %p", this, dir[1], idx[1], filter); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, filter); + + if ((res = spa_node_port_enum_params_sync(node[1], + info[1]->port->direction, port_id[1], + SPA_PARAM_EnumFormat, &idx[1], + filter, format, builder)) != 1) { + if (res == 0 && filter != NULL) { + idx[1] = 0; + goto again; + } + *error = spa_aprintf("error %s enum formats: %s", dir[1], spa_strerror(res)); + goto error; + } + + pw_log_debug("%p: Got filtered:", this); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, *format); + } else { + res = -EBADF; + *error = spa_aprintf("error bad node state"); + goto error; + } + return res; +error: + if (res == 0) + res = -EINVAL; + return res; +} + static int do_negotiate(struct pw_impl_link *this) { struct pw_context *context = this->context; @@ -265,65 +394,71 @@ static int do_negotiate(struct pw_impl_link *this) int res = -EIO, res2; struct spa_pod *format = NULL, *current; char *error = NULL; - bool changed = true; - struct pw_impl_port *input, *output; + bool changed = false; + struct port_info *info[2]; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t index, busy_id; - uint32_t in_state, out_state; - struct spa_node *in_node, *out_node; - uint32_t in_port, out_port; + uint32_t state[2]; + struct spa_node *node[2]; + uint32_t port_id[2]; + const char *dir[2]; if (this->info.state >= PW_LINK_STATE_NEGOTIATING) return 0; - input = this->input; - output = this->output; + /* driver nodes have lower priority for selecting the format. + * Higher priority nodes go into info[0] */ + if (this->output->node->driver) { + info[0] = &impl->input; + info[1] = &impl->output; + } else { + info[0] = &impl->output; + info[1] = &impl->input; + } + state[0] = info[0]->port->state; + state[1] = info[1]->port->state; - in_state = input->state; - out_state = output->state; + dir[0] = pw_direction_as_string(info[0]->port->direction), + dir[1] = pw_direction_as_string(info[1]->port->direction), - pw_log_debug("%p: in_state:%d out_state:%d", this, in_state, out_state); + pw_log_info("%p: %s:%d -> %s:%d", this, dir[0], state[0], dir[1], state[1]); - if (in_state != PW_IMPL_PORT_STATE_CONFIGURE && out_state != PW_IMPL_PORT_STATE_CONFIGURE) + if (state[0] != PW_IMPL_PORT_STATE_CONFIGURE && state[1] != PW_IMPL_PORT_STATE_CONFIGURE) return 0; link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL); - input = this->input; - output = this->output; #if 0 - in_node = input->mix; - in_port = this->rt.in_mix.port.port_id; - out_node = output->mix; - out_port = this->rt.out_mix.port.port_id; + node[0] = info[0]->port->mix; + port_id[0] = this->rt.in_mix.port.port_id; + node[1] = info[1]->port->mix; + port_id[1] = this->rt.out_mix.port.port_id; #else - in_node = input->node->node; - in_port = input->port_id; - out_node = output->node->node; - out_port = output->port_id; + node[0] = info[0]->node->node; + port_id[0] = info[0]->port->port_id; + node[1] = info[1]->node->node; + port_id[1] = info[1]->port->port_id; #endif /* find a common format for the ports */ - if ((res = pw_context_find_format(context, - output, SPA_ID_INVALID, - input, SPA_ID_INVALID, - NULL, 0, NULL, - &format, &b, &error)) < 0) { + if ((res = link_find_format(this, info, port_id, &format, &b, &error)) < 0) { format = NULL; goto error; } format = spa_pod_copy(format); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); spa_pod_fixate(format); + pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); spa_pod_builder_init(&b, buffer, sizeof(buffer)); - /* if output port had format and is idle, check if it changed. If so, renegotiate */ - if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) { + /* if port 1 had format and is idle, check if it changed. If so, renegotiate */ + if (state[1] > PW_IMPL_PORT_STATE_CONFIGURE && info[1]->node->info.state == PW_NODE_STATE_IDLE) { index = 0; - res = spa_node_port_enum_params_sync(out_node, - output->direction, out_port, + res = spa_node_port_enum_params_sync(node[1], + info[1]->port->direction, port_id[1], SPA_PARAM_Format, &index, NULL, ¤t, &b); switch (res) { @@ -337,27 +472,29 @@ static int do_negotiate(struct pw_impl_link *this) res = -EBADF; SPA_FALLTHROUGH default: - error = spa_aprintf("error get output format: %s", spa_strerror(res)); + error = spa_aprintf("error get %s format: %s", + pw_direction_as_string(info[1]->port->direction), + spa_strerror(res)); goto error; } if (current == NULL || spa_pod_compare(current, format) != 0) { - pw_log_debug("%p: output format change, renegotiate", this); + pw_log_debug("%p: %s format change, renegotiate", this, + pw_direction_as_string(info[1]->port->direction)); if (current) pw_log_pod(SPA_LOG_LEVEL_DEBUG, current); pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); - pw_impl_node_set_state(output->node, PW_NODE_STATE_SUSPENDED); - out_state = PW_IMPL_PORT_STATE_CONFIGURE; + pw_impl_node_set_state(info[1]->node, PW_NODE_STATE_SUSPENDED); + state[1] = PW_IMPL_PORT_STATE_CONFIGURE; } else { pw_log_debug("%p: format was already set", this); - changed = false; } } - /* if input port had format and is idle, check if it changed. If so, renegotiate */ - if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) { + /* if port 0 had format and is idle, check if it changed. If so, renegotiate */ + if (state[0] > PW_IMPL_PORT_STATE_CONFIGURE && info[0]->node->info.state == PW_NODE_STATE_IDLE) { index = 0; - res = spa_node_port_enum_params_sync(in_node, - input->direction, in_port, + res = spa_node_port_enum_params_sync(node[0], + info[0]->port->direction, port_id[0], SPA_PARAM_Format, &index, NULL, ¤t, &b); switch (res) { @@ -371,20 +508,22 @@ static int do_negotiate(struct pw_impl_link *this) res = -EBADF; SPA_FALLTHROUGH default: - error = spa_aprintf("error get input format: %s", spa_strerror(res)); + error = spa_aprintf("error get %s format: %s", + pw_direction_as_string(info[0]->port->direction), + spa_strerror(res)); goto error; } if (current == NULL || spa_pod_compare(current, format) != 0) { - pw_log_debug("%p: input format change, renegotiate", this); + pw_log_debug("%p: %s format change, renegotiate", this, + pw_direction_as_string(info[0]->port->direction)); if (current) pw_log_pod(SPA_LOG_LEVEL_DEBUG, current); pw_log_pod(SPA_LOG_LEVEL_DEBUG, format); - pw_impl_node_set_state(input->node, PW_NODE_STATE_SUSPENDED); - in_state = PW_IMPL_PORT_STATE_CONFIGURE; + pw_impl_node_set_state(info[0]->node, PW_NODE_STATE_SUSPENDED); + state[0] = PW_IMPL_PORT_STATE_CONFIGURE; } else { pw_log_debug("%p: format was already set", this); - changed = false; } } @@ -393,47 +532,51 @@ static int do_negotiate(struct pw_impl_link *this) format, spa_pod_is_fixated(format)); pw_log_pod(SPA_LOG_LEVEL_INFO, format); - if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) { - pw_log_debug("%p: doing set format on output", this); - if ((res = pw_impl_port_set_param(output, + if (state[1] == PW_IMPL_PORT_STATE_CONFIGURE) { + pw_log_debug("%p: doing set format on %s", this, dir[1]); + if ((res = pw_impl_port_set_param(info[1]->port, SPA_PARAM_Format, 0, format)) < 0) { - error = spa_aprintf("error set output format: %d (%s)", res, spa_strerror(res)); - pw_log_error("tried to set output format:"); + error = spa_aprintf("error set %s format: %d (%s)", dir[1], + res, spa_strerror(res)); + pw_log_error("tried to set %s format:", dir[1]); pw_log_pod(SPA_LOG_LEVEL_ERROR, format); goto error; } + pw_log_debug("%s set format: %d", dir[1], res); if (SPA_RESULT_IS_ASYNC(res)) { - pw_log_debug("output set format %d", res); - busy_id = pw_work_queue_add(impl->work, &this->output_link, - spa_node_sync(output->node->node, res), + busy_id = pw_work_queue_add(impl->work, info[1], + spa_node_sync(info[1]->node->node, res), complete_ready, this); - output_set_busy_id(this, busy_id, res); + port_set_busy_id(this, info[1], busy_id, res); } else { - complete_ready(&this->output_link, this, res, SPA_ID_INVALID); + complete_ready(info[1], this, res, SPA_ID_INVALID); } + changed = true; } - if (in_state == PW_IMPL_PORT_STATE_CONFIGURE) { - pw_log_debug("%p: doing set format on input", this); - if ((res2 = pw_impl_port_set_param(input, + if (state[0] == PW_IMPL_PORT_STATE_CONFIGURE) { + pw_log_debug("%p: doing set format on %s", this, dir[0]); + if ((res2 = pw_impl_port_set_param(info[0]->port, SPA_PARAM_Format, 0, format)) < 0) { - error = spa_aprintf("error set input format: %d (%s)", res2, spa_strerror(res2)); - pw_log_error("tried to set input format:"); + error = spa_aprintf("error set %s format: %d (%s)", dir[0], + res2, spa_strerror(res2)); + pw_log_error("tried to set %s format:", dir[0]); pw_log_pod(SPA_LOG_LEVEL_ERROR, format); goto error; } + pw_log_debug("%s set format: %d", dir[0], res2); if (SPA_RESULT_IS_ASYNC(res2)) { - pw_log_debug("input set format %d", res2); - busy_id = pw_work_queue_add(impl->work, &this->input_link, - spa_node_sync(input->node->node, res2), + busy_id = pw_work_queue_add(impl->work, info[0], + spa_node_sync(info[0]->node->node, res2), complete_ready, this); - input_set_busy_id(this, busy_id, res2); + port_set_busy_id(this, info[0], busy_id, res2); if (res == 0) res = res2; } else { - complete_ready(&this->input_link, this, res2, SPA_ID_INVALID); + complete_ready(info[0], this, res2, SPA_ID_INVALID); } + changed = true; } free(this->info.format); @@ -446,21 +589,23 @@ static int do_negotiate(struct pw_impl_link *this) return res; error: - pw_context_debug_port_params(context, in_node, - input->direction, in_port, + pw_context_debug_port_params(context, node[0], + info[0]->port->direction, port_id[0], SPA_PARAM_EnumFormat, res, "input format (%s)", error); - pw_context_debug_port_params(context, out_node, - output->direction, out_port, + pw_context_debug_port_params(context, node[1], + info[1]->port->direction, port_id[1], SPA_PARAM_EnumFormat, res, "output format (%s)", error); link_update_state(this, PW_LINK_STATE_ERROR, res, error); free(format); return res; } -static int port_set_io(struct pw_impl_link *this, struct pw_impl_port *port, uint32_t id, - void *data, size_t size, struct pw_impl_port_mix *mix) +static int port_set_io(struct pw_impl_link *this, struct port_info *info, uint32_t id, + void *data, size_t size) { int res = 0; + struct pw_impl_port *port = info->port; + struct pw_impl_port_mix *mix = info->mix; pw_log_debug("%p: %s port %p %d.%d set io: %d %p %zd", this, pw_direction_as_string(port->direction), @@ -590,14 +735,14 @@ static int do_allocation(struct pw_impl_link *this) goto error_clear; } if (SPA_RESULT_IS_ASYNC(res)) { - busy_id = pw_work_queue_add(impl->work, &this->output_link, + busy_id = pw_work_queue_add(impl->work, &impl->output, spa_node_sync(output->node->node, res), complete_paused, this); - output_set_busy_id(this, busy_id, res); + port_set_busy_id(this, &impl->output, busy_id, res); if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) return 0; } else { - complete_paused(&this->output_link, this, res, SPA_ID_INVALID); + complete_paused(&impl->output, this, res, SPA_ID_INVALID); } } @@ -613,12 +758,12 @@ static int do_allocation(struct pw_impl_link *this) } if (SPA_RESULT_IS_ASYNC(res)) { - busy_id = pw_work_queue_add(impl->work, &this->input_link, + busy_id = pw_work_queue_add(impl->work, &impl->input, spa_node_sync(input->node->node, res), complete_paused, this); - input_set_busy_id(this, busy_id, res); + port_set_busy_id(this, &impl->input, busy_id, res); } else { - complete_paused(&this->input_link, this, res, SPA_ID_INVALID); + complete_paused(&impl->input, this, res, SPA_ID_INVALID); } return 0; @@ -639,7 +784,7 @@ int pw_impl_link_activate(struct pw_impl_link *this) pw_link_state_as_string(this->info.state)); if (this->destroyed || impl->activated || !this->prepared || - !impl->inode->runnable || !impl->onode->runnable) + !impl->input.node->runnable || !impl->output.node->runnable) return 0; if (impl->async) { @@ -650,11 +795,9 @@ int pw_impl_link_activate(struct pw_impl_link *this) io_size = sizeof(struct spa_io_buffers); } - if ((res = port_set_io(this, this->input, io_type, this->io, - io_size, &this->rt.in_mix)) < 0) + if ((res = port_set_io(this, &impl->input, io_type, this->io, io_size)) < 0) goto error; - if ((res = port_set_io(this, this->output, io_type, this->io, - io_size, &this->rt.out_mix)) < 0) + if ((res = port_set_io(this, &impl->output, io_type, this->io, io_size)) < 0) goto error_clean; impl->activated = true; @@ -664,7 +807,7 @@ int pw_impl_link_activate(struct pw_impl_link *this) return 0; error_clean: - port_set_io(this, this->input, io_type, NULL, 0, &this->rt.in_mix); + port_set_io(this, &impl->input, io_type, NULL, 0); error: pw_log_error("%p: can't activate link: %s", this, spa_strerror(res)); return res; @@ -719,13 +862,13 @@ static void check_states(void *obj, void *user_data, int res, uint32_t id) if (output->busy_count > 0) { pw_log_debug("%p: output port %p was busy %d", this, output, output->busy_count); res = spa_node_sync(output->node->node, 0); - pw_work_queue_add(impl->work, &this->output_link, res, complete_sync, this); + pw_work_queue_add(impl->work, &impl->output, res, complete_sync, this); goto exit; } else if (input->busy_count > 0) { pw_log_debug("%p: input port %p was busy %d", this, input, input->busy_count); res = spa_node_sync(input->node->node, 0); - pw_work_queue_add(impl->work, &this->input_link, res, complete_sync, this); + pw_work_queue_add(impl->work, &impl->input, res, complete_sync, this); goto exit; } @@ -745,61 +888,65 @@ exit: this, -EBUSY, (pw_work_func_t) check_states, this); } -static void input_remove(struct pw_impl_link *this, struct pw_impl_port *port) +static void input_remove(struct pw_impl_link *this) { struct impl *impl = (struct impl *) this; - struct pw_impl_port_mix *mix = &this->rt.in_mix; + struct port_info *info = &impl->input; + struct pw_impl_port_mix *mix = info->mix; + struct pw_impl_port *port = info->port; int res; pw_log_debug("%p: remove input port %p", this, port); - input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + port_set_busy_id(this, info, SPA_ID_INVALID, SPA_ID_INVALID); - spa_hook_remove(&impl->input_port_listener); - spa_hook_remove(&impl->input_node_listener); - spa_hook_remove(&impl->input_global_listener); + spa_hook_remove(&info->port_listener); + spa_hook_remove(&info->node_listener); + spa_hook_remove(&info->global_listener); spa_list_remove(&this->input_link); - pw_impl_port_emit_link_removed(this->input, this); + pw_impl_port_emit_link_removed(port, this); - pw_impl_port_recalc_latency(this->input); - pw_impl_port_recalc_tag(this->input); + pw_impl_port_recalc_latency(port); + pw_impl_port_recalc_tag(port); - if ((res = port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0, mix)) < 0) + if ((res = port_set_io(this, info, SPA_IO_Buffers, NULL, 0)) < 0) pw_log_warn("%p: port %p set_io error %s", this, port, spa_strerror(res)); if ((res = pw_impl_port_use_buffers(port, mix, 0, NULL, 0)) < 0) pw_log_warn("%p: port %p clear error %s", this, port, spa_strerror(res)); pw_impl_port_release_mix(port, mix); - pw_work_queue_cancel(impl->work, &this->input_link, SPA_ID_INVALID); + pw_work_queue_cancel(impl->work, info, SPA_ID_INVALID); this->input = NULL; } -static void output_remove(struct pw_impl_link *this, struct pw_impl_port *port) +static void output_remove(struct pw_impl_link *this) { struct impl *impl = (struct impl *) this; - struct pw_impl_port_mix *mix = &this->rt.out_mix; + struct port_info *info = &impl->output; + struct pw_impl_port_mix *mix = info->mix; + struct pw_impl_port *port = info->port; pw_log_debug("%p: remove output port %p", this, port); - output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + port_set_busy_id(this, info, SPA_ID_INVALID, SPA_ID_INVALID); - spa_hook_remove(&impl->output_port_listener); - spa_hook_remove(&impl->output_node_listener); - spa_hook_remove(&impl->output_global_listener); + spa_hook_remove(&info->port_listener); + spa_hook_remove(&info->node_listener); + spa_hook_remove(&info->global_listener); spa_list_remove(&this->output_link); - pw_impl_port_emit_link_removed(this->output, this); + pw_impl_port_emit_link_removed(port, this); - pw_impl_port_recalc_latency(this->output); - pw_impl_port_recalc_tag(this->output); + pw_impl_port_recalc_latency(port); + pw_impl_port_recalc_tag(port); /* we don't clear output buffers when the link goes away. They will get * cleared when the node goes to suspend */ pw_impl_port_release_mix(port, mix); - pw_work_queue_cancel(impl->work, &this->output_link, SPA_ID_INVALID); + pw_work_queue_cancel(impl->work, info, SPA_ID_INVALID); this->output = NULL; } @@ -809,9 +956,9 @@ int pw_impl_link_prepare(struct pw_impl_link *this) pw_log_debug("%p: prepared:%d preparing:%d in_active:%d out_active:%d passive:%u", this, this->prepared, this->preparing, - impl->inode->active, impl->onode->active, this->passive); + impl->input.node->active, impl->output.node->active, this->passive); - if (!impl->inode->active || !impl->onode->active) + if (!impl->input.node->active || !impl->output.node->active) return 0; if (this->destroyed || this->preparing || this->prepared) @@ -834,10 +981,8 @@ int pw_impl_link_deactivate(struct pw_impl_link *this) if (!impl->activated) return 0; - port_set_io(this, this->output, SPA_IO_Buffers, NULL, 0, - &this->rt.out_mix); - port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0, - &this->rt.in_mix); + port_set_io(this, &impl->output, SPA_IO_Buffers, NULL, 0); + port_set_io(this, &impl->input, SPA_IO_Buffers, NULL, 0); impl->activated = false; pw_log_info("(%s) deactivated", this->name); @@ -1010,6 +1155,16 @@ static const struct pw_impl_port_events output_port_events = { static void node_result(struct impl *impl, void *obj, int seq, int res, uint32_t type, const void *result) { + struct port_info *info = obj; + struct pw_impl_port *port = info->port; + + pw_log_trace("%p: %s port %p result seq:%d %d res:%d type:%u", + impl, pw_direction_as_string(port->direction), + port, seq, SPA_RESULT_ASYNC_SEQ(seq), res, type); + + if (type == SPA_RESULT_TYPE_NODE_ERROR && info->pending_seq == seq) + info->result = res; + if (SPA_RESULT_IS_ASYNC(seq)) pw_work_queue_complete(impl->work, obj, SPA_RESULT_ASYNC_SEQ(seq), res); } @@ -1017,27 +1172,13 @@ static void node_result(struct impl *impl, void *obj, static void input_node_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *impl = data; - struct pw_impl_port *port = impl->this.input; - pw_log_trace("%p: input port %p result seq:%d %d res:%d type:%u", - impl, port, seq, SPA_RESULT_ASYNC_SEQ(seq), res, type); - - if (type == SPA_RESULT_TYPE_NODE_ERROR && impl->input_pending_seq == seq) - impl->input_result = res; - - node_result(impl, &impl->this.input_link, seq, res, type, result); + node_result(impl, &impl->input, seq, res, type, result); } static void output_node_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *impl = data; - struct pw_impl_port *port = impl->this.output; - pw_log_trace("%p: output port %p result seq:%d %d res:%d type:%u", - impl, port, seq, SPA_RESULT_ASYNC_SEQ(seq), res, type); - - if (type == SPA_RESULT_TYPE_NODE_ERROR && impl->output_pending_seq == seq) - impl->output_result = res; - - node_result(impl, &impl->this.output_link, seq, res, type, result); + node_result(impl, &impl->output, seq, res, type, result); } static void node_active_changed(void *data, bool active) @@ -1312,8 +1453,8 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, if (impl == NULL) goto error_no_mem; - impl->input_busy_id = SPA_ID_INVALID; - impl->output_busy_id = SPA_ID_INVALID; + impl->input.busy_id = SPA_ID_INVALID; + impl->output.busy_id = SPA_ID_INVALID; this = &impl->this; this->feedback = pw_impl_node_can_reach(input_node, output_node, 0); @@ -1363,12 +1504,12 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, if ((res = pw_impl_port_init_mix(input, &this->rt.in_mix)) < 0) goto error_input_mix; - pw_impl_port_add_listener(input, &impl->input_port_listener, &input_port_events, impl); - pw_impl_node_add_listener(input_node, &impl->input_node_listener, &input_node_events, impl); - pw_global_add_listener(input->global, &impl->input_global_listener, &input_global_events, impl); - pw_impl_port_add_listener(output, &impl->output_port_listener, &output_port_events, impl); - pw_impl_node_add_listener(output_node, &impl->output_node_listener, &output_node_events, impl); - pw_global_add_listener(output->global, &impl->output_global_listener, &output_global_events, impl); + pw_impl_port_add_listener(input, &impl->input.port_listener, &input_port_events, impl); + pw_impl_node_add_listener(input_node, &impl->input.node_listener, &input_node_events, impl); + pw_global_add_listener(input->global, &impl->input.global_listener, &input_global_events, impl); + pw_impl_port_add_listener(output, &impl->output.port_listener, &output_port_events, impl); + pw_impl_node_add_listener(output_node, &impl->output.node_listener, &output_node_events, impl); + pw_global_add_listener(output->global, &impl->output.global_listener, &output_global_events, impl); input_node->live = output_node->live; @@ -1380,14 +1521,18 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, select_io(this); + impl->input.port = input; + impl->output.port = output; if (this->feedback) { - impl->inode = output_node; - impl->onode = input_node; + impl->input.node = output_node; + impl->output.node = input_node; } else { - impl->onode = output_node; - impl->inode = input_node; + impl->output.node = output_node; + impl->input.node = input_node; } + impl->input.mix = &this->rt.in_mix; + impl->output.mix = &this->rt.out_mix; pw_log_debug("%p: constructed out:%p:%d.%d -> in:%p:%d.%d", impl, output_node, output->port_id, this->rt.out_mix.port.port_id, @@ -1411,8 +1556,8 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, pw_impl_port_recalc_tag(output); pw_impl_port_recalc_tag(input); - if (impl->onode != impl->inode) - this->peer = pw_node_peer_ref(impl->onode, impl->inode); + if (impl->output.node != impl->input.node) + this->peer = pw_node_peer_ref(impl->output.node, impl->input.node); return this; @@ -1555,8 +1700,8 @@ void pw_impl_link_destroy(struct pw_impl_link *link) try_unlink_controls(impl, link->output, link->input); - output_remove(link, link->output); - input_remove(link, link->input); + output_remove(link); + input_remove(link); if (link->global) { spa_hook_remove(&link->global_listener); diff --git a/src/pipewire/private.h b/src/pipewire/private.h index cddd3b820..75db3fe43 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -1265,19 +1265,6 @@ struct pw_control { void *user_data; }; -/** Find a good format between 2 ports */ -int pw_context_find_format(struct pw_context *context, - struct pw_impl_port *output, - uint32_t output_mix, - struct pw_impl_port *input, - uint32_t input_mix, - struct pw_properties *props, - uint32_t n_format_filters, - struct spa_pod **format_filters, - struct spa_pod **format, - struct spa_pod_builder *builder, - char **error); - int pw_context_debug_port_params(struct pw_context *context, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, int err, const char *debug, ...); From 41665e6bb80fc4af19d6d099bf9e3cb92e3f57f4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 16 Apr 2025 11:00:28 +0200 Subject: [PATCH 0160/1014] examples: handle I420 and IYUV formats They are represented in PipeWire with 3 planes now. --- src/examples/sdl.h | 2 +- src/examples/video-play.c | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/examples/sdl.h b/src/examples/sdl.h index f96ed26ea..9efcfc493 100644 --- a/src/examples/sdl.h +++ b/src/examples/sdl.h @@ -151,7 +151,7 @@ static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct s if (id == 0) continue; if (c++ == 0) - spa_pod_builder_id(b, id); + spa_pod_builder_id(b, SPA_VIDEO_FORMAT_UNKNOWN); spa_pod_builder_id(b, id); } /* then all the other ones SDL can convert from/to */ diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 6a12f2cc9..3449aefa7 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -177,15 +177,21 @@ on_process(void *_data) /* copy video image in texture */ if (data->is_yuv) { + void *datas[4]; sstride = data->stride; - SDL_UpdateYUVTexture(data->texture, - NULL, - sdata, - sstride, - SPA_PTROFF(sdata, sstride * data->size.height, void), - sstride / 2, - SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void), - sstride / 2); + if (buf->n_datas == 1) { + datas[0] = sdata; + datas[1] = SPA_PTROFF(sdata, sstride * data->size.height, void); + datas[2] = SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void); + } else { + datas[0] = sdata; + datas[1] = buf->datas[1].data; + datas[2] = buf->datas[2].data; + } + SDL_UpdateYUVTexture(data->texture, NULL, + datas[0], sstride, + datas[1], sstride / 2, + datas[2], sstride / 2); } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { @@ -286,7 +292,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) const struct spa_pod *params[5]; Uint32 sdl_format; void *d; - int32_t mult, size; + int32_t mult, size, blocks; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); @@ -360,9 +366,11 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) case SDL_PIXELFORMAT_IYUV: size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; + blocks = 3; break; default: size = data->stride * data->size.height; + blocks = 1; break; } @@ -376,7 +384,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1< Date: Thu, 24 Apr 2025 13:21:07 +0200 Subject: [PATCH 0161/1014] alsa-seq: add the source only on success Otherwise if we have an error with the timerfd, we have the source fd added and we close the device. --- spa/plugins/alsa/alsa-seq.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 3b740f47e..9cb707814 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -404,7 +404,6 @@ int spa_alsa_seq_open(struct seq_state *state) state->sys.source.func = alsa_seq_on_sys; state->sys.source.data = state; - spa_loop_add_source(state->main_loop, &state->sys.source); /* increase event queue timer resolution */ snd_seq_queue_timer_alloca(&timer); @@ -449,6 +448,8 @@ int spa_alsa_seq_open(struct seq_state *state) state->timerfd = res; + spa_loop_add_source(state->main_loop, &state->sys.source); + state->opened = true; return 0; From 14f48c7a1ca4765cef5ca6af6d110cf01997994c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Apr 2025 09:45:04 +0200 Subject: [PATCH 0162/1014] acp: probe max 192 channels Thr 256 channel probe can cause overflows because we use uint8_t for the channel count in some places. Fixes #4669 --- spa/plugins/alsa/acp/acp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index c2ab555f0..bcd78c81d 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -16,7 +16,7 @@ void *_acp_log_data; struct spa_i18n *acp_i18n; -#define DEFAULT_CHANNELS 256u +#define DEFAULT_CHANNELS 255u #define DEFAULT_RATE 48000u #define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ From 84c18df6267c5f237b392f89f19d24475512068c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Apr 2025 09:51:20 +0200 Subject: [PATCH 0163/1014] ebur128: fix port name Fixes #4667 --- spa/plugins/filter-graph/ebur128_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c index 92904e102..a7aeeef99 100644 --- a/spa/plugins/filter-graph/ebur128_plugin.c +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -356,7 +356,7 @@ static struct spa_fga_port ebur128_ports[] = { .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_SHORTTERM, - .name = "Shorttem LUFS", + .name = "Shortterm LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_GLOBAL, From 93ee66dcab2d48849ce1379e894b7a9361225898 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Apr 2025 11:12:06 +0200 Subject: [PATCH 0164/1014] netjack2: Improve docs Remove unused option. Clarify that channels and midi port are chosen by the server. See #4666 --- src/modules/module-netjack2-driver.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 3675a33f7..ce889cd4a 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -63,11 +63,12 @@ * - `source.ip =`: IP address to bind to, default "0.0.0.0" * - `source.port =`: port to bind to, default 0 (allocate) * - `netjack2.client-name`: the name of the NETJACK2 client. - * - `netjack2.save`: if jack port connections should be save automatically. Can also be - * placed per stream. * - `netjack2.latency`: the latency in cycles, default 2 * - `audio.channels`: the number of audio ports. Can also be added to the stream props. + * Will always be configured to the channel count of the manager. The audio.position + * can however be used to assign an audio position. * - `midi.ports`: the number of midi ports. Can also be added to the stream props. + * Will always be configured to the number of midi ports on the manager. * - `source.props`: Extra properties for the source filter. * - `sink.props`: Extra properties for the sink filter. * @@ -95,7 +96,6 @@ * args = { * #driver.mode = duplex * #netjack2.client-name = PipeWire - * #netjack2.save = false * #netjack2.latency = 2 * #midi.ports = 0 * #audio.channels = 2 @@ -151,7 +151,6 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( source.ip= ) " \ "( source.port= ) " \ "( netjack2.client-name= ) " \ - "( netjack2.save= ) " \ "( netjack2.latency= ) " \ "( midi.ports= ) " \ "( audio.channels= ) " \ From b6cb76bf8d1f47f27e29745e07c1960c6fe0668d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Apr 2025 11:13:14 +0200 Subject: [PATCH 0165/1014] netjack2: implement ifname in the driver See #4666 --- src/modules/module-netjack2-driver.c | 14 +++++++++++--- src/modules/module-netjack2-manager.c | 3 +-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index ce889cd4a..199dbe9cf 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -705,7 +705,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen) static int make_socket(struct sockaddr_storage *src, socklen_t src_len, struct sockaddr_storage *dst, socklen_t dst_len, - bool loop, int ttl, int dscp) + bool loop, int ttl, int dscp, const char *ifname) { int af, fd, val, res; struct timeval timeout; @@ -721,7 +721,13 @@ static int make_socket(struct sockaddr_storage *src, socklen_t src_len, pw_log_error("setsockopt failed: %m"); goto error; } - +#ifdef SO_BINDTODEVICE + if (ifname && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) { + res = -errno; + pw_log_error("setsockopt(SO_BINDTODEVICE) failed: %m"); + goto error; + } +#endif #ifdef SO_PRIORITY val = 6; if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) @@ -981,9 +987,11 @@ static int create_netjack2_socket(struct impl *impl) impl->ttl = pw_properties_get_uint32(impl->props, "net.ttl", DEFAULT_NET_TTL); impl->loop = pw_properties_get_bool(impl->props, "net.loop", DEFAULT_NET_LOOP); impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); + str = pw_properties_get(impl->props, "local.ifname"); fd = make_socket(&impl->src_addr, impl->src_len, - &impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp); + &impl->dst_addr, impl->dst_len, impl->loop, impl->ttl, impl->dscp, + str); if (fd < 0) { res = -errno; pw_log_error("can't create socket: %s", spa_strerror(res)); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 8229b5d5f..dfbce12e1 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -1082,8 +1082,7 @@ static int create_netjack2_socket(struct impl *impl) impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); str = pw_properties_get(impl->props, "local.ifname"); - fd = make_announce_socket(&impl->src_addr, impl->src_len, - pw_properties_get(impl->props, "local.ifname")); + fd = make_announce_socket(&impl->src_addr, impl->src_len, str); if (fd < 0) { res = fd; pw_log_error("can't create socket: %s", spa_strerror(res)); From 07f033c8dada967079d5abefb360b6f8e805239e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 28 Apr 2025 12:23:15 +0200 Subject: [PATCH 0166/1014] netjack2: keep position io per stream Keep a position info for the stream it was set and then use the position info for the stream that is driving the graph. Otherwise we might use a destroyed position info. --- src/modules/module-netjack2-driver.c | 41 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 199dbe9cf..fbb687dd5 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -181,6 +181,8 @@ struct stream { struct pw_filter *filter; struct spa_hook listener; + struct spa_io_position *position; + struct spa_audio_info_raw info; uint32_t n_midi; @@ -221,8 +223,6 @@ struct impl { struct spa_hook core_proxy_listener; struct spa_hook core_listener; - struct spa_io_position *position; - struct stream source; struct stream sink; @@ -368,11 +368,10 @@ static void source_process(void *d, struct spa_io_position *position) static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) { struct stream *s = data; - struct impl *impl = s->impl; if (port_data == NULL) { switch (id) { case SPA_IO_Position: - impl->position = area; + s->position = area; break; default: break; @@ -615,6 +614,24 @@ static inline uint64_t get_time_nsec(struct impl *impl) return nsec; } +static void update_clock(struct impl *impl, struct stream *s, uint64_t nsec, uint32_t nframes) +{ + if (s->position) { + struct spa_io_clock *c = &s->position->clock; + + c->nsec = nsec; + c->rate = SPA_FRACTION(1, impl->samplerate); + c->position = impl->frame_time; + c->duration = nframes; + c->delay = 0; + c->rate_diff = 1.0; + c->next_nsec = nsec; + + c->target_rate = c->rate; + c->target_duration = c->duration; + } +} + static void on_data_io(void *data, int fd, uint32_t mask) { @@ -654,20 +671,6 @@ on_data_io(void *data, int fd, uint32_t mask) pw_log_warn("Xrun netjack2:%u PipeWire:%u", impl->nj2_xrun, impl->pw_xrun); impl->new_xrun = false; } - if (impl->position) { - struct spa_io_clock *c = &impl->position->clock; - - c->nsec = nsec; - c->rate = SPA_FRACTION(1, impl->samplerate); - c->position = impl->frame_time; - c->duration = nframes; - c->delay = 0; - c->rate_diff = 1.0; - c->next_nsec = nsec; - - c->target_rate = c->rate; - c->target_duration = c->duration; - } if (!source_running) netjack2_recv_data(&impl->peer, NULL, 0, NULL, 0); @@ -675,11 +678,13 @@ on_data_io(void *data, int fd, uint32_t mask) impl->done = false; impl->triggered = true; impl->driving = MODE_SOURCE; + update_clock(impl, &impl->source, nsec, nframes); pw_filter_trigger_process(impl->source.filter); } else if (impl->mode == MODE_SINK && sink_running) { impl->done = false; impl->triggered = true; impl->driving = MODE_SINK; + update_clock(impl, &impl->sink, nsec, nframes); pw_filter_trigger_process(impl->sink.filter); } else { sink_running = false; From 3d6e77e96df7f85c7cfc7b433f7a520727bc9e3f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 09:16:01 +0200 Subject: [PATCH 0167/1014] netjack2: warn when the trigger fails --- src/modules/module-netjack2-driver.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index fbb687dd5..be7af2749 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -679,13 +679,15 @@ on_data_io(void *data, int fd, uint32_t mask) impl->triggered = true; impl->driving = MODE_SOURCE; update_clock(impl, &impl->source, nsec, nframes); - pw_filter_trigger_process(impl->source.filter); + if (pw_filter_trigger_process(impl->source.filter) < 0) + pw_log_warn("source not ready"); } else if (impl->mode == MODE_SINK && sink_running) { impl->done = false; impl->triggered = true; impl->driving = MODE_SINK; update_clock(impl, &impl->sink, nsec, nframes); - pw_filter_trigger_process(impl->sink.filter); + if (pw_filter_trigger_process(impl->sink.filter) < 0) + pw_log_warn("sink not ready"); } else { sink_running = false; impl->done = true; From 40dbc5f00f3afbf5d9599b8d77b939607cf87880 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 09:16:54 +0200 Subject: [PATCH 0168/1014] netjack2: keep per stream io_position And handle the trigger failure with a warning and fallback. --- src/modules/module-netjack2-manager.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index dfbce12e1..5c2bb854e 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -183,6 +183,8 @@ struct stream { struct pw_filter *filter; struct spa_hook listener; + struct spa_io_position *position; + struct spa_audio_info_raw info; uint32_t n_midi; @@ -202,8 +204,6 @@ struct follower { struct spa_list link; struct impl *impl; - struct spa_io_position *position; - struct stream source; struct stream sink; @@ -354,9 +354,8 @@ static void sink_process(void *d, struct spa_io_position *position) pw_loop_update_io(s->impl->data_loop, follower->socket, SPA_IO_IN); } -static void source_process(void *d, struct spa_io_position *position) +static inline void handle_source_process(struct stream *s, struct spa_io_position *position) { - struct stream *s = d; struct follower *follower = s->follower; uint32_t nframes = position->clock.duration; struct data_info midi[s->n_ports]; @@ -369,6 +368,12 @@ static void source_process(void *d, struct spa_io_position *position) netjack2_recv_data(&follower->peer, midi, n_midi, audio, n_audio); } +static void source_process(void *d, struct spa_io_position *position) +{ + struct stream *s = d; + handle_source_process(s, position); +} + static void follower_free(struct follower *follower) { struct impl *impl = follower->impl; @@ -473,18 +478,20 @@ on_data_io(void *data, int fd, uint32_t mask) if (mask & SPA_IO_IN) { pw_loop_update_io(impl->data_loop, follower->socket, 0); - pw_filter_trigger_process(follower->source.filter); + if (pw_filter_trigger_process(follower->source.filter) < 0) { + pw_log_warn("source not ready"); + handle_source_process(&follower->source, follower->source.position); + } } } static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) { struct stream *s = data; - struct follower *follower = s->follower; if (port_data == NULL) { switch (id) { case SPA_IO_Position: - follower->position = area; + s->position = area; break; default: break; From 78fe234bfe09a7443b5abdbc4764dc3dd591508c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 09:17:35 +0200 Subject: [PATCH 0169/1014] netjack2: copy the node.group to streams Just in case we want them to be scheduled in the same group. --- src/modules/module-netjack2-manager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 5c2bb854e..432fb6e47 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -1309,6 +1309,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_NETWORK); + copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); copy_props(impl, props, PW_KEY_NODE_LOCK_QUANTUM); From 18a5f884beade1dd3b9355095f5ad78d4a4a5ed2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 16:26:30 +0200 Subject: [PATCH 0170/1014] netjack2: make function to clear events Make a separate function to clear events instead of passing NULL as the midi buffer and segfaulting. --- src/modules/module-netjack2/peer.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 411bd6abc..3870b3817 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -368,17 +368,27 @@ static void midi_to_netjack2(struct netjack2_peer *peer, buf->write_pos); } +static inline void netjack2_clear_midi(float *dst, uint32_t size) +{ + struct spa_pod_builder b = { 0, }; + struct spa_pod_frame f; + spa_pod_builder_init(&b, dst, size); + spa_pod_builder_push_sequence(&b, &f, 0); + spa_pod_builder_pop(&b, &f); +} + static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_buffer *buf) { struct spa_pod_builder b = { 0, }; uint32_t i; struct spa_pod_frame f; - size_t offset = size - buf->write_pos - - sizeof(*buf) - (buf->event_count * sizeof(struct nj2_midi_event)); + size_t offset = size - buf->write_pos - sizeof(*buf) - + (buf->event_count * sizeof(struct nj2_midi_event)); spa_pod_builder_init(&b, dst, size); spa_pod_builder_push_sequence(&b, &f, 0); - for (i = 0; buf != NULL && i < buf->event_count; i++) { + + for (i = 0; i < buf->event_count; i++) { struct nj2_midi_event *ev = &buf->event[i]; uint8_t *data; size_t s; @@ -1088,7 +1098,7 @@ static int netjack2_recv_data(struct netjack2_peer *peer, } for (i = 0; i < n_midi; i++) { if (!midi[i].filled && midi[i].data != NULL) - netjack2_to_midi(midi[i].data, peer->params.period_size * sizeof(float), NULL); + netjack2_clear_midi(midi[i].data, peer->params.period_size * sizeof(float)); } peer->sync.cycle = ntohl(header.cycle); return 0; From 3856d296467bd4c2ac9531d68d019b949110236a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 17:17:14 +0200 Subject: [PATCH 0171/1014] netjack2: implement driver and manager roles correctly The manager is actually not supposed to decide much about the number of audio and midi ports. It should just suggest a default when connecting driver doesn't know. Add a audio.ports parameters to manager and driver to suggest/ask for the amount of audio ports. Let the audio.position/audio.channels be a specification of the channel mask in case it matches the requested channels, otherwise use AUX channels for the ports. This means that we must derive the mode (sink/source/audio/midi) from the ports that are negotiated in the manager and the driver, so delay this until after negotiation. Make sure all the possible modes work. For midi only streams, we can't wait for the session manager to perform a PortConfig so do that ourselves. Make sure we only use a source trigger when we have a sink. Fixes #4666 --- src/modules/module-netjack2-driver.c | 132 ++++++++++--------- src/modules/module-netjack2-manager.c | 178 ++++++++++++++++---------- 2 files changed, 180 insertions(+), 130 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index be7af2749..c2f1148ed 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -53,7 +53,6 @@ * * ## Module Options * - * - `driver.mode`: the driver mode, sink|source|duplex, default duplex * - `local.ifname = `: interface name to use * - `net.ip =`: multicast IP address, default "225.3.19.154" * - `net.port =`: control port, default 19000 @@ -64,11 +63,10 @@ * - `source.port =`: port to bind to, default 0 (allocate) * - `netjack2.client-name`: the name of the NETJACK2 client. * - `netjack2.latency`: the latency in cycles, default 2 - * - `audio.channels`: the number of audio ports. Can also be added to the stream props. - * Will always be configured to the channel count of the manager. The audio.position - * can however be used to assign an audio position. + * - `audio.ports`: the number of audio ports. Can also be added to the stream props. + * A value of -1 will configure to the number of audio ports on the manager. * - `midi.ports`: the number of midi ports. Can also be added to the stream props. - * Will always be configured to the number of midi ports on the manager. + * A value of -1 will configure to the number of midi ports on the manager. * - `source.props`: Extra properties for the source filter. * - `sink.props`: Extra properties for the sink filter. * @@ -94,10 +92,10 @@ * context.modules = [ * { name = libpipewire-module-netjack2-driver * args = { - * #driver.mode = duplex * #netjack2.client-name = PipeWire * #netjack2.latency = 2 * #midi.ports = 0 + * #audio.ports = -1 * #audio.channels = 2 * #audio.position = [ FL FR ] * source.props = { @@ -133,15 +131,13 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define NETWORK_MAX_LATENCY 30 #define DEFAULT_CLIENT_NAME "PipeWire" -#define DEFAULT_CHANNELS 2 -#define DEFAULT_POSITION "[ FL FR ]" -#define DEFAULT_MIDI_PORTS 1 +#define DEFAULT_MIDI_PORTS -1 +#define DEFAULT_AUDIO_PORTS -1 -#define FOLLOWER_INIT_TIMEOUT 1 +#define FOLLOWER_INIT_TIMEOUT 10000 #define FOLLOWER_INIT_RETRY -1 #define MODULE_USAGE "( remote.name= ) " \ - "( driver.mode= ) " \ "( local.ifname= ) " \ "( net.ip= ) " \ "( net.port= ) " \ @@ -152,9 +148,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( source.port= ) " \ "( netjack2.client-name= ) " \ "( netjack2.latency= ) " \ - "( midi.ports= ) " \ - "( audio.channels= ) " \ - "( audio.position= ) " \ + "( audio.ports= ) " \ + "( midi.ports= ) " \ + "( audio.channels= ) " \ + "( audio.position= ) " \ "( source.props= ) " \ "( sink.props= ) " @@ -181,11 +178,13 @@ struct stream { struct pw_filter *filter; struct spa_hook listener; + int32_t wanted_n_midi; + int32_t wanted_n_audio; + struct spa_io_position *position; struct spa_audio_info_raw info; - uint32_t n_midi; uint32_t n_ports; struct port *ports[MAX_PORTS]; @@ -456,6 +455,7 @@ static void make_stream_ports(struct stream *s) s->ports[i] = port; } + pw_filter_set_active(s->filter, true); } static struct spa_pod *make_props_param(struct spa_pod_builder *b, @@ -521,7 +521,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t id, case SPA_PARAM_PortConfig: pw_log_debug("PortConfig"); make_stream_ports(s); - pw_filter_set_active(s->filter, true); break; case SPA_PARAM_Props: pw_log_debug("Props"); @@ -556,6 +555,7 @@ static int make_stream(struct stream *s, const char *name) const struct spa_pod *params[4]; uint8_t buffer[1024]; struct spa_pod_builder b; + int res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -581,12 +581,18 @@ static int make_stream(struct stream *s, const char *name) SPA_PARAM_Format, &s->info); params[n_params++] = make_props_param(&b, &s->volume); - return pw_filter_connect(s->filter, + if ((res = pw_filter_connect(s->filter, PW_FILTER_FLAG_INACTIVE | PW_FILTER_FLAG_DRIVER | PW_FILTER_FLAG_RT_PROCESS | PW_FILTER_FLAG_CUSTOM_LATENCY, - params, n_params); + params, n_params)) < 0) + return res; + + if (s->info.channels == 0) + make_stream_ports(s); + + return res; } static int create_filters(struct impl *impl) @@ -801,13 +807,12 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p int res; struct netjack2_peer *peer = &impl->peer; uint32_t i; + const char *media; pw_log_info("got follower setup"); nj2_dump_session_params(params); nj2_session_params_ntoh(&peer->params, params); - SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); - SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); if (peer->params.send_audio_channels < 0 || peer->params.recv_audio_channels < 0 || @@ -822,20 +827,28 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p pw_loop_update_io(impl->main_loop, impl->setup_socket, 0); - impl->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; - impl->source.info.rate = peer->params.sample_rate; - if ((uint32_t)peer->params.send_audio_channels != impl->source.info.channels) { - impl->source.info.channels = peer->params.send_audio_channels; - for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) - impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + impl->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + if (impl->sink.n_ports > MAX_PORTS) { + pw_log_warn("Too many follower sink ports %d > %d", impl->sink.n_ports, MAX_PORTS); + return -EINVAL; } - impl->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; impl->sink.info.rate = peer->params.sample_rate; - if ((uint32_t)peer->params.recv_audio_channels != impl->sink.info.channels) { - impl->sink.info.channels = peer->params.recv_audio_channels; - for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { + impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < impl->sink.info.channels; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } + impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + if (impl->source.n_ports > MAX_PORTS) { + pw_log_warn("Too many follower source ports %d > %d", impl->source.n_ports, MAX_PORTS); + return -EINVAL; + } + impl->source.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { + impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < impl->source.info.channels; i++) + impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } impl->samplerate = peer->params.sample_rate; impl->period_size = peer->params.period_size; @@ -855,6 +868,20 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p pw_properties_setf(impl->source.props, PW_KEY_NODE_FORCE_QUANTUM, "%u", impl->period_size); + media = impl->sink.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get(impl->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(impl->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media); + + media = impl->source.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get(impl->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(impl->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media); + + impl->mode = 0; + if (impl->source.n_ports > 0) + impl->mode |= MODE_SOURCE; + if (impl->sink.n_ports > 0) + impl->mode |= MODE_SINK; + if ((res = create_filters(impl)) < 0) return res; @@ -954,10 +981,10 @@ static int send_follower_available(struct impl *impl) snprintf(params.follower_name, sizeof(params.follower_name), "%s", pw_get_host_name()); params.mtu = htonl(impl->mtu); params.transport_sync = htonl(0); - params.send_audio_channels = htonl(-1); - params.recv_audio_channels = htonl(-1); - params.send_midi_channels = htonl(-1); - params.recv_midi_channels = htonl(-1); + params.send_audio_channels = htonl(impl->sink.wanted_n_audio); + params.recv_audio_channels = htonl(impl->source.wanted_n_audio); + params.send_midi_channels = htonl(impl->sink.wanted_n_midi); + params.recv_midi_channels = htonl(impl->source.wanted_n_midi); params.sample_encoder = htonl(NJ2_ENCODER_FLOAT); params.follower_sync_mode = htonl(1); params.network_latency = htonl(impl->latency); @@ -1165,8 +1192,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio { spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), - SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_POSITION, NULL); @@ -1234,20 +1260,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->sink.impl = impl; impl->sink.direction = PW_DIRECTION_INPUT; - impl->mode = MODE_DUPLEX; - if ((str = pw_properties_get(props, "driver.mode")) != NULL) { - if (spa_streq(str, "source")) { - impl->mode = MODE_SOURCE; - } else if (spa_streq(str, "sink")) { - impl->mode = MODE_SINK; - } else if (spa_streq(str, "duplex")) { - impl->mode = MODE_DUPLEX; - } else { - pw_log_error("invalid driver.mode '%s'", str); - res = -EINVAL; - goto error; - } - } impl->latency = pw_properties_get_uint32(impl->props, "netjack2.latency", DEFAULT_NETWORK_LATENCY); @@ -1259,11 +1271,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); - pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "40000"); pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "netjack2_driver_send"); - pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source"); pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "40001"); pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "netjack2_driver_receive"); @@ -1278,22 +1288,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); + copy_props(impl, props, "midi.ports"); + copy_props(impl, props, "audio.ports"); parse_audio_info(impl->source.props, &impl->source.info); parse_audio_info(impl->sink.props, &impl->sink.info); - impl->source.n_midi = pw_properties_get_uint32(impl->source.props, + impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); - impl->sink.n_midi = pw_properties_get_uint32(impl->sink.props, + impl->sink.wanted_n_midi = pw_properties_get_int32(impl->sink.props, "midi.ports", DEFAULT_MIDI_PORTS); - - impl->source.n_ports = impl->source.n_midi + impl->source.info.channels; - impl->sink.n_ports = impl->sink.n_midi + impl->sink.info.channels; - if (impl->source.n_ports > MAX_PORTS || impl->sink.n_ports > MAX_PORTS) { - pw_log_error("too many ports"); - res = -EINVAL; - goto error; - } + impl->source.wanted_n_audio = pw_properties_get_int32(impl->source.props, + "audio.ports", DEFAULT_AUDIO_PORTS); + impl->sink.wanted_n_audio = pw_properties_get_int32(impl->sink.props, + "audio.ports", DEFAULT_AUDIO_PORTS); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 432fb6e47..30cedb795 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -67,8 +67,11 @@ * - `netjack2.period-size`: the buffer size to use, default 1024 * - `netjack2.encoding`: the encoding, float|opus|int, default float * - `netjack2.kbps`: the number of kilobits per second when encoding, default 64 - * - `audio.channels`: the number of audio ports. Can also be added to the stream props. - * - `midi.ports`: the number of midi ports. Can also be added to the stream props. + * - `audio.ports`: the number of audio ports. Can also be added to the stream props. This + * is the default suggestion for drivers that don't specify any number of audio channels. + * - `midi.ports`: the number of midi ports. Can also be added to the stream props. This + * is the default suggestion for drivers that don't specify any number of midi channels. + * - `audio.position`: default channel position for the number of audio.ports. * - `source.props`: Extra properties for the source filter. * - `sink.props`: Extra properties for the sink filter. * @@ -99,6 +102,7 @@ * #netjack2.period-size = 1024 * #netjack2.encoding = float # float|opus * #netjack2.kbps = 64 + * #audio.ports = 0 * #midi.ports = 0 * #audio.channels = 2 * #audio.position = [ FL FR ] @@ -137,8 +141,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_PERIOD_SIZE 1024 #define DEFAULT_ENCODING "float" #define DEFAULT_KBPS 64 -#define DEFAULT_CHANNELS 2 -#define DEFAULT_POSITION "[ FL FR ]" +#define DEFAULT_AUDIO_PORTS 2 #define DEFAULT_MIDI_PORTS 1 #define MODULE_USAGE "( remote.name= ) " \ @@ -151,8 +154,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( netjack2.connect= ) " \ "( netjack2.sample-rate= ) "\ "( netjack2.period-size= ) " \ - "( midi.ports= ) " \ - "( audio.channels= ) " \ + "( midi.ports= ) " \ + "( audio.channels= ) " \ "( audio.position= ) " \ "( source.props= ) " \ "( sink.props= ) " @@ -186,8 +189,9 @@ struct stream { struct spa_io_position *position; struct spa_audio_info_raw info; - + uint32_t n_audio; uint32_t n_midi; + uint32_t n_ports; struct port *ports[MAX_PORTS]; @@ -204,6 +208,11 @@ struct follower { struct spa_list link; struct impl *impl; +#define MODE_SINK (1<<0) +#define MODE_SOURCE (1<<1) +#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) + uint32_t mode; + struct stream source; struct stream sink; @@ -235,10 +244,6 @@ struct impl { struct pw_loop *data_loop; struct spa_system *system; -#define MODE_SINK (1<<0) -#define MODE_SOURCE (1<<1) -#define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) - uint32_t mode; struct pw_properties *props; struct pw_properties *sink_props; struct pw_properties *source_props; @@ -371,6 +376,11 @@ static inline void handle_source_process(struct stream *s, struct spa_io_positio static void source_process(void *d, struct spa_io_position *position) { struct stream *s = d; + struct follower *follower = s->follower; + + if (!(follower->mode & MODE_SINK)) + sink_process(&follower->sink, position); + handle_source_process(s, position); } @@ -478,9 +488,15 @@ on_data_io(void *data, int fd, uint32_t mask) if (mask & SPA_IO_IN) { pw_loop_update_io(impl->data_loop, follower->socket, 0); - if (pw_filter_trigger_process(follower->source.filter) < 0) { - pw_log_warn("source not ready"); - handle_source_process(&follower->source, follower->source.position); + if (follower->mode & MODE_SOURCE) { + if (pw_filter_trigger_process(follower->source.filter) < 0) { + pw_log_warn("source not ready"); + handle_source_process(&follower->source, follower->source.position); + } + } else { + /* There is no source, handle the source receive side (without ports) + * with the sink position io */ + handle_source_process(&follower->source, follower->sink.position); } } } @@ -528,6 +544,9 @@ static void make_stream_ports(struct stream *s) struct spa_latency_info latency; const struct spa_pod *params[1]; + if (s->ready) + return; + for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; if (port != NULL) { @@ -578,6 +597,9 @@ static void make_stream_ports(struct stream *s) s->ports[i] = port; } + s->ready = true; + if (s->follower->started) + pw_filter_set_active(s->filter, true); } static struct spa_pod *make_props_param(struct spa_pod_builder *b, @@ -643,9 +665,6 @@ static void stream_param_changed(void *data, void *port_data, uint32_t id, case SPA_PARAM_PortConfig: pw_log_debug("PortConfig"); make_stream_ports(s); - s->ready = true; - if (s->follower->started) - pw_filter_set_active(s->filter, true); break; case SPA_PARAM_Props: pw_log_debug("Props"); @@ -681,6 +700,7 @@ static int make_stream(struct stream *s, const char *name) uint8_t buffer[1024]; struct spa_pod_builder b; uint32_t flags; + int res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -700,7 +720,8 @@ static int make_stream(struct stream *s, const char *name) } else { pw_filter_add_listener(s->filter, &s->listener, &source_events, s); - flags |= PW_FILTER_FLAG_TRIGGER; + if (s->follower->mode & MODE_SINK) + flags |= PW_FILTER_FLAG_TRIGGER; } reset_volume(&s->volume, s->info.channels); @@ -712,18 +733,23 @@ static int make_stream(struct stream *s, const char *name) SPA_PARAM_Format, &s->info); params[n_params++] = make_props_param(&b, &s->volume); - return pw_filter_connect(s->filter, flags, params, n_params); + if ((res = pw_filter_connect(s->filter, flags, params, n_params)) < 0) + return res; + + if (s->info.channels == 0) + make_stream_ports(s); + + return res; } static int create_filters(struct follower *follower) { - struct impl *impl = follower->impl; int res = 0; - if (impl->mode & MODE_SINK) + if (follower->mode & MODE_SINK) res = make_stream(&follower->sink, "NETJACK2 Send"); - if (impl->mode & MODE_SOURCE) + if (follower->mode & MODE_SOURCE) res = make_stream(&follower->source, "NETJACK2 Receive"); return res; @@ -867,6 +893,8 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param struct follower *follower; char buffer[256]; struct netjack2_peer *peer; + uint32_t i; + const char *media; pw_log_info("got follower available"); nj2_dump_session_params(params); @@ -898,6 +926,12 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param parse_audio_info(follower->source.props, &follower->source.info); parse_audio_info(follower->sink.props, &follower->sink.info); + follower->source.n_audio = pw_properties_get_uint32(follower->source.props, + "audio.ports", follower->source.info.channels ? + follower->source.info.channels : DEFAULT_AUDIO_PORTS); + follower->sink.n_audio = pw_properties_get_uint32(follower->sink.props, + "audio.ports", follower->sink.info.channels ? + follower->sink.info.channels : DEFAULT_AUDIO_PORTS); follower->source.n_midi = pw_properties_get_uint32(follower->source.props, "midi.ports", DEFAULT_MIDI_PORTS); follower->sink.n_midi = pw_properties_get_uint32(follower->sink.props, @@ -932,29 +966,65 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param peer->params.sample_encoder = impl->encoding; peer->params.kbps = impl->kbps; + /* params send and recv are from the client point of view and reversed for the + * manager, so when the client sends, we receive in a source etc. We swap the params + * later after we replied to the client. */ if (peer->params.send_audio_channels < 0) - peer->params.send_audio_channels = follower->sink.info.channels; + peer->params.send_audio_channels = follower->source.n_audio; if (peer->params.recv_audio_channels < 0) - peer->params.recv_audio_channels = follower->source.info.channels; + peer->params.recv_audio_channels = follower->sink.n_audio; if (peer->params.send_midi_channels < 0) - peer->params.send_midi_channels = follower->sink.n_midi; + peer->params.send_midi_channels = follower->source.n_midi; if (peer->params.recv_midi_channels < 0) - peer->params.recv_midi_channels = follower->source.n_midi; + peer->params.recv_midi_channels = follower->sink.n_midi; follower->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; - follower->source.info.rate = peer->params.sample_rate; - follower->source.info.channels = peer->params.send_audio_channels; + follower->source.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.send_audio_channels != follower->source.info.channels) { + follower->source.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < follower->source.info.channels; i++) + follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } follower->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; - follower->sink.info.rate = peer->params.sample_rate; - follower->sink.info.channels = peer->params.recv_audio_channels; + follower->sink.info.rate = peer->params.sample_rate; + if ((uint32_t)peer->params.recv_audio_channels != follower->sink.info.channels) { + follower->sink.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + for (i = 0; i < follower->sink.info.channels; i++) + follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + } - follower->source.n_ports = follower->source.n_midi + follower->source.info.channels; - follower->sink.n_ports = follower->sink.n_midi + follower->sink.info.channels; if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { - pw_log_error("too many ports"); + pw_log_error("too many ports source:%d sink:%d max:%d", follower->source.n_ports, + follower->sink.n_ports, MAX_PORTS); res = -EINVAL; goto cleanup; } + media = follower->sink.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get_bool(follower->sink.props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(follower->sink.props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(follower->sink.props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "Stream/Input/%s", media); + } else { + if (pw_properties_get(follower->sink.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->sink.props, PW_KEY_MEDIA_CLASS, "%s/Sink", media); + } + media = follower->source.info.channels > 0 ? "Audio" : "Midi"; + if (pw_properties_get_bool(follower->source.props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(follower->source.props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(follower->source.props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "Stream/Output/%s", media); + } else { + if (pw_properties_get(follower->source.props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_setf(follower->source.props, PW_KEY_MEDIA_CLASS, "%s/Source", media); + } + + follower->mode = 0; + if (follower->sink.n_ports > 0) + follower->mode |= MODE_SINK; + if (follower->source.n_ports > 0) + follower->mode |= MODE_SOURCE; if ((res = create_filters(follower)) < 0) goto create_failed; @@ -1009,6 +1079,10 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param nj2_dump_session_params(params); send(follower->socket->fd, params, sizeof(*params), 0); + /* now swap send and recv to make it match our point of view */ + SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); + SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); + return 0; create_failed: @@ -1179,8 +1253,7 @@ static void parse_audio_info(const struct pw_properties *props, struct spa_audio { spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), - SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_POSITION, NULL); @@ -1244,20 +1317,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->main_loop = pw_context_get_main_loop(context); impl->system = impl->main_loop->system; - impl->mode = MODE_DUPLEX; - if ((str = pw_properties_get(props, "tunnel.mode")) != NULL) { - if (spa_streq(str, "source")) { - impl->mode = MODE_SOURCE; - } else if (spa_streq(str, "sink")) { - impl->mode = MODE_SINK; - } else if (spa_streq(str, "duplex")) { - impl->mode = MODE_DUPLEX; - } else { - pw_log_error("invalid tunnel.mode '%s'", str); - res = -EINVAL; - goto error; - } - } impl->samplerate = pw_properties_get_uint32(impl->props, "netjack2.sample-rate", DEFAULT_SAMPLE_RATE); impl->period_size = pw_properties_get_uint32(impl->props, "netjack2.period-size", @@ -1316,27 +1375,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOCK_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + copy_props(impl, props, "audio.ports"); + copy_props(impl, props, "midi.ports"); copy_props(impl, props, "netjack2.connect"); - if (pw_properties_get_bool(impl->sink_props, "netjack2.connect", DEFAULT_CONNECT)) { - if (pw_properties_get(impl->sink_props, PW_KEY_NODE_AUTOCONNECT) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_NODE_AUTOCONNECT, "true"); - if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Stream/Input/Audio"); - } else { - if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - } - if (pw_properties_get_bool(impl->source_props, "netjack2.connect", DEFAULT_CONNECT)) { - if (pw_properties_get(impl->source_props, PW_KEY_NODE_AUTOCONNECT) == NULL) - pw_properties_set(impl->source_props, PW_KEY_NODE_AUTOCONNECT, "true"); - if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Stream/Output/Audio"); - } else { - if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) - pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); - } - impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); From c7b318523d6afc02e3ea2d7a0451c7b8c3de7d88 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 17:35:40 +0200 Subject: [PATCH 0172/1014] netjack2: set timeout to sane value again --- src/modules/module-netjack2-driver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index c2f1148ed..7946821f4 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -134,7 +134,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_MIDI_PORTS -1 #define DEFAULT_AUDIO_PORTS -1 -#define FOLLOWER_INIT_TIMEOUT 10000 +#define FOLLOWER_INIT_TIMEOUT 1 #define FOLLOWER_INIT_RETRY -1 #define MODULE_USAGE "( remote.name= ) " \ From 6e8db1cd4a5832b46eba039dbfb0ced12ffb1c5d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 18:09:02 +0200 Subject: [PATCH 0173/1014] netjack2: handle connection errors in more cases --- src/modules/module-netjack2-manager.c | 18 +++++++++++++++--- src/modules/module-netjack2/peer.c | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 30cedb795..b690ec771 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -359,6 +359,15 @@ static void sink_process(void *d, struct spa_io_position *position) pw_loop_update_io(s->impl->data_loop, follower->socket, SPA_IO_IN); } +static int stop_follower(struct follower *follower); + +static int do_stop_follower(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + stop_follower(user_data); + return 0; +} + static inline void handle_source_process(struct stream *s, struct spa_io_position *position) { struct follower *follower = s->follower; @@ -369,7 +378,10 @@ static inline void handle_source_process(struct stream *s, struct spa_io_positio set_info(s, nframes, midi, &n_midi, audio, &n_audio); - netjack2_manager_sync_wait(&follower->peer); + if (netjack2_manager_sync_wait(&follower->peer) < 0) { + pw_loop_invoke(s->impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); + return; + } netjack2_recv_data(&follower->peer, midi, n_midi, audio, n_audio); } @@ -481,8 +493,8 @@ on_data_io(void *data, int fd, uint32_t mask) if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { pw_log_warn("error:%08x", mask); - pw_loop_destroy_source(impl->data_loop, follower->socket); - follower->socket = NULL; + pw_loop_update_io(impl->data_loop, follower->socket, 0); + pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); return; } if (mask & SPA_IO_IN) { diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 3870b3817..833b22723 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -758,7 +758,7 @@ static inline int32_t netjack2_driver_sync_wait(struct netjack2_peer *peer) receive_error: pw_log_warn("recv error: %m"); - return 0; + return -errno; } static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer) @@ -802,7 +802,7 @@ static inline int32_t netjack2_manager_sync_wait(struct netjack2_peer *peer) receive_error: pw_log_warn("recv error: %m"); - return 0; + return -errno; } static int netjack2_recv_midi(struct netjack2_peer *peer, struct nj2_packet_header *header, uint32_t *count, From 743d86500c78f32e251fd0036a66aa51754023f3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Apr 2025 18:36:54 +0200 Subject: [PATCH 0174/1014] netjack2: fix trace_fp compilation --- src/modules/module-netjack2-driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 7946821f4..f85ece7a8 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -670,8 +670,8 @@ on_data_io(void *data, int fd, uint32_t mask) impl->frame_time += nframes; - pw_log_trace_fp("process %d %u %u %p %"PRIu64, nframes, source_running, - sink_running, impl->position, impl->frame_time); + pw_log_trace_fp("process %d %u %u %"PRIu64, nframes, source_running, + sink_running, impl->frame_time); if (impl->new_xrun) { pw_log_warn("Xrun netjack2:%u PipeWire:%u", impl->nj2_xrun, impl->pw_xrun); From a9083c7519562c3cbe4259b966c207ac4231eb08 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 11:00:42 +0200 Subject: [PATCH 0175/1014] netjack2: improve shutdown Destroy the sources from the io handler immediately when there is an error so that we don't end up in endless error wakeups. Schedule the free from the main loop and make sure only one can ever run. --- src/modules/module-netjack2-manager.c | 39 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index b690ec771..64ad7fb3b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -236,6 +236,7 @@ struct follower { unsigned int done:1; unsigned int new_xrun:1; unsigned int started:1; + unsigned int freeing:1; }; struct impl { @@ -289,6 +290,7 @@ static void stream_destroy(void *d) struct stream *s = d; uint32_t i; + s->running = false; spa_hook_remove(&s->listener); for (i = 0; i < s->n_ports; i++) s->ports[i] = NULL; @@ -400,20 +402,35 @@ static void follower_free(struct follower *follower) { struct impl *impl = follower->impl; + if (follower->freeing) + return; + + follower->freeing = true; + spa_list_remove(&follower->link); - if (follower->source.filter) + if (follower->socket) { + pw_loop_destroy_source(impl->data_loop, follower->socket); + follower->socket = NULL; + } + if (follower->setup_socket) { + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->setup_socket = NULL; + } + + if (follower->source.filter) { pw_filter_destroy(follower->source.filter); - if (follower->sink.filter) + follower->source.filter = NULL; + } + if (follower->sink.filter) { pw_filter_destroy(follower->sink.filter); + follower->sink.filter = NULL; + } pw_properties_free(follower->source.props); + follower->source.props = NULL; pw_properties_free(follower->sink.props); - - if (follower->socket) - pw_loop_destroy_source(impl->data_loop, follower->socket); - if (follower->setup_socket) - pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->sink.props = NULL; netjack2_cleanup(&follower->peer); free(follower); @@ -447,10 +464,13 @@ static void on_setup_io(void *data, int fd, uint32_t mask) { struct follower *follower = data; + struct impl *impl = follower->impl; if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { pw_log_warn("error:%08x", mask); - stop_follower(follower); + pw_loop_destroy_source(impl->main_loop, follower->setup_socket); + follower->setup_socket = NULL; + pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); return; } if (mask & SPA_IO_IN) { @@ -493,7 +513,8 @@ on_data_io(void *data, int fd, uint32_t mask) if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { pw_log_warn("error:%08x", mask); - pw_loop_update_io(impl->data_loop, follower->socket, 0); + pw_loop_destroy_source(impl->data_loop, follower->socket); + follower->socket = NULL; pw_loop_invoke(impl->main_loop, do_stop_follower, 0, NULL, 0, false, follower); return; } From 664359a020dc6b0a3796b4e2f6a759093dc4c1a7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 11:02:58 +0200 Subject: [PATCH 0176/1014] netjack2: use strncpy to copy the header It pads the remaining bytes in the header with 0 bytes so that the memory doesn't contain uninitialized data. --- src/modules/module-netjack2/peer.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 833b22723..b289c9425 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -429,7 +429,7 @@ static int netjack2_send_sync(struct netjack2_peer *peer, uint32_t nframes) is_last = peer->params.send_midi_channels == 0 && peer->params.send_audio_channels == 0 ? 1 : 0; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('s'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -483,7 +483,7 @@ static int netjack2_send_midi(struct netjack2_peer *peer, uint32_t nframes, max_size = peer->params.mtu - sizeof(header); num_packets = (midi_size + max_size-1) / max_size; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('m'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -535,7 +535,7 @@ static int netjack2_send_float(struct netjack2_peer *peer, uint32_t nframes, sub_period_bytes = sub_period_size * sizeof(float) + sizeof(int32_t); num_packets = nframes / sub_period_size; - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -610,7 +610,7 @@ static int netjack2_send_opus(struct netjack2_peer *peer, uint32_t nframes, } } - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); @@ -678,7 +678,7 @@ static int netjack2_send_int(struct netjack2_peer *peer, uint32_t nframes, memset(ap, 0, max_encoded); } - strcpy(header.type, "header"); + strncpy(header.type, "header", sizeof(header.type)); header.data_type = htonl('a'); header.data_stream = htonl(peer->our_stream); header.id = htonl(peer->params.id); From 237c0197c63bd092b8d44cbe31b0663dbf63e065 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 12:20:31 +0200 Subject: [PATCH 0177/1014] filter-chain: add props only once Only add the properties offset in the builder after we actually got a property or else we end up with the same property twice. --- src/modules/module-filter-chain.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 1446cc437..537b8c7d0 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1153,10 +1153,11 @@ static int setup_streams(struct impl *impl) SPA_PARAM_EnumFormat, &impl->capture_info); for (i = 0;; i++) { - if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) - *offs = b.b.state.offset; + uint32_t save = b.b.state.offset; if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1) break; + if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) + *offs = save; } if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) From aa67998e2631f71668ad838abf066d15b2f7c2fb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 12:21:39 +0200 Subject: [PATCH 0178/1014] filter-graph: process controls after loading the graph Do some of the counting of the nodes and controls when we add a node to make things easier later. Do the setup of the graph controls after loading the graph because we know exactly how many controls we will have. Fixes controls being unavailable until the filter-graph is activated. --- spa/plugins/filter-graph/filter-graph.c | 64 ++++++++++++------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 9c5edc116..2ef3794ef 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -160,6 +160,7 @@ struct volume { struct graph { struct impl *impl; + uint32_t n_nodes; struct spa_list node_list; struct spa_list link_list; @@ -1391,6 +1392,8 @@ static int load_node(struct graph *graph, struct spa_json *json) parse_control(node, &control); spa_list_append(&graph->node_list, &node->link); + graph->n_nodes++; + graph->n_control += desc->n_control; return 0; } @@ -1621,22 +1624,6 @@ error: return res; } -/* any default values for the controls are set in the first instance - * of the control data. Duplicate this to the other instances now. */ -static void setup_node_controls(struct node *node) -{ - uint32_t i, j; - uint32_t n_hndl = node->n_hndl; - uint32_t n_ports = node->desc->n_control; - struct port *ports = node->control_port; - - for (i = 0; i < n_ports; i++) { - struct port *port = &ports[i]; - for (j = 1; j < n_hndl; j++) - port->control_data[j] = port->control_data[0]; - } -} - static struct node *find_next_node(struct graph *graph) { struct node *node; @@ -1669,7 +1656,7 @@ static int setup_graph(struct graph *graph) struct link *link; struct graph_port *gp; struct graph_hndl *gh; - uint32_t i, j, n, n_nodes, n_input, n_output, n_control, n_hndl = 0; + uint32_t i, j, n, n_input, n_output, n_hndl = 0; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; @@ -1756,16 +1743,6 @@ static int setup_graph(struct graph *graph) } spa_log_info(impl->log, "using %d instances %d %d", n_hndl, n_input, n_output); - /* now go over all nodes and create instances. */ - n_control = 0; - n_nodes = 0; - spa_list_for_each(node, &graph->node_list, link) { - node->n_hndl = n_hndl; - desc = node->desc; - n_control += desc->n_control; - n_nodes++; - setup_node_controls(node); - } graph->n_input = 0; graph->input = calloc(n_input * 16 * n_hndl, sizeof(struct graph_port)); graph->n_output = 0; @@ -1904,13 +1881,12 @@ static int setup_graph(struct graph *graph) /* order all nodes based on dependencies */ graph->n_hndl = 0; - graph->hndl = calloc(n_nodes * n_hndl, sizeof(struct graph_hndl)); - graph->n_control = 0; - graph->control_port = calloc(n_control, sizeof(struct port *)); + graph->hndl = calloc(graph->n_nodes * n_hndl, sizeof(struct graph_hndl)); while (true) { if ((node = find_next_node(graph)) == NULL) break; + node->n_hndl = n_hndl; desc = node->desc; d = desc->desc; @@ -1929,11 +1905,12 @@ static int setup_graph(struct graph *graph) spa_list_for_each(link, &node->notify_port[i].link_list, output_link) link->input->node->n_deps--; } - - /* collect all control ports on the graph */ for (i = 0; i < desc->n_control; i++) { - graph->control_port[graph->n_control] = &node->control_port[i]; - graph->n_control++; + /* any default values for the controls are set in the first instance + * of the control data. Duplicate this to the other instances now. */ + struct port *port = &node->control_port[i]; + for (j = 1; j < n_hndl; j++) + port->control_data[j] = port->control_data[0]; } } res = 0; @@ -1941,6 +1918,23 @@ error: return res; } +static int setup_graph_controls(struct graph *graph) +{ + struct node *node; + uint32_t i, n_control = 0; + + graph->control_port = calloc(graph->n_control, sizeof(struct port *)); + if (graph->control_port == NULL) + return -errno; + + spa_list_for_each(node, &graph->node_list, link) { + /* collect all control ports on the graph */ + for (i = 0; i < node->desc->n_control; i++) + graph->control_port[n_control++] = &node->control_port[i]; + } + return 0; +} + /** * filter.graph = { * nodes = [ @@ -2111,6 +2105,8 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) while (spa_json_get_string(poutputs, key, sizeof(key)) > 0) graph->output_names[graph->n_output_names++] = strdup(key); } + if ((res = setup_graph_controls(graph)) < 0) + return res; return 0; } From 8d3cb2cd844ddc9accc1ccb6066a91e3695beeb1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 13:18:34 +0200 Subject: [PATCH 0179/1014] filter-graph: clean control_port array on unload Because we created it when we loaded the graph. --- spa/plugins/filter-graph/filter-graph.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 2ef3794ef..693b08200 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1644,8 +1644,6 @@ static void unsetup_graph(struct graph *graph) graph->output = NULL; free(graph->hndl); graph->hndl = NULL; - free(graph->control_port); - graph->control_port = NULL; } static int setup_graph(struct graph *graph) @@ -2128,6 +2126,8 @@ static void graph_free(struct graph *graph) for (i = 0; i < graph->n_output_names; i++) free(graph->output_names[i]); free(graph->output_names); + free(graph->control_port); + graph->control_port = NULL; } static const struct spa_filter_graph_methods impl_filter_graph = { From 2ea8d3430cd4edfbc87b5fdcd8f80ad436d86678 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Apr 2025 15:10:15 +0200 Subject: [PATCH 0180/1014] tools: add c key to reset ERR counters Reset the ERR counters when pressing the c key. This only makes the current pw-top start counting from 0 again, it does not change anything globally on the server. It's still usefull because you can use it to let it count the number of new errors since the keypress. --- doc/dox/programs/pw-top.1.md | 10 ++++++++++ src/tools/pw-top.c | 25 +++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/doc/dox/programs/pw-top.1.md b/doc/dox/programs/pw-top.1.md index c57c33bc4..460fd80d4 100644 --- a/doc/dox/programs/pw-top.1.md +++ b/doc/dox/programs/pw-top.1.md @@ -177,6 +177,16 @@ Names are prefixed by *+* when they are linked to a driver (entry above with no +) \endparblock +# COMMANDS + +The following keys can be used in the interactive mode: + +\par q +Quit + +\par c +Clear the ERR counters. This does *not* clear the counters globally, +it will only reset the counters in this instance of *pw-top*. # OPTIONS diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index fc4da9239..d92462fa1 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -53,7 +53,9 @@ struct node { char name[MAX_NAME+1]; enum pw_node_state state; struct measurement measurement; + uint32_t measurement_base; struct driver info; + uint32_t info_base; struct node *driver; uint32_t generation; char format[MAX_FORMAT+1]; @@ -486,8 +488,9 @@ static const char *state_as_string(enum pw_node_state state, uint32_t transport) return "!"; } -static void print_node(struct data *d, struct driver *i, struct node *n, int y) +static void print_node(struct data *d, struct node *dr, struct node *n, int y) { + struct driver *i = &dr->info; char buf1[64]; char buf2[64]; char buf3[64]; @@ -534,7 +537,8 @@ static void print_node(struct data *d, struct driver *i, struct node *n, int y) print_perc(buf3, active, 64, waiting, quantum), print_perc(buf4, active, 64, busy, quantum), n->measurement.xrun_count == XRUN_INVALID ? - i->xrun_count : n->measurement.xrun_count, + i->xrun_count - dr->info_base : + n->measurement.xrun_count - n->measurement_base, active ? n->format : "", n->driver == n ? "" : " + ", n->name); @@ -570,7 +574,7 @@ static void do_refresh(struct data *d, bool force_refresh) if (n->driver != n) continue; - print_node(d, &n->info, n, y++); + print_node(d, n, n, y++); if(!d->batch_mode && y > LINES) break; @@ -581,7 +585,7 @@ static void do_refresh(struct data *d, bool force_refresh) if (f->driver != n || f == n) continue; - print_node(d, &n->info, f, y++); + print_node(d, n, f, y++); if(!d->batch_mode && y > LINES) break; @@ -612,6 +616,16 @@ static void do_timeout(void *data, uint64_t expirations) do_refresh(d, true); } +static void reset_xruns(struct data *d) +{ + struct node *n; + spa_list_for_each(n, &d->node_list, link) { + n->info_base = n->info.xrun_count; + n->measurement_base = n->measurement.xrun_count; + } + do_refresh(d, true); +} + static void profiler_profile(void *data, const struct spa_pod *pod) { struct data *d = data; @@ -788,6 +802,9 @@ static void do_handle_io(void *data, int fd, uint32_t mask) case 'q': pw_main_loop_quit(d->loop); break; + case 'c': + reset_xruns(d); + break; default: do_refresh(d, !d->batch_mode); break; From 92643f77f9010063af555fa10c0441eb049c18c5 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 25 Apr 2025 21:38:25 +0300 Subject: [PATCH 0181/1014] bluez5: backend-native: fix sco HUP|ERR debug message Print the error in td->err (the SO_ERROR) or else let the user know this is a regular hangup. The previous printout was always reporting EAGAIN (remainder in errno from the event loop code, I suppose) as an error, without any real data. --- spa/plugins/bluez5/backend-native.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 906e5268d..0e94227a1 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2841,11 +2841,20 @@ static int sco_release_cb(void *data) static void sco_event(struct spa_source *source) { struct spa_bt_transport *t = source->data; + struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { - spa_log_debug(backend->log, "transport %p: error on SCO socket: %s", t, strerror(errno)); + /* sco_ready() reads the socket error status in td->err */ sco_ready(t); + + if (td->err < 0) { + spa_log_info(backend->log, "transport %p: SCO socket error: %s (%d)", + t, strerror(-td->err), td->err); + } else { + spa_log_debug(backend->log, "transport %p: SCO socket hangup", t); + } + if (source->loop) spa_loop_remove_source(source->loop, source); if (t->fd >= 0) { From 39e079d8ac1ff51b20895012d5ed4dd9a61c09d7 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 29 Apr 2025 16:26:05 -0400 Subject: [PATCH 0182/1014] spa: video: Add H.265 as a format Quite similar to H.264. Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4674 --- spa/include/spa/param/format.h | 3 + spa/include/spa/param/video/format-utils.h | 1 + spa/include/spa/param/video/h265-utils.h | 79 ++++++++++++++++++++++ spa/include/spa/param/video/h265.h | 49 ++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 spa/include/spa/param/video/h265-utils.h create mode 100644 spa/include/spa/param/video/h265.h diff --git a/spa/include/spa/param/format.h b/spa/include/spa/param/format.h index eb3b851be..486f5d8ef 100644 --- a/spa/include/spa/param/format.h +++ b/spa/include/spa/param/format.h @@ -67,6 +67,7 @@ enum spa_media_subtype { SPA_MEDIA_SUBTYPE_vp8, SPA_MEDIA_SUBTYPE_vp9, SPA_MEDIA_SUBTYPE_bayer, + SPA_MEDIA_SUBTYPE_h265, SPA_MEDIA_SUBTYPE_START_Image = 0x30000, SPA_MEDIA_SUBTYPE_jpeg, @@ -132,6 +133,8 @@ enum spa_format { SPA_FORMAT_VIDEO_level, /**< (Int) */ SPA_FORMAT_VIDEO_H264_streamFormat, /**< (Id enum spa_h264_stream_format) */ SPA_FORMAT_VIDEO_H264_alignment, /**< (Id enum spa_h264_alignment) */ + SPA_FORMAT_VIDEO_H265_streamFormat, /**< (Id enum spa_h265_stream_format) */ + SPA_FORMAT_VIDEO_H265_alignment, /**< (Id enum spa_h265_alignment) */ /* Image Format keys */ SPA_FORMAT_START_Image = 0x30000, diff --git a/spa/include/spa/param/video/format-utils.h b/spa/include/spa/param/video/format-utils.h index 9efbef612..43bbb22c8 100644 --- a/spa/include/spa/param/video/format-utils.h +++ b/spa/include/spa/param/video/format-utils.h @@ -14,6 +14,7 @@ extern "C" { #include #include #include +#include #include #ifndef SPA_API_VIDEO_FORMAT_UTILS diff --git a/spa/include/spa/param/video/h265-utils.h b/spa/include/spa/param/video/h265-utils.h new file mode 100644 index 000000000..62a797811 --- /dev/null +++ b/spa/include/spa/param/video/h265-utils.h @@ -0,0 +1,79 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_VIDEO_H265_UTILS_H +#define SPA_VIDEO_H265_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include + +#ifndef SPA_API_VIDEO_H265_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_H265_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_H265_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_H265_UTILS int +spa_format_video_h265_parse(const struct spa_pod *format, + struct spa_video_info_h265 *info) +{ + return spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), + SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), + SPA_FORMAT_VIDEO_H265_streamFormat, SPA_POD_OPT_Id(&info->stream_format), + SPA_FORMAT_VIDEO_H265_alignment, SPA_POD_OPT_Id(&info->alignment)); +} + +SPA_API_VIDEO_H265_UTILS struct spa_pod * +spa_format_video_h265_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_video_info_h265 *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_h265), + 0); + if (info->size.width != 0 && info->size.height != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 0); + if (info->framerate.denom != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 0); + if (info->max_framerate.denom != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&info->max_framerate), 0); + if (info->stream_format != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_H265_streamFormat, SPA_POD_Id(info->stream_format), 0); + if (info->alignment != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_H265_alignment, SPA_POD_Id(info->alignment), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_VIDEO_H265_UTILS_H */ diff --git a/spa/include/spa/param/video/h265.h b/spa/include/spa/param/video/h265.h new file mode 100644 index 000000000..41b111147 --- /dev/null +++ b/spa/include/spa/param/video/h265.h @@ -0,0 +1,49 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_VIDEO_H265_H +#define SPA_VIDEO_H265_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include + +enum spa_h265_stream_format { + SPA_H265_STREAM_FORMAT_UNKNOWN = 0, + SPA_H265_STREAM_FORMAT_HVC1, + SPA_H265_STREAM_FORMAT_HEV1, + SPA_H265_STREAM_FORMAT_BYTESTREAM +}; + +enum spa_h265_alignment { + SPA_H265_ALIGNMENT_UNKNOWN = 0, + SPA_H265_ALIGNMENT_AU, + SPA_H265_ALIGNMENT_NAL +}; + +struct spa_video_info_h265 { + struct spa_rectangle size; + struct spa_fraction framerate; + struct spa_fraction max_framerate; + enum spa_h265_stream_format stream_format; + enum spa_h265_alignment alignment; +}; + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_VIDEO_H265_H */ From 21a468a9cd0874751217feadd094aede397e148d Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 29 Apr 2025 16:27:05 -0400 Subject: [PATCH 0183/1014] gst: Add H.265 support Same as H.264 for now, we might eventually want to add more stream formats etc. --- src/gst/gstpipewireformat.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 903d216e2..59765c360 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -43,6 +43,7 @@ static const struct media_type media_type_map[] = { { "image/jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg }, { "video/x-jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg }, { "video/x-h264", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_h264 }, + { "video/x-h265", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_h265 }, { "audio/x-mulaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw }, { "audio/x-alaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw }, { "audio/mpeg", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_mp3 }, @@ -1198,6 +1199,12 @@ gst_caps_from_format (const struct spa_pod *format) "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", NULL); + } + else if (media_subtype == SPA_MEDIA_SUBTYPE_h265) { + res = gst_caps_new_simple ("video/x-h265", + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", + NULL); } else { return NULL; } From 7f07448a809c976bfb468995062223d35fdacdf3 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Fri, 28 Mar 2025 14:18:10 +0000 Subject: [PATCH 0184/1014] spa: acp: Allow Volume control which supports mute to be used as a hardware mute Some devices have a hardware volume control, but not a dedicated hardware mute control. In some of these cases, the volume control is described as having a hardware mute when volume is 0. This is described in the TLV information of the volume control, when the SNDRV_CTL_TLVD_DB_SCALE_MUTE flag is set in the TLV structure. If set, alsa-lib will set the minimum dB value to -99999.99dB, which can be detected inside PipeWire. PipeWire can then use this hardware volume control to apply hardware mute, when set. In order to be able to set volumes and mutes separately, changing the volume whilst muted will save the value, but not write it to the hardware. When the device is unmuted, the saved value will be restored. Signed-off-by: Stefan Binding --- spa/plugins/alsa/acp/acp.c | 61 +++++++++++++++++++++++++++---- spa/plugins/alsa/acp/alsa-mixer.h | 1 + 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index bcd78c81d..07e9080d9 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -1266,8 +1266,15 @@ static int read_volume(pa_alsa_device *dev) if (!dev->mixer_handle) return 0; - if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0) - return res; + if (dev->mixer_path->has_volume_mute && dev->muted) { + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&r, &dev->hardware_volume, dev->base_volume); + pa_log_debug("Reading cached volume only."); + } else { + if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, + &dev->mapping->channel_map, &r)) < 0) + return res; + } /* Shift down by the base volume, so that 0dB becomes maximum volume */ pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume); @@ -1294,6 +1301,7 @@ static int read_volume(pa_alsa_device *dev) static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) { pa_cvolume r; + bool write_to_hw; if (v != &dev->real_volume) dev->real_volume = *v; @@ -1312,8 +1320,10 @@ static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume); + write_to_hw = !(dev->mixer_path->has_volume_mute && dev->muted); + if (pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, - &r, false, true) < 0) + &r, false, write_to_hw) < 0) return; /* Shift down by the base volume, so that 0dB becomes maximum volume */ @@ -1370,8 +1380,18 @@ static int read_mute(pa_alsa_device *dev) if (!dev->mixer_handle) return 0; - if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0) - return res; + if (dev->mixer_path->has_volume_mute) { + pa_cvolume mute_vol; + pa_cvolume r; + + pa_cvolume_mute(&mute_vol, dev->mapping->channel_map.channels); + if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0) + return res; + mute = pa_cvolume_equal(&mute_vol, &r); + } else { + if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0) + return res; + } if (mute == dev->muted) return 0; @@ -1400,7 +1420,25 @@ static void set_mute(pa_alsa_device *dev, bool mute) if (!dev->mixer_handle) return; - pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute); + if (dev->mixer_path->has_volume_mute) { + pa_cvolume r; + + if (mute) { + pa_cvolume_mute(&r, dev->mapping->channel_map.channels); + pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, + &r, false, true); + } else { + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume); + pa_log_debug("Restoring volume: %d", pa_cvolume_max(&dev->real_volume)); + if (pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, + &r, false, true) < 0) + pa_log_error("Unable to restore volume %d during unmute", + pa_cvolume_max(&dev->real_volume)); + } + } else { + pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute); + } } static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev) @@ -1436,6 +1474,11 @@ static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev) dev->base_volume = pa_sw_volume_from_dB(-dev->mixer_path->max_dB); dev->n_volume_steps = PA_VOLUME_NORM+1; + /* If minimum volume is set to -99999 dB, then volume control supports + * mute */ + if (dev->mixer_path->min_dB == -99999.99 && !dev->mixer_path->has_mute) + dev->mixer_path->has_volume_mute = true; + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(dev->base_volume)); } else { dev->decibel_volume = false; @@ -1450,7 +1493,8 @@ static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev) dev->device.base_volume = (float)pa_sw_volume_to_linear(dev->base_volume); dev->device.volume_step = 1.0f / dev->n_volume_steps; - if (impl->soft_mixer || !dev->mixer_path || !dev->mixer_path->has_mute) { + if (impl->soft_mixer || !dev->mixer_path || + (!dev->mixer_path->has_mute && !dev->mixer_path->has_volume_mute)) { dev->read_mute = NULL; dev->set_mute = NULL; pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); @@ -1458,7 +1502,8 @@ static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev) } else { dev->read_mute = read_mute; dev->set_mute = set_mute; - pa_log_info("Using hardware mute control."); + pa_log_info("Using hardware %smute control.", + dev->mixer_path->has_volume_mute ? "volume-" : ""); dev->device.flags |= ACP_DEVICE_HW_MUTE; } } diff --git a/spa/plugins/alsa/acp/alsa-mixer.h b/spa/plugins/alsa/acp/alsa-mixer.h index cbfac4ab7..75d4a03db 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.h +++ b/spa/plugins/alsa/acp/alsa-mixer.h @@ -225,6 +225,7 @@ struct pa_alsa_path { bool has_mute:1; bool has_volume:1; bool has_dB:1; + bool has_volume_mute:1; bool mute_during_activation:1; /* These two are used during probing only */ bool has_req_any:1; From 8e62b08e5877083b40520ed5267c5b72e97d2fe6 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 25 Apr 2025 20:24:27 +0300 Subject: [PATCH 0185/1014] bluez5: hfp-hf: don't change hf_state after sending AT+BCS The +BCS event may interrupt any of the initialization commands after SLC is established and by changing the state here we may lose track of the initialization sequence. There is no reason to have the hfp_hf_bcs state anyway. If the initialization sequence is over, we can remain in the hfp_hf_vgm state. --- spa/plugins/bluez5/backend-native.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 0e94227a1..f87cdbbd7 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -142,8 +142,7 @@ enum hfp_hf_state { hfp_hf_slc1, hfp_hf_slc2, hfp_hf_vgs, - hfp_hf_vgm, - hfp_hf_bcs + hfp_hf_vgm }; enum hsp_hs_state { @@ -2012,8 +2011,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) /* send codec selection to AG */ rfcomm_send_cmd(rfcomm, "AT+BCS=%u", selected_codec); - rfcomm->hf_state = hfp_hf_bcs; - if (!rfcomm->transport || (rfcomm->transport->codec != selected_codec) ) { if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport From 533c67710ed811f8f64a79119f6df846602ef466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 29 Apr 2025 15:29:12 +0200 Subject: [PATCH 0186/1014] bluez5: backend-native: Fix rfcomm_send_volume_cmd() This function always returns true. Change to not returning anything. --- spa/plugins/bluez5/backend-native.c | 36 +++++++++++------------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index f87cdbbd7..c734ad726 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -537,7 +537,7 @@ static bool rfcomm_hsp_ag(struct rfcomm *rfcomm, char* buf) return true; } -static bool rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) +static void rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) { struct spa_bt_transport_volume *t_volume; const char *format; @@ -565,8 +565,6 @@ static bool rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) spa_assert_not_reached(); rfcomm_send_cmd(rfcomm, "%s=%d", format, hw_volume); - - return true; } static bool rfcomm_hsp_hs(struct rfcomm *rfcomm, char* buf) @@ -595,15 +593,11 @@ static bool rfcomm_hsp_hs(struct rfcomm *rfcomm, char* buf) } } else if (spa_streq(buf, "OK")) { if (rfcomm->hs_state == hsp_hs_init2) { - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hs_state = hsp_hs_vgs; - else - rfcomm->hs_state = hsp_hs_init1; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); + rfcomm->hs_state = hsp_hs_vgs; } else if (rfcomm->hs_state == hsp_hs_vgs) { - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) - rfcomm->hs_state = hsp_hs_vgm; - else - rfcomm->hs_state = hsp_hs_init1; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); + rfcomm->hs_state = hsp_hs_vgm; } } @@ -2464,13 +2458,13 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) /* Report volume on SLC establishment */ SPA_FALLTHROUGH; case hfp_hf_slc2: - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hf_state = hfp_hf_vgs; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); + rfcomm->hf_state = hfp_hf_vgs; break; case hfp_hf_vgs: rfcomm->hf_state = hfp_hf_slc1; - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) - rfcomm->hf_state = hfp_hf_vgm; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); + rfcomm->hf_state = hfp_hf_vgm; break; default: break; @@ -2999,15 +2993,11 @@ static void sco_listen_event(struct spa_source *source) /* Report initial volume to remote */ if (t->profile == SPA_BT_PROFILE_HSP_AG) { - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hs_state = hsp_hs_vgs; - else - rfcomm->hs_state = hsp_hs_init1; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); + rfcomm->hs_state = hsp_hs_vgs; } else if (t->profile == SPA_BT_PROFILE_HFP_AG) { - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hf_state = hfp_hf_vgs; - else - rfcomm->hf_state = hfp_hf_slc1; + rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); + rfcomm->hf_state = hfp_hf_vgs; } spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_PENDING); From 612cbf517682db36429e65805f75a0c83ace1c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Tue, 29 Apr 2025 15:24:38 +0200 Subject: [PATCH 0187/1014] bluez5: hfp-hf: Fix HFP HF states hfp_hf_slc1 is not used so remove it, and rename hfp_hf_slc2 to hfp_hf_clcc to be consistent with other states. --- spa/plugins/bluez5/backend-native.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index c734ad726..fa78545fa 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -139,8 +139,7 @@ enum hfp_hf_state { hfp_hf_ccwa, hfp_hf_cmee, hfp_hf_nrec, - hfp_hf_slc1, - hfp_hf_slc2, + hfp_hf_clcc, hfp_hf_vgs, hfp_hf_vgm }; @@ -2445,11 +2444,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } SPA_FALLTHROUGH; case hfp_hf_nrec: - rfcomm->hf_state = hfp_hf_slc1; - if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, "AT+CLCC"); - rfcomm->hf_state = hfp_hf_slc2; + rfcomm->hf_state = hfp_hf_clcc; break; } else { // TODO: Create calls if CIND reports one during SLC setup @@ -2457,12 +2454,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) /* Report volume on SLC establishment */ SPA_FALLTHROUGH; - case hfp_hf_slc2: + case hfp_hf_clcc: rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); rfcomm->hf_state = hfp_hf_vgs; break; case hfp_hf_vgs: - rfcomm->hf_state = hfp_hf_slc1; rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); rfcomm->hf_state = hfp_hf_vgm; break; @@ -2995,7 +2991,10 @@ static void sco_listen_event(struct spa_source *source) if (t->profile == SPA_BT_PROFILE_HSP_AG) { rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); rfcomm->hs_state = hsp_hs_vgs; - } else if (t->profile == SPA_BT_PROFILE_HFP_AG) { + } else if (t->profile == SPA_BT_PROFILE_HFP_AG && rfcomm->hf_state > hfp_hf_vgs) { + /* Report volume only if SLC and setup sequence has been completed + * else this could break the sequence. + * The volumes will be reported at the end of the setup sequence. */ rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); rfcomm->hf_state = hfp_hf_vgs; } From 8120493edb45ea191058a23ff00717158edc52f5 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Thu, 16 Jan 2025 17:21:03 +0530 Subject: [PATCH 0188/1014] bluez5-dbus: Track ASHA HiSyncId & Side information in transport ASHA devices with the same HiSyncId are a pair and this will be used later on to set them up together with a combine sink like device set for BAP. Side information is required for channel information when setting up the combine sink. --- spa/plugins/bluez5/bluez5-dbus.c | 29 +++++++++++++++++++++++------ spa/plugins/bluez5/defs.h | 4 ++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index dc77ffed2..585ed68d4 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -155,6 +155,9 @@ struct spa_bt_remote_endpoint { int capabilities_len; bool delay_reporting; bool acceptor; + + bool asha_right_side; + uint64_t hisyncid; }; #define METADATA_MAX_LEN 255 @@ -2720,6 +2723,12 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en free(remote_endpoint->transport_path); remote_endpoint->transport_path = strdup(value); } + else if (spa_streq(key, "Side")) { + if (spa_streq(value, "right")) + remote_endpoint->asha_right_side = true; + else + remote_endpoint->asha_right_side = false; + } } else if (type == DBUS_TYPE_BOOLEAN) { int value; @@ -2776,11 +2785,11 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->capabilities_len = len; } } - /* HiSyncId property is present for ASHA */ + /* + * HiSyncId property is present for ASHA. An ASHA "left" and + * "right" device pair will always have the same "HiSyncId". + */ else if (spa_streq(key, "HiSyncId")) { - /* - * TODO: Required for Stereo support in ASHA, for now just log. - */ DBusMessageIter iter; uint8_t *value; int len; @@ -2791,7 +2800,12 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en dbus_message_iter_recurse(&it[1], &iter); dbus_message_iter_get_fixed_array(&iter, &value, &len); - spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); + if (len != 8 /* HiSyncId will always be 8 bytes */) + goto next; + + remote_endpoint->hisyncid = *(uint64_t *)value; + + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%zd", remote_endpoint, key, remote_endpoint->hisyncid); } else spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); @@ -4142,6 +4156,8 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, transport->profile = SPA_BT_PROFILE_ASHA_SINK; transport->media_codec = codec; transport->device = remote_endpoint->device; + transport->hisyncid = remote_endpoint->hisyncid; + transport->asha_right_side = remote_endpoint->asha_right_side; spa_list_append(&remote_endpoint->device->transport_list, &transport->device_link); @@ -4157,7 +4173,8 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, transport_sync_volume(transport); - spa_log_debug(monitor->log, "ASHA transport setup complete"); + const char *side = transport->asha_right_side ? "right" : "left"; + spa_log_debug(monitor->log, "ASHA transport setup complete for %s side", side); return 0; } diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index c45bf3496..a97b9ecb4 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -687,6 +687,10 @@ struct spa_bt_transport { struct spa_hook_list listener_list; struct spa_callbacks impl; + /* For ASHA */ + bool asha_right_side; + uint64_t hisyncid; + /* user_data must be the last item in the struct */ void *user_data; }; From 74fe7728d22455922002bd741c20fd442fbbc7f6 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 21 Jan 2025 19:21:13 +0530 Subject: [PATCH 0189/1014] bluez5: media-sink: Set up node group for ASHA ASHA devices with the same HiSyncId should use the same node group/driver. --- spa/plugins/bluez5/media-sink.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 49dc5de79..79660f1a5 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1496,6 +1496,10 @@ static void emit_node_info(struct impl *this, bool full) this->transport->device->adapter->address, this->transport->bap_big); node_group = node_group_buf; + } else if (this->transport && (this->transport->profile & SPA_BT_PROFILE_ASHA_SINK)) { + spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-asha-%zd\"]", + this->transport->hisyncid); + node_group = node_group_buf; } const char *codec_profile = this->codec->asha ? "ASHA" : (this->codec->bap ? "BAP" : "A2DP"); From db47c3e44241e2d08b299a7f104fe4b601b84454 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 21 Jan 2025 16:44:28 +0530 Subject: [PATCH 0190/1014] bluez5-device: Expose HiSyncId and Side information on ASHA node --- spa/plugins/bluez5/bluez5-device.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index b6357e9cd..99bfed8b1 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -651,7 +651,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, { struct spa_bt_device *device = this->bt_dev; struct spa_device_object_info info; - struct spa_dict_item items[11]; + struct spa_dict_item items[13]; uint32_t n_items = 0; char transport[32], str_id[32], object_path[512]; bool is_dyn_node = SPA_FLAG_IS_SET(id, DYNAMIC_NODE_ID_FLAG); @@ -696,6 +696,14 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, items[n_items] = SPA_DICT_ITEM_INIT("object.path", object_path); n_items++; } + if (t->media_codec->asha) { + char hisyncid[32] = { 0 }; + spa_scnprintf(hisyncid, sizeof(hisyncid), "%zd", t->hisyncid); + items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.asha.hisyncid", hisyncid); + n_items++; + items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.asha.side", t->asha_right_side ? "right" : "left"); + n_items++; + } info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; From 69b5fe839553394965c347da641c08e9f206cd39 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 3 Mar 2025 11:48:27 +0530 Subject: [PATCH 0191/1014] bluez5: g722: Do not set sequence number in start_encode In ASHA, we might need to sync sequence numbers between left and right side media sink. If the sequence number is added in start encode, it becomes difficult to set the sequence number again when a call to reset_buffer in media sink might have happened already. This effectively reverts commit ab5f81b9a. --- spa/plugins/bluez5/asha-codec-g722.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/asha-codec-g722.c b/spa/plugins/bluez5/asha-codec-g722.c index 1043b4fe8..3e2ec699a 100644 --- a/spa/plugins/bluez5/asha-codec-g722.c +++ b/spa/plugins/bluez5/asha-codec-g722.c @@ -18,6 +18,7 @@ static struct spa_log *spalog; struct impl { g722_encode_state_t encode; unsigned int codesize; + uint8_t seqnum; }; static int codec_reduce_bitpool(void *data) @@ -44,10 +45,12 @@ static int codec_get_block_size(void *data) static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { - /* Payload for ASHA must be preceded by 1-byte sequence number */ - *(uint8_t *)dst = seqnum % 256; + struct impl *this = data; - return 1; + /* Payload for ASHA must be preceded by 1-byte sequence number */ + this->seqnum = seqnum % 256; + + return 0; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, @@ -118,6 +121,7 @@ static int codec_encode(void *data, size_t *dst_out, int *need_flush) { struct impl *this = data; + uint8_t *dest = (uint8_t *)dst; size_t src_sz; int ret; @@ -133,13 +137,16 @@ static int codec_encode(void *data, src_sz = (src_size > this->codesize) ? this->codesize : src_size; - ret = g722_encode(&this->encode, dst, src, src_sz / 2 /* S16LE */); + *dest = this->seqnum; + dest++; + + ret = g722_encode(&this->encode, dest, src, src_sz / 2 /* S16LE */); if (ret < 0) { spa_log_error(spalog, "encode error: %d", ret); return -EIO; } - *dst_out = ret; + *dst_out = ret + ASHA_HEADER_SZ; *need_flush = NEED_FLUSH_ALL; return src_sz; From 24843a73c0ea450da75dade10826bc600d447c06 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 10 Feb 2025 22:16:44 +0530 Subject: [PATCH 0192/1014] bluez5: media-sink: Support for ASHA stereo --- spa/plugins/bluez5/media-sink.c | 313 ++++++++++++++++++++++++++++++-- 1 file changed, 301 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 79660f1a5..82abd417f 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -104,6 +104,26 @@ struct port { struct spa_bt_rate_control ratectl; }; +#define ASHA_ENCODED_PKT_SZ 161 /* 160 bytes encoded + 1 byte sequence number */ +#define ASHA_CONN_INTERVAL 20 * SPA_NSEC_PER_MSEC + +struct spa_bt_asha { + struct spa_source flush_source; + struct spa_source timer_source; + int timerfd; + + uint8_t buf[512]; + uint8_t seqnum_pending; + + uint64_t prev_time; + uint64_t next_time; + + unsigned int first_send:1; + unsigned int flush_pending:1; + unsigned int poll_pending:1; + unsigned int set_timer:1; +}; + struct impl { struct spa_handle handle; struct spa_node node; @@ -182,15 +202,37 @@ struct impl { uint32_t header_size; uint32_t block_count; uint16_t seqnum; + uint64_t last_seqnum; uint32_t timestamp; uint64_t sample_count; uint8_t tmp_buffer[BUFFER_SIZE]; uint32_t tmp_buffer_used; uint32_t fd_buffer_size; + + struct spa_bt_asha *asha; + struct spa_list asha_link; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) +static struct spa_list asha_sinks; + +static struct impl *find_other_asha(struct impl *this) +{ + struct impl *other; + + spa_list_for_each(other, &asha_sinks, asha_link) { + if (this == other) + continue; + + if (this->transport->hisyncid == other->transport->hisyncid) { + return other; + } + } + + return NULL; +} + static void reset_props(struct impl *this, struct props *props) { props->latency_offset = 0; @@ -299,6 +341,29 @@ static int set_timers(struct impl *this) return set_timeout(this, this->following ? 0 : this->next_time); } +static int set_asha_timeout(struct impl *this, uint64_t time) +{ + struct itimerspec ts; + + ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; + ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; + ts.it_interval.tv_sec = 0; + ts.it_interval.tv_nsec = 0; + + return spa_system_timerfd_settime(this->data_system, + this->asha->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); +} + +static int set_asha_timer(struct impl *this) +{ + struct timespec now; + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); + this->asha->prev_time = this->asha->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + return set_asha_timeout(this, this->asha->next_time); +} + static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; @@ -559,7 +624,7 @@ static int reset_buffer(struct impl *this) this->need_flush = 0; this->block_count = 0; this->fragment = false; - this->timestamp = this->codec->bap ? (get_reference_time(this, NULL) / SPA_NSEC_PER_USEC) + this->timestamp = (this->codec->bap || this->codec->asha) ? (get_reference_time(this, NULL) / SPA_NSEC_PER_USEC) : this->sample_count; this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer, sizeof(this->buffer), @@ -763,17 +828,18 @@ static void enable_flush_timer(struct impl *this, bool enabled) static int flush_data(struct impl *this, uint64_t now_time) { - int written; - uint32_t total_frames; struct port *port = &this->port; + bool is_asha = this->codec->asha; + uint32_t total_frames; + int written; int unused_buffer; spa_assert(this->transport_started); /* I/O in error state? */ - if (this->transport == NULL || !this->flush_source.loop) + if (this->transport == NULL || (!this->flush_source.loop && !is_asha)) return -EIO; - if (!this->flush_timer_source.loop && !this->transport->iso_io) + if (!this->flush_timer_source.loop && !this->transport->iso_io && !is_asha) return -EIO; if (this->transport->iso_io && !this->iso_pending) @@ -791,6 +857,7 @@ again: return res; } } + while (!spa_list_is_empty(&port->ready) && !this->need_flush) { uint8_t *src; uint32_t n_bytes, n_frames; @@ -871,6 +938,33 @@ again: return 0; } + if (is_asha) { + struct spa_bt_asha *asha = this->asha; + + if (this->need_flush && !asha->flush_pending) { + /* + * For ASHA, we cannot send more than one encoded + * packet at a time and can only send them spaced + * 20 ms apart which is the ASHA connection interval. + * All encoded packets will be 160 bytes + 1 byte + * sequence number. + * + * Unlike the A2DP flow below, we cannot delay the + * output by 1 packet. While that might work for the + * mono case, for stereo that make the two sides be + * out of sync with each other and if the two sides + * differ by more than 3 credits, we would have to + * drop packets or the devices themselves might drop + * the connection. + */ + memcpy(asha->buf, this->buffer, this->buffer_used); + asha->flush_pending = true; + reset_buffer(this); + } + + return 0; + } + if (this->flush_pending) { spa_log_trace(this->log, "%p: wait for flush timer", this); return 0; @@ -1196,6 +1290,142 @@ static void media_on_timeout(struct spa_source *source) set_timeout(this, this->next_time); } +static void media_asha_flush_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + struct port *port = &this->port; + struct spa_bt_asha *asha = this->asha; + const char *address = this->transport->device->address; + struct timespec ts; + int res, written; + uint64_t exp, now; + uint8_t seqnum; + + if (this->started) { + if ((res = spa_system_timerfd_read(this->data_system, asha->timerfd, &exp)) < 0) { + if (res != -EAGAIN) + spa_log_warn(this->log, "error reading ASHA timerfd: %s", + spa_strerror(res)); + return; + } + } + + spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); + now = SPA_TIMESPEC_TO_NSEC(&ts); + + asha->next_time = (uint64_t)(asha->prev_time + ASHA_CONN_INTERVAL * port->ratectl.corr); + asha->prev_time = asha->next_time; + + if (asha->poll_pending) { + /* + * We have pending data to send but we could not send it + * before the connection interval elapsed. + */ + asha->poll_pending = false; + spa_log_trace(this->log, "%p: ASHA failed to send seqnum %d for %s", + this, asha->seqnum_pending, address); + } + + if (asha->flush_pending) { + if (!asha->first_send) { + /* + * Sync sequence numbers on first send. If the other + * side has already started sending or the current + * side is coming up later, we need to start the + * sequence number based on the other side. + */ + struct impl *other = find_other_asha(this); + if (other && other->asha->first_send) { + uint16_t init_seqnum = other->seqnum - 1; + + spa_log_trace(this->log, "%p: ASHA using seqnum %d for %s", + this, init_seqnum, address); + + asha->buf[0] = init_seqnum; + this->seqnum = init_seqnum; + + reset_buffer(this); + } + + asha->first_send = true; + } + + seqnum = asha->buf[0]; + written = send(asha->flush_source.fd, asha->buf, + ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); + /* + * For ASHA, when we are out of LE credits and cannot write to + * the socket, return value of `send` will be -EAGAIN. + * + * If we fail to send here, send on the next `poll` which + * ideally will be a few ms away on receiving LE credits. We + * cannot delay the flush till the next cycle. + */ + if (written < 0) { + asha->seqnum_pending = seqnum; + asha->poll_pending = true; + asha->flush_pending = false; + spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on timer for %s, written:%d", + this, seqnum, address, -errno); + goto skip_flush; + } + + if (written > 0) { + asha->flush_pending = false; + spa_log_trace(this->log, "%p: ASHA flush %d seqnum for %s, ts:%u", + this, seqnum, address, this->timestamp); + } + } + + flush_data(this, now); + +skip_flush: + set_asha_timeout(this, asha->next_time); +} + + +static void media_asha_cb(struct spa_source *source) +{ + struct impl *this = source->data; + struct spa_bt_asha *asha = this->asha; + const char *address = this->transport->device->address; + uint8_t seqnum; + int written; + + if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { + spa_log_error(this->log, "%p: ASHA source error %d on %s", this, source->rmask, address); + + if (asha->flush_source.loop) + spa_loop_remove_source(this->data_loop, &asha->flush_source); + + return; + } + + if (source->rmask & SPA_IO_OUT) { + if (this->transport == NULL || !asha->poll_pending) { + return; + } + + seqnum = asha->buf[0]; + written = send(asha->flush_source.fd, asha->buf, + ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); + /* + * For ASHA, when we are out of LE credits and cannot write to + * the socket, return value of `send` will be -EAGAIN. + */ + if (written < 0) { + spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on poll for %s, written:%d", + this, seqnum, address, -errno); + } + + if (written > 0) { + asha->poll_pending = false; + spa_log_trace(this->log, "%p: ASHA flush %d seqnum for %s", + this, seqnum, address); + } + } +} + static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { @@ -1212,6 +1442,7 @@ static int transport_start(struct impl *this) socklen_t len; uint8_t *conf; uint32_t flags; + bool is_asha; if (this->transport_started) return 0; @@ -1226,6 +1457,7 @@ static int transport_start(struct impl *this) conf = this->transport->configuration; size = this->transport->configuration_len; + is_asha = this->codec->asha; spa_log_debug(this->log, "Transport configuration:"); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); @@ -1299,7 +1531,7 @@ static int transport_start(struct impl *this) this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); - if (!this->transport->iso_io) { + if (!this->transport->iso_io && !is_asha) { this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; this->flush_timer_source.func = media_on_flush_timeout; @@ -1308,12 +1540,39 @@ static int transport_start(struct impl *this) spa_loop_add_source(this->data_loop, &this->flush_timer_source); } - this->flush_source.data = this; - this->flush_source.fd = this->transport->fd; - this->flush_source.func = media_on_flush_error; - this->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; - this->flush_source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->flush_source); + if (!is_asha) { + this->flush_source.data = this; + this->flush_source.fd = this->transport->fd; + this->flush_source.func = media_on_flush_error; + this->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; + this->flush_source.rmask = 0; + spa_loop_add_source(this->data_loop, &this->flush_source); + } + + if (is_asha) { + struct spa_bt_asha *asha = this->asha; + + asha->first_send = false; + asha->flush_pending = false; + asha->poll_pending = false; + asha->set_timer = false; + + asha->timer_source.data = this; + asha->timer_source.fd = this->asha->timerfd; + asha->timer_source.func = media_asha_flush_timeout; + asha->timer_source.mask = SPA_IO_IN; + asha->timer_source.rmask = 0; + spa_loop_add_source(this->data_loop, &asha->timer_source); + + asha->flush_source.data = this; + asha->flush_source.fd = this->transport->fd; + asha->flush_source.func = media_asha_cb; + asha->flush_source.mask = SPA_IO_OUT | SPA_IO_ERR | SPA_IO_HUP; + asha->flush_source.rmask = 0; + spa_loop_add_source(this->data_loop, &asha->flush_source); + + spa_list_append(&asha_sinks, &this->asha_link); + } this->resync = RESYNC_CYCLES; this->flush_pending = false; @@ -1401,6 +1660,13 @@ static int do_remove_transport_source(struct spa_loop *loop, spa_loop_remove_source(this->data_loop, &this->flush_source); if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); + if (this->codec->asha) { + if (this->asha->timer_source.loop) + spa_loop_remove_source(this->data_loop, &this->asha->timer_source); + if (this->asha->flush_source.loop) + spa_loop_remove_source(this->data_loop, &this->asha->flush_source); + spa_list_remove(&this->asha_link); + } enable_flush_timer(this, false); if (this->transport->iso_io) @@ -1991,6 +2257,11 @@ static int impl_node_process(void *object) return SPA_STATUS_STOPPED; } + if (this->codec->asha && !this->asha->set_timer) { + this->asha->set_timer = true; + set_asha_timer(this); + } + return SPA_STATUS_HAVE_DATA; } @@ -2116,6 +2387,10 @@ static int impl_clear(struct spa_handle *handle) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); spa_system_close(this->data_system, this->flush_timerfd); + if (this->codec->asha) { + spa_system_close(this->data_system, this->asha->timerfd); + free(this->asha); + } return 0; } @@ -2259,6 +2534,20 @@ impl_init(const struct spa_handle_factory *factory, this->flush_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + if (this->codec->asha) { + this->asha = calloc(1, sizeof(struct spa_bt_asha)); + if (this->asha == NULL) + return -ENOMEM; + + if (!spa_list_is_initialized(&asha_sinks)) { + spa_list_init(&asha_sinks); + spa_log_info(this->log, "Initialized ASHA media sink list"); + } + + this->asha->timerfd = spa_system_timerfd_create(this->data_system, + CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + } + return 0; } From 4d22296d3a905663013490c9449bc0e5b0e8af4e Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 21 Apr 2025 06:08:47 -0400 Subject: [PATCH 0193/1014] bluez5: media-sink: Use reference time for ASHA sequence numbers This improves the sequence number generation by tying it to get_reference_time. --- spa/plugins/bluez5/media-sink.c | 54 +++++++++++++++++---------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 82abd417f..5b05279af 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -105,7 +105,7 @@ struct port { }; #define ASHA_ENCODED_PKT_SZ 161 /* 160 bytes encoded + 1 byte sequence number */ -#define ASHA_CONN_INTERVAL 20 * SPA_NSEC_PER_MSEC +#define ASHA_CONN_INTERVAL (20 * SPA_NSEC_PER_MSEC) struct spa_bt_asha { struct spa_source flush_source; @@ -115,10 +115,10 @@ struct spa_bt_asha { uint8_t buf[512]; uint8_t seqnum_pending; + uint64_t ref_t0; uint64_t prev_time; uint64_t next_time; - unsigned int first_send:1; unsigned int flush_pending:1; unsigned int poll_pending:1; unsigned int set_timer:1; @@ -1290,6 +1290,21 @@ static void media_on_timeout(struct spa_source *source) set_timeout(this, this->next_time); } +static uint64_t asha_seqnum(struct impl *this) +{ + uint64_t tn = get_reference_time(this, NULL); + double dt = tn - this->asha->ref_t0; + double num_packets = dt / ASHA_CONN_INTERVAL; + + spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %g", + tn, this->asha->ref_t0, num_packets); + + if (this->asha->ref_t0 > tn) + return 0; + + return (uint64_t)round(num_packets) % 256; +} + static void media_asha_flush_timeout(struct spa_source *source) { struct impl *this = source->data; @@ -1327,29 +1342,6 @@ static void media_asha_flush_timeout(struct spa_source *source) } if (asha->flush_pending) { - if (!asha->first_send) { - /* - * Sync sequence numbers on first send. If the other - * side has already started sending or the current - * side is coming up later, we need to start the - * sequence number based on the other side. - */ - struct impl *other = find_other_asha(this); - if (other && other->asha->first_send) { - uint16_t init_seqnum = other->seqnum - 1; - - spa_log_trace(this->log, "%p: ASHA using seqnum %d for %s", - this, init_seqnum, address); - - asha->buf[0] = init_seqnum; - this->seqnum = init_seqnum; - - reset_buffer(this); - } - - asha->first_send = true; - } - seqnum = asha->buf[0]; written = send(asha->flush_source.fd, asha->buf, ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); @@ -1377,6 +1369,7 @@ static void media_asha_flush_timeout(struct spa_source *source) } } + this->seqnum = asha_seqnum(this); flush_data(this, now); skip_flush: @@ -1552,7 +1545,6 @@ static int transport_start(struct impl *this) if (is_asha) { struct spa_bt_asha *asha = this->asha; - asha->first_send = false; asha->flush_pending = false; asha->poll_pending = false; asha->set_timer = false; @@ -2258,6 +2250,16 @@ static int impl_node_process(void *object) } if (this->codec->asha && !this->asha->set_timer) { + struct impl *other = find_other_asha(this); + if (other && other->asha->ref_t0 != 0) { + this->asha->ref_t0 = other->asha->ref_t0; + this->seqnum = asha_seqnum(this); + reset_buffer(this); + } else { + this->asha->ref_t0 = get_reference_time(this, NULL); + this->seqnum = 0; + } + this->asha->set_timer = true; set_asha_timer(this); } From efb4a1df259e6fa3ec431fe04563c630a4d35d68 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 21 Apr 2025 06:49:42 -0400 Subject: [PATCH 0194/1014] bluez5: media-sink: Snap timer to ASHA connection interval For ASHA stereo, the timer for flushing packets on both sides of a pair should be as closed to each other as possible. Also set seqnum before first flush so other side sends the correct packets at the start. --- spa/plugins/bluez5/media-sink.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 5b05279af..a10d7b4b4 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -116,6 +116,7 @@ struct spa_bt_asha { uint8_t seqnum_pending; uint64_t ref_t0; + uint64_t ref_timer; uint64_t prev_time; uint64_t next_time; @@ -354,12 +355,23 @@ static int set_asha_timeout(struct impl *this, uint64_t time) this->asha->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } -static int set_asha_timer(struct impl *this) +static int set_asha_timer(struct impl *this, bool snap) { struct timespec now; + uint64_t now_ns, offset = 0; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - this->asha->prev_time = this->asha->next_time = SPA_TIMESPEC_TO_NSEC(&now); + now_ns = SPA_TIMESPEC_TO_NSEC(&now); + + if (snap) { + /* If requested, snap our wakeup time to a 20ms interval */ + offset = (now_ns - this->asha->ref_timer) % ASHA_CONN_INTERVAL; + } else { + /* Else, remember when we started this timer so the other side can use it */ + this->asha->ref_timer = now_ns; + } + + this->asha->prev_time = this->asha->next_time = now_ns + offset; return set_asha_timeout(this, this->asha->next_time); } @@ -2243,25 +2255,26 @@ static int impl_node_process(void *object) setup_matching(this); - spa_log_trace(this->log, "%p: on process time:%"PRIu64, this, this->process_time); - if ((res = flush_data(this, this->current_time)) < 0) { - io->status = res; - return SPA_STATUS_STOPPED; - } - if (this->codec->asha && !this->asha->set_timer) { struct impl *other = find_other_asha(this); if (other && other->asha->ref_t0 != 0) { this->asha->ref_t0 = other->asha->ref_t0; this->seqnum = asha_seqnum(this); reset_buffer(this); + set_asha_timer(this, true); } else { this->asha->ref_t0 = get_reference_time(this, NULL); this->seqnum = 0; + set_asha_timer(this, false); } this->asha->set_timer = true; - set_asha_timer(this); + } + + spa_log_trace(this->log, "%p: on process time:%"PRIu64, this, this->process_time); + if ((res = flush_data(this, this->current_time)) < 0) { + io->status = res; + return SPA_STATUS_STOPPED; } return SPA_STATUS_HAVE_DATA; From c5b5476aa46ee70b0671c75af51bf6b5a3679b60 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 22 Apr 2025 11:56:11 +0530 Subject: [PATCH 0195/1014] bluez5: media-sink: Skip samples to align audio data for ASHA For ASHA, we want the media sinks to send packets where the ASHA packet sequence number corresponds to time position of audio in that packet. --- spa/plugins/bluez5/media-sink.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index a10d7b4b4..0286348fb 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -117,6 +117,8 @@ struct spa_bt_asha { uint64_t ref_t0; uint64_t ref_timer; + uint64_t skip_frames; + uint64_t prev_time; uint64_t next_time; @@ -366,9 +368,12 @@ static int set_asha_timer(struct impl *this, bool snap) if (snap) { /* If requested, snap our wakeup time to a 20ms interval */ offset = (now_ns - this->asha->ref_timer) % ASHA_CONN_INTERVAL; + this->asha->skip_frames = offset * this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; + spa_log_debug(this->log, "will skip %"PRIu64 " frames", this->asha->skip_frames); } else { /* Else, remember when we started this timer so the other side can use it */ this->asha->ref_timer = now_ns; + this->asha->skip_frames = 0; } this->asha->prev_time = this->asha->next_time = now_ns + offset; @@ -886,6 +891,14 @@ again: avail = d[0].chunk->size - port->ready_offset; avail /= port->frame_size; + if (is_asha && this->asha->skip_frames) { + /* Skip initial frames if needed */ + uint64_t skip = SPA_MIN(this->asha->skip_frames, avail); + spa_log_trace(this->log, "skipping %"PRIu64 " frames", skip); + avail -= skip; + this->asha->skip_frames -= skip; + } + offs = index % d[0].maxsize; n_frames = avail; n_bytes = n_frames * port->frame_size; From ce8abfc5cceb57bbf1331522280e585fcdd7db64 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 22 Apr 2025 16:41:30 -0400 Subject: [PATCH 0196/1014] bluez5: media-sink: Refine ASHA other-side timer setup further Let's just directly use the next timer value from the other side directly, and the reference time as well to calculate the expected next sample position we want to send from on this side. --- spa/plugins/bluez5/media-sink.c | 45 +++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 0286348fb..3004a3501 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -116,7 +116,6 @@ struct spa_bt_asha { uint8_t seqnum_pending; uint64_t ref_t0; - uint64_t ref_timer; uint64_t skip_frames; uint64_t prev_time; @@ -220,6 +219,8 @@ struct impl { static struct spa_list asha_sinks; +static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret); + static struct impl *find_other_asha(struct impl *this) { struct impl *other; @@ -357,26 +358,38 @@ static int set_asha_timeout(struct impl *this, uint64_t time) this->asha->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } -static int set_asha_timer(struct impl *this, bool snap) +static int set_asha_timer(struct impl *this, struct impl *other) { - struct timespec now; - uint64_t now_ns, offset = 0; + uint64_t time; - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - now_ns = SPA_TIMESPEC_TO_NSEC(&now); + if (other) { + /* Try to line up our timer with the other side, and drop samples so we're sending + * the same sample position on both sides */ + uint64_t other_samples = (get_reference_time(other, NULL) - other->asha->ref_t0) * + this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; - if (snap) { - /* If requested, snap our wakeup time to a 20ms interval */ - offset = (now_ns - this->asha->ref_timer) % ASHA_CONN_INTERVAL; - this->asha->skip_frames = offset * this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; - spa_log_debug(this->log, "will skip %"PRIu64 " frames", this->asha->skip_frames); + if (other->asha->next_time < this->process_time) { + /* Other side has not yet been scheduled in this graph cycle, we expect + * there might be one packet left from the previous cycle at most */ + time = other->asha->next_time + ASHA_CONN_INTERVAL; + other_samples += ASHA_CONN_INTERVAL * + this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; + } else { + /* Other side has set up its next cycle, catch up */ + time = other->asha->next_time; + } + + /* Since the quantum and packet size aren't correlated, drop any samples from this + * cycle that might have been used to send a packet starting in the previous cycle */ + this->asha->skip_frames = other_samples % this->process_duration; } else { - /* Else, remember when we started this timer so the other side can use it */ - this->asha->ref_timer = now_ns; + time = this->process_time; this->asha->skip_frames = 0; } - this->asha->prev_time = this->asha->next_time = now_ns + offset; + spa_log_debug(this->log, "will skip %"PRIu64 " frames", this->asha->skip_frames); + + this->asha->next_time = time; return set_asha_timeout(this, this->asha->next_time); } @@ -2274,11 +2287,11 @@ static int impl_node_process(void *object) this->asha->ref_t0 = other->asha->ref_t0; this->seqnum = asha_seqnum(this); reset_buffer(this); - set_asha_timer(this, true); + set_asha_timer(this, other); } else { this->asha->ref_t0 = get_reference_time(this, NULL); this->seqnum = 0; - set_asha_timer(this, false); + set_asha_timer(this, NULL); } this->asha->set_timer = true; From b02b69b2718d130b09ffd6c1c13e39ecf36ab291 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 25 Apr 2025 13:03:52 +0530 Subject: [PATCH 0197/1014] bluez5: media-sink: Drop a redundant ASHA state variable --- spa/plugins/bluez5/media-sink.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 3004a3501..77d1159e9 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -118,7 +118,6 @@ struct spa_bt_asha { uint64_t ref_t0; uint64_t skip_frames; - uint64_t prev_time; uint64_t next_time; unsigned int flush_pending:1; @@ -1366,8 +1365,7 @@ static void media_asha_flush_timeout(struct spa_source *source) spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); now = SPA_TIMESPEC_TO_NSEC(&ts); - asha->next_time = (uint64_t)(asha->prev_time + ASHA_CONN_INTERVAL * port->ratectl.corr); - asha->prev_time = asha->next_time; + asha->next_time += (uint64_t)(ASHA_CONN_INTERVAL * port->ratectl.corr); if (asha->poll_pending) { /* From 1b6830f68f29c44421837324ba810676119c6ed3 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 28 Apr 2025 11:42:42 +0530 Subject: [PATCH 0198/1014] bluez5: media-sink: Improvements for ASHA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean up as per feedback from Pauli and Barnabás. --- spa/plugins/bluez5/media-sink.c | 42 +++++++++++++-------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 77d1159e9..ea5581ee3 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -116,8 +116,6 @@ struct spa_bt_asha { uint8_t seqnum_pending; uint64_t ref_t0; - uint64_t skip_frames; - uint64_t next_time; unsigned int flush_pending:1; @@ -216,8 +214,9 @@ struct impl { #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) -static struct spa_list asha_sinks; +static struct spa_list asha_sinks = SPA_LIST_INIT(&asha_sinks); +static void drop_frames(struct impl *this, uint32_t req); static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret); static struct impl *find_other_asha(struct impl *this) @@ -380,14 +379,11 @@ static int set_asha_timer(struct impl *this, struct impl *other) /* Since the quantum and packet size aren't correlated, drop any samples from this * cycle that might have been used to send a packet starting in the previous cycle */ - this->asha->skip_frames = other_samples % this->process_duration; + drop_frames(this, other_samples % this->process_duration); } else { time = this->process_time; - this->asha->skip_frames = 0; } - spa_log_debug(this->log, "will skip %"PRIu64 " frames", this->asha->skip_frames); - this->asha->next_time = time; return set_asha_timeout(this, this->asha->next_time); @@ -903,14 +899,6 @@ again: avail = d[0].chunk->size - port->ready_offset; avail /= port->frame_size; - if (is_asha && this->asha->skip_frames) { - /* Skip initial frames if needed */ - uint64_t skip = SPA_MIN(this->asha->skip_frames, avail); - spa_log_trace(this->log, "skipping %"PRIu64 " frames", skip); - avail -= skip; - this->asha->skip_frames -= skip; - } - offs = index % d[0].maxsize; n_frames = avail; n_bytes = n_frames * port->frame_size; @@ -1330,16 +1318,16 @@ static void media_on_timeout(struct spa_source *source) static uint64_t asha_seqnum(struct impl *this) { uint64_t tn = get_reference_time(this, NULL); - double dt = tn - this->asha->ref_t0; - double num_packets = dt / ASHA_CONN_INTERVAL; + uint64_t dt = tn - this->asha->ref_t0; + uint64_t num_packets = (dt + ASHA_CONN_INTERVAL / 2) / ASHA_CONN_INTERVAL; - spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %g", + spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %ld", tn, this->asha->ref_t0, num_packets); if (this->asha->ref_t0 > tn) return 0; - return (uint64_t)round(num_packets) % 256; + return num_packets % 256; } static void media_asha_flush_timeout(struct spa_source *source) @@ -1393,6 +1381,10 @@ static void media_asha_flush_timeout(struct spa_source *source) asha->seqnum_pending = seqnum; asha->poll_pending = true; asha->flush_pending = false; + + SPA_FLAG_UPDATE(asha->flush_source.mask, SPA_IO_OUT, 1); + spa_loop_update_source(this->data_loop, &asha->flush_source); + spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on timer for %s, written:%d", this, seqnum, address, -errno); goto skip_flush; @@ -1435,6 +1427,9 @@ static void media_asha_cb(struct spa_source *source) return; } + SPA_FLAG_UPDATE(asha->flush_source.mask, SPA_IO_OUT, 0); + spa_loop_update_source(this->data_loop, &asha->flush_source); + seqnum = asha->buf[0]; written = send(asha->flush_source.fd, asha->buf, ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); @@ -1595,7 +1590,7 @@ static int transport_start(struct impl *this) asha->flush_source.data = this; asha->flush_source.fd = this->transport->fd; asha->flush_source.func = media_asha_cb; - asha->flush_source.mask = SPA_IO_OUT | SPA_IO_ERR | SPA_IO_HUP; + asha->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; asha->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &asha->flush_source); @@ -1791,7 +1786,7 @@ static void emit_node_info(struct impl *this, bool full) this->transport->bap_big); node_group = node_group_buf; } else if (this->transport && (this->transport->profile & SPA_BT_PROFILE_ASHA_SINK)) { - spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-asha-%zd\"]", + spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-asha-%" PRIu64 "d\"]", this->transport->hisyncid); node_group = node_group_buf; } @@ -2578,11 +2573,6 @@ impl_init(const struct spa_handle_factory *factory, if (this->asha == NULL) return -ENOMEM; - if (!spa_list_is_initialized(&asha_sinks)) { - spa_list_init(&asha_sinks); - spa_log_info(this->log, "Initialized ASHA media sink list"); - } - this->asha->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); } From 9586ef891e68d492a335f261cf5074c1c320b18a Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 29 Apr 2025 13:11:21 +0530 Subject: [PATCH 0199/1014] bluez5: Use device set for ASHA While ASHA does not really use the D-Bus device set interface but since ASHA has a device-wide HiSyncId and needs to handle left and right side via a combine sink, use the device set notion for ASHA as well. --- spa/plugins/bluez5/bluez5-dbus.c | 20 ++++- spa/plugins/bluez5/bluez5-device.c | 118 ++++++++++++++++++----------- 2 files changed, 92 insertions(+), 46 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 585ed68d4..2b12c05d0 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -2113,7 +2113,8 @@ static void device_update_set_status(struct spa_bt_device *device, bool force, c int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { device->connected_profiles |= profile; - if (profile & SPA_BT_PROFILE_BAP_DUPLEX) + if (profile & SPA_BT_PROFILE_BAP_DUPLEX || + profile & SPA_BT_PROFILE_ASHA_SINK) device_update_set_status(device, true, NULL); spa_bt_device_check_profiles(device, false); spa_bt_device_emit_profiles_changed(device, profile); @@ -2145,7 +2146,10 @@ static bool device_set_update_leader(struct spa_bt_set_membership *set) * appear under a specific device. */ spa_bt_for_each_set_member(s, set) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX)) + bool bap_duplex = s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX; + bool is_asha = s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; + + if (!bap_duplex && !is_asha) continue; if (leader == NULL || s->rank < leader->rank || @@ -2972,6 +2976,7 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_device *device = transport->device; + char hisyncid[32] = { 0 }; spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); @@ -3020,6 +3025,12 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) if (transport->profile & SPA_BT_PROFILE_BAP_DUPLEX) device_update_set_status(device, true, NULL); + if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) { + spa_scnprintf(hisyncid, sizeof(hisyncid), "/asha/%" PRIu64, transport->hisyncid); + device_update_set_status(device, true, hisyncid); + device_remove_device_set(device, hisyncid); + } + spa_bt_device_emit_profiles_changed(device, transport->profile); } @@ -4114,6 +4125,7 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, const struct media_codec * const * const media_codecs = monitor->media_codecs; const struct media_codec *codec = NULL; struct spa_bt_transport *transport; + char hisyncid[32] = { 0 }; char *tpath; if (!remote_endpoint->transport_path) { @@ -4173,6 +4185,10 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, transport_sync_volume(transport); + spa_scnprintf(hisyncid, sizeof(hisyncid), "/asha/%" PRIu64, transport->hisyncid); + device_add_device_set(transport->device, hisyncid, transport->asha_right_side ? 1 : 0); + device_update_set_status(transport->device, true, hisyncid); + const char *side = transport->asha_right_side ? "right" : "left"; spa_log_debug(monitor->log, "ASHA transport setup complete for %s side", side); diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 99bfed8b1..d2c1031ac 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -684,7 +684,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.a2dp-duplex", "true"); n_items++; } - if (in_device_set) { + if (in_device_set || t->media_codec->asha) { items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.set", this->device_set.path); n_items++; items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.internal", "true"); @@ -696,14 +696,6 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, items[n_items] = SPA_DICT_ITEM_INIT("object.path", object_path); n_items++; } - if (t->media_codec->asha) { - char hisyncid[32] = { 0 }; - spa_scnprintf(hisyncid, sizeof(hisyncid), "%zd", t->hisyncid); - items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.asha.hisyncid", hisyncid); - n_items++; - items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.asha.side", t->asha_right_side ? "right" : "left"); - n_items++; - } info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; @@ -1000,6 +992,7 @@ static void device_set_update(struct impl *this, struct device_set *dset) spa_list_for_each(set, membership_list, link) { struct spa_bt_set_membership *s; int num_devices = 0; + bool is_asha_member = false; device_set_clear(this, dset); @@ -1008,44 +1001,71 @@ static void device_set_update(struct impl *this, struct device_set *dset) bool active = false; uint32_t source_id = DEVICE_ID_SOURCE; uint32_t sink_id = DEVICE_ID_SINK; + bool bap_duplex = s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX; + bool is_asha = s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX)) + if (!bap_duplex && !is_asha) continue; - spa_list_for_each(t, &s->device->transport_list, device_link) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)) - continue; - if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SOURCE)) - continue; - if (dset->sources >= SPA_N_ELEMENTS(dset->source)) - break; + if (is_asha) { + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!(s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) + continue; + if (!transport_enabled(t, SPA_BT_PROFILE_ASHA_SINK)) + continue; + if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) + break; - active = true; - dset->source[dset->sources].impl = this; - dset->source[dset->sources].transport = t; - dset->source[dset->sources].id = source_id; - source_id += 2; - spa_bt_transport_add_listener(t, &dset->source[dset->sources].listener, - &device_set_transport_events, &dset->source[dset->sources]); - ++dset->sources; + active = true; + is_asha_member = true; + dset->leader = set->leader = t->asha_right_side; + dset->path = strdup(set->path); + dset->sink[dset->sinks].impl = this; + dset->sink[dset->sinks].transport = t; + dset->sink[dset->sinks].id = sink_id; + sink_id += 2; + spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, + &device_set_transport_events, &dset->sink[dset->sinks]); + ++dset->sinks; + } } - spa_list_for_each(t, &s->device->transport_list, device_link) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SINK)) - continue; - if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SINK)) - continue; - if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) - break; + if (bap_duplex) { + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)) + continue; + if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SOURCE)) + continue; + if (dset->sources >= SPA_N_ELEMENTS(dset->source)) + break; - active = true; - dset->sink[dset->sinks].impl = this; - dset->sink[dset->sinks].transport = t; - dset->sink[dset->sinks].id = sink_id; - sink_id += 2; - spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, - &device_set_transport_events, &dset->sink[dset->sinks]); - ++dset->sinks; + active = true; + dset->source[dset->sources].impl = this; + dset->source[dset->sources].transport = t; + dset->source[dset->sources].id = source_id; + source_id += 2; + spa_bt_transport_add_listener(t, &dset->source[dset->sources].listener, + &device_set_transport_events, &dset->source[dset->sources]); + ++dset->sources; + } + + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SINK)) + continue; + if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SINK)) + continue; + if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) + break; + + active = true; + dset->sink[dset->sinks].impl = this; + dset->sink[dset->sinks].transport = t; + dset->sink[dset->sinks].id = sink_id; + sink_id += 2; + spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, + &device_set_transport_events, &dset->sink[dset->sinks]); + ++dset->sinks; + } } if (active) @@ -1062,8 +1082,10 @@ static void device_set_update(struct impl *this, struct device_set *dset) /* XXX: device set nodes for BAP server not supported, * XXX: it'll appear as multiple streams */ - dset->path = NULL; - dset->leader = false; + if (!is_asha_member) { + dset->path = NULL; + dset->leader = false; + } } if (num_devices > 1) @@ -1126,10 +1148,13 @@ static int emit_nodes(struct impl *this) break; case DEVICE_PROFILE_ASHA: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) { + struct device_set *set = &this->device_set; t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); if (t) { this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + if (set->sink_enabled && set->leader) + emit_device_set_node(this, DEVICE_ID_SINK_SET); } else { spa_log_warn(this->log, "Unable to find transport for ASHA"); } @@ -1409,7 +1434,8 @@ static bool device_set_needs_update(struct impl *this) struct device_set dset = { .impl = this }; bool changed; - if (this->profile != DEVICE_PROFILE_BAP) + if (this->profile != DEVICE_PROFILE_BAP && + this->profile != DEVICE_PROFILE_ASHA) return false; device_set_update(this, &dset); @@ -1490,7 +1516,8 @@ static void device_set_changed(void *userdata) { struct impl *this = userdata; - if (this->profile != DEVICE_PROFILE_BAP) + if (this->profile != DEVICE_PROFILE_BAP && + this->profile != DEVICE_PROFILE_ASHA) return; if (!device_set_needs_update(this)) { @@ -1851,6 +1878,9 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * n_sink++; priority = 1; + if (this->device_set.sink_enabled) + n_sink = this->device_set.leader ? 1 : 0; + break; } case DEVICE_PROFILE_A2DP: From 270eda63a97a11f45b94b0dd1673601b23b3b3c9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 3 May 2025 18:19:22 +0300 Subject: [PATCH 0200/1014] bluez5: different icon for A2DP & HFP output routes Set different icons for A2DP & HFP output routes, so that they look different (in Gnome). Don't call the non-HFP output route as "headset" or "handsfree" in this case, to be less ambiguous about microphone availability. Also set device.icon-name for the device too. --- spa/plugins/bluez5/bluez5-dbus.c | 10 ++++--- spa/plugins/bluez5/bluez5-device.c | 42 ++++++++++++++++++++++++++++-- spa/plugins/bluez5/defs.h | 25 ++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 2b12c05d0..ace1c2ed8 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1721,8 +1721,9 @@ static void emit_device_info(struct spa_bt_monitor *monitor, { struct spa_device_object_info info; char dev[32], name[128], class[16], vendor_id[64], product_id[64], product_id_tot[67]; - struct spa_dict_item items[23]; + struct spa_dict_item items[24]; uint32_t n_items = 0; + enum spa_bt_form_factor ff; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; @@ -1731,6 +1732,8 @@ static void emit_device_info(struct spa_bt_monitor *monitor, SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; + ff = spa_bt_form_factor_from_class(device->bluetooth_class); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); @@ -1745,9 +1748,8 @@ static void emit_device_info(struct spa_bt_monitor *monitor, items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, vendor_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, product_id_tot); } - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, - spa_bt_form_factor_name( - spa_bt_form_factor_from_class(device->bluetooth_class))); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, spa_bt_form_factor_name(ff)); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, spa_bt_form_factor_icon_name(ff)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_STRING, device->address); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, device->icon); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, device->path); diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index d2c1031ac..92c824d27 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -534,7 +534,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id) struct spa_bt_device *device = this->bt_dev; struct node *node = &this->nodes[id]; struct spa_device_object_info info; - struct spa_dict_item items[8]; + struct spa_dict_item items[9]; char str_id[32], members_json[8192], channels_json[512]; struct device_set_member *members; uint32_t n_members; @@ -2150,6 +2150,22 @@ static bool profile_has_route(uint32_t profile, uint32_t route) return false; } +static bool device_has_route(struct impl *this, uint32_t route) +{ + bool found = false; + + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX) + found = found || profile_has_route(DEVICE_PROFILE_A2DP, route); + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_AUDIO) + found = found || profile_has_route(DEVICE_PROFILE_BAP, route); + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + found = found || profile_has_route(DEVICE_PROFILE_HSP_HFP, route); + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) + found = found || profile_has_route(DEVICE_PROFILE_ASHA, route); + + return found; +} + static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t route, uint32_t profile) { @@ -2157,6 +2173,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, struct spa_pod_frame f[2]; enum spa_direction direction; const char *name_prefix, *description, *hfp_description, *port_type; + const char *port_icon_name = NULL; enum spa_bt_form_factor ff; enum spa_bluetooth_audio_codec codec; enum spa_param_availability available; @@ -2250,6 +2267,20 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, dev = DEVICE_ID_SINK; available = this->device_set.sink_enabled ? SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; + + if (device_has_route(this, ROUTE_HF_OUTPUT)) { + /* Distinguish A2DP vs. HFP output routes */ + switch (ff) { + case SPA_BT_FORM_FACTOR_HEADSET: + case SPA_BT_FORM_FACTOR_HANDSFREE: + port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADPHONE); + /* Don't call it "headset", the HF one has the mic */ + description = _("Headphone"); + break; + default: + break; + } + } break; case ROUTE_HF_OUTPUT: direction = SPA_DIRECTION_OUTPUT; @@ -2257,6 +2288,8 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, description = hfp_description; dev = DEVICE_ID_SINK; available = SPA_PARAM_AVAILABILITY_yes; + if (device_has_route(this, ROUTE_OUTPUT)) + port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADSET); break; case ROUTE_SET_INPUT: if (!(this->device_set.source_enabled && this->device_set.leader)) @@ -2292,11 +2325,16 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, 0); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0); spa_pod_builder_push_struct(b, &f[1]); - spa_pod_builder_int(b, 1); + spa_pod_builder_int(b, port_icon_name ? 2 : 1); spa_pod_builder_add(b, SPA_POD_String("port.type"), SPA_POD_String(port_type), NULL); + if (port_icon_name) + spa_pod_builder_add(b, + SPA_POD_String("device.icon-name"), + SPA_POD_String(port_icon_name), + NULL); spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); spa_pod_builder_push_array(b, &f[1]); diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index a97b9ecb4..dd092a626 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -427,6 +427,31 @@ static inline const char *spa_bt_form_factor_name(enum spa_bt_form_factor ff) } } +static inline const char *spa_bt_form_factor_icon_name(enum spa_bt_form_factor ff) +{ + switch (ff) { + case SPA_BT_FORM_FACTOR_HEADSET: + return "audio-headset-bluetooth"; + case SPA_BT_FORM_FACTOR_HANDSFREE: + return "audio-handsfree-bluetooth"; + case SPA_BT_FORM_FACTOR_MICROPHONE: + return "audio-input-microphone-bluetooth"; + case SPA_BT_FORM_FACTOR_SPEAKER: + return "audio-speakers-bluetooth"; + case SPA_BT_FORM_FACTOR_HEADPHONE: + return "audio-headphones-bluetooth"; + case SPA_BT_FORM_FACTOR_PORTABLE: + return "multimedia-player-bluetooth"; + case SPA_BT_FORM_FACTOR_PHONE: + return "phone-bluetooth"; + case SPA_BT_FORM_FACTOR_CAR: + case SPA_BT_FORM_FACTOR_HIFI: + case SPA_BT_FORM_FACTOR_UNKNOWN: + default: + return "audio-card-bluetooth"; + } +} + static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t bluetooth_class) { uint32_t major, minor; From 584ea400c32399e02060ae7e987b4d455eb9868e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 3 May 2025 18:59:10 +0300 Subject: [PATCH 0201/1014] bluez5: fix spelling "Headphone" -> "Headphones" "Headphone" is usually plural --- spa/plugins/bluez5/bluez5-device.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 92c824d27..c22042a0b 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -2210,7 +2210,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, break; case SPA_BT_FORM_FACTOR_HEADPHONE: name_prefix = "headphone"; - description = _("Headphone"); + description = _("Headphones"); hfp_description = _("Handsfree"); port_type = "headphones"; break; @@ -2275,7 +2275,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, case SPA_BT_FORM_FACTOR_HANDSFREE: port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADPHONE); /* Don't call it "headset", the HF one has the mic */ - description = _("Headphone"); + description = _("Headphones"); break; default: break; From 2940c9ff7b8134f6d2388bb79a303b4066888cbd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 25 Apr 2025 12:42:55 +0200 Subject: [PATCH 0202/1014] examples: add rate as a constant --- src/examples/export-sink.c | 1 + src/examples/local-v4l2.c | 1 + src/examples/sdl.h | 2 +- src/examples/video-dsp-play.c | 1 + src/examples/video-play-fixate.c | 1 + src/examples/video-play-pull.c | 1 + src/examples/video-play-reneg.c | 1 + src/examples/video-play.c | 5 +++-- 8 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/examples/export-sink.c b/src/examples/export-sink.c index 8ecad3e01..fe79c3cfa 100644 --- a/src/examples/export-sink.c +++ b/src/examples/export-sink.c @@ -26,6 +26,7 @@ #define WIDTH 640 #define HEIGHT 480 #define BPP 3 +#define RATE 30 #include "sdl.h" diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c index 3a2ef35dc..3e00b979a 100644 --- a/src/examples/local-v4l2.c +++ b/src/examples/local-v4l2.c @@ -13,6 +13,7 @@ #define WIDTH 640 #define HEIGHT 480 +#define RATE 30 #define BPP 3 #define MAX_BUFFERS 32 diff --git a/src/examples/sdl.h b/src/examples/sdl.h index 9efcfc493..27b768bed 100644 --- a/src/examples/sdl.h +++ b/src/examples/sdl.h @@ -170,7 +170,7 @@ static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct s &SPA_RECTANGLE(info->max_texture_width, info->max_texture_height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( - &SPA_FRACTION(25,1), + &SPA_FRACTION(RATE,1), &SPA_FRACTION(0,1), &SPA_FRACTION(30,1)), 0); diff --git a/src/examples/video-dsp-play.c b/src/examples/video-dsp-play.c index ad645d7a0..40deb8500 100644 --- a/src/examples/video-dsp-play.c +++ b/src/examples/video-dsp-play.c @@ -22,6 +22,7 @@ #define WIDTH 640 #define HEIGHT 480 #define BPP 3 +#define RATE 30 #define MAX_BUFFERS 64 diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 8503a6b3d..1a9128351 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -23,6 +23,7 @@ #define WIDTH 640 #define HEIGHT 480 +#define RATE 30 #define MAX_BUFFERS 64 #define MAX_MOD 8 diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index 3ae69f55d..bc66c6601 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -21,6 +21,7 @@ #define WIDTH 640 #define HEIGHT 480 +#define RATE 30 #define MAX_BUFFERS 64 diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index b8e83d233..60a3a8bb4 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -21,6 +21,7 @@ #define WIDTH 640 #define HEIGHT 480 +#define RATE 30 #define MAX_BUFFERS 64 diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 3449aefa7..3abaf54dc 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -22,8 +22,9 @@ #include -#define WIDTH 640 -#define HEIGHT 480 +#define WIDTH 1920 +#define HEIGHT 1080 +#define RATE 30 #define MAX_BUFFERS 64 From 9255e07c3a8e3ed4c07dc6f69880be16088f328b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 10:04:10 +0200 Subject: [PATCH 0203/1014] pod: move related functions closer to eachother --- spa/include/spa/pod/iter.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index f4958251c..3c6b9e8af 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -422,6 +422,14 @@ SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_find_prop(const struct spa_p return spa_pod_object_find_prop((const struct spa_pod_object *)pod, start, key); } +SPA_API_POD_ITER int spa_pod_object_has_props(const struct spa_pod_object *pod) +{ + struct spa_pod_prop *res; + SPA_POD_OBJECT_FOREACH(pod, res) + return 1; + return 0; +} + SPA_API_POD_ITER int spa_pod_object_fixate(struct spa_pod_object *pod) { struct spa_pod_prop *res; @@ -432,14 +440,6 @@ SPA_API_POD_ITER int spa_pod_object_fixate(struct spa_pod_object *pod) } return 0; } - -SPA_API_POD_ITER int spa_pod_fixate(struct spa_pod *pod) -{ - if (!spa_pod_is_object(pod)) - return -EINVAL; - return spa_pod_object_fixate((struct spa_pod_object *)pod); -} - SPA_API_POD_ITER int spa_pod_object_is_fixated(const struct spa_pod_object *pod) { struct spa_pod_prop *res; @@ -451,12 +451,11 @@ SPA_API_POD_ITER int spa_pod_object_is_fixated(const struct spa_pod_object *pod) return 1; } -SPA_API_POD_ITER int spa_pod_object_has_props(const struct spa_pod_object *pod) +SPA_API_POD_ITER int spa_pod_fixate(struct spa_pod *pod) { - struct spa_pod_prop *res; - SPA_POD_OBJECT_FOREACH(pod, res) - return 1; - return 0; + if (!spa_pod_is_object(pod)) + return -EINVAL; + return spa_pod_object_fixate((struct spa_pod_object *)pod); } SPA_API_POD_ITER int spa_pod_is_fixated(const struct spa_pod *pod) From faf5ae0c2f3833095c83d5e2da5008476aaaf810 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 10:12:26 +0200 Subject: [PATCH 0204/1014] impl-link: improve negotiation Make a function to create a filter. This is a pod that has all valid defaults fixated and the invalid ones left unfixated. Use this filter is a first attempt to negotiate a link format. The effect is that a format will be chosen first that matches all the valid defaults as much as possible instead of negotiating to the first thing that matches. Suppose we have a higher priority port with the format: foo/bar key: { default:1024, min:1, max:2048 } And another port with two params: foo/bar key: 512 rate: 2/1 foo/bar key: 1024 rate: 30/1 By first trying key: 1024 we negotiate to the more specific second property with the higher rate. --- spa/include/spa/pod/filter.h | 26 ++++++++++++++++++++++++++ src/pipewire/impl-link.c | 15 ++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 331999738..3a2e4868f 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -379,6 +379,32 @@ spa_pod_filter(struct spa_pod_builder *b, return res; } +SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) +{ + struct spa_pod_prop *res; + int count = 0; + + SPA_POD_OBJECT_FOREACH(pod, res) { + if (res->value.type == SPA_TYPE_Choice && + !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) { + uint32_t nvals, choice; + struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); + const void *vals = SPA_POD_BODY(v); + if (spa_pod_compare_is_valid_choice(v->type, v->size, + vals, vals, nvals, choice)) { + ((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None; + count++; + } + } + } + return count; +} +SPA_API_POD_FILTER int spa_pod_filter_make(struct spa_pod *pod) +{ + if (!spa_pod_is_object(pod)) + return -EINVAL; + return spa_pod_filter_object_make((struct spa_pod_object *)pod); +} /** * \} */ diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index ce9a8689f..5b38117e4 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -336,6 +337,8 @@ static int link_find_format(struct pw_impl_link *this, } } } else if (state[0] == PW_IMPL_PORT_STATE_CONFIGURE && state[1] == PW_IMPL_PORT_STATE_CONFIGURE) { + bool do_filter = true; + int count = 0; again: /* both ports need a format, we start with a format from port 0 and use that * as a filter for port 1. Because the filter has higher priority, its @@ -353,11 +356,20 @@ static int link_find_format(struct pw_impl_link *this, if (res < 0) *error = spa_aprintf("error %s enum formats: %s", dir[0], spa_strerror(res)); - else + else if (do_filter && count > 0) { + do_filter = false; + idx[0] = 0; + goto again; + } else { *error = spa_aprintf("no more %s formats", dir[0]); + } goto error; } } + if (do_filter && filter) + if ((res = spa_pod_filter_make(filter)) > 0) + count += res; + pw_log_debug("%p: enum %s %d with filter: %p", this, dir[1], idx[1], filter); pw_log_pod(SPA_LOG_LEVEL_DEBUG, filter); @@ -1069,6 +1081,7 @@ static void port_param_changed(struct pw_impl_link *this, uint32_t id, if (inport) pw_impl_port_update_state(inport, target, 0, NULL); + pw_log_info("%p: format changed", this); this->preparing = this->prepared = false; link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); pw_impl_link_prepare(this); From 14eb03a821f575f7b7403eb46c70139a60eeda98 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 10:24:16 +0200 Subject: [PATCH 0205/1014] videoconvert: Enumerate follower params better Make sure we increment the next counter even when we are in passthrough mode or the property is not readable. --- spa/plugins/audioconvert/audioadapter.c | 26 ++++++++++++++---------- spa/plugins/videoconvert/videoadapter.c | 27 +++++++++++++++---------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 2fd36f45b..345a1d902 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -142,19 +142,23 @@ static int follower_enum_params(struct impl *this, struct spa_pod_builder *builder) { int res; - if (result->next < 0x100000 && - this->follower != this->target) { - if ((res = node_enum_params_sync(this, this->target, - id, &result->next, filter, &result->param, builder)) == 1) - return res; + if (result->next < 0x100000) { + if (this->follower != this->target && + this->convert_params_flags[idx] & SPA_PARAM_INFO_READ) { + if ((res = node_enum_params_sync(this, this->target, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + } result->next = 0x100000; } - if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { - result->next &= 0xfffff; - if ((res = node_enum_params_sync(this, this->follower, - id, &result->next, filter, &result->param, builder)) == 1) { - result->next |= 0x100000; - return res; + if (result->next < 0x200000) { + if (this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + result->next &= 0xfffff; + if ((res = node_enum_params_sync(this, this->follower, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } } result->next = 0x200000; } diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 3cb464501..ecb7cdff4 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -142,19 +142,23 @@ static int follower_enum_params(struct impl *this, struct spa_pod_builder *builder) { int res; - if (result->next < 0x100000 && - this->follower != this->target) { - if ((res = node_enum_params_sync(this, this->target, - id, &result->next, filter, &result->param, builder)) == 1) - return res; + if (result->next < 0x100000) { + if (this->follower != this->target && + this->convert_params_flags[idx] & SPA_PARAM_INFO_READ) { + if ((res = node_enum_params_sync(this, this->target, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + } result->next = 0x100000; } - if (result->next < 0x200000 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { - result->next &= 0xfffff; - if ((res = node_enum_params_sync(this, this->follower, - id, &result->next, filter, &result->param, builder)) == 1) { - result->next |= 0x100000; - return res; + if (result->next < 0x200000) { + if (this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + result->next &= 0xfffff; + if ((res = node_enum_params_sync(this, this->follower, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } } result->next = 0x200000; } @@ -763,6 +767,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); } else { + /* add converter ports */ configure_convert(this, mode); } link_io(this); From 399ff82ab253adcf5f68679cacc615446b3bdc14 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 10:33:07 +0200 Subject: [PATCH 0206/1014] adapter: always negotiate from convert to follower Make the converter format a higher priority in all cases. The converter has been negotiated first and is able to make a better suggestion for the ideal format in all cases. --- spa/plugins/audioconvert/audioadapter.c | 49 ++++++++----------------- spa/plugins/videoconvert/videoadapter.c | 49 ++++++++----------------- 2 files changed, 30 insertions(+), 68 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 345a1d902..ca44f7d64 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -946,7 +946,6 @@ static int negotiate_format(struct impl *this) uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; - struct spa_node *src, *dst; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); @@ -964,36 +963,13 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); - if (this->direction == SPA_DIRECTION_INPUT) { - src = this->target; - dst = this->follower; - } else { - src = this->follower; - dst = this->target; - } - - - /* first try the ideal converter format, which is likely passthrough */ - tstate = 0; - fres = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &tstate, - NULL, &format, &b); - if (fres == 1) { - fstate = 0; - res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &fstate, - format, &format, &b); - if (res == 1) - goto found; - } - - /* then try something the follower can accept */ + /* The target has been negotiated on its other ports and so it can propose + * a passthrough format or an ideal conversion. We use the suggestions of the + * target to find the best follower format */ for (fstate = 0;;) { format = NULL; - res = node_port_enum_params_sync(this, src, - SPA_DIRECTION_OUTPUT, 0, + res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -1002,18 +978,23 @@ static int negotiate_format(struct impl *this) else if (res <= 0) break; + if (format != NULL) + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + tstate = 0; - fres = node_port_enum_params_sync(this, dst, - SPA_DIRECTION_INPUT, 0, + fres = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) continue; + if (format != NULL) + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + res = fres; break; } -found: if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); @@ -1283,8 +1264,8 @@ static void follower_info(void *data, const struct spa_node_info *info) struct impl *this = data; uint32_t i; - spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, - info->change_mask); + spa_log_debug(this->log, "%p: info change:%08"PRIx64" %d:%d", this, + info->change_mask, info->max_input_ports, info->max_output_ports); if (this->follower_removing) return; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index ecb7cdff4..d706239e6 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -948,7 +948,6 @@ static int negotiate_format(struct impl *this) uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; - struct spa_node *src, *dst; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); @@ -966,36 +965,13 @@ static int negotiate_format(struct impl *this) spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); - if (this->direction == SPA_DIRECTION_INPUT) { - src = this->target; - dst = this->follower; - } else { - src = this->follower; - dst = this->target; - } - - - /* first try the ideal converter format, which is likely passthrough */ - tstate = 0; - fres = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &tstate, - NULL, &format, &b); - if (fres == 1) { - fstate = 0; - res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, - SPA_PARAM_EnumFormat, &fstate, - format, &format, &b); - if (res == 1) - goto found; - } - - /* then try something the follower can accept */ + /* The target has been negotiated on its other ports and so it can propose + * a passthrough format or an ideal conversion. We use the suggestions of the + * target to find the best follower format */ for (fstate = 0;;) { format = NULL; - res = node_port_enum_params_sync(this, src, - SPA_DIRECTION_OUTPUT, 0, + res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &fstate, NULL, &format, &b); @@ -1004,18 +980,23 @@ static int negotiate_format(struct impl *this) else if (res <= 0) break; + if (format != NULL) + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + tstate = 0; - fres = node_port_enum_params_sync(this, dst, - SPA_DIRECTION_INPUT, 0, + fres = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_EnumFormat, &tstate, format, &format, &b); if (fres == 0 && res == 1) continue; + if (format != NULL) + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + res = fres; break; } -found: if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); @@ -1285,8 +1266,8 @@ static void follower_info(void *data, const struct spa_node_info *info) struct impl *this = data; uint32_t i; - spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, - info->change_mask); + spa_log_debug(this->log, "%p: info change:%08"PRIx64" %d:%d", this, + info->change_mask, info->max_input_ports, info->max_output_ports); if (this->follower_removing) return; From df52086c4705e68b4aefcf85030030b36cf7945a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 11:17:23 +0200 Subject: [PATCH 0207/1014] videoconvert: always restore old change_mask --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 41e2da14b..5071a9b01 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -213,8 +213,8 @@ static void emit_node_info(struct impl *this, bool full) } } spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; } + this->info.change_mask = old; } static void emit_port_info(struct impl *this, struct port *port, bool full) @@ -250,8 +250,8 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) } } spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); - port->info.change_mask = old; } + port->info.change_mask = old; } static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, From 4dd98b4ff85f92ae1709d1397da2f161b5fda700 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 11:19:15 +0200 Subject: [PATCH 0208/1014] videoconvert: Use converter direction from properties --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 5071a9b01..4dbdb3ae7 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -594,15 +594,9 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m i = dir->n_ports++; init_port(this, direction, i, false, false, true); } - /* when output is convert mode, we are in OUTPUT (merge) mode, we always output all - * the incoming data to output. When output is DSP, we need to output quantum size - * chunks. */ - this->direction = this->dir[SPA_DIRECTION_OUTPUT].mode == SPA_PARAM_PORT_CONFIG_MODE_convert ? - SPA_DIRECTION_OUTPUT : SPA_DIRECTION_INPUT; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; - this->params[IDX_Props].user++; this->params[IDX_PortConfig].user++; return 0; } @@ -2115,6 +2109,12 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); else if (spa_streq(k, "monitor.passthrough")) this->monitor_passthrough = spa_atob(s); + else if (spa_streq(k, "convert.direction")) { + if (spa_streq(s, "output")) + this->direction = SPA_DIRECTION_OUTPUT; + else + this->direction = SPA_DIRECTION_INPUT; + } else videoconvert_set_param(this, k, s); } From 77e6471a04e50c857b48e18a2fae39b2ddcf6112 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 11:20:15 +0200 Subject: [PATCH 0209/1014] videoconvert: free codec resources Free encoder and decoder resource properly when we renegotiate. --- .../videoconvert/videoconvert-ffmpeg.c | 160 ++++++++++++------ 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 4dbdb3ae7..a03c578c2 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -118,11 +117,11 @@ struct dir { unsigned int have_profile:1; struct spa_pod *tag; enum AVPixelFormat pix_fmt; - int width; - int height; + struct spa_rectangle size; + struct spa_fraction framerate; ptrdiff_t linesizes[4]; - size_t size[4]; + size_t plane_size[4]; unsigned int control:1; }; @@ -172,7 +171,6 @@ struct impl { char group_name[128]; struct { - const AVCodec *codec; AVCodecContext *context; AVPacket *packet; AVFrame *frame; @@ -182,7 +180,6 @@ struct impl { AVFrame *frame; } convert; struct { - const AVCodec *codec; AVCodecContext *context; AVFrame *frame; AVPacket *packet; @@ -822,46 +819,64 @@ static enum AVPixelFormat format_to_pix_fmt(uint32_t format) return AV_PIX_FMT_NONE; } -static int get_format(struct dir *dir, int *width, int *height, uint32_t *format) +static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, + struct spa_fraction *framerate) { if (dir->have_format) { switch (dir->format.media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: *format = dir->format.info.dsp.format; - *width = 640; - *height = 480; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); break; case SPA_MEDIA_SUBTYPE_raw: - *width = dir->format.info.raw.size.width; - *height = dir->format.info.raw.size.height; *format = dir->format.info.raw.format; + *size = dir->format.info.raw.size; + *framerate = dir->format.info.raw.framerate; break; case SPA_MEDIA_SUBTYPE_mjpg: - *width = dir->format.info.mjpg.size.width; - *height = dir->format.info.mjpg.size.height; + *format = SPA_VIDEO_FORMAT_I420; + *size = dir->format.info.mjpg.size; + *framerate = dir->format.info.mjpg.framerate; break; case SPA_MEDIA_SUBTYPE_h264: - *width = dir->format.info.h264.size.width; - *height = dir->format.info.h264.size.height; + *format = SPA_VIDEO_FORMAT_I420; + *size = dir->format.info.h264.size; + *framerate = dir->format.info.h264.framerate; break; default: - *width = 640; - *height = 480; + *format = SPA_VIDEO_FORMAT_I420; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); break; } } else { - *width = 640; - *height = 480; + *format = SPA_VIDEO_FORMAT_I420; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); } return 0; } +static inline void free_decoder(struct impl *this) +{ + avcodec_free_context(&this->decoder.context); + av_packet_free(&this->decoder.packet); +} + +static inline void free_encoder(struct impl *this) +{ + avcodec_free_context(&this->encoder.context); + av_packet_free(&this->encoder.packet); + av_frame_free(&this->encoder.frame); +} static int setup_convert(struct impl *this) { struct dir *in, *out; uint32_t in_format = 0, out_format = 0; int decoder_id = 0, encoder_id = 0; + const AVCodec *codec; in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -875,8 +890,8 @@ static int setup_convert(struct impl *this) if (!in->have_format || !out->have_format) return -EIO; - get_format(in, &in->width, &in->height, &in_format); - get_format(out, &out->width, &out->height, &out_format); + get_format(in, &in_format, &in->size, &in->framerate); + get_format(out, &out_format, &out->size, &out->framerate); switch (in->format.media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: @@ -891,11 +906,17 @@ static int setup_convert(struct impl *this) encoder_id = AV_CODEC_ID_MJPEG; out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; - out->format.info.raw.size = SPA_RECTANGLE(in->width, in->height); + out->format.info.raw.size = in->size; + out->format.info.raw.framerate = in->framerate; out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; case SPA_MEDIA_SUBTYPE_h264: encoder_id = AV_CODEC_ID_H264; + out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; + out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; + out->format.info.raw.size = in->size; + out->format.info.raw.framerate = in->framerate; + out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; default: spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, @@ -907,8 +928,8 @@ static int setup_convert(struct impl *this) switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_mjpg: /* passthrough if same dimensions or else reencode */ - if (in->width != out->width || - in->height != out->height) { + if (in->size.width != out->size.width || + in->size.height != out->size.height) { encoder_id = decoder_id = AV_CODEC_ID_MJPEG; out->pix_fmt = AV_PIX_FMT_YUVJ420P; } @@ -928,8 +949,8 @@ static int setup_convert(struct impl *this) switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_h264: /* passthrough if same dimensions or else reencode */ - if (in->width != out->width || - in->height != out->height) + if (in->size.width != out->size.width || + in->size.height != out->size.height) encoder_id = decoder_id = AV_CODEC_ID_H264; break; case SPA_MEDIA_SUBTYPE_raw: @@ -949,12 +970,16 @@ static int setup_convert(struct impl *this) return -ENOTSUP; } + if (in->pix_fmt == AV_PIX_FMT_NONE || out->pix_fmt == AV_PIX_FMT_NONE) { + spa_log_warn(this->log, "%p: unsupported pixel format", this); + return -ENOTSUP; + } if (decoder_id) { - if ((this->decoder.codec = avcodec_find_decoder(decoder_id)) == NULL) { + if ((codec = avcodec_find_decoder(decoder_id)) == NULL) { spa_log_error(this->log, "failed to find %d decoder", decoder_id); return -ENOTSUP; } - if ((this->decoder.context = avcodec_alloc_context3(this->decoder.codec)) == NULL) + if ((this->decoder.context = avcodec_alloc_context3(codec)) == NULL) return -EIO; if ((this->decoder.packet = av_packet_alloc()) == NULL) @@ -962,20 +987,24 @@ static int setup_convert(struct impl *this) this->decoder.context->flags2 |= AV_CODEC_FLAG2_FAST; - if (avcodec_open2(this->decoder.context, this->decoder.codec, NULL) < 0) { + if (avcodec_open2(this->decoder.context, codec, NULL) < 0) { spa_log_error(this->log, "failed to open decoder codec"); return -EIO; } + spa_log_info(this->log, "%p: using decoder %s", this, codec->name); + } else { + free_decoder(this); } + av_frame_free(&this->decoder.frame); if ((this->decoder.frame = av_frame_alloc()) == NULL) return -EIO; if (encoder_id) { - if ((this->encoder.codec = avcodec_find_encoder(encoder_id)) == NULL) { + if ((codec = avcodec_find_encoder(encoder_id)) == NULL) { spa_log_error(this->log, "failed to find %d encoder", encoder_id); return -ENOTSUP; } - if ((this->encoder.context = avcodec_alloc_context3(this->encoder.codec)) == NULL) + if ((this->encoder.context = avcodec_alloc_context3(codec)) == NULL) return -EIO; if ((this->encoder.packet = av_packet_alloc()) == NULL) @@ -985,15 +1014,21 @@ static int setup_convert(struct impl *this) this->encoder.context->flags2 |= AV_CODEC_FLAG2_FAST; this->encoder.context->time_base.num = 1; - this->encoder.context->width = out->width; - this->encoder.context->height = out->height; + this->encoder.context->width = out->size.width; + this->encoder.context->height = out->size.height; this->encoder.context->pix_fmt = out->pix_fmt; - if (avcodec_open2(this->encoder.context, this->encoder.codec, NULL) < 0) { + if (avcodec_open2(this->encoder.context, codec, NULL) < 0) { spa_log_error(this->log, "failed to open encoder codec"); return -EIO; } - } + spa_log_info(this->log, "%p: using encoder %s", this, codec->name); + } else { + free_encoder(this); + } + sws_freeContext(this->convert.context); + this->convert.context = NULL; + av_frame_free(&this->convert.frame); if ((this->convert.frame = av_frame_alloc()) == NULL) return -EIO; @@ -1098,10 +1133,11 @@ static int port_enum_formats(void *object, struct impl *this = object; struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(direction)]; struct spa_pod_frame f[1]; - int width, height; + struct spa_rectangle size; + struct spa_fraction framerate; uint32_t format = 0; - get_format(other, &width, &height, &format); + get_format(other, &format, &size, &framerate); switch (index) { case 0: @@ -1144,7 +1180,7 @@ static int port_enum_formats(void *object, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(size.width, size.height), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); @@ -1161,7 +1197,7 @@ static int port_enum_formats(void *object, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(size.width, size.height), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); @@ -1573,7 +1609,7 @@ static int port_set_format(void *object, if (linesizes[i]) port->blocks++; } - av_image_fill_plane_sizes(dir->size, + av_image_fill_plane_sizes(dir->plane_size, pix_fmt, info.info.raw.size.height, dir->linesizes); port->size = av_image_get_buffer_size(pix_fmt, @@ -1582,6 +1618,11 @@ static int port_set_format(void *object, break; } + case SPA_MEDIA_SUBTYPE_h264: + port->stride = 0; + port->size = info.info.h264.size.width * info.info.h264.size.height; + port->blocks = 1; + break; case SPA_MEDIA_SUBTYPE_mjpg: port->stride = 0; port->size = info.info.mjpg.size.width * info.info.mjpg.size.height; @@ -1906,7 +1947,7 @@ static int impl_node_process(void *object) sbuf->id, dbuf->id); /* do decoding */ - if (this->decoder.codec) { + if (this->decoder.context) { this->decoder.packet->data = sbuf->datas[0]; this->decoder.packet->size = sbuf->buf->datas[0].chunk->size; @@ -1926,16 +1967,19 @@ static int impl_node_process(void *object) } in->pix_fmt = f->format; - in->width = f->width; - in->height = f->height; + in->size.width = f->width; + in->size.height = f->height; + for (uint32_t i = 0; i < 4; ++i) { + datas[i] = f->data[i]; + strides[i] = f->linesize[i]; + sizes[i] = out->plane_size[i]; + } } else { f = this->decoder.frame; f->format = in->pix_fmt; - f->width = in->width; - f->height = in->height; + f->width = in->size.width; + f->height = in->size.height; for (uint32_t i = 0; i < sbuf->buf->n_datas; ++i) { - spa_log_trace(this->log, "in %u %u %p %d", sbuf->id, i, - sbuf->datas[i], sbuf->buf->datas[i].chunk->size); datas[i] = f->data[i] = sbuf->datas[i]; strides[i] = f->linesize[i] = sbuf->buf->datas[i].chunk->stride; sizes[i] = sbuf->buf->datas[i].chunk->size; @@ -1944,25 +1988,30 @@ static int impl_node_process(void *object) /* do conversion */ if (f->format != out->pix_fmt || - f->width != out->width || - f->height != out->height) { + f->width != (int)out->size.width || + f->height != (int)out->size.height) { if (this->convert.context == NULL) { + const AVPixFmtDescriptor *in_fmt = av_pix_fmt_desc_get(f->format); + const AVPixFmtDescriptor *out_fmt = av_pix_fmt_desc_get(out->pix_fmt); this->convert.context = sws_getContext( f->width, f->height, f->format, - out->width, out->height, out->pix_fmt, + out->size.width, out->size.height, out->pix_fmt, 0, NULL, NULL, NULL); + spa_log_info(this->log, "%p: using convert %dx%d:%s -> %dx%d:%s", + this, f->width, f->height, in_fmt->name, + out->size.width, out->size.height, out_fmt->name); } spa_log_trace(this->log, "convert"); sws_scale_frame(this->convert.context, this->convert.frame, f); f = this->convert.frame; - for (uint32_t i = 0; i < dbuf->buf->n_datas; ++i) { + for (uint32_t i = 0; i < 4; ++i) { datas[i] = f->data[i]; strides[i] = f->linesize[i]; - sizes[i] = out->size[i]; + sizes[i] = out->plane_size[i]; } } /* do encoding */ - if (this->encoder.codec) { + if (this->encoder.context) { if ((res = avcodec_send_frame(this->encoder.context, f)) < 0) { spa_log_error(this->log, "failed to send frame to codec: %d", res); return -EIO; @@ -2054,6 +2103,11 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; + free_decoder(this); + free_encoder(this); + av_frame_free(&this->decoder.frame); + av_frame_free(&this->convert.frame); + free_dir(&this->dir[SPA_DIRECTION_INPUT]); free_dir(&this->dir[SPA_DIRECTION_OUTPUT]); return 0; From 86dd93785796ad9c7628d68cd18deee89fc89c11 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 11:54:18 +0200 Subject: [PATCH 0210/1014] videoconvert: move event emission at end of functions Make a function to emit all pending events and trigger it at the end of operations that can change params. --- .../videoconvert/videoconvert-ffmpeg.c | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index a03c578c2..0a2511324 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -251,6 +251,22 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) port->info.change_mask = old; } +static void emit_info(struct impl *this, bool full) +{ + struct port *p; + uint32_t i; + + emit_node_info(this, full); + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + if ((p = GET_IN_PORT(this, i)) && p->valid) + emit_port_info(this, p, full); + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + if ((p = GET_OUT_PORT(this, i)) && p->valid) + emit_port_info(this, p, full); + } +} + static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, bool is_dsp, bool is_monitor, bool is_control) { @@ -269,7 +285,7 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); @@ -308,7 +324,6 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p spa_log_debug(this->log, "%p: add port %d:%d %d %d %d", this, direction, port_id, is_dsp, is_monitor, is_control); - emit_port_info(this, port, true); return 0; } @@ -639,17 +654,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) return res; - - emit_node_info(this, false); break; } case SPA_PARAM_Props: - if (apply_props(this, param) > 0) - emit_node_info(this, false); + apply_props(this, param); break; default: return -ENOENT; } + emit_info(this, false); return 0; } @@ -1025,7 +1038,7 @@ static int setup_convert(struct impl *this) spa_log_info(this->log, "%p: using encoder %s", this, codec->name); } else { free_encoder(this); - } + } sws_freeContext(this->convert.context); this->convert.context = NULL; av_frame_free(&this->convert.frame); @@ -1034,8 +1047,6 @@ static int setup_convert(struct impl *this) this->setup = true; - emit_node_info(this, false); - return 0; } @@ -1081,24 +1092,15 @@ impl_node_add_listener(void *object, void *data) { struct impl *this = object; - uint32_t i; struct spa_hook_list save; - struct port *p; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); - for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { - if ((p = GET_IN_PORT(this, i)) && p->valid) - emit_port_info(this, p, true); - } - for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { - if ((p = GET_OUT_PORT(this, i)) && p->valid) - emit_port_info(this, p, true); - } + emit_info(this, true); + spa_hook_list_join(&this->hooks, &save); return 0; @@ -1440,7 +1442,6 @@ static int port_set_latency(void *object, oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; - emit_port_info(this, oport, false); } } else { spa_latency_info_combine_start(&info, other); @@ -1468,14 +1469,12 @@ static int port_set_latency(void *object, oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; - emit_port_info(this, oport, false); } } } if (emit) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; - emit_port_info(this, port, false); } return 0; } @@ -1513,12 +1512,10 @@ static int port_set_tag(void *object, oport = GET_PORT(this, other, i); oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Tag].user++; - emit_port_info(this, oport, false); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Tag].user++; - emit_port_info(this, port, false); return 0; } @@ -1662,8 +1659,6 @@ static int port_set_format(void *object, port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } - emit_port_info(this, port, false); - return 0; } @@ -1675,6 +1670,7 @@ impl_node_port_set_param(void *object, const struct spa_pod *param) { struct impl *this = object; + int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -1685,14 +1681,19 @@ impl_node_port_set_param(void *object, switch (id) { case SPA_PARAM_Latency: - return port_set_latency(this, direction, port_id, flags, param); + res = port_set_latency(this, direction, port_id, flags, param); + break; case SPA_PARAM_Tag: - return port_set_tag(this, direction, port_id, flags, param); + res = port_set_tag(this, direction, port_id, flags, param); + break; case SPA_PARAM_Format: - return port_set_format(this, direction, port_id, flags, param); + res = port_set_format(this, direction, port_id, flags, param); + break; default: return -ENOENT; } + emit_info(this, false); + return res; } static inline void queue_buffer(struct impl *this, struct port *port, uint32_t id) From 6373827a401106af1cfa6c20bb1cc06a8636d15a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 12:10:02 +0200 Subject: [PATCH 0211/1014] videoconvert: use a table for format conversion Use a table with format conversion values so that we can also use flags for the formats. --- .../videoconvert/videoconvert-ffmpeg.c | 292 ++++++++---------- 1 file changed, 126 insertions(+), 166 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 0a2511324..580105a91 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -267,6 +267,132 @@ static void emit_info(struct impl *this, bool full) } } +struct format_info { + enum AVPixelFormat pix_fmt; + uint32_t format; +#define FORMAT_DSP (1<<0) +#define FORMAT_COMMON (1<<1) + uint32_t flags; +}; + +static struct format_info format_info[] = +{ +#if defined AV_PIX_FMT_AYUV + { AV_PIX_FMT_AYUV, SPA_VIDEO_FORMAT_AYUV, FORMAT_DSP | FORMAT_COMMON }, +#else + { AV_PIX_FMT_YUV444P, SPA_VIDEO_FORMAT_Y444, FORMAT_DSP | FORMAT_COMMON }, +#endif + { AV_PIX_FMT_RGBA, SPA_VIDEO_FORMAT_RGBA, FORMAT_DSP | FORMAT_COMMON }, + + { AV_PIX_FMT_YUYV422, SPA_VIDEO_FORMAT_YUY2, FORMAT_COMMON }, + { AV_PIX_FMT_UYVY422, SPA_VIDEO_FORMAT_UYVY, FORMAT_COMMON }, + { AV_PIX_FMT_YVYU422, SPA_VIDEO_FORMAT_YVYU, FORMAT_COMMON }, + { AV_PIX_FMT_YUV420P, SPA_VIDEO_FORMAT_I420, FORMAT_COMMON }, + + //{ AV_PIX_FMT_BGR0, SPA_VIDEO_FORMAT_BGRx }, + //{ AV_PIX_FMT_BGRA, SPA_VIDEO_FORMAT_BGRA }, + //{ AV_PIX_FMT_ARGB, SPA_VIDEO_FORMAT_ARGB }, + //{ AV_PIX_FMT_ABGR, SPA_VIDEO_FORMAT_ABGR }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_YV12 }, + + //{ AV_PIX_FMT_RGB0, SPA_VIDEO_FORMAT_RGBx }, + //{ AV_PIX_FMT_0RGB, SPA_VIDEO_FORMAT_xRGB }, + //{ AV_PIX_FMT_0BGR, SPA_VIDEO_FORMAT_xBGR }, + + //{ AV_PIX_FMT_RGB24, SPA_VIDEO_FORMAT_RGB }, + //{ AV_PIX_FMT_BGR24, SPA_VIDEO_FORMAT_BGR }, + //{ AV_PIX_FMT_YUV411P, SPA_VIDEO_FORMAT_Y41B }, + //{ AV_PIX_FMT_YUV422P, SPA_VIDEO_FORMAT_Y42B }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v210 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v216 }, + + //{ AV_PIX_FMT_NV12, SPA_VIDEO_FORMAT_NV12 }, + //{ AV_PIX_FMT_NV21, SPA_VIDEO_FORMAT_NV21 }, + //{ AV_PIX_FMT_GRAY8, SPA_VIDEO_FORMAT_GRAY8 }, + //{ AV_PIX_FMT_GRAY16BE, SPA_VIDEO_FORMAT_GRAY16_BE }, + //{ AV_PIX_FMT_GRAY16LE, SPA_VIDEO_FORMAT_GRAY16_LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v308 }, + //{ AV_PIX_FMT_RGB565, SPA_VIDEO_FORMAT_RGB16 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGR16 }, + //{ AV_PIX_FMT_RGB555, SPA_VIDEO_FORMAT_RGB15 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGR15 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_UYVP }, + //{ AV_PIX_FMT_YUVA420P, SPA_VIDEO_FORMAT_A420 }, + //{ AV_PIX_FMT_PAL8, SPA_VIDEO_FORMAT_RGB8P }, + //{ AV_PIX_FMT_YUV410P, SPA_VIDEO_FORMAT_YUV9 }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_YVU9 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_IYU1 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ARGB64 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_AYUV64 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_r210 }, + + //{ AV_PIX_FMT_YUV420P10BE, SPA_VIDEO_FORMAT_I420_10BE }, + //{ AV_PIX_FMT_YUV420P10LE, SPA_VIDEO_FORMAT_I420_10LE }, + //{ AV_PIX_FMT_YUV422P10BE, SPA_VIDEO_FORMAT_I422_10BE }, + //{ AV_PIX_FMT_YUV422P10LE, SPA_VIDEO_FORMAT_I422_10LE }, + //{ AV_PIX_FMT_YUV444P10BE, SPA_VIDEO_FORMAT_Y444_10BE }, + //{ AV_PIX_FMT_YUV444P10LE, SPA_VIDEO_FORMAT_Y444_10LE }, + //{ AV_PIX_FMT_GBRP, SPA_VIDEO_FORMAT_GBR }, + //{ AV_PIX_FMT_GBRP10BE, SPA_VIDEO_FORMAT_GBR_10BE }, + //{ AV_PIX_FMT_GBRP10LE, SPA_VIDEO_FORMAT_GBR_10LE }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV16 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV24 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV12_64Z32 }, + + //{ AV_PIX_FMT_YUVA420P10BE, SPA_VIDEO_FORMAT_A420_10BE }, + //{ AV_PIX_FMT_YUVA420P10LE, SPA_VIDEO_FORMAT_A420_10LE }, + //{ AV_PIX_FMT_YUVA422P10BE, SPA_VIDEO_FORMAT_A422_10BE }, + //{ AV_PIX_FMT_YUVA422P10LE, SPA_VIDEO_FORMAT_A422_10LE }, + //{ AV_PIX_FMT_YUVA444P10BE, SPA_VIDEO_FORMAT_A444_10BE }, + //{ AV_PIX_FMT_YUVA444P10LE, SPA_VIDEO_FORMAT_A444_10LE }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV61 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_P010_10BE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_P010_10LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_IYU2 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_VYUY }, + + //{ AV_PIX_FMT_GBRAP, SPA_VIDEO_FORMAT_GBRA }, + //{ AV_PIX_FMT_GBRAP10BE, SPA_VIDEO_FORMAT_GBRA_10BE }, + //{ AV_PIX_FMT_GBRAP10LE, SPA_VIDEO_FORMAT_GBRA_10LE }, + //{ AV_PIX_FMT_GBRP12BE, SPA_VIDEO_FORMAT_GBR_12BE }, + //{ AV_PIX_FMT_GBRP12LE, SPA_VIDEO_FORMAT_GBR_12LE }, + //{ AV_PIX_FMT_GBRAP12BE, SPA_VIDEO_FORMAT_GBRA_12BE }, + //{ AV_PIX_FMT_GBRAP12LE, SPA_VIDEO_FORMAT_GBRA_12LE }, + //{ AV_PIX_FMT_YUV420P12BE, SPA_VIDEO_FORMAT_I420_12BE }, + //{ AV_PIX_FMT_YUV420P12LE, SPA_VIDEO_FORMAT_I420_12LE }, + //{ AV_PIX_FMT_YUV422P12BE, SPA_VIDEO_FORMAT_I422_12BE }, + //{ AV_PIX_FMT_YUV422P12LE, SPA_VIDEO_FORMAT_I422_12LE }, + //{ AV_PIX_FMT_YUV444P12BE, SPA_VIDEO_FORMAT_Y444_12BE }, + //{ AV_PIX_FMT_YUV444P12LE, SPA_VIDEO_FORMAT_Y444_12LE }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_F16 }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_F32 }, + + //{ AV_PIX_FMT_X2RGB10LE, SPA_VIDEO_FORMAT_xRGB_210LE }, + //{ AV_PIX_FMT_X2BGR10LE, SPA_VIDEO_FORMAT_xBGR_210LE }, + + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBx_102LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGRx_102LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ARGB_210LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ABGR_210LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_102LE }, + //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGRA_102LE }, +}; + +static enum AVPixelFormat format_to_pix_fmt(uint32_t format) +{ + SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { + if (i->format == format) + return i->pix_fmt; + } + return AV_PIX_FMT_NONE; +} + static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, bool is_dsp, bool is_monitor, bool is_control) { @@ -666,172 +792,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, return 0; } -static enum AVPixelFormat format_to_pix_fmt(uint32_t format) -{ - switch (format) { - case SPA_VIDEO_FORMAT_I420: - return AV_PIX_FMT_YUV420P; - case SPA_VIDEO_FORMAT_YV12: - break; - case SPA_VIDEO_FORMAT_YUY2: - return AV_PIX_FMT_YUYV422; - case SPA_VIDEO_FORMAT_UYVY: - return AV_PIX_FMT_UYVY422; - case SPA_VIDEO_FORMAT_AYUV: - break; - case SPA_VIDEO_FORMAT_RGBx: - return AV_PIX_FMT_RGB0; - case SPA_VIDEO_FORMAT_BGRx: - return AV_PIX_FMT_BGR0; - case SPA_VIDEO_FORMAT_xRGB: - return AV_PIX_FMT_0RGB; - case SPA_VIDEO_FORMAT_xBGR: - return AV_PIX_FMT_0BGR; - case SPA_VIDEO_FORMAT_RGBA: - return AV_PIX_FMT_RGBA; - case SPA_VIDEO_FORMAT_BGRA: - return AV_PIX_FMT_BGRA; - case SPA_VIDEO_FORMAT_ARGB: - return AV_PIX_FMT_ARGB; - case SPA_VIDEO_FORMAT_ABGR: - return AV_PIX_FMT_ABGR; - case SPA_VIDEO_FORMAT_RGB: - return AV_PIX_FMT_RGB24; - case SPA_VIDEO_FORMAT_BGR: - return AV_PIX_FMT_BGR24; - case SPA_VIDEO_FORMAT_Y41B: - return AV_PIX_FMT_YUV411P; - case SPA_VIDEO_FORMAT_Y42B: - return AV_PIX_FMT_YUV422P; - case SPA_VIDEO_FORMAT_YVYU: - return AV_PIX_FMT_YVYU422; - case SPA_VIDEO_FORMAT_Y444: - return AV_PIX_FMT_YUV444P; - case SPA_VIDEO_FORMAT_v210: - case SPA_VIDEO_FORMAT_v216: - break; - case SPA_VIDEO_FORMAT_NV12: - return AV_PIX_FMT_NV12; - case SPA_VIDEO_FORMAT_NV21: - return AV_PIX_FMT_NV21; - case SPA_VIDEO_FORMAT_GRAY8: - return AV_PIX_FMT_GRAY8; - case SPA_VIDEO_FORMAT_GRAY16_BE: - return AV_PIX_FMT_GRAY16BE; - case SPA_VIDEO_FORMAT_GRAY16_LE: - return AV_PIX_FMT_GRAY16LE; - case SPA_VIDEO_FORMAT_v308: - break; - case SPA_VIDEO_FORMAT_RGB16: - return AV_PIX_FMT_RGB565; - case SPA_VIDEO_FORMAT_BGR16: - break; - case SPA_VIDEO_FORMAT_RGB15: - return AV_PIX_FMT_RGB555; - case SPA_VIDEO_FORMAT_BGR15: - case SPA_VIDEO_FORMAT_UYVP: - break; - case SPA_VIDEO_FORMAT_A420: - return AV_PIX_FMT_YUVA420P; - case SPA_VIDEO_FORMAT_RGB8P: - return AV_PIX_FMT_PAL8; - case SPA_VIDEO_FORMAT_YUV9: - return AV_PIX_FMT_YUV410P; - case SPA_VIDEO_FORMAT_YVU9: - case SPA_VIDEO_FORMAT_IYU1: - case SPA_VIDEO_FORMAT_ARGB64: - case SPA_VIDEO_FORMAT_AYUV64: - case SPA_VIDEO_FORMAT_r210: - break; - case SPA_VIDEO_FORMAT_I420_10BE: - return AV_PIX_FMT_YUV420P10BE; - case SPA_VIDEO_FORMAT_I420_10LE: - return AV_PIX_FMT_YUV420P10LE; - case SPA_VIDEO_FORMAT_I422_10BE: - return AV_PIX_FMT_YUV422P10BE; - case SPA_VIDEO_FORMAT_I422_10LE: - return AV_PIX_FMT_YUV422P10LE; - case SPA_VIDEO_FORMAT_Y444_10BE: - return AV_PIX_FMT_YUV444P10BE; - case SPA_VIDEO_FORMAT_Y444_10LE: - return AV_PIX_FMT_YUV444P10LE; - case SPA_VIDEO_FORMAT_GBR: - return AV_PIX_FMT_GBRP; - case SPA_VIDEO_FORMAT_GBR_10BE: - return AV_PIX_FMT_GBRP10BE; - case SPA_VIDEO_FORMAT_GBR_10LE: - return AV_PIX_FMT_GBRP10LE; - case SPA_VIDEO_FORMAT_NV16: - case SPA_VIDEO_FORMAT_NV24: - case SPA_VIDEO_FORMAT_NV12_64Z32: - break; - case SPA_VIDEO_FORMAT_A420_10BE: - return AV_PIX_FMT_YUVA420P10BE; - case SPA_VIDEO_FORMAT_A420_10LE: - return AV_PIX_FMT_YUVA420P10LE; - case SPA_VIDEO_FORMAT_A422_10BE: - return AV_PIX_FMT_YUVA422P10BE; - case SPA_VIDEO_FORMAT_A422_10LE: - return AV_PIX_FMT_YUVA422P10LE; - case SPA_VIDEO_FORMAT_A444_10BE: - return AV_PIX_FMT_YUVA444P10BE; - case SPA_VIDEO_FORMAT_A444_10LE: - return AV_PIX_FMT_YUVA444P10LE; - case SPA_VIDEO_FORMAT_NV61: - case SPA_VIDEO_FORMAT_P010_10BE: - case SPA_VIDEO_FORMAT_P010_10LE: - case SPA_VIDEO_FORMAT_IYU2: - case SPA_VIDEO_FORMAT_VYUY: - break; - case SPA_VIDEO_FORMAT_GBRA: - return AV_PIX_FMT_GBRAP; - case SPA_VIDEO_FORMAT_GBRA_10BE: - return AV_PIX_FMT_GBRAP10BE; - case SPA_VIDEO_FORMAT_GBRA_10LE: - return AV_PIX_FMT_GBRAP10LE; - case SPA_VIDEO_FORMAT_GBR_12BE: - return AV_PIX_FMT_GBRP12BE; - case SPA_VIDEO_FORMAT_GBR_12LE: - return AV_PIX_FMT_GBRP12LE; - case SPA_VIDEO_FORMAT_GBRA_12BE: - return AV_PIX_FMT_GBRAP12BE; - case SPA_VIDEO_FORMAT_GBRA_12LE: - return AV_PIX_FMT_GBRAP12LE; - case SPA_VIDEO_FORMAT_I420_12BE: - return AV_PIX_FMT_YUV420P12BE; - case SPA_VIDEO_FORMAT_I420_12LE: - return AV_PIX_FMT_YUV420P12LE; - case SPA_VIDEO_FORMAT_I422_12BE: - return AV_PIX_FMT_YUV422P12BE; - case SPA_VIDEO_FORMAT_I422_12LE: - return AV_PIX_FMT_YUV422P12LE; - case SPA_VIDEO_FORMAT_Y444_12BE: - return AV_PIX_FMT_YUV444P12BE; - case SPA_VIDEO_FORMAT_Y444_12LE: - return AV_PIX_FMT_YUV444P12LE; - - case SPA_VIDEO_FORMAT_RGBA_F16: - case SPA_VIDEO_FORMAT_RGBA_F32: - break; - - case SPA_VIDEO_FORMAT_xRGB_210LE: - return AV_PIX_FMT_X2RGB10LE; - case SPA_VIDEO_FORMAT_xBGR_210LE: - return AV_PIX_FMT_X2BGR10LE; - - case SPA_VIDEO_FORMAT_RGBx_102LE: - case SPA_VIDEO_FORMAT_BGRx_102LE: - case SPA_VIDEO_FORMAT_ARGB_210LE: - case SPA_VIDEO_FORMAT_ABGR_210LE: - case SPA_VIDEO_FORMAT_RGBA_102LE: - case SPA_VIDEO_FORMAT_BGRA_102LE: - break; - default: - break; - } - return AV_PIX_FMT_NONE; -} - static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, struct spa_fraction *framerate) { From aedbe51043353192f2082c0b4aee623f0ad76b3a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 12:12:09 +0200 Subject: [PATCH 0212/1014] videoconvert: move get_format around --- .../videoconvert/videoconvert-ffmpeg.c | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 580105a91..827a01160 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -393,6 +393,45 @@ static enum AVPixelFormat format_to_pix_fmt(uint32_t format) return AV_PIX_FMT_NONE; } +static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, + struct spa_fraction *framerate) +{ + if (dir->have_format) { + switch (dir->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_dsp: + *format = dir->format.info.dsp.format; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); + break; + case SPA_MEDIA_SUBTYPE_raw: + *format = dir->format.info.raw.format; + *size = dir->format.info.raw.size; + *framerate = dir->format.info.raw.framerate; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + *format = SPA_VIDEO_FORMAT_I420; + *size = dir->format.info.mjpg.size; + *framerate = dir->format.info.mjpg.framerate; + break; + case SPA_MEDIA_SUBTYPE_h264: + *format = SPA_VIDEO_FORMAT_I420; + *size = dir->format.info.h264.size; + *framerate = dir->format.info.h264.framerate; + break; + default: + *format = SPA_VIDEO_FORMAT_I420; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); + break; + } + } else { + *format = SPA_VIDEO_FORMAT_I420; + *size = SPA_RECTANGLE(640, 480); + *framerate = SPA_FRACTION(30, 1); + } + return 0; +} + static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, bool is_dsp, bool is_monitor, bool is_control) { @@ -792,45 +831,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, return 0; } -static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, - struct spa_fraction *framerate) -{ - if (dir->have_format) { - switch (dir->format.media_subtype) { - case SPA_MEDIA_SUBTYPE_dsp: - *format = dir->format.info.dsp.format; - *size = SPA_RECTANGLE(640, 480); - *framerate = SPA_FRACTION(30, 1); - break; - case SPA_MEDIA_SUBTYPE_raw: - *format = dir->format.info.raw.format; - *size = dir->format.info.raw.size; - *framerate = dir->format.info.raw.framerate; - break; - case SPA_MEDIA_SUBTYPE_mjpg: - *format = SPA_VIDEO_FORMAT_I420; - *size = dir->format.info.mjpg.size; - *framerate = dir->format.info.mjpg.framerate; - break; - case SPA_MEDIA_SUBTYPE_h264: - *format = SPA_VIDEO_FORMAT_I420; - *size = dir->format.info.h264.size; - *framerate = dir->format.info.h264.framerate; - break; - default: - *format = SPA_VIDEO_FORMAT_I420; - *size = SPA_RECTANGLE(640, 480); - *framerate = SPA_FRACTION(30, 1); - break; - } - } else { - *format = SPA_VIDEO_FORMAT_I420; - *size = SPA_RECTANGLE(640, 480); - *framerate = SPA_FRACTION(30, 1); - } - return 0; -} - static inline void free_decoder(struct impl *this) { avcodec_free_context(&this->decoder.context); From 80f700876d79beffbd4fbbd9509d12a8cfe01381 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 12:33:22 +0200 Subject: [PATCH 0213/1014] videoconvert: refactor enum_params Move the enum_param implementation out of the main enum_param loop. This makes it easier to read the individual functions. --- .../videoconvert/videoconvert-ffmpeg.c | 378 +++++++++--------- 1 file changed, 196 insertions(+), 182 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 827a01160..36d6ced0a 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -503,6 +503,58 @@ static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t return 0; } +static int node_param_enum_port_config(struct impl *this, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + { + struct dir *dir = &this->dir[index]; + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_EnumPortConfig, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_dsp, + SPA_PARAM_PORT_CONFIG_MODE_convert), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); + return 1; + } + } + return 0; +} + +static int node_param_port_config(struct impl *this, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + { + struct dir *dir = &this->dir[index];; + struct spa_pod_frame f[1]; + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig); + spa_pod_builder_add(b, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), + 0); + + if (dir->have_format) { + spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); + spa_format_video_build(b, SPA_PARAM_PORT_CONFIG_format, + &dir->format); + } + *param = spa_pod_builder_pop(b, &f[0]); + return 1; + } + } + return 0; +} + static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -513,6 +565,7 @@ static int impl_node_enum_params(void *object, int seq, uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; + int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -524,92 +577,27 @@ static int impl_node_enum_params(void *object, int seq, spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = NULL; switch (id) { case SPA_PARAM_EnumPortConfig: - { - struct dir *dir; - switch (result.index) { - case 0: - dir = &this->dir[SPA_DIRECTION_INPUT];; - break; - case 1: - dir = &this->dir[SPA_DIRECTION_OUTPUT];; - break; - default: - return 0; - } - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, id, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, - SPA_PARAM_PORT_CONFIG_MODE_none, - SPA_PARAM_PORT_CONFIG_MODE_none, - SPA_PARAM_PORT_CONFIG_MODE_dsp, - SPA_PARAM_PORT_CONFIG_MODE_convert), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), - SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); + res = node_param_enum_port_config(this, result.index, ¶m, &b); break; - } case SPA_PARAM_PortConfig: - { - struct dir *dir; - struct spa_pod_frame f[1]; - - switch (result.index) { - case 0: - dir = &this->dir[SPA_DIRECTION_INPUT];; - break; - case 1: - dir = &this->dir[SPA_DIRECTION_OUTPUT];; - break; - default: - return 0; - } - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); - spa_pod_builder_add(&b, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), - SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), - 0); - - if (dir->have_format) { - spa_pod_builder_prop(&b, SPA_PARAM_PORT_CONFIG_format, 0); - spa_format_video_build(&b, SPA_PARAM_PORT_CONFIG_format, - &dir->format); - } - param = spa_pod_builder_pop(&b, &f[0]); + res = node_param_port_config(this, result.index, ¶m, &b); break; - } case SPA_PARAM_PropInfo: - { - switch (result.index) { - default: - return 0; - } + res = 0; break; - } - case SPA_PARAM_Props: - { - struct spa_pod_frame f[2]; - - switch (result.index) { - case 0: - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Props, id); - param = spa_pod_builder_pop(&b, &f[0]); - break; - default: - return 0; - } + res = 0; break; - } default: return 0; } + if (res <= 0) + return res; - if (spa_pod_filter(&b, &result.param, param, filter) < 0) + if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -1086,14 +1074,10 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) +static int port_param_enum_format(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *builder) { - struct impl *this = object; - struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; struct spa_pod_frame f[1]; struct spa_rectangle size; struct spa_fraction framerate; @@ -1103,12 +1087,12 @@ static int port_enum_formats(void *object, switch (index) { case 0: - if (PORT_IS_DSP(this, direction, port_id)) { + if (port->is_dsp) { struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( .format = SPA_VIDEO_FORMAT_DSP_F32); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &info); - } else if (PORT_IS_CONTROL(this, direction, port_id)) { + } else if (port->is_control) { *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), @@ -1124,8 +1108,7 @@ static int port_enum_formats(void *object, } break; case 1: - if (PORT_IS_DSP(this, direction, port_id) || - PORT_IS_CONTROL(this, direction, port_id)) + if (port->is_dsp || port->is_control) return 0; spa_pod_builder_push_object(builder, &f[0], @@ -1149,8 +1132,7 @@ static int port_enum_formats(void *object, *param = spa_pod_builder_pop(builder, &f[0]); break; case 2: - if (PORT_IS_DSP(this, direction, port_id) || - PORT_IS_CONTROL(this, direction, port_id)) + if (port->is_dsp || port->is_control) return 0; spa_pod_builder_push_object(builder, &f[0], @@ -1171,6 +1153,122 @@ static int port_enum_formats(void *object, return 1; } + +static int port_param_format(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + uint32_t id = SPA_PARAM_Format; + + if (!port->have_format) + return -EIO; + if (index != 0) + return 0; + + if (port->is_dsp) { + *param = spa_format_video_dsp_build(b, id, &port->format.info.dsp); + } else if (port->is_control) { + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, id, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_Int( + (1u<format); + } + return 1; +} + +static int port_param_buffers(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + uint32_t id = SPA_PARAM_Buffers; + uint32_t size, min, max, def; + struct port *other; + + if (!port->have_format) + return -EIO; + if (index != 0) + return 0; + + if (port->is_dsp) { + size = 1024 * 1024 * 16; + } else { + size = port->size; + } + + other = GET_PORT(this, SPA_DIRECTION_REVERSE(port->direction), port->id); + if (other->n_buffers > 0) { + min = other->n_buffers; + } else { + min = 2; + } + max = MAX_BUFFERS; + def = SPA_CLAMP(8u, min, MAX_BUFFERS); + + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(def, min, max), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + size, 16, INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int( + port->stride, 0, INT32_MAX)); + return 1; +} + +static int port_param_meta(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + return 1; + } + return 0; +} + +static int port_param_io(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + return 1; + } + return 0; +} + +static int port_param_latency(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + *param = spa_latency_build(b, SPA_PARAM_Latency, &port->latency[index]); + return 1; + } + return 0; +} + +static int port_param_tag(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + if (port->is_monitor) + index = index ^ 1; + *param = this->dir[index].tag; + return 1; + } + return 0; +} + static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -1178,8 +1276,8 @@ impl_node_port_enum_params(void *object, int seq, const struct spa_pod *filter) { struct impl *this = object; - struct port *port, *other; - struct spa_pod *param; + struct port *port; + const struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; @@ -1203,118 +1301,34 @@ impl_node_port_enum_params(void *object, int seq, spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = NULL; switch (id) { case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(object, direction, port_id, result.index, ¶m, &b)) <= 0) - return res; + res = port_param_enum_format(this, port, result.index, ¶m, &b); break; case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (PORT_IS_DSP(this, direction, port_id)) - param = spa_format_video_dsp_build(&b, id, &port->format.info.dsp); - else if (PORT_IS_CONTROL(this, direction, port_id)) - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, id, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_Int( - (1u<format); + res = port_param_format(this, port, result.index, ¶m, &b); break; case SPA_PARAM_Buffers: - { - uint32_t size, min, max, def; - - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (PORT_IS_DSP(this, direction, port_id)) { - size = 1024 * 1024 * 16; - } else { - size = port->size; - } - - other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); - if (other->n_buffers > 0) { - min = other->n_buffers; - } else { - min = 2; - } - max = MAX_BUFFERS; - def = SPA_CLAMP(8u, min, MAX_BUFFERS); - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(def, min, max), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size, 16, INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int( - port->stride, 0, INT32_MAX)); + res = port_param_buffers(this, port, result.index, ¶m, &b); break; - } case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } + res = port_param_meta(this, port, result.index, ¶m, &b); break; case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } + res = port_param_io(this, port, result.index, ¶m, &b); break; case SPA_PARAM_Latency: - switch (result.index) { - case 0: case 1: - { - uint32_t idx = result.index; - param = spa_latency_build(&b, id, &port->latency[idx]); - break; - } - default: - return 0; - } + res = port_param_latency(this, port, result.index, ¶m, &b); break; case SPA_PARAM_Tag: - switch (result.index) { - case 0: case 1: - { - uint32_t idx = result.index; - if (port->is_monitor) - idx = idx ^ 1; - param = this->dir[idx].tag; - if (param == NULL) - goto next; - break; - } - default: - return 0; - } + res = port_param_tag(this, port, result.index, ¶m, &b); break; default: return -ENOENT; } + if (res <= 0) + return res; if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; From 46d376cb788b7c7485f8f63129222ac51db28d53 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 12:54:41 +0200 Subject: [PATCH 0214/1014] videoconvert: pass the param id to enum functions --- .../videoconvert/videoconvert-ffmpeg.c | 86 +++++++++---------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 36d6ced0a..2c8f70097 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -503,7 +503,7 @@ static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t return 0; } -static int node_param_enum_port_config(struct impl *this, uint32_t index, +static int node_param_enum_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { @@ -511,7 +511,7 @@ static int node_param_enum_port_config(struct impl *this, uint32_t index, { struct dir *dir = &this->dir[index]; *param = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_EnumPortConfig, + SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, SPA_PARAM_PORT_CONFIG_MODE_none, @@ -526,7 +526,7 @@ static int node_param_enum_port_config(struct impl *this, uint32_t index, return 0; } -static int node_param_port_config(struct impl *this, uint32_t index, +static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { @@ -535,7 +535,7 @@ static int node_param_port_config(struct impl *this, uint32_t index, struct dir *dir = &this->dir[index];; struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig); + SPA_TYPE_OBJECT_ParamPortConfig, id); spa_pod_builder_add(b, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), @@ -580,10 +580,10 @@ static int impl_node_enum_params(void *object, int seq, param = NULL; switch (id) { case SPA_PARAM_EnumPortConfig: - res = node_param_enum_port_config(this, result.index, ¶m, &b); + res = node_param_enum_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PortConfig: - res = node_param_port_config(this, result.index, ¶m, &b); + res = node_param_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PropInfo: res = 0; @@ -1074,8 +1074,8 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static int port_param_enum_format(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *builder) +static int port_param_enum_format(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; struct spa_pod_frame f[1]; @@ -1090,18 +1090,17 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t if (port->is_dsp) { struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( .format = SPA_VIDEO_FORMAT_DSP_F32); - *param = spa_format_video_dsp_build(builder, - SPA_PARAM_EnumFormat, &info); + *param = spa_format_video_dsp_build(b, id, &info); } else if (port->is_control) { - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( (1u<have_format) { - *param = spa_format_video_build(builder, SPA_PARAM_EnumFormat, &other->format); + *param = spa_format_video_build(b, id, &other->format); } else { *param = NULL; } @@ -1111,9 +1110,8 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t if (port->is_dsp || port->is_control) return 0; - spa_pod_builder_push_object(builder, &f[0], - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, @@ -1129,15 +1127,14 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); - *param = spa_pod_builder_pop(builder, &f[0]); + *param = spa_pod_builder_pop(b, &f[0]); break; case 2: if (port->is_dsp || port->is_control) return 0; - spa_pod_builder_push_object(builder, &f[0], - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( @@ -1145,7 +1142,7 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), 0); - *param = spa_pod_builder_pop(builder, &f[0]); + *param = spa_pod_builder_pop(b, &f[0]); break; default: return 0; @@ -1154,11 +1151,9 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t } -static int port_param_format(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_format(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { - uint32_t id = SPA_PARAM_Format; - if (!port->have_format) return -EIO; if (index != 0) @@ -1179,10 +1174,9 @@ static int port_param_format(struct impl *this, struct port *port, uint32_t inde return 1; } -static int port_param_buffers(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_buffers(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { - uint32_t id = SPA_PARAM_Buffers; uint32_t size, min, max, def; struct port *other; @@ -1217,13 +1211,13 @@ static int port_param_buffers(struct impl *this, struct port *port, uint32_t ind return 1; } -static int port_param_meta(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_meta(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); return 1; @@ -1231,13 +1225,13 @@ static int port_param_meta(struct impl *this, struct port *port, uint32_t index, return 0; } -static int port_param_io(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_io(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO, + SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); return 1; @@ -1245,19 +1239,19 @@ static int port_param_io(struct impl *this, struct port *port, uint32_t index, return 0; } -static int port_param_latency(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_latency(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: - *param = spa_latency_build(b, SPA_PARAM_Latency, &port->latency[index]); + *param = spa_latency_build(b, id, &port->latency[index]); return 1; } return 0; } -static int port_param_tag(struct impl *this, struct port *port, uint32_t index, - const struct spa_pod **param, struct spa_pod_builder *b) +static int port_param_tag(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: @@ -1304,25 +1298,25 @@ impl_node_port_enum_params(void *object, int seq, param = NULL; switch (id) { case SPA_PARAM_EnumFormat: - res = port_param_enum_format(this, port, result.index, ¶m, &b); + res = port_param_enum_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Format: - res = port_param_format(this, port, result.index, ¶m, &b); + res = port_param_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Buffers: - res = port_param_buffers(this, port, result.index, ¶m, &b); + res = port_param_buffers(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Meta: - res = port_param_meta(this, port, result.index, ¶m, &b); + res = port_param_meta(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_IO: - res = port_param_io(this, port, result.index, ¶m, &b); + res = port_param_io(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Latency: - res = port_param_latency(this, port, result.index, ¶m, &b); + res = port_param_latency(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Tag: - res = port_param_tag(this, port, result.index, ¶m, &b); + res = port_param_tag(this, port, id, result.index, ¶m, &b); break; default: return -ENOENT; From 1904521a4dcf5bd56827a688cf8862c9fa9ab4d6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 10:49:35 +0200 Subject: [PATCH 0215/1014] videoconvert: add PeerFormats support Make a new PeerFormats param that can be set on ports to let it know about the possible peer formats. This can be used by converters to calculate an optimum conversion. make the videoadpter query the follower formats, simplify them and then set them as PeerFormats on the converter. Implement peerformats in videoconvert. This makes EnumFormat on the port depend on the negotiated format of the peer. It will suggest a Format that most closely matches the current negotiated format with the available PeerFormats. This then makes it possible to negotiate to the format that would require the least amount of conversions. --- spa/include/spa/param/param-types.h | 1 + spa/include/spa/param/param.h | 1 + spa/include/spa/pod/simplify.h | 188 ++++++++ spa/plugins/videoconvert/videoadapter.c | 112 +++-- .../videoconvert/videoconvert-ffmpeg.c | 449 ++++++++++++++++-- 5 files changed, 643 insertions(+), 108 deletions(-) create mode 100644 spa/include/spa/pod/simplify.h diff --git a/spa/include/spa/param/param-types.h b/spa/include/spa/param/param-types.h index ebb8d988b..62ffe7da9 100644 --- a/spa/include/spa/param/param-types.h +++ b/spa/include/spa/param/param-types.h @@ -41,6 +41,7 @@ static const struct spa_type_info spa_type_param[] = { { SPA_PARAM_Latency, SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_INFO_PARAM_ID_BASE "Latency", NULL }, { SPA_PARAM_ProcessLatency, SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_INFO_PARAM_ID_BASE "ProcessLatency", NULL }, { SPA_PARAM_Tag, SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_INFO_PARAM_ID_BASE "Tag", NULL }, + { SPA_PARAM_PeerFormats, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ID_BASE "PeerFormats", NULL }, { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/param.h b/spa/include/spa/param/param.h index 51c442c35..be8deb54c 100644 --- a/spa/include/spa/param/param.h +++ b/spa/include/spa/param/param.h @@ -40,6 +40,7 @@ enum spa_param_type { SPA_PARAM_Latency, /**< latency reporting, a SPA_TYPE_OBJECT_ParamLatency */ SPA_PARAM_ProcessLatency, /**< processing latency, a SPA_TYPE_OBJECT_ParamProcessLatency */ SPA_PARAM_Tag, /**< tag reporting, a SPA_TYPE_OBJECT_ParamTag. Since 0.3.79 */ + SPA_PARAM_PeerFormats, /**< peer formats, a SPA_TYPE_Struct of SPA_TYPE_OBJECT_Format. Since 1.5.0 */ }; /** information about a parameter */ diff --git a/spa/include/spa/pod/simplify.h b/spa/include/spa/pod/simplify.h new file mode 100644 index 000000000..dc7803194 --- /dev/null +++ b/spa/include/spa/pod/simplify.h @@ -0,0 +1,188 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_POD_SIMPLIFY_H +#define SPA_POD_SIMPLIFY_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef SPA_API_POD_SIMPLIFY + #ifdef SPA_API_IMPL + #define SPA_API_POD_SIMPLIFY SPA_API_IMPL + #else + #define SPA_API_POD_SIMPLIFY static inline + #endif +#endif + +/** + * \addtogroup spa_pod + * \{ + */ + +SPA_API_POD_SIMPLIFY int +spa_pod_simplify_merge(struct spa_pod_builder *b, const struct spa_pod *pod1, const struct spa_pod *pod2) +{ + const struct spa_pod_object *o1, *o2; + const struct spa_pod_prop *p1, *p2; + struct spa_pod_frame f[2]; + int res = 0, count = 0; + + if (pod1->type != pod2->type) + return -ENOTSUP; + if (pod1->type != SPA_TYPE_Object) + return -ENOTSUP; + + o1 = (const struct spa_pod_object*) pod1; + o2 = (const struct spa_pod_object*) pod2; + + spa_pod_builder_push_object(b, &f[0], o1->body.type, o1->body.id); + p2 = NULL; + SPA_POD_OBJECT_FOREACH(o1, p1) { + p2 = spa_pod_object_find_prop(o2, p2, p1->key); + if (p2 == NULL) + goto error_enoent; + + if (spa_pod_compare(&p1->value, &p2->value) == 0) { + spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); + } + else { + uint32_t i, n_vals1, n_vals2, choice1, choice2, size; + const struct spa_pod *vals1, *vals2; + void *alt1, *alt2, *a1, *a2; + + count++; + if (count > 1) + goto error_einval; + + vals1 = spa_pod_get_values(&p1->value, &n_vals1, &choice1); + vals2 = spa_pod_get_values(&p2->value, &n_vals2, &choice2); + + if (vals1->type != vals2->type) + goto error_einval; + + size = vals1->size; + + alt1 = SPA_POD_BODY(vals1); + alt2 = SPA_POD_BODY(vals2); + + if ((choice1 == SPA_CHOICE_None && choice2 == SPA_CHOICE_None) || + (choice1 == SPA_CHOICE_None && choice2 == SPA_CHOICE_Enum) || + (choice1 == SPA_CHOICE_Enum && choice2 == SPA_CHOICE_None) || + (choice1 == SPA_CHOICE_Enum && choice2 == SPA_CHOICE_Enum)) { + spa_pod_builder_prop(b, p1->key, p1->flags); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_child(b, size, vals1->type); + for (i = 0, a1 = alt1; i < n_vals1; i++, a1 = SPA_PTROFF(a1,size,void)) { + if (i == 0 && n_vals1 == 1) + spa_pod_builder_raw(b, a1, size); + spa_pod_builder_raw(b, a1, size); + } + for (i = 0, a2 = alt2; i < n_vals2; i++, a2 = SPA_PTROFF(a2,size,void)) { + spa_pod_builder_raw(b, a2, size); + } + spa_pod_builder_pop(b, &f[1]); + } else { + goto error_einval; + } + } + } + p1 = NULL; + SPA_POD_OBJECT_FOREACH(o2, p2) { + p1 = spa_pod_object_find_prop(o1, p1, p2->key); + if (p1 == NULL) + goto error_enoent; + } +done: + spa_pod_builder_pop(b, &f[0]); + return res; + +error_einval: + res = -EINVAL; + goto done; +error_enoent: + res = -ENOENT; + goto done; +} + +SPA_API_POD_SIMPLIFY int +spa_pod_simplify_struct(struct spa_pod_builder *b, const struct spa_pod *pod, uint32_t pod_size) +{ + struct spa_pod *p1 = NULL, *p2; + struct spa_pod_frame f; + struct spa_pod_builder_state state; + uint32_t p1offs; + + spa_pod_builder_push_struct(b, &f); + SPA_POD_STRUCT_FOREACH(pod, p2) { + spa_pod_builder_get_state(b, &state); + if (p1 == NULL || spa_pod_simplify_merge(b, p1, p2) < 0) { + spa_pod_builder_reset(b, &state); + spa_pod_builder_raw_padded(b, p2, SPA_POD_SIZE(p2)); + p1offs = state.offset; + p1 = SPA_PTROFF(b->data, p1offs, struct spa_pod); + } else { + void *pnew = SPA_PTROFF(b->data, state.offset, void); + p1 = SPA_PTROFF(b->data, p1offs, struct spa_pod); + spa_pod_builder_remove(b, SPA_POD_SIZE(p1)); + memmove(p1, pnew, SPA_POD_SIZE(pnew)); + } + } + spa_pod_builder_pop(b, &f); + return 0; +} + +SPA_API_POD_SIMPLIFY int +spa_pod_simplify(struct spa_pod_builder *b, struct spa_pod **result, const struct spa_pod *pod) +{ + int res = 0; + struct spa_pod_builder_state state; + + spa_return_val_if_fail(pod != NULL, -EINVAL); + spa_return_val_if_fail(b != NULL, -EINVAL); + + spa_pod_builder_get_state(b, &state); + + if (!spa_pod_is_struct(pod)) { + res = spa_pod_builder_raw_padded(b, pod, SPA_POD_SIZE(pod)); + } else { + struct spa_pod_dynamic_builder db; + spa_pod_dynamic_builder_continue(&db, b); + res = spa_pod_simplify_struct(&db.b, pod, SPA_POD_SIZE(pod)); + if (res >= 0) + res = spa_pod_builder_raw_padded(b, db.b.data, db.b.state.offset); + spa_pod_dynamic_builder_clean(&db); + } + + if (res >= 0 && result) { + *result = (struct spa_pod*)spa_pod_builder_deref(b, state.offset); + if (*result == NULL) + res = -ENOSPC; + } + return res; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_POD_SIMPLIFY_H */ diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index d706239e6..1dadf2f14 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -907,6 +908,54 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return res; } +static int update_param_peer_formats(struct impl *impl) +{ + uint8_t buffer[4096]; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + uint32_t state = 0; + struct spa_pod *param; + struct spa_pod_frame f; + int res; + + if (!impl->recheck_format) + return 0; + + spa_log_debug(impl->log, "updating peer formats"); + + spa_node_send_command(impl->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); + + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_push_struct(&b.b, &f); + + while (true) { + res = node_port_enum_params_sync(impl, impl->follower, + impl->direction, 0, + SPA_PARAM_EnumFormat, &state, + NULL, ¶m, &b.b); + if (res != 1) + break; + } + param = spa_pod_builder_pop(&b.b, &f); + + spa_node_send_command(impl->follower, + &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); + + spa_pod_simplify(&b.b, ¶m, param); + spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); + + res = spa_node_port_set_param(impl->target, + SPA_DIRECTION_REVERSE(impl->direction), 0, + SPA_PARAM_PeerFormats, 0, param); + + impl->recheck_format = false; + + spa_log_debug(impl->log, "done updating peer formats: %d", res); + + return 0; +} + + static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, struct spa_pod_object *o1, struct spa_pod_object *o2) { @@ -958,7 +1007,7 @@ static int negotiate_format(struct impl *this) if (this->have_format && !this->recheck_format) return 0; - this->recheck_format = false; + update_param_peer_formats(this); spa_pod_builder_init(&b, buffer, sizeof(buffer)); @@ -1618,55 +1667,6 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return spa_node_remove_port(this->target, direction, port_id); } -static int -port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction direction, - uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - uint8_t buffer[4096]; - struct spa_pod_builder b = { 0 }; - int res; - uint32_t count = 0; - struct spa_result_node_params result; - - result.id = id; - result.next = start; -next: - result.index = result.next; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - if (result.next < 0x100000) { - /* Enumerate follower formats first, until we have enough or we run out */ - if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, - &result.next, filter, &result.param, &b)) != 1) { - if (res == 0 || res == -ENOENT) { - result.next = 0x100000; - goto next; - } else { - spa_log_error(this->log, "could not enum follower format: %s", spa_strerror(res)); - return res; - } - } - } else if (result.next < 0x200000) { - /* Then enumerate converter formats */ - result.next &= 0xfffff; - if ((res = node_port_enum_params_sync(this, this->convert, direction, port_id, id, - &result.next, filter, &result.param, &b)) != 1) { - return res; - } else { - result.next |= 0x100000; - } - } - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count < num) - goto next; - - return 0; -} - static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -1683,12 +1683,7 @@ impl_node_port_enum_params(void *object, int seq, spa_log_debug(this->log, "%p: %d %u %u %u", this, seq, id, start, num); - /* We only need special handling for EnumFormat in convert mode */ - if (id == SPA_PARAM_EnumFormat && this->mode == SPA_PARAM_PORT_CONFIG_MODE_convert) - return port_enum_formats_for_convert(this, seq, direction, port_id, id, - start, num, filter); - else - return spa_node_port_enum_params(this->target, seq, direction, port_id, id, + return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); } @@ -1903,7 +1898,7 @@ static int load_converter(struct impl *this, const struct spa_dict *info, factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); if (factory_name == NULL) - return 0; + factory_name = "video.convert.ffmpeg"; if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); @@ -2061,6 +2056,9 @@ impl_init(const struct spa_handle_factory *factory, &this->follower_listener, &follower_node_events, this); spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); + if (this->convert != NULL) + update_param_peer_formats(this); + // TODO: adapt port bootstrap for arbitrary converter (incl. dummy) if (this->convert) { spa_node_add_listener(this->convert, diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 2c8f70097..3852ce187 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #undef SPA_LOG_TOPIC_DEFAULT @@ -74,6 +76,7 @@ struct port { uint64_t info_all; struct spa_port_info info; + #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 @@ -81,9 +84,14 @@ struct port { #define IDX_Buffers 4 #define IDX_Latency 5 #define IDX_Tag 6 -#define N_PORT_PARAMS 7 +#define IDX_PeerFormats 7 +#define N_PORT_PARAMS 8 struct spa_param_info params[N_PORT_PARAMS]; + struct spa_pod *peer_format_pod; + const struct spa_pod **peer_formats; + uint32_t n_peer_formats; + struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; @@ -463,6 +471,7 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); + port->params[IDX_PeerFormats] = SPA_PARAM_INFO(SPA_PARAM_PeerFormats, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; @@ -499,6 +508,11 @@ static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t if (port == NULL || !port->valid) return -ENOENT; port->valid = false; + free(port->peer_formats); + port->peer_formats = NULL; + port->n_peer_formats = 0; + free(port->peer_format_pod); + port->peer_format_pod = NULL; spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } @@ -763,6 +777,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_PortConfig].user++; + return 0; } @@ -959,7 +974,6 @@ static int setup_convert(struct impl *this) av_frame_free(&this->decoder.frame); if ((this->decoder.frame = av_frame_alloc()) == NULL) return -EIO; - if (encoder_id) { if ((codec = avcodec_find_encoder(encoder_id)) == NULL) { spa_log_error(this->log, "failed to find %d encoder", encoder_id); @@ -1074,73 +1088,264 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static int port_param_enum_format(struct impl *this, struct port *port, uint32_t id, - uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) +static void add_video_formats(struct spa_pod_builder *b, uint32_t def) { - struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; + uint32_t i; struct spa_pod_frame f[1]; + + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_Enum, 0); + if (def == SPA_ID_INVALID) + def = format_info[0].format; + spa_pod_builder_id(b, def); + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (format_info[i].pix_fmt != AV_PIX_FMT_NONE) + spa_pod_builder_id(b, format_info[i].format); + } + spa_pod_builder_pop(b, &f[0]); +} + +static struct spa_pod *transform_format(struct impl *this, struct port *port, const struct spa_pod *format, + uint32_t id, struct spa_pod_builder *b) +{ + uint32_t media_type, media_subtype; + struct spa_pod_object *obj; + const struct spa_pod_prop *prop; + struct spa_pod_frame f[2]; + + if (!spa_format_parse(format, &media_type, &media_subtype) || + media_type != SPA_MEDIA_TYPE_video) { + return NULL; + } + + obj = (struct spa_pod_object*)format; + spa_pod_builder_push_object(b, &f[0], obj->body.type, id); + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_FORMAT_mediaType: + spa_pod_builder_prop(b, prop->key, prop->flags); + spa_pod_builder_id(b, SPA_MEDIA_TYPE_video); + break; + case SPA_FORMAT_mediaSubtype: + spa_pod_builder_prop(b, prop->key, prop->flags); + spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw); + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + break; + case SPA_MEDIA_SUBTYPE_mjpg: + add_video_formats(b, SPA_VIDEO_FORMAT_I420); + break; + case SPA_MEDIA_SUBTYPE_h264: + add_video_formats(b, SPA_VIDEO_FORMAT_I420); + break; + default: + return NULL; + } + break; + case SPA_FORMAT_VIDEO_format: + { + uint32_t i, j, n_vals, choice, *id_vals; + struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); + + if (!spa_pod_is_id(val)) + return 0; + + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + id_vals = SPA_POD_BODY(val); + spa_pod_builder_id(b, id_vals[0]); + /* first add all supported formats */ + for (i = 1; i < n_vals; i++) { + for (j = 0; j < i; j++) { + if (id_vals[j] == id_vals[i]) + break; + } + if (j == i && format_to_pix_fmt(id_vals[i]) != AV_PIX_FMT_NONE) + spa_pod_builder_id(b, id_vals[i]); + } + /* then add all other supported formats */ + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (format_info[i].pix_fmt == AV_PIX_FMT_NONE) + continue; + for (j = 1; j < n_vals; j++) { + if (format_info[i].format == id_vals[j]) + break; + } + if (j == n_vals) + spa_pod_builder_id(b, format_info[i].format); + } + spa_pod_builder_pop(b, &f[1]); + break; + } + default: + spa_pod_builder_raw_padded(b, prop, SPA_POD_PROP_SIZE(prop)); + break; + } + } + return spa_pod_builder_pop(b, &f[0]); +} + +static int diff_value(struct impl *impl, uint32_t type, uint32_t size, const void *v1, const void *v2) +{ + switch (type) { + case SPA_TYPE_None: + return 0; + case SPA_TYPE_Bool: + return (!!*(int32_t *)v1) - (!!*(int32_t *)v2); + case SPA_TYPE_Id: + return (*(uint32_t *)v1) != (*(uint32_t *)v2); + case SPA_TYPE_Int: + return *(int32_t *)v1 - *(int32_t *)v2; + case SPA_TYPE_Long: + return *(int64_t *)v1 - *(int64_t *)v2; + case SPA_TYPE_Float: + return (int)(*(float *)v1 - *(float *)v2); + case SPA_TYPE_Double: + return (int)(*(double *)v1 - *(double *)v2); + case SPA_TYPE_String: + return strcmp((char *)v1, (char *)v2); + case SPA_TYPE_Bytes: + return memcmp((char *)v1, (char *)v2, size); + case SPA_TYPE_Rectangle: + { + const struct spa_rectangle *rec1 = (struct spa_rectangle *) v1, + *rec2 = (struct spa_rectangle *) v2; + uint64_t n1 = ((uint64_t) rec1->width) * rec1->height; + uint64_t n2 = ((uint64_t) rec2->width) * rec2->height; + if (rec1->width == rec2->width && rec1->height == rec2->height) + return 0; + else if (n1 < n2) + return -(n2 - n1); + else if (n1 > n2) + return n1 - n2; + else if (rec1->width == rec2->width) + return (int)rec1->height - (int)rec2->height; + else + return (int)rec1->width - (int)rec2->width; + } + case SPA_TYPE_Fraction: + { + const struct spa_fraction *f1 = (struct spa_fraction *) v1, + *f2 = (struct spa_fraction *) v2; + uint64_t n1, n2; + n1 = ((uint64_t) f1->num) * f2->denom; + n2 = ((uint64_t) f2->num) * f1->denom; + return (int) (n1 - n2); + } + default: + break; + } + return 0; +} + +static int diff_prop(struct impl *impl, struct spa_pod_prop *prop, + uint32_t type, const void *target, bool fix) +{ + uint32_t i, n_vals, choice, size; + struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); + void *vals, *v, *best = NULL; + int res = INT_MAX; + + if (SPA_POD_TYPE(val) != type) + return -EINVAL; + + size = SPA_POD_BODY_SIZE(val); + vals = SPA_POD_BODY(val); + + switch (choice) { + case SPA_CHOICE_None: + case SPA_CHOICE_Enum: + for (i = 0, v = vals; i < n_vals; i++, v = SPA_PTROFF(v, size, void)) { + int diff = SPA_ABS(diff_value(impl, type, size, v, target)); + if (diff < res) { + res = diff; + best = v; + } + } + if (fix) { + if (best != NULL && best != vals) + memcpy(vals, best, size); + if (spa_pod_is_choice(&prop->value)) + SPA_POD_CHOICE_TYPE(&prop->value) = SPA_CHOICE_None; + } + break; + default: + return res; + } + return res; +} + +static int calc_diff(struct impl *impl, struct spa_pod *param, struct dir *dir, bool fix) +{ + struct spa_pod_object *obj = (struct spa_pod_object*)param; + struct spa_pod_prop *prop; struct spa_rectangle size; struct spa_fraction framerate; - uint32_t format = 0; + uint32_t format; + int diff = 0; - get_format(other, &format, &size, &framerate); + if (!dir->have_format) + return -1; + + get_format(dir, &format, &size, &framerate); + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_FORMAT_VIDEO_format: + diff += diff_prop(impl, prop, SPA_TYPE_Id, &format, fix); + break; + case SPA_FORMAT_VIDEO_size: + diff += diff_prop(impl, prop, SPA_TYPE_Rectangle, &size, fix); + break; + case SPA_FORMAT_VIDEO_framerate: + diff += diff_prop(impl, prop, SPA_TYPE_Fraction, &framerate, fix); + break; + default: + break; + } + } + return diff; +} + +static int all_formats(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) +{ + struct spa_pod_frame f[1]; switch (index) { case 0: - if (port->is_dsp) { - struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( - .format = SPA_VIDEO_FORMAT_DSP_F32); - *param = spa_format_video_dsp_build(b, id, &info); - } else if (port->is_control) { - *param = spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_Format, id, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( - (1u<have_format) { - *param = spa_format_video_build(b, id, &other->format); - } else { - *param = NULL; - } - } - break; - case 1: - if (port->is_dsp || port->is_control) - return 0; - - 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); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, - format ? format : SPA_VIDEO_FORMAT_YUY2, - SPA_VIDEO_FORMAT_YUY2, - SPA_VIDEO_FORMAT_I420, - SPA_VIDEO_FORMAT_UYVY, - SPA_VIDEO_FORMAT_YVYU, - SPA_VIDEO_FORMAT_RGBA, - SPA_VIDEO_FORMAT_BGRx), - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(size.width, size.height), - &SPA_RECTANGLE(1, 1), - &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + 0); + add_video_formats(b, SPA_ID_INVALID); + *param = spa_pod_builder_pop(b, &f[0]); + break; + case 1: + if (this->direction != port->direction) + return 0; + + /* JPEG */ + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), 0); *param = spa_pod_builder_pop(b, &f[0]); break; case 2: - if (port->is_dsp || port->is_control) + if (this->direction != port->direction) return 0; - spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + /* H264 */ + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), - SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( - &SPA_RECTANGLE(size.width, size.height), - &SPA_RECTANGLE(1, 1), - &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_h264), 0); *param = spa_pod_builder_pop(b, &f[0]); break; @@ -1150,6 +1355,71 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t return 1; } +static int port_param_enum_format(struct impl *this, struct port *port, uint32_t id, + uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) +{ + struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; + + spa_log_debug(this->log, "%p %d %d %d %d", port, port->valid, this->direction, port->direction, index); + + if (index == 0) { + if (port->is_dsp) { + struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( + .format = SPA_VIDEO_FORMAT_DSP_F32); + *param = spa_format_video_dsp_build(b, id, &info); + return 1; + } else if (port->is_control) { + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, id, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( + (1u<have_format) { + /* peer format */ + *param = spa_format_video_build(b, id, &other->format); + } + return 1; + } else if (this->direction != port->direction) { + struct port *oport = GET_PORT(this, SPA_DIRECTION_REVERSE(port->direction), port->id); + if (oport != NULL && oport->valid && + index - 1 < oport->n_peer_formats) { + const struct spa_pod *p = oport->peer_formats[index-1]; + *param = transform_format(this, port, p, id, b); + } else { + return all_formats(this, port, id, index - 1 - + (oport ? oport->n_peer_formats : 0), param, b); + + } + } else if (index == 1) { + const struct spa_pod *best = NULL; + int best_diff = INT_MAX; + uint32_t i; + + for (i = 0; i < port->n_peer_formats; i++) { + const struct spa_pod *p = port->peer_formats[i]; + int diff = calc_diff(this, (struct spa_pod*)p, other, false); + if (diff < 0) + break; + if (diff < best_diff) { + best_diff = diff; + best = p; + } + } + if (best) { + uint32_t offset = b->state.offset; + struct spa_pod *p; + spa_pod_builder_primitive(b, best); + p = spa_pod_builder_deref(b, offset); + calc_diff(this, p, other, true); + *param = p; + } + } else { + return all_formats(this, port, id, index - 2, param, b); + } + return 1; +} static int port_param_format(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) @@ -1263,6 +1533,16 @@ static int port_param_tag(struct impl *this, struct port *port, uint32_t id, return 0; } +static int port_param_peer_formats(struct impl *this, struct port *port, uint32_t index, + const struct spa_pod **param, struct spa_pod_builder *b) +{ + if (index >= port->n_peer_formats) + return 0; + + *param = port->peer_formats[port->n_peer_formats - index]; + return 1; +} + static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -1318,6 +1598,9 @@ impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_Tag: res = port_param_tag(this, port, id, result.index, ¶m, &b); break; + case SPA_PARAM_PeerFormats: + res = port_param_peer_formats(this, port, result.index, ¶m, &b); + break; default: return -ENOENT; } @@ -1631,6 +1914,68 @@ static int port_set_format(void *object, } +static int port_set_peer_formats(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *formats) +{ + struct impl *this = object; + struct port *port, *oport; + int res = 0; + uint32_t i; + const struct spa_pod *format; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + static uint32_t subtypes[] = { + SPA_MEDIA_SUBTYPE_raw, + SPA_MEDIA_SUBTYPE_mjpg, + SPA_MEDIA_SUBTYPE_h264 }; + + spa_return_val_if_fail(spa_pod_is_struct(formats), -EINVAL); + + port = GET_PORT(this, direction, port_id); + oport = GET_PORT(this, other, port_id); + + free(port->peer_formats); + port->peer_formats = NULL; + free(port->peer_format_pod); + port->peer_format_pod = NULL; + port->n_peer_formats = 0; + + if (formats) { + uint32_t count = 0; + port->peer_format_pod = spa_pod_copy(formats); + + for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) { + SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) { + uint32_t media_type, media_subtype; + if (!spa_format_parse(format, &media_type, &media_subtype) || + media_type != SPA_MEDIA_TYPE_video || + media_subtype != subtypes[i]) + continue; + count++; + } + } + port->peer_formats = calloc(count, sizeof(struct spa_pod *)); + for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) { + SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) { + uint32_t media_type, media_subtype; + if (!spa_format_parse(format, &media_type, &media_subtype) || + media_type != SPA_MEDIA_TYPE_video || + media_subtype != subtypes[i]) + continue; + port->peer_formats[port->n_peer_formats++] = format; + } + } + } + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_EnumFormat].user++; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_EnumFormat].user++; + port->params[IDX_PeerFormats].user++; + return res; +} + static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -1657,6 +2002,9 @@ impl_node_port_set_param(void *object, case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; + case SPA_PARAM_PeerFormats: + res = port_set_peer_formats(this, direction, port_id, flags, param); + break; default: return -ENOENT; } @@ -1760,7 +2108,6 @@ impl_node_port_use_buffers(void *object, spa_log_debug(this->log, "buffer %d: mem:%d passthrough:%p maxsize:%d", i, j, b->datas[j], d[j].maxsize); } - } else { for (j = 0; j < n_datas; j++) { void *data = d[j].data; From 6161cb3ec57c3ce019bd8467089b7638f089804b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 5 May 2025 13:19:54 +0200 Subject: [PATCH 0216/1014] videoadapter: disable ffmpeg converter by default again --- spa/plugins/videoconvert/videoadapter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 1dadf2f14..55fa9f669 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1898,7 +1898,7 @@ static int load_converter(struct impl *this, const struct spa_dict *info, factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); if (factory_name == NULL) - factory_name = "video.convert.ffmpeg"; + return 0; if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); From df2369f6e1dfe1739e5bd97d06cbabf1ce04d471 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 6 May 2025 09:28:13 +0200 Subject: [PATCH 0217/1014] videoconvert: fix enum format for control and dsp ports They just have 1 format object. --- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 3852ce187..994e74e03 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1361,6 +1361,8 @@ static int port_param_enum_format(struct impl *this, struct port *port, uint32_t struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; spa_log_debug(this->log, "%p %d %d %d %d", port, port->valid, this->direction, port->direction, index); + if ((port->is_dsp || port->is_control) && index > 0) + return 0; if (index == 0) { if (port->is_dsp) { From 0ebf664515bce076e88326c76481f50b36023c54 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 6 May 2025 10:07:09 +0200 Subject: [PATCH 0218/1014] videoconvert: refactor add_video_formats Rework the function a little so that it can take a list of formats and only add the supported formats from that list and only once. It can optionally select only the DSP formats. --- .../videoconvert/videoconvert-ffmpeg.c | 134 +++++++++++------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 994e74e03..bdf4ba589 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -278,35 +278,43 @@ static void emit_info(struct impl *this, bool full) struct format_info { enum AVPixelFormat pix_fmt; uint32_t format; + uint32_t dsp_format; #define FORMAT_DSP (1<<0) #define FORMAT_COMMON (1<<1) uint32_t flags; }; +#if defined AV_PIX_FMT_AYUV +#define VIDEO_FORMAT_DSP_AYUV SPA_VIDEO_FORMAT_AYUV +#else +#define VIDEO_FORMAT_DSP_AYUV SPA_VIDEO_FORMAT_Y444 +#endif +#define VIDEO_FORMAT_DSP_RGBA SPA_VIDEO_FORMAT_RGBA + static struct format_info format_info[] = { #if defined AV_PIX_FMT_AYUV - { AV_PIX_FMT_AYUV, SPA_VIDEO_FORMAT_AYUV, FORMAT_DSP | FORMAT_COMMON }, + { AV_PIX_FMT_AYUV, SPA_VIDEO_FORMAT_AYUV, VIDEO_FORMAT_DSP_AYUV, FORMAT_DSP | FORMAT_COMMON }, #else - { AV_PIX_FMT_YUV444P, SPA_VIDEO_FORMAT_Y444, FORMAT_DSP | FORMAT_COMMON }, + { AV_PIX_FMT_YUV444P, SPA_VIDEO_FORMAT_Y444, VIDEO_FORMAT_DSP_AYUV, FORMAT_DSP | FORMAT_COMMON }, #endif - { AV_PIX_FMT_RGBA, SPA_VIDEO_FORMAT_RGBA, FORMAT_DSP | FORMAT_COMMON }, + { AV_PIX_FMT_RGBA, SPA_VIDEO_FORMAT_RGBA, VIDEO_FORMAT_DSP_RGBA, FORMAT_DSP | FORMAT_COMMON }, - { AV_PIX_FMT_YUYV422, SPA_VIDEO_FORMAT_YUY2, FORMAT_COMMON }, - { AV_PIX_FMT_UYVY422, SPA_VIDEO_FORMAT_UYVY, FORMAT_COMMON }, - { AV_PIX_FMT_YVYU422, SPA_VIDEO_FORMAT_YVYU, FORMAT_COMMON }, - { AV_PIX_FMT_YUV420P, SPA_VIDEO_FORMAT_I420, FORMAT_COMMON }, + { AV_PIX_FMT_YUYV422, SPA_VIDEO_FORMAT_YUY2, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, + { AV_PIX_FMT_UYVY422, SPA_VIDEO_FORMAT_UYVY, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, + { AV_PIX_FMT_YVYU422, SPA_VIDEO_FORMAT_YVYU, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, + { AV_PIX_FMT_YUV420P, SPA_VIDEO_FORMAT_I420, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, - //{ AV_PIX_FMT_BGR0, SPA_VIDEO_FORMAT_BGRx }, - //{ AV_PIX_FMT_BGRA, SPA_VIDEO_FORMAT_BGRA }, - //{ AV_PIX_FMT_ARGB, SPA_VIDEO_FORMAT_ARGB }, - //{ AV_PIX_FMT_ABGR, SPA_VIDEO_FORMAT_ABGR }, + { AV_PIX_FMT_BGR0, SPA_VIDEO_FORMAT_BGRx, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, + { AV_PIX_FMT_BGRA, SPA_VIDEO_FORMAT_BGRA, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, + { AV_PIX_FMT_ARGB, SPA_VIDEO_FORMAT_ARGB, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, + { AV_PIX_FMT_ABGR, SPA_VIDEO_FORMAT_ABGR, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_YV12 }, - //{ AV_PIX_FMT_RGB0, SPA_VIDEO_FORMAT_RGBx }, - //{ AV_PIX_FMT_0RGB, SPA_VIDEO_FORMAT_xRGB }, - //{ AV_PIX_FMT_0BGR, SPA_VIDEO_FORMAT_xBGR }, + { AV_PIX_FMT_RGB0, SPA_VIDEO_FORMAT_RGBx, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, + { AV_PIX_FMT_0RGB, SPA_VIDEO_FORMAT_xRGB, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, + { AV_PIX_FMT_0BGR, SPA_VIDEO_FORMAT_xBGR, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, //{ AV_PIX_FMT_RGB24, SPA_VIDEO_FORMAT_RGB }, //{ AV_PIX_FMT_BGR24, SPA_VIDEO_FORMAT_BGR }, @@ -392,13 +400,19 @@ static struct format_info format_info[] = //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGRA_102LE }, }; -static enum AVPixelFormat format_to_pix_fmt(uint32_t format) +static struct format_info *format_info_for_format(uint32_t format) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { if (i->format == format) - return i->pix_fmt; + return i; } - return AV_PIX_FMT_NONE; + return NULL; +} + +static enum AVPixelFormat format_to_pix_fmt(uint32_t format) +{ + struct format_info *i = format_info_for_format(format); + return i ? i->pix_fmt : AV_PIX_FMT_NONE; } static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, @@ -1088,19 +1102,52 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static void add_video_formats(struct spa_pod_builder *b, uint32_t def) +static void add_video_formats(struct spa_pod_builder *b, + uint32_t *vals, uint32_t n_vals, bool is_dsp) { - uint32_t i; struct spa_pod_frame f[1]; + uint32_t ids[SPA_N_ELEMENTS(format_info) + 1], n_ids = 0, i, j, fmt; spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_Enum, 0); - if (def == SPA_ID_INVALID) - def = format_info[0].format; - spa_pod_builder_id(b, def); - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].pix_fmt != AV_PIX_FMT_NONE) - spa_pod_builder_id(b, format_info[i].format); + + if (n_vals == 0) { + vals = &format_info[0].format; + n_vals = 1; + } + /* all supported formats */ + for (i = 0; i < n_vals; i++) { + struct format_info *fi = format_info_for_format(vals[i]); + if (fi == NULL) + continue; + + fmt = is_dsp ? fi->dsp_format : fi->format; + + for (j = 0; j < n_ids; j++) { + if (ids[j] == fmt) + break; + } + if (j == n_ids) + ids[n_ids++] = fmt; + } + /* then add all other supported formats */ + SPA_FOR_EACH_ELEMENT_VAR(format_info, fi) { + if (fi->pix_fmt == AV_PIX_FMT_NONE) + continue; + + fmt = is_dsp ? fi->dsp_format : fi->format; + + for (j = 0; j < n_ids; j++) { + if (fmt == ids[j]) + break; + } + if (j == n_ids) + ids[n_ids++] = fmt; + } + for (i = 0; i < n_ids; i++) { + spa_pod_builder_id(b, ids[i]); + if (i == 0) + spa_pod_builder_id(b, ids[i]); } spa_pod_builder_pop(b, &f[0]); } @@ -1108,7 +1155,7 @@ static void add_video_formats(struct spa_pod_builder *b, uint32_t def) static struct spa_pod *transform_format(struct impl *this, struct port *port, const struct spa_pod *format, uint32_t id, struct spa_pod_builder *b) { - uint32_t media_type, media_subtype; + uint32_t media_type, media_subtype, fmt; struct spa_pod_object *obj; const struct spa_pod_prop *prop; struct spa_pod_frame f[2]; @@ -1129,14 +1176,15 @@ static struct spa_pod *transform_format(struct impl *this, struct port *port, co case SPA_FORMAT_mediaSubtype: spa_pod_builder_prop(b, prop->key, prop->flags); spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw); + fmt = SPA_VIDEO_FORMAT_I420; switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: break; case SPA_MEDIA_SUBTYPE_mjpg: - add_video_formats(b, SPA_VIDEO_FORMAT_I420); + add_video_formats(b, &fmt, 1, port->is_dsp); break; case SPA_MEDIA_SUBTYPE_h264: - add_video_formats(b, SPA_VIDEO_FORMAT_I420); + add_video_formats(b, &fmt, 1, port->is_dsp); break; default: return NULL; @@ -1144,37 +1192,15 @@ static struct spa_pod *transform_format(struct impl *this, struct port *port, co break; case SPA_FORMAT_VIDEO_format: { - uint32_t i, j, n_vals, choice, *id_vals; + uint32_t n_vals, choice, *id_vals; struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); if (!spa_pod_is_id(val)) return 0; - spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); id_vals = SPA_POD_BODY(val); - spa_pod_builder_id(b, id_vals[0]); - /* first add all supported formats */ - for (i = 1; i < n_vals; i++) { - for (j = 0; j < i; j++) { - if (id_vals[j] == id_vals[i]) - break; - } - if (j == i && format_to_pix_fmt(id_vals[i]) != AV_PIX_FMT_NONE) - spa_pod_builder_id(b, id_vals[i]); - } - /* then add all other supported formats */ - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].pix_fmt == AV_PIX_FMT_NONE) - continue; - for (j = 1; j < n_vals; j++) { - if (format_info[i].format == id_vals[j]) - break; - } - if (j == n_vals) - spa_pod_builder_id(b, format_info[i].format); - } - spa_pod_builder_pop(b, &f[1]); + + add_video_formats(b, id_vals, n_vals, port->is_dsp); break; } default: @@ -1320,7 +1346,7 @@ static int all_formats(struct impl *this, struct port *port, uint32_t id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); - add_video_formats(b, SPA_ID_INVALID); + add_video_formats(b, NULL, 0, port->is_dsp); *param = spa_pod_builder_pop(b, &f[0]); break; case 1: From 20246b5c0e57d3052cb3902e48eda5eb7651f420 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 6 May 2025 10:44:14 +0200 Subject: [PATCH 0219/1014] netjack2: add driver.mode again This configures the default number of audio and midi ports per stream to 0 depending on the mode. Improve docs a little. See #4666 --- src/modules/module-netjack2-driver.c | 42 +++++++++++++++++++++++++-- src/modules/module-netjack2-manager.c | 15 +++++++++- src/modules/module-netjack2/packets.h | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index f85ece7a8..e1f5eb6ca 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -45,7 +45,26 @@ /** \page page_module_netjack2_driver Netjack2 driver * * The netjack2-driver module provides a source or sink that is following a - * netjack2 manager. + * netjack2 manager. It is meant to be used over stable (ethernet) network + * connections with minimal latency and jitter. + * + * The driver normally decides how many ports it will send and receive from the + * manager. By default however, these values are set to -1 so that the manager + * decides on the number of ports. + * + * With the global or per stream audio.port and midi.ports properties this + * behaviour can be adjusted. + * + * The driver will send out UDP messages on a (typically) multicast address to + * inform the manager of the available driver. This will then instruct the manager + * to configure and start the driver. + * + * On the driver side, a sink and/or source with the specified numner of audio and + * midi ports will be created. On the manager side there will be a corresponding + * source and/or sink created respectively. + * + * The driver will be scheduled with exactly the same period as the manager but with + * a configurable number of periods of delay (see netjack2.latency, default 2). * * ## Module Name * @@ -53,6 +72,10 @@ * * ## Module Options * + * - `driver.mode`: the driver mode, sink|source|duplex, default duplex. This set the + * per stream audio.port and midi.ports default from -1 to 0. sink mode defaults to + * no source ports, source mode to no sink ports and duplex leaves the defaults as + * they are. * - `local.ifname = `: interface name to use * - `net.ip =`: multicast IP address, default "225.3.19.154" * - `net.port =`: control port, default 19000 @@ -99,7 +122,7 @@ * #audio.channels = 2 * #audio.position = [ FL FR ] * source.props = { - * # extra sink properties + * # extra source properties * } * sink.props = { * # extra sink properties @@ -138,6 +161,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define FOLLOWER_INIT_RETRY -1 #define MODULE_USAGE "( remote.name= ) " \ + "( driver.mode= ) " \ "( local.ifname= ) " \ "( net.ip= ) " \ "( net.port= ) " \ @@ -1260,6 +1284,20 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->sink.impl = impl; impl->sink.direction = PW_DIRECTION_INPUT; + if ((str = pw_properties_get(props, "driver.mode")) != NULL) { + if (spa_streq(str, "source")) { + pw_properties_set(impl->sink.props, "audio.ports", "0"); + pw_properties_set(impl->sink.props, "midi.ports", "0"); + } else if (spa_streq(str, "sink")) { + pw_properties_set(impl->source.props, "audio.ports", "0"); + pw_properties_set(impl->source.props, "midi.ports", "0"); + } else if (!spa_streq(str, "duplex")) { + pw_log_error("invalid driver.mode '%s'", str); + res = -EINVAL; + goto error; + } + } + impl->latency = pw_properties_get_uint32(impl->props, "netjack2.latency", DEFAULT_NETWORK_LATENCY); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 64ad7fb3b..634a42d58 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -49,6 +49,19 @@ * The netjack2 manager module listens for new netjack2 driver messages and will * start a communication channel with them. * + * Messages are received on a (typically) multicast address. + * + * Normally, the driver will specify the number of send and receive channels it + * wants to set up with the manager. If the driver however specifies a don't-care + * value of -1, the audio.ports and midi.ports configuration values of the manager + * are used. + * + * The manager will create the corresponding streams to send and receive data + * to/from the drivers. These are usually sink and sources but with the + * netjack2.connect property, these will be streams that will be autoconnected to + * the default source and sink by the session manager. + * + * * ## Module Name * * `libpipewire-module-netjack2-manager` @@ -107,7 +120,7 @@ * #audio.channels = 2 * #audio.position = [ FL FR ] * source.props = { - * # extra sink properties + * # extra source properties * } * sink.props = { * # extra sink properties diff --git a/src/modules/module-netjack2/packets.h b/src/modules/module-netjack2/packets.h index af60a6c78..69791cd11 100644 --- a/src/modules/module-netjack2/packets.h +++ b/src/modules/module-netjack2/packets.h @@ -120,7 +120,7 @@ struct nj2_packet_header { uint32_t cycle; /* process cycle counter */ uint32_t sub_cycle; /* midi/audio subcycle counter */ int32_t frames; /* process cycle size in frames (can be -1 to indicate entire buffer) */ - uint32_t is_last; /* is it the last packet of a given cycle ('y' or 'n') */ + uint32_t is_last; /* is it the last packet of a given cycle (1=yes or 0=no) */ } __attribute__ ((packed)); #define UDP_HEADER_SIZE 64 /* 40 bytes for IP header in IPV6, 20 in IPV4, 8 for UDP, so take 64 */ From 8c8fd97698d96a8919a098b0cb7b9a3494429a16 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 6 May 2025 19:15:16 +0200 Subject: [PATCH 0220/1014] filter-chain: support ProcessLatency Allow setting ProcessLatency on the nodes and add this latency to the reported Latency. See #4678 --- src/modules/module-filter-chain.c | 72 +++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 537b8c7d0..dcd3d403d 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -844,6 +844,9 @@ struct impl { struct spa_hook graph_listener; uint32_t n_inputs; uint32_t n_outputs; + + struct spa_latency_info latency[2]; + struct spa_process_latency_info process_latency; }; static void capture_destroy(void *d) @@ -938,7 +941,8 @@ done: pw_stream_queue_buffer(impl->playback, out); } -static void param_latency_changed(struct impl *impl, const struct spa_pod *param) +static void param_latency_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction) { struct spa_latency_info latency; uint8_t buffer[1024]; @@ -948,7 +952,11 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param if (param == NULL || spa_latency_parse(param, &latency) < 0) return; + impl->latency[latency.direction] = latency; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if (latency.direction != direction) + spa_process_latency_info_add(&impl->process_latency, &latency); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); if (latency.direction == SPA_DIRECTION_INPUT) @@ -957,7 +965,38 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param pw_stream_update_params(impl->playback, params, 1); } -static void param_tag_changed(struct impl *impl, const struct spa_pod *param) +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction) +{ + uint8_t buffer[1024]; + struct spa_pod_builder b; + struct spa_process_latency_info process_latency; + struct spa_latency_info latency; + const struct spa_pod *params[1]; + + if (param == NULL) + spa_zero(process_latency); + else if (spa_process_latency_parse(param, &process_latency) < 0) + return; + + impl->process_latency = process_latency; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + latency = impl->latency[SPA_DIRECTION_INPUT]; + spa_process_latency_info_add(&process_latency, &latency); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + pw_stream_update_params(impl->playback, params, 1); + + latency = impl->latency[SPA_DIRECTION_OUTPUT]; + spa_process_latency_info_add(&process_latency, &latency); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + pw_stream_update_params(impl->capture, params, 1); +} + +static void param_tag_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction) { struct spa_tag_info tag; const struct spa_pod *params[1] = { param }; @@ -1035,10 +1074,9 @@ static void io_changed(void *data, uint32_t id, void *area, uint32_t size) } } -static void param_changed(void *data, uint32_t id, const struct spa_pod *param, - bool capture) +static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod *param, + enum spa_direction direction, struct pw_stream *stream, struct pw_stream *other) { - struct impl *impl = data; struct spa_filter_graph *graph = impl->graph; int res; @@ -1059,27 +1097,29 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param, } case SPA_PARAM_Props: if (param != NULL) - spa_filter_graph_set_props(impl->graph, - capture ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT, param); - + spa_filter_graph_set_props(impl->graph, direction, param); break; case SPA_PARAM_Latency: - param_latency_changed(impl, param); + param_latency_changed(impl, param, direction); + break; + case SPA_PARAM_ProcessLatency: + param_process_latency_changed(impl, param, direction); break; case SPA_PARAM_Tag: - param_tag_changed(impl, param); + param_tag_changed(impl, param, direction); break; } return; error: - pw_stream_set_error(capture ? impl->capture : impl->playback, + pw_stream_set_error(direction == SPA_DIRECTION_INPUT ? impl->capture : impl->playback, res, "can't start graph: %s", spa_strerror(res)); } static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param) { - param_changed(data, id, param, true); + struct impl *impl = data; + param_changed(impl, id, param, SPA_DIRECTION_INPUT, impl->capture, impl->playback); } static const struct pw_stream_events in_stream_events = { @@ -1093,7 +1133,8 @@ static const struct pw_stream_events in_stream_events = { static void playback_param_changed(void *data, uint32_t id, const struct spa_pod *param) { - param_changed(data, id, param, false); + struct impl *impl = data; + param_changed(impl, id, param, SPA_DIRECTION_OUTPUT, impl->playback, impl->capture); } static void playback_destroy(void *d) @@ -1164,6 +1205,11 @@ static int setup_streams(struct impl *impl) *offs = b.b.state.offset; spa_filter_graph_get_props(graph, &b.b, NULL); + if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) + *offs = b.b.state.offset; + spa_process_latency_build(&b.b, + SPA_PARAM_ProcessLatency, &impl->process_latency); + n_params = pw_array_get_len(&offsets, uint32_t); if (n_params == 0) { res = -ENOMEM; From d96485190d9966db681c667e9b389677c30f84e9 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 7 May 2025 13:02:10 +0530 Subject: [PATCH 0221/1014] bluez5: media-sink: Fix format specifier for log This should fix the build on 32-bit systems. --- spa/plugins/bluez5/media-sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index ea5581ee3..ce97f1fc2 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1321,7 +1321,7 @@ static uint64_t asha_seqnum(struct impl *this) uint64_t dt = tn - this->asha->ref_t0; uint64_t num_packets = (dt + ASHA_CONN_INTERVAL / 2) / ASHA_CONN_INTERVAL; - spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %ld", + spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %zu", tn, this->asha->ref_t0, num_packets); if (this->asha->ref_t0 > tn) From 445ca819cee0060599375784012fe5e4e8d4e8b1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 09:38:24 +0200 Subject: [PATCH 0222/1014] bluez: fix format specifier zu is for size_t, PRIu64 is for uint64_t --- spa/plugins/bluez5/media-sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index ce97f1fc2..38dd81c07 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1321,7 +1321,7 @@ static uint64_t asha_seqnum(struct impl *this) uint64_t dt = tn - this->asha->ref_t0; uint64_t num_packets = (dt + ASHA_CONN_INTERVAL / 2) / ASHA_CONN_INTERVAL; - spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %zu", + spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %"PRIu64, tn, this->asha->ref_t0, num_packets); if (this->asha->ref_t0 > tn) From a419f69bbb58f72543db3dbc11c843b0fb22f0c0 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Mon, 5 May 2025 15:15:15 +0530 Subject: [PATCH 0223/1014] bluez5-device: Refactor device_set_update for ASHA & BAP --- spa/plugins/bluez5/bluez5-device.c | 175 ++++++++++++++++++----------- 1 file changed, 111 insertions(+), 64 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index c22042a0b..8629eaa4c 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -966,7 +966,76 @@ static const struct spa_bt_transport_events device_set_transport_events = { .destroy = device_set_transport_destroy, }; -static void device_set_update(struct impl *this, struct device_set *dset) +static void device_set_update_asha(struct impl *this, struct device_set *dset) +{ + struct spa_bt_device *device = this->bt_dev; + struct spa_bt_set_membership *set; + struct spa_bt_set_membership tmp_set = { + .device = device, + .rank = 0, + .leader = true, + .path = device->path, + .others = SPA_LIST_INIT(&tmp_set.others), + }; + struct spa_list tmp_set_list = SPA_LIST_INIT(&tmp_set_list); + struct spa_list *membership_list = &device->set_membership_list; + + /* + * If no device set, use a dummy one, so that we can handle also those devices + * here (they may have multiple transports regardless). + */ + if (spa_list_is_empty(membership_list)) { + spa_list_append(&tmp_set_list, &tmp_set.link); + membership_list = &tmp_set_list; + } + + spa_list_for_each(set, membership_list, link) { + struct spa_bt_set_membership *s; + int num_devices = 0; + + device_set_clear(this, dset); + + spa_bt_for_each_set_member(s, set) { + struct spa_bt_transport *t; + bool active = false; + uint32_t sink_id = DEVICE_ID_SINK; + + if (!(s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) + continue; + + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!transport_enabled(t, SPA_BT_PROFILE_ASHA_SINK)) + continue; + if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) + break; + + active = true; + dset->leader = set->leader = t->asha_right_side; + dset->path = strdup(set->path); + dset->sink[dset->sinks].impl = this; + dset->sink[dset->sinks].transport = t; + dset->sink[dset->sinks].id = sink_id; + sink_id += 2; + spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, + &device_set_transport_events, &dset->sink[dset->sinks]); + ++dset->sinks; + } + + if (active) + ++num_devices; + } + + spa_log_debug(this->log, "%p: %s belongs to ASHA set %s leader:%d", this, + device->path, set->path, set->leader); + + if (num_devices > 1) + break; + } + + dset->sink_enabled = dset->path && (dset->sinks > 1); +} + +static void device_set_update_bap(struct impl *this, struct device_set *dset) { struct spa_bt_device *device = this->bt_dev; struct spa_bt_set_membership *set; @@ -992,7 +1061,6 @@ static void device_set_update(struct impl *this, struct device_set *dset) spa_list_for_each(set, membership_list, link) { struct spa_bt_set_membership *s; int num_devices = 0; - bool is_asha_member = false; device_set_clear(this, dset); @@ -1001,71 +1069,44 @@ static void device_set_update(struct impl *this, struct device_set *dset) bool active = false; uint32_t source_id = DEVICE_ID_SOURCE; uint32_t sink_id = DEVICE_ID_SINK; - bool bap_duplex = s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX; - bool is_asha = s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; - if (!bap_duplex && !is_asha) + if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX)) continue; - if (is_asha) { - spa_list_for_each(t, &s->device->transport_list, device_link) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) - continue; - if (!transport_enabled(t, SPA_BT_PROFILE_ASHA_SINK)) - continue; - if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) - break; + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)) + continue; + if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SOURCE)) + continue; + if (dset->sources >= SPA_N_ELEMENTS(dset->source)) + break; - active = true; - is_asha_member = true; - dset->leader = set->leader = t->asha_right_side; - dset->path = strdup(set->path); - dset->sink[dset->sinks].impl = this; - dset->sink[dset->sinks].transport = t; - dset->sink[dset->sinks].id = sink_id; - sink_id += 2; - spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, - &device_set_transport_events, &dset->sink[dset->sinks]); - ++dset->sinks; - } + active = true; + dset->source[dset->sources].impl = this; + dset->source[dset->sources].transport = t; + dset->source[dset->sources].id = source_id; + source_id += 2; + spa_bt_transport_add_listener(t, &dset->source[dset->sources].listener, + &device_set_transport_events, &dset->source[dset->sources]); + ++dset->sources; } - if (bap_duplex) { - spa_list_for_each(t, &s->device->transport_list, device_link) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)) - continue; - if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SOURCE)) - continue; - if (dset->sources >= SPA_N_ELEMENTS(dset->source)) - break; + spa_list_for_each(t, &s->device->transport_list, device_link) { + if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SINK)) + continue; + if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SINK)) + continue; + if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) + break; - active = true; - dset->source[dset->sources].impl = this; - dset->source[dset->sources].transport = t; - dset->source[dset->sources].id = source_id; - source_id += 2; - spa_bt_transport_add_listener(t, &dset->source[dset->sources].listener, - &device_set_transport_events, &dset->source[dset->sources]); - ++dset->sources; - } - - spa_list_for_each(t, &s->device->transport_list, device_link) { - if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SINK)) - continue; - if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SINK)) - continue; - if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) - break; - - active = true; - dset->sink[dset->sinks].impl = this; - dset->sink[dset->sinks].transport = t; - dset->sink[dset->sinks].id = sink_id; - sink_id += 2; - spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, - &device_set_transport_events, &dset->sink[dset->sinks]); - ++dset->sinks; - } + active = true; + dset->sink[dset->sinks].impl = this; + dset->sink[dset->sinks].transport = t; + dset->sink[dset->sinks].id = sink_id; + sink_id += 2; + spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, + &device_set_transport_events, &dset->sink[dset->sinks]); + ++dset->sinks; } if (active) @@ -1082,10 +1123,8 @@ static void device_set_update(struct impl *this, struct device_set *dset) /* XXX: device set nodes for BAP server not supported, * XXX: it'll appear as multiple streams */ - if (!is_asha_member) { - dset->path = NULL; - dset->leader = false; - } + dset->path = NULL; + dset->leader = false; } if (num_devices > 1) @@ -1096,6 +1135,14 @@ static void device_set_update(struct impl *this, struct device_set *dset) dset->source_enabled = dset->path && (dset->sources > 1); } +static void device_set_update(struct impl *this, struct device_set *dset) +{ + if (this->profile == DEVICE_PROFILE_BAP) + device_set_update_bap(this, dset); + else if (this->profile == DEVICE_PROFILE_ASHA) + device_set_update_asha(this, dset); +} + static bool device_set_equal(struct device_set *a, struct device_set *b) { unsigned int i; From e68111b4aa0212abc4b21904224b18f2598f7721 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 6 May 2025 16:11:19 +0530 Subject: [PATCH 0224/1014] media-sink: Fix sequence number sync for ASHA The two sides of a ASHA pair rarely if ever start together and the sequence number was always a bit off due to the stateful nature of reset_buffer and ASHA needing the sequence number to be matched to the other side. Simplify this by setting the sequence number for ASHA just before flushing. --- spa/plugins/bluez5/asha-codec-g722.c | 9 ++------- spa/plugins/bluez5/media-sink.c | 8 +++----- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/asha-codec-g722.c b/spa/plugins/bluez5/asha-codec-g722.c index 3e2ec699a..8faa920cb 100644 --- a/spa/plugins/bluez5/asha-codec-g722.c +++ b/spa/plugins/bluez5/asha-codec-g722.c @@ -18,7 +18,6 @@ static struct spa_log *spalog; struct impl { g722_encode_state_t encode; unsigned int codesize; - uint8_t seqnum; }; static int codec_reduce_bitpool(void *data) @@ -45,11 +44,6 @@ static int codec_get_block_size(void *data) static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { - struct impl *this = data; - - /* Payload for ASHA must be preceded by 1-byte sequence number */ - this->seqnum = seqnum % 256; - return 0; } @@ -137,7 +131,8 @@ static int codec_encode(void *data, src_sz = (src_size > this->codesize) ? this->codesize : src_size; - *dest = this->seqnum; + /* Sequence number will be set in media-sink before flushing */ + *dest = 0; dest++; ret = g722_encode(&this->encode, dest, src, src_sz / 2 /* S16LE */); diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 38dd81c07..e335889af 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1339,7 +1339,6 @@ static void media_asha_flush_timeout(struct spa_source *source) struct timespec ts; int res, written; uint64_t exp, now; - uint8_t seqnum; if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, asha->timerfd, &exp)) < 0) { @@ -1366,7 +1365,7 @@ static void media_asha_flush_timeout(struct spa_source *source) } if (asha->flush_pending) { - seqnum = asha->buf[0]; + asha->buf[0] = this->seqnum; written = send(asha->flush_source.fd, asha->buf, ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); /* @@ -1386,14 +1385,14 @@ static void media_asha_flush_timeout(struct spa_source *source) spa_loop_update_source(this->data_loop, &asha->flush_source); spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on timer for %s, written:%d", - this, seqnum, address, -errno); + this, this->seqnum, address, -errno); goto skip_flush; } if (written > 0) { asha->flush_pending = false; spa_log_trace(this->log, "%p: ASHA flush %d seqnum for %s, ts:%u", - this, seqnum, address, this->timestamp); + this, this->seqnum, address, this->timestamp); } } @@ -2279,7 +2278,6 @@ static int impl_node_process(void *object) if (other && other->asha->ref_t0 != 0) { this->asha->ref_t0 = other->asha->ref_t0; this->seqnum = asha_seqnum(this); - reset_buffer(this); set_asha_timer(this, other); } else { this->asha->ref_t0 = get_reference_time(this, NULL); From afa7ebc03250481bd36ff98bb5bf3106c68f6af1 Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Tue, 6 May 2025 16:13:40 +0530 Subject: [PATCH 0225/1014] media-sink: Drop packet if send fails for ASHA One of the ideas behind retrying the sending of a failed packet with the poll callback was to make sure that we do not end up with missing seqnums by missing received credit due to some jitter. However, the rate matching behaviour for ASHA is not clear and we do not seem to face problems in local testing by just dropping the packet. --- spa/plugins/bluez5/media-sink.c | 52 --------------------------------- 1 file changed, 52 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index e335889af..5bc1811dd 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -113,13 +113,11 @@ struct spa_bt_asha { int timerfd; uint8_t buf[512]; - uint8_t seqnum_pending; uint64_t ref_t0; uint64_t next_time; unsigned int flush_pending:1; - unsigned int poll_pending:1; unsigned int set_timer:1; }; @@ -1354,16 +1352,6 @@ static void media_asha_flush_timeout(struct spa_source *source) asha->next_time += (uint64_t)(ASHA_CONN_INTERVAL * port->ratectl.corr); - if (asha->poll_pending) { - /* - * We have pending data to send but we could not send it - * before the connection interval elapsed. - */ - asha->poll_pending = false; - spa_log_trace(this->log, "%p: ASHA failed to send seqnum %d for %s", - this, asha->seqnum_pending, address); - } - if (asha->flush_pending) { asha->buf[0] = this->seqnum; written = send(asha->flush_source.fd, asha->buf, @@ -1371,19 +1359,9 @@ static void media_asha_flush_timeout(struct spa_source *source) /* * For ASHA, when we are out of LE credits and cannot write to * the socket, return value of `send` will be -EAGAIN. - * - * If we fail to send here, send on the next `poll` which - * ideally will be a few ms away on receiving LE credits. We - * cannot delay the flush till the next cycle. */ if (written < 0) { - asha->seqnum_pending = seqnum; - asha->poll_pending = true; asha->flush_pending = false; - - SPA_FLAG_UPDATE(asha->flush_source.mask, SPA_IO_OUT, 1); - spa_loop_update_source(this->data_loop, &asha->flush_source); - spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on timer for %s, written:%d", this, this->seqnum, address, -errno); goto skip_flush; @@ -1409,8 +1387,6 @@ static void media_asha_cb(struct spa_source *source) struct impl *this = source->data; struct spa_bt_asha *asha = this->asha; const char *address = this->transport->device->address; - uint8_t seqnum; - int written; if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_error(this->log, "%p: ASHA source error %d on %s", this, source->rmask, address); @@ -1420,33 +1396,6 @@ static void media_asha_cb(struct spa_source *source) return; } - - if (source->rmask & SPA_IO_OUT) { - if (this->transport == NULL || !asha->poll_pending) { - return; - } - - SPA_FLAG_UPDATE(asha->flush_source.mask, SPA_IO_OUT, 0); - spa_loop_update_source(this->data_loop, &asha->flush_source); - - seqnum = asha->buf[0]; - written = send(asha->flush_source.fd, asha->buf, - ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); - /* - * For ASHA, when we are out of LE credits and cannot write to - * the socket, return value of `send` will be -EAGAIN. - */ - if (written < 0) { - spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on poll for %s, written:%d", - this, seqnum, address, -errno); - } - - if (written > 0) { - asha->poll_pending = false; - spa_log_trace(this->log, "%p: ASHA flush %d seqnum for %s", - this, seqnum, address); - } - } } static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, @@ -1576,7 +1525,6 @@ static int transport_start(struct impl *this) struct spa_bt_asha *asha = this->asha; asha->flush_pending = false; - asha->poll_pending = false; asha->set_timer = false; asha->timer_source.data = this; From 27fecd3c5634fbf3c7c39d884d43240ec4f6854a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Mon, 5 May 2025 11:28:20 +0200 Subject: [PATCH 0226/1014] bluez5: backend-native: Fix hangup of waiting call 3-way incoming calls are created in waiting state. When those calls are hang-up before being active, the +CIEV: (callsetup = 0) should also be managed for waiting calls. --- spa/plugins/bluez5/backend-native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index fa78545fa..e42963c34 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2088,7 +2088,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || - call->state == CALL_STATE_INCOMING) { + call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { call->state = CALL_STATE_DISCONNECTED; telephony_call_notify_updated_props(call); telephony_call_destroy(call); From 958ae367174f2d1f2cb8c3280a758f1a75439dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Mon, 5 May 2025 12:44:20 +0200 Subject: [PATCH 0227/1014] spa: Add default: statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to use the library in projects that use `-Wswitch-default` without any #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-default" #pragma GCC diagnostic pop This is useful as as the header is being pulled in via pipewire/wireplumber headers into projects that might have this warning enabled and would otherwise fail to build with -Werror. Signed-off-by: Guido Günther --- spa/include/spa/utils/json-core.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 31bf772f1..10c87e68c 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -268,6 +268,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) if (--utf8_remain == 0) iter->state = __STRING | flag; continue; + default: + break; } _SPA_ERROR(CHARACTERS_NOT_ALLOWED); case __ESC: @@ -276,12 +278,17 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) case 'n': case 'r': case 't': case 'u': iter->state = __STRING | flag; continue; + default: + break; } _SPA_ERROR(INVALID_ESCAPE); case __COMMENT: switch (cur) { case '\n': case '\r': iter->state = __STRUCT | flag; + break; + default: + break; } break; default: @@ -299,6 +306,8 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) case __COMMENT: /* trailing comment */ return 0; + default: + break; } if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) { From 5bf3a0c454b66e948c1162aa5de513559b9e1547 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 10:44:57 +0200 Subject: [PATCH 0228/1014] filter-graph: add a debug node Add a node that logs audio or control data to the log. --- spa/plugins/filter-graph/builtin_plugin.c | 55 +++++++++++++++++++++++ src/modules/module-filter-chain.c | 12 ++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index f50ffd770..b5af75010 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "audio-plugin.h" @@ -2453,6 +2454,58 @@ static const struct spa_fga_descriptor sqrt_desc = { .cleanup = builtin_cleanup, }; +/* debug */ +static void debug_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[0], *out = impl->port[1]; + float *control = impl->port[2], *notify = impl->port[3]; + + if (in != NULL) { + spa_debug_log_mem(impl->log, SPA_LOG_LEVEL_INFO, 0, in, SampleCount * sizeof(float)); + if (out != NULL) + memcpy(out, in, SampleCount * sizeof(float)); + } + if (control != NULL) { + spa_log_info(impl->log, "control: %f", control[0]); + if (notify != NULL) + notify[0] = control[0]; + } +} + + +static struct spa_fga_port debug_ports[] = { + { .index = 0, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, +}; + +static const struct spa_fga_descriptor debug_desc = { + .name = "debug", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(debug_ports), + .ports = debug_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = debug_run, + .cleanup = builtin_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -2510,6 +2563,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &abs_desc; case 26: return &sqrt_desc; + case 27: + return &debug_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index dcd3d403d..c91a320d0 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -580,7 +580,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * and that can be used to control the processing of the audio. Some of these ports * contain values in LUFS, or "Loudness Units relative to Full Scale". These are * negative values, closer to 0 is louder. You can use the lufs2gain plugin to - * convert this value to again to adjust a volume (See below). + * convert this value to a gain to adjust a volume (See below). * * "Momentary LUFS" contains the momentary loudness measurement with a 400ms window * and 75% overlap. It works mostly like an R.M.S. meter. @@ -638,6 +638,16 @@ extern struct spa_handle_factory spa_filter_graph_factory; * control value "Gain". This gain can be used as input for the builtin `linear` * node, for example, to adust the gain. * + * ### debug + * + * The debug plugin can be used to debug the audio and control data of other plugins. + * + * It has an "In" input port and an "Out" output data ports. The data from "In" will + * be copied to "Out" and the data will be dumped into the INFO log. + * + * There is also a "Control" input port and an "Notify" output control ports. The + * control from "Control" will be copied to "Notify" and the control value will be + * dumped into the INFO log. * * ## General options * From 42b9b0eb4cb9fe30c9ea38ef25a271039b721d1e Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 7 May 2025 18:29:01 +0530 Subject: [PATCH 0229/1014] bluez5-dbus: Fix the audio channel position for ASHA For stereo to work, we need to advertise the channel position based on whether the side is left or right. --- spa/plugins/bluez5/bluez5-dbus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index ace1c2ed8..231815773 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4180,7 +4180,7 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, transport->volumes[SPA_BT_VOLUME_ID_TX].active = true; transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; transport->n_channels = 1; - transport->channels[0] = SPA_AUDIO_CHANNEL_MONO; + transport->channels[0] = transport->asha_right_side ? SPA_AUDIO_CHANNEL_FR : SPA_AUDIO_CHANNEL_FL; spa_bt_device_add_profile(transport->device, transport->profile); spa_bt_device_connect_profile(transport->device, transport->profile); From a30a988606dfcb7dabb01f02669617ddbd24fdbd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 12:55:00 +0200 Subject: [PATCH 0230/1014] filter-graph: remove useless check --- spa/plugins/filter-graph/filter-graph.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 693b08200..ca0095260 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -309,9 +309,8 @@ static int impl_process(void *object, if (out[i] == NULL) continue; - port = i < graph->n_output ? &graph->output[i] : NULL; - - if (port && port->desc) + port = &graph->output[i]; + if (port->desc) port->desc->connect_port(*port->hndl, port->port, out[i]); else memset(out[i], 0, n_samples * sizeof(float)); From 5a4e8bb45e61947659d76d59ac462865712e6961 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 12:55:59 +0200 Subject: [PATCH 0231/1014] filter-graph: ensure we can call setup_graph multiple times We need to reset the fields used for sorting so that we can run the setup_graph code multiple times. --- spa/plugins/filter-graph/filter-graph.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index ca0095260..1576e9ac8 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -116,9 +116,12 @@ struct node { void *hndl[MAX_HNDL]; unsigned int n_deps; - unsigned int visited:1; + unsigned int disabled:1; unsigned int control_changed:1; + + unsigned int n_sort_deps; + unsigned int sorted:1; }; struct link { @@ -1627,8 +1630,8 @@ static struct node *find_next_node(struct graph *graph) { struct node *node; spa_list_for_each(node, &graph->node_list, link) { - if (node->n_deps == 0 && !node->visited) { - node->visited = true; + if (node->n_sort_deps == 0 && !node->sorted) { + node->sorted = true; return node; } } @@ -1876,9 +1879,13 @@ static int setup_graph(struct graph *graph) } } - /* order all nodes based on dependencies */ graph->n_hndl = 0; graph->hndl = calloc(graph->n_nodes * n_hndl, sizeof(struct graph_hndl)); + /* order all nodes based on dependencies, first reset fields */ + spa_list_for_each(node, &graph->node_list, link) { + node->sorted = false; + node->n_sort_deps = node->n_deps; + } while (true) { if ((node = find_next_node(graph)) == NULL) break; @@ -1896,11 +1903,11 @@ static int setup_graph(struct graph *graph) } for (i = 0; i < desc->n_output; i++) { spa_list_for_each(link, &node->output_port[i].link_list, output_link) - link->input->node->n_deps--; + link->input->node->n_sort_deps--; } for (i = 0; i < desc->n_notify; i++) { spa_list_for_each(link, &node->notify_port[i].link_list, output_link) - link->input->node->n_deps--; + link->input->node->n_sort_deps--; } for (i = 0; i < desc->n_control; i++) { /* any default values for the controls are set in the first instance From d277b3b62ed36d703fa6a35b39a050e7c6811667 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 12:57:04 +0200 Subject: [PATCH 0232/1014] filter-graph: add a LATENCY hint for control ports Some ports can have latency information about the plugin, mark those ports with the LATENCY HINT. Also decouple the LADSPA hint flags from the SPA ones. --- spa/plugins/filter-graph/audio-plugin.h | 7 ++++--- spa/plugins/filter-graph/ladspa_plugin.c | 10 +++++++++- spa/plugins/filter-graph/lv2_plugin.c | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/spa/plugins/filter-graph/audio-plugin.h b/spa/plugins/filter-graph/audio-plugin.h index e05d7a80a..d8925ea33 100644 --- a/spa/plugins/filter-graph/audio-plugin.h +++ b/spa/plugins/filter-graph/audio-plugin.h @@ -33,9 +33,10 @@ struct spa_fga_port { #define SPA_FGA_PORT_AUDIO (1ULL << 3) uint64_t flags; -#define SPA_FGA_HINT_BOOLEAN (1ULL << 2) -#define SPA_FGA_HINT_SAMPLE_RATE (1ULL << 3) -#define SPA_FGA_HINT_INTEGER (1ULL << 5) +#define SPA_FGA_HINT_BOOLEAN (1ULL << 0) +#define SPA_FGA_HINT_SAMPLE_RATE (1ULL << 1) +#define SPA_FGA_HINT_INTEGER (1ULL << 2) +#define SPA_FGA_HINT_LATENCY (1ULL << 3) uint64_t hint; float def; float min; diff --git a/spa/plugins/filter-graph/ladspa_plugin.c b/spa/plugins/filter-graph/ladspa_plugin.c index 45026c8e7..bad13e89f 100644 --- a/spa/plugins/filter-graph/ladspa_plugin.c +++ b/spa/plugins/filter-graph/ladspa_plugin.c @@ -116,7 +116,15 @@ static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port lower = d->PortRangeHints[p].LowerBound; upper = d->PortRangeHints[p].UpperBound; - port->hint = hint; + port->hint = 0; + if (hint & LADSPA_HINT_TOGGLED) + port->hint |= SPA_FGA_HINT_BOOLEAN; + if (hint & LADSPA_HINT_SAMPLE_RATE) + port->hint |= SPA_FGA_HINT_SAMPLE_RATE; + if (hint & LADSPA_HINT_INTEGER) + port->hint |= SPA_FGA_HINT_INTEGER; + if (spa_streq(port->name, "latency")) + port->hint |= SPA_FGA_HINT_LATENCY; port->def = get_default(port, hint, lower, upper); port->min = lower; port->max = upper; diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index a2af22d6a..51ca4bc6a 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -399,6 +399,8 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const struct descriptor *desc; uint32_t i; float *mins, *maxes, *controls; + bool latent; + uint32_t latency_index; desc = calloc(1, sizeof(*desc)); if (desc == NULL) @@ -424,6 +426,9 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const maxes = alloca(desc->desc.n_ports * sizeof(float)); controls = alloca(desc->desc.n_ports * sizeof(float)); + latent = lilv_plugin_has_latency(p->p); + latency_index = latent ? lilv_plugin_get_latency_port_index(p->p) : 0; + lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls); for (i = 0; i < desc->desc.n_ports; i++) { @@ -445,6 +450,9 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const fp->flags |= SPA_FGA_PORT_AUDIO; fp->hint = 0; + if (latent && latency_index == i) + fp->flags |= SPA_FGA_HINT_LATENCY; + fp->min = mins[i]; fp->max = maxes[i]; fp->def = controls[i]; From e545efdb6e8684e25edfa867110c8c77980faada Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 15:56:12 +0200 Subject: [PATCH 0233/1014] filter-chain: implement filter-graph latency Collect the latency of the graph in filter-chain. We do this by first inspecting the LATENCY ports on the plugins and us the notify value as the latency on the node. We then walk the graph from source to sink and for each node take the max latency of all linked upstream peer nodes. We end up with the max latency of the graph and emit this in the graph properties. We then listen for the graph latency property and use that to update the process_latency of the filter-chain, which will then update the latency on the filter-chain ports. Fixes #4678 --- spa/plugins/filter-graph/filter-graph.c | 110 +++++++++++++++++------- src/modules/module-filter-chain.c | 57 ++++++++---- 2 files changed, 118 insertions(+), 49 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 1576e9ac8..12aeffe7e 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -117,6 +117,9 @@ struct node { unsigned int n_deps; + uint32_t latency_index; + float latency; + unsigned int disabled:1; unsigned int control_changed:1; @@ -138,6 +141,7 @@ struct graph_port { const struct spa_fga_descriptor *desc; void **hndl; uint32_t port; + struct node *node; unsigned next:1; }; @@ -194,6 +198,8 @@ struct graph { uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs_position; + float latency; + unsigned activated:1; unsigned setup:1; }; @@ -245,7 +251,7 @@ static void emit_filter_graph_info(struct impl *impl, bool full) if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask || full) { - char n_inputs[64], n_outputs[64]; + char n_inputs[64], n_outputs[64], latency[64]; struct spa_dict_item items[6]; struct spa_dict dict = SPA_DICT(items, 0); char in_pos[SPA_AUDIO_MAX_CHANNELS * 8]; @@ -266,6 +272,8 @@ static void emit_filter_graph_info(struct impl *impl, bool full) graph->n_outputs_position, graph->outputs_position); items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos); } + items[dict.n_items++] = SPA_DICT_ITEM("latency", + spa_dtoa(latency, sizeof(latency), graph->latency)); impl->info.props = &dict; spa_filter_graph_emit_info(&impl->hooks, &impl->info); impl->info.props = NULL; @@ -1344,6 +1352,7 @@ static int load_node(struct graph *graph, struct spa_json *json) node->graph = graph; node->desc = desc; snprintf(node->name, sizeof(node->name), "%s", name); + node->latency_index = SPA_IDX_INVALID; node->input_port = calloc(desc->n_input, sizeof(struct port)); node->output_port = calloc(desc->n_output, sizeof(struct port)); @@ -1385,6 +1394,8 @@ static int load_node(struct graph *graph, struct spa_json *json) port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->notify[i]; + if (desc->desc->ports[port->p].hint & SPA_FGA_HINT_LATENCY) + node->latency_index = i; spa_list_init(&port->link_list); } if (have_config) @@ -1480,6 +1491,36 @@ static int impl_deactivate(void *object) return 0; } +static void sort_reset(struct graph *graph) +{ + struct node *node; + spa_list_for_each(node, &graph->node_list, link) { + node->sorted = false; + node->n_sort_deps = node->n_deps; + } +} +static struct node *sort_next_node(struct graph *graph) +{ + struct node *node; + spa_list_for_each(node, &graph->node_list, link) { + if (node->n_sort_deps == 0 && !node->sorted) { + uint32_t i; + struct link *link; + node->sorted = true; + for (i = 0; i < node->desc->n_output; i++) { + spa_list_for_each(link, &node->output_port[i].link_list, output_link) + link->input->node->n_sort_deps--; + } + for (i = 0; i < node->desc->n_notify; i++) { + spa_list_for_each(link, &node->notify_port[i].link_list, output_link) + link->input->node->n_sort_deps--; + } + return node; + } + } + return NULL; +} + static int setup_graph(struct graph *graph); static int impl_activate(void *object, const struct spa_dict *props) @@ -1494,7 +1535,7 @@ static int impl_activate(void *object, const struct spa_dict *props) const struct spa_fga_plugin *p; uint32_t i, j, max_samples = impl->quantum_limit, n_ports; int res; - float *sd, *dd, *data; + float *sd, *dd, *data, latency; const char *rate, *str; if (graph->activated) @@ -1527,7 +1568,6 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((res = setup_graph(graph)) < 0) return res; graph->setup = true; - emit_filter_graph_info(impl, false); } /* first make instances */ @@ -1618,7 +1658,35 @@ static int impl_activate(void *object, const struct spa_dict *props) d->control_changed(node->hndl[i]); } } + /* calculate latency */ + sort_reset(graph); + while ((node = sort_next_node(graph)) != NULL) { + latency = 0.0f; + for (i = 0; i < node->desc->n_input; i++) { + spa_list_for_each(link, &node->input_port[i].link_list, input_link) + latency = fmaxf(latency, link->output->node->latency); + } + if (node->latency_index != SPA_IDX_INVALID) { + port = &node->notify_port[node->latency_index]; + latency += port->control_data[0]; + + } + node->latency = latency; + spa_log_debug(impl->log, "%s latency:%f", node->name, latency); + } + latency = 0.0f; + for (i = 0; i < graph->n_outputs; i++) { + struct graph_port *port = &graph->output[i]; + latency = fmaxf(latency, port->node->latency); + } + + if (graph->latency != latency) { + graph->latency = latency; + impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; + spa_log_info(impl->log, "graph latency:%f", latency); + } + emit_filter_graph_info(impl, false); spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); return 0; error: @@ -1626,18 +1694,6 @@ error: return res; } -static struct node *find_next_node(struct graph *graph) -{ - struct node *node; - spa_list_for_each(node, &graph->node_list, link) { - if (node->n_sort_deps == 0 && !node->sorted) { - node->sorted = true; - return node; - } - } - return NULL; -} - static void unsetup_graph(struct graph *graph) { free(graph->input); @@ -1653,7 +1709,6 @@ static int setup_graph(struct graph *graph) struct impl *impl = graph->impl; struct node *node, *first, *last; struct port *port; - struct link *link; struct graph_port *gp; struct graph_hndl *gh; uint32_t i, j, n, n_input, n_output, n_hndl = 0; @@ -1758,6 +1813,7 @@ static int setup_graph(struct graph *graph) spa_log_info(impl->log, "input port %s[%d]:%s", first->name, i, d->ports[desc->input[j]].name); gp->desc = d; + gp->node = first; gp->hndl = &first->hndl[i]; gp->port = desc->input[j]; } @@ -1806,6 +1862,7 @@ static int setup_graph(struct graph *graph) peer->external = graph->n_input; gp = &graph->input[graph->n_input++]; gp->desc = peer->node->desc->desc; + gp->node = peer->node; gp->hndl = &peer->node->hndl[i]; gp->port = peer->p; gp->next = true; @@ -1822,6 +1879,7 @@ static int setup_graph(struct graph *graph) port->external = graph->n_input; gp = &graph->input[graph->n_input++]; gp->desc = d; + gp->node = port->node; gp->hndl = &port->node->hndl[i]; gp->port = port->p; gp->next = false; @@ -1837,6 +1895,7 @@ static int setup_graph(struct graph *graph) spa_log_info(impl->log, "output port %s[%d]:%s", last->name, i, d->ports[desc->output[j]].name); gp->desc = d; + gp->node = last; gp->hndl = &last->hndl[i]; gp->port = desc->output[j]; } @@ -1871,6 +1930,7 @@ static int setup_graph(struct graph *graph) port->node->name, i, d->ports[port->p].name); port->external = graph->n_output; gp->desc = d; + gp->node = port->node; gp->hndl = &port->node->hndl[i]; gp->port = port->p; } @@ -1882,14 +1942,8 @@ static int setup_graph(struct graph *graph) graph->n_hndl = 0; graph->hndl = calloc(graph->n_nodes * n_hndl, sizeof(struct graph_hndl)); /* order all nodes based on dependencies, first reset fields */ - spa_list_for_each(node, &graph->node_list, link) { - node->sorted = false; - node->n_sort_deps = node->n_deps; - } - while (true) { - if ((node = find_next_node(graph)) == NULL) - break; - + sort_reset(graph); + while ((node = sort_next_node(graph)) != NULL) { node->n_hndl = n_hndl; desc = node->desc; d = desc->desc; @@ -1901,14 +1955,6 @@ static int setup_graph(struct graph *graph) gh->desc = d; } } - for (i = 0; i < desc->n_output; i++) { - spa_list_for_each(link, &node->output_port[i].link_list, output_link) - link->input->node->n_sort_deps--; - } - for (i = 0; i < desc->n_notify; i++) { - spa_list_for_each(link, &node->notify_port[i].link_list, output_link) - link->input->node->n_sort_deps--; - } for (i = 0; i < desc->n_control; i++) { /* any default values for the controls are set in the first instance * of the control data. Duplicate this to the other instances now. */ diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index c91a320d0..4fad7f9cb 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -975,14 +975,34 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param pw_stream_update_params(impl->playback, params, 1); } -static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, - enum spa_direction direction) +static void update_process_latency(struct impl *impl) { uint8_t buffer[1024]; struct spa_pod_builder b; - struct spa_process_latency_info process_latency; struct spa_latency_info latency; - const struct spa_pod *params[1]; + const struct spa_pod *params[2]; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + latency = impl->latency[SPA_DIRECTION_INPUT]; + spa_process_latency_info_add(&impl->process_latency, &latency); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + if (impl->playback) + pw_stream_update_params(impl->playback, params, 1); + + latency = impl->latency[SPA_DIRECTION_OUTPUT]; + spa_process_latency_info_add(&impl->process_latency, &latency); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + params[1] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); + + if (impl->capture) + pw_stream_update_params(impl->capture, params, 2); +} + +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction) +{ + struct spa_process_latency_info process_latency; if (param == NULL) spa_zero(process_latency); @@ -990,19 +1010,7 @@ static void param_process_latency_changed(struct impl *impl, const struct spa_po return; impl->process_latency = process_latency; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - latency = impl->latency[SPA_DIRECTION_INPUT]; - spa_process_latency_info_add(&process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - pw_stream_update_params(impl->playback, params, 1); - - latency = impl->latency[SPA_DIRECTION_OUTPUT]; - spa_process_latency_info_add(&process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - pw_stream_update_params(impl->capture, params, 1); + update_process_latency(impl); } static void param_tag_changed(struct impl *impl, const struct spa_pod *param, @@ -1282,6 +1290,9 @@ static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct impl *impl = object; + struct spa_dict *props = info->props; + uint32_t i; + if (impl->capture_info.channels == 0) impl->capture_info.channels = info->n_inputs; if (impl->playback_info.channels == 0) @@ -1294,6 +1305,18 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) copy_position(&impl->capture_info, &impl->playback_info); copy_position(&impl->playback_info, &impl->capture_info); } + for (i = 0; props && i < props->n_items; i++) { + const char *k = props->items[i].key; + const char *s = props->items[i].value; + pw_log_info("%s %s", k, s); + if (spa_streq(k, "latency")) { + double latency; + if (spa_atod(s, &latency)) { + impl->process_latency.rate = (int32_t)latency; + update_process_latency(impl); + } + } + } } static void graph_apply_props(void *object, enum spa_direction direction, const struct spa_pod *props) From dbca286de8a47e9ce97f7ee02dfdaddae40a4612 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 16:37:14 +0200 Subject: [PATCH 0234/1014] filter-graph: add latency reporting to the convolver --- spa/plugins/filter-graph/builtin_plugin.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index b5af75010..ecd304fe6 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -687,7 +687,8 @@ struct convolver_impl { struct spa_log *log; struct spa_fga_dsp *dsp; unsigned long rate; - float *port[2]; + float *port[3]; + unsigned long latency; struct convolver *conv; }; @@ -1068,6 +1069,8 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s if (impl->conv == NULL) goto error; + impl->latency = n_samples; + free(samples); return impl; @@ -1101,8 +1104,20 @@ static struct spa_fga_port convolve_ports[] = { .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, + { .index = 2, + .name = "latency", + .hint = SPA_FGA_HINT_LATENCY, + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, }; +static void convolver_activate(void * Instance) +{ + struct convolver_impl *impl = Instance; + if (impl->port[2] != NULL) + impl->port[2][0] = impl->latency; +} + static void convolver_deactivate(void * Instance) { struct convolver_impl *impl = Instance; @@ -1114,17 +1129,20 @@ static void convolve_run(void * Instance, unsigned long SampleCount) struct convolver_impl *impl = Instance; if (impl->port[1] != NULL && impl->port[0] != NULL) convolver_run(impl->conv, impl->port[1], impl->port[0], SampleCount); + if (impl->port[2] != NULL) + impl->port[2][0] = impl->latency; } static const struct spa_fga_descriptor convolve_desc = { .name = "convolver", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, - .n_ports = 2, + .n_ports = SPA_N_ELEMENTS(convolve_ports), .ports = convolve_ports, .instantiate = convolver_instantiate, .connect_port = convolver_connect_port, + .activate = convolver_activate, .deactivate = convolver_deactivate, .run = convolve_run, .cleanup = convolver_cleanup, From 8ee51cd88f47c2598756e1b82aa03b9b384ae81f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 16:55:44 +0200 Subject: [PATCH 0235/1014] filter-graph: add latency support for the delay plugin --- spa/plugins/filter-graph/builtin_plugin.c | 42 +++++++++++++++++------ src/modules/module-filter-chain.c | 3 ++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index ecd304fe6..b38681f9d 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -1163,6 +1163,7 @@ struct delay_impl { uint32_t buffer_samples; float *buffer; uint32_t ptr; + float latency; }; static void delay_cleanup(void * Instance) @@ -1180,7 +1181,7 @@ static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct struct spa_json it[1]; const char *val; char key[256]; - float max_delay = 1.0f; + float max_delay = 1.0f, latency = 0.0f; int len; if (config == NULL) { @@ -1200,12 +1201,19 @@ static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct spa_log_error(pl->log, "delay:max-delay requires a number"); return NULL; } + } else if (spa_streq(key, "latency")) { + if (spa_json_parse_float(val, len, &latency) <= 0) { + spa_log_error(pl->log, "delay:latency requires a number"); + return NULL; + } } else { spa_log_warn(pl->log, "delay: ignoring config key: '%s'", key); } } if (max_delay <= 0.0f) max_delay = 1.0f; + if (latency <= 0.0f) + latency = 0.0f; impl = calloc(1, sizeof(*impl)); if (impl == NULL) @@ -1216,7 +1224,9 @@ static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct impl->log = pl->log; impl->rate = SampleRate; impl->buffer_samples = SPA_ROUND_UP_N((uint32_t)(max_delay * impl->rate), 64); - spa_log_info(impl->log, "max-delay:%f seconds rate:%lu samples:%d", max_delay, impl->rate, impl->buffer_samples); + impl->latency = latency * impl->rate; + spa_log_info(impl->log, "max-delay:%f seconds rate:%lu samples:%d latency:%f", + max_delay, impl->rate, impl->buffer_samples, impl->latency); impl->buffer = calloc(impl->buffer_samples * 2 + 64, sizeof(float)); if (impl->buffer == NULL) { @@ -1230,26 +1240,32 @@ static void delay_connect_port(void * Instance, unsigned long Port, float * DataLocation) { struct delay_impl *impl = Instance; - if (Port > 2) - return; impl->port[Port] = DataLocation; } +static void delay_activate(void * Instance) +{ + struct delay_impl *impl = Instance; + if (impl->port[3] != NULL) + impl->port[3][0] = impl->latency; +} + static void delay_run(void * Instance, unsigned long SampleCount) { struct delay_impl *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; float delay = impl->port[2][0]; - if (in == NULL || out == NULL) - return; - if (delay != impl->delay) { impl->delay_samples = SPA_CLAMP((uint32_t)(delay * impl->rate), 0u, impl->buffer_samples-1); impl->delay = delay; } - spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, - impl->delay_samples, out, in, SampleCount); + if (in != NULL && out == NULL) { + spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, + impl->delay_samples, out, in, SampleCount); + } + if (impl->port[3] != NULL) + impl->port[3][0] = impl->latency; } static struct spa_fga_port delay_ports[] = { @@ -1266,17 +1282,23 @@ static struct spa_fga_port delay_ports[] = { .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = 0.0f, .max = 100.0f }, + { .index = 3, + .name = "latency", + .hint = SPA_FGA_HINT_LATENCY, + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, }; static const struct spa_fga_descriptor delay_desc = { .name = "delay", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, - .n_ports = 3, + .n_ports = SPA_N_ELEMENTS(delay_ports), .ports = delay_ports, .instantiate = delay_instantiate, .connect_port = delay_connect_port, + .activate = delay_activate, .run = delay_run, .cleanup = delay_cleanup, }; diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 4fad7f9cb..8302c1c8e 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -379,6 +379,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * label = delay * config = { * "max-delay" = ... + * "latency" = ... * } * control = { * "Delay (s)" = ... @@ -392,6 +393,8 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * - `max-delay` the maximum delay in seconds. The "Delay (s)" parameter will * be clamped to this value. + * - `latency` the latency in seconds. This is 0 by default but in some cases + * the delay can be used to introduce latency with this option. * * ### Invert * From 8bcf0460d150ba56f75416501aa4866797df5a88 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 17:18:15 +0200 Subject: [PATCH 0236/1014] filter-graph: add latency option to the convolver Sometimes you want to use the convolver as a delay without adding latency so add a latency option to tweak the automatic latency reporting. Use this in the upmix rear delay filters. --- spa/plugins/filter-graph/builtin_plugin.c | 15 ++++++++++++--- .../filter-chain/sink-upmix-5.1-filter.conf | 2 ++ src/modules/module-filter-chain.c | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index b38681f9d..260f36c53 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -688,7 +688,7 @@ struct convolver_impl { struct spa_fga_dsp *dsp; unsigned long rate; float *port[3]; - unsigned long latency; + float latency; struct convolver *conv; }; @@ -927,7 +927,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s char *filenames[MAX_RATES] = { 0 }; int blocksize = 0, tailsize = 0; int resample_quality = RESAMPLE_DEFAULT_QUALITY; - float gain = 1.0f, delay = 0.0f; + float gain = 1.0f, delay = 0.0f, latency = -1.0f; unsigned long rate; errno = EINVAL; @@ -1009,6 +1009,12 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s return NULL; } } + else if (spa_streq(key, "latency")) { + if (spa_json_parse_float(val, len, &latency) <= 0) { + spa_log_error(pl->log, "convolver:latency requires a number"); + return NULL; + } + } else { spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key); } @@ -1069,7 +1075,10 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s if (impl->conv == NULL) goto error; - impl->latency = n_samples; + if (latency < 0.0f) + impl->latency = n_samples; + else + impl->latency = latency * impl->rate; free(samples); diff --git a/src/daemon/filter-chain/sink-upmix-5.1-filter.conf b/src/daemon/filter-chain/sink-upmix-5.1-filter.conf index 197733045..5ac4e7650 100644 --- a/src/daemon/filter-chain/sink-upmix-5.1-filter.conf +++ b/src/daemon/filter-chain/sink-upmix-5.1-filter.conf @@ -87,6 +87,7 @@ context.modules = [ delay = 0.012 filename = "/hilbert" length = 33 + latency = 0.0 } } { @@ -101,6 +102,7 @@ context.modules = [ delay = 0.012 filename = "/hilbert" length = 33 + latency = 0.0 } } ] diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 8302c1c8e..8b4e54ac0 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -330,6 +330,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * length = ... * channel = ... * resample_quality = ... + * latency = ... * } * ... * } @@ -361,6 +362,8 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `channel` The channel to use from the file as the IR. * - `resample_quality` The resample quality in case the IR does not match the graph * samplerate. + * - `latency` The extra latency in seconds to report. When left unspecified (or < 0.0) + * the convolver latency will be the length of the IR. * * ### Delay * From 8e4c211a80d86a88cd56de0e8626a432c285cd70 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 7 May 2025 17:19:38 +0200 Subject: [PATCH 0237/1014] filter-graph: add latency reporting to the sofa plugin --- spa/plugins/filter-graph/sofa_plugin.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/spa/plugins/filter-graph/sofa_plugin.c b/spa/plugins/filter-graph/sofa_plugin.c index d348bc97a..2942ed3bc 100644 --- a/spa/plugins/filter-graph/sofa_plugin.c +++ b/spa/plugins/filter-graph/sofa_plugin.c @@ -30,7 +30,7 @@ struct spatializer_impl { struct spa_log *log; unsigned long rate; - float *port[6]; + float *port[7]; int n_samples, blocksize, tailsize; float *tmp[2]; @@ -312,6 +312,7 @@ static void spatializer_run(void * Instance, unsigned long SampleCount) convolver_run(impl->l_conv[0], impl->port[2], impl->port[0], SampleCount); convolver_run(impl->r_conv[0], impl->port[2], impl->port[1], SampleCount); } + impl->port[6][0] = impl->n_samples; } static void spatializer_connect_port(void * Instance, unsigned long Port, @@ -346,6 +347,12 @@ static void spatializer_control_changed(void * Instance) spatializer_reload(Instance); } +static void spatializer_activate(void * Instance) +{ + struct spatializer_impl *impl = Instance; + impl->port[6][0] = impl->n_samples; +} + static void spatializer_deactivate(void * Instance) { struct spatializer_impl *impl = Instance; @@ -385,17 +392,23 @@ static struct spa_fga_port spatializer_ports[] = { .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 100.0f }, + { .index = 6, + .name = "latency", + .hint = SPA_FGA_HINT_LATENCY, + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, }; static const struct spa_fga_descriptor spatializer_desc = { .name = "spatializer", - .n_ports = 6, + .n_ports = SPA_N_ELEMENTS(spatializer_ports), .ports = spatializer_ports, .instantiate = spatializer_instantiate, .connect_port = spatializer_connect_port, .control_changed = spatializer_control_changed, + .activate = spatializer_activate, .deactivate = spatializer_deactivate, .run = spatializer_run, .cleanup = spatializer_cleanup, From 46f57406049d9901840b9b74a45d20fd5be15100 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 10:52:57 +0200 Subject: [PATCH 0238/1014] filter-graph: warn about unaligned streams Keep track of min/max latencies and warn when streams are unaligned. --- spa/plugins/filter-graph/filter-graph.c | 60 ++++++++++++++++++------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 12aeffe7e..2844eb0bb 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -116,9 +116,10 @@ struct node { void *hndl[MAX_HNDL]; unsigned int n_deps; - uint32_t latency_index; - float latency; + + float min_latency; + float max_latency; unsigned int disabled:1; unsigned int control_changed:1; @@ -198,7 +199,8 @@ struct graph { uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs_position; - float latency; + float min_latency; + float max_latency; unsigned activated:1; unsigned setup:1; @@ -273,7 +275,8 @@ static void emit_filter_graph_info(struct impl *impl, bool full) items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos); } items[dict.n_items++] = SPA_DICT_ITEM("latency", - spa_dtoa(latency, sizeof(latency), graph->latency)); + spa_dtoa(latency, sizeof(latency), + (graph->min_latency + graph->max_latency) / 2.0f)); impl->info.props = &dict; spa_filter_graph_emit_info(&impl->hooks, &impl->info); impl->info.props = NULL; @@ -1535,7 +1538,7 @@ static int impl_activate(void *object, const struct spa_dict *props) const struct spa_fga_plugin *p; uint32_t i, j, max_samples = impl->quantum_limit, n_ports; int res; - float *sd, *dd, *data, latency; + float *sd, *dd, *data, min_latency, max_latency; const char *rate, *str; if (graph->activated) @@ -1661,30 +1664,55 @@ static int impl_activate(void *object, const struct spa_dict *props) /* calculate latency */ sort_reset(graph); while ((node = sort_next_node(graph)) != NULL) { - latency = 0.0f; + min_latency = FLT_MAX; + max_latency = 0.0f; for (i = 0; i < node->desc->n_input; i++) { - spa_list_for_each(link, &node->input_port[i].link_list, input_link) - latency = fmaxf(latency, link->output->node->latency); + spa_list_for_each(link, &node->input_port[i].link_list, input_link) { + min_latency = fminf(min_latency, link->output->node->min_latency); + max_latency = fmaxf(max_latency, link->output->node->max_latency); + } } + min_latency = min_latency == FLT_MAX ? 0.0f : min_latency; + if (node->latency_index != SPA_IDX_INVALID) { port = &node->notify_port[node->latency_index]; - latency += port->control_data[0]; + min_latency += port->control_data[0]; + max_latency += port->control_data[0]; } - node->latency = latency; - spa_log_debug(impl->log, "%s latency:%f", node->name, latency); + node->min_latency = min_latency; + node->max_latency = max_latency; + spa_log_info(impl->log, "%s latency:%f-%f", node->name, min_latency, max_latency); } - latency = 0.0f; + min_latency = FLT_MAX; + max_latency = 0.0f; for (i = 0; i < graph->n_outputs; i++) { struct graph_port *port = &graph->output[i]; - latency = fmaxf(latency, port->node->latency); + max_latency = fmaxf(max_latency, port->node->max_latency); + min_latency = fminf(min_latency, port->node->min_latency); } + min_latency = min_latency == FLT_MAX ? 0.0f : min_latency; - if (graph->latency != latency) { - graph->latency = latency; + spa_log_info(impl->log, "graph latency min:%f max:%f", min_latency, max_latency); + if (min_latency != max_latency) { + spa_log_warn(impl->log, "graph has unaligned latency min:%f max:%f, " + "consider adding delays or tweak node latency to " + "align the signals", min_latency, max_latency); + for (i = 0; i < graph->n_outputs; i++) { + struct graph_port *port = &graph->output[i]; + if (min_latency != port->node->min_latency || + max_latency != port->node->max_latency) + spa_log_warn(impl->log, "output port %d from %s min:%f max:%f", + i, port->node->name, + port->node->min_latency, port->node->max_latency); + } + + } + if (graph->min_latency != min_latency || graph->max_latency != max_latency) { + graph->min_latency = min_latency; + graph->max_latency = max_latency; impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; - spa_log_info(impl->log, "graph latency:%f", latency); } emit_filter_graph_info(impl, false); spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); From 68c5f417089f1f29e3ccedac9612fef414036e18 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 12:15:58 +0200 Subject: [PATCH 0239/1014] filter-graph: fix port find logic We need both ports to be NULL (failed to find the ports as audio ports) when we try to link control/notify ports. --- spa/plugins/filter-graph/filter-graph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 2844eb0bb..480452b1e 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1132,7 +1132,7 @@ static int parse_link(struct graph *graph, struct spa_json *json) out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT); in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT); - if (out_port == NULL && out_port == NULL) { + if (out_port == NULL && in_port == NULL) { /* try control ports */ out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL); in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); From bca83c8eee6cff1c8bf6598e11929c87f78069db Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 12:17:38 +0200 Subject: [PATCH 0240/1014] filter-graph: fix lv2 latency hint --- spa/plugins/filter-graph/lv2_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 51ca4bc6a..47566bc9f 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -451,7 +451,7 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const fp->hint = 0; if (latent && latency_index == i) - fp->flags |= SPA_FGA_HINT_LATENCY; + fp->hint |= SPA_FGA_HINT_LATENCY; fp->min = mins[i]; fp->max = maxes[i]; From d5e2cc94cdf8b8ad22d8068eb459da0dcece4cf7 Mon Sep 17 00:00:00 2001 From: Taruntej Kanakamalla Date: Fri, 18 Apr 2025 23:01:56 +0530 Subject: [PATCH 0241/1014] gst: sink: update clock before every trigger process Get the clock pointer using the io_changed stream event. and update the clock before triggering the process The clock needs to be updated in the data loop thread and before triggering the process so move the calls to `pw_stream_trigger_process` from gstreamer thread context into the data loop thread context by invoking a callback and update the clock inside the data loop callback before the trigger --- src/gst/gstpipewiresink.c | 98 +++++++++++++++++++++++++++++++++++-- src/gst/gstpipewiresink.h | 2 + src/gst/gstpipewirestream.h | 3 ++ 3 files changed, 99 insertions(+), 4 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 79b341e2c..ac8985c29 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -40,6 +40,9 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); #define DEFAULT_PROP_SLAVE_METHOD GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE #define DEFAULT_PROP_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO +#define MAX_ERROR_MS 1 +#define RESYNC_TIMEOUT_MS 10 + enum { PROP_0, @@ -377,6 +380,7 @@ gst_pipewire_sink_init (GstPipeWireSink * sink) sink->mode = DEFAULT_PROP_MODE; sink->use_bufferpool = DEFAULT_PROP_USE_BUFFERPOOL; sink->is_rawvideo = false; + sink->first_buffer = true; GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK); @@ -690,7 +694,15 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) GST_WARNING_OBJECT (pwsink, "can't send buffer %s", spa_strerror(res)); } else { data->queued = TRUE; - GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p; gstbuffer %p ",data->b, buffer); + GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p size: %"PRIu64"; gstbuffer %p ",data->b, data->b->size, buffer); + if (pwsink->first_buffer) { + pwsink->first_buffer = false; + pwsink->first_buffer_pts = GST_BUFFER_PTS(buffer); + } + stream->position = gst_util_uint64_scale_int(GST_BUFFER_PTS(buffer) - pwsink->first_buffer_pts, pwsink->rate, 1 * GST_SECOND); + + // have the buffer duration value minimum as 1, in case of video where rate is 0 (not applicable) + stream->buf_duration = SPA_MAX((uint64_t)1, gst_util_uint64_scale_int(GST_BUFFER_DURATION(buffer), pwsink->rate, 1 * GST_SECOND)); } switch (pwsink->slave_method) { @@ -702,6 +714,52 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) } } +static void update_time (GstPipeWireSink *pwsink) +{ + struct spa_io_position *p = pwsink->stream->io_position; + double err = 0.0, corr = 1.0; + guint64 now; + double_t max_err = pwsink->rate * MAX_ERROR_MS/1000; + double_t resync_timeout = pwsink->rate * RESYNC_TIMEOUT_MS/1000; + + if (pwsink->first_buffer) { + // use the target duration before the first buffer + pwsink->stream->buf_duration = p->clock.target_duration; + spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); + } + + now = pw_stream_get_nsec(pwsink->stream->pwstream); + err = (double)gst_util_uint64_scale(now, pwsink->rate, 1 *GST_SECOND) - + (double)gst_util_uint64_scale(p->clock.next_nsec, pwsink->rate, 1 *GST_SECOND); + + GST_LOG_OBJECT(pwsink, "err is %f max err is %f now %lu next is %lu", err, max_err, now, p->clock.next_nsec); + + if (fabs(err) > max_err) { + if (fabs(err) > resync_timeout) { + GST_WARNING_OBJECT(pwsink, "err %f exceeds resync timeout, resetting", err); + spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); + err = 0.0; + } else { + err = SPA_CLAMPD(err, -max_err, max_err); + } + } + corr = spa_dll_update(&pwsink->stream->dll, err); + + p->clock.nsec = now; + p->clock.position = pwsink->stream->position; + p->clock.duration = pwsink->stream->buf_duration; + /* we don't have a way to estimate the target (next cycle) buffer duration + * so use the current buffer duration + */ + p->clock.target_duration = pwsink->stream->buf_duration; + p->clock.rate = SPA_FRACTION(1, pwsink->rate); + // current time plus duration scaled with correlation + p->clock.next_nsec = now + (uint64_t)(p->clock.duration / corr * GST_SECOND / pwsink->rate); + p->clock.rate_diff = corr; + + GST_DEBUG_OBJECT(pwsink, "now %lu, position %lu, duration %lu, rate :%d, next : %lu, delay is %ld, rate_diff is %f", + p->clock.nsec, p->clock.position, p->clock.duration, pwsink->rate, p->clock.next_nsec, p->clock.delay,p->clock.rate_diff); +} static void on_process (void *data) @@ -711,6 +769,24 @@ on_process (void *data) g_cond_signal (&pwsink->stream->pool->cond); } +static int invoke_trigger_process(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + + GstPipeWireSink *pwsink = user_data; + + + /* Note: We cannot use the rate for computation of other clock params + * in case of video because the rate for video is set as 0 in the _setcaps. + * So skip update time for video (i.e. when rate is 0). The video buffers + * get timestamp from the SPA_META_Header anyway + */ + + if (pwsink->rate) + update_time(pwsink); + return pw_stream_trigger_process(pwsink->stream->pwstream); +} + static void on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { @@ -726,7 +802,7 @@ on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state sta break; case PW_STREAM_STATE_STREAMING: if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_stream_trigger_process (pwsink->stream->pwstream); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); break; case PW_STREAM_STATE_ERROR: /* make the error permanent, if it is not already; @@ -1023,7 +1099,7 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) gst_buffer_unref (b); if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_stream_trigger_process (pwsink->stream->pwstream); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); } } else { GST_TRACE_OBJECT(pwsink, "Buffer is from pipewirepool"); @@ -1031,7 +1107,7 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) do_send_buffer (pwsink, buffer); if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_stream_trigger_process (pwsink->stream->pwstream); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); } done_unlock: @@ -1045,6 +1121,19 @@ not_negotiated: } } +static void +on_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + GstPipeWireSink *pwsink = data; + + switch (id) { + case SPA_IO_Position: + GST_DEBUG_OBJECT(pwsink, "got io position %p", area); + pwsink->stream->io_position = area; + break; + } +} + static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = on_state_changed, @@ -1052,6 +1141,7 @@ static const struct pw_stream_events stream_events = { .add_buffer = on_add_buffer, .remove_buffer = on_remove_buffer, .process = on_process, + .io_changed = on_io_changed, }; static GstStateChangeReturn diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h index f4a961f9a..5816f7a15 100644 --- a/src/gst/gstpipewiresink.h +++ b/src/gst/gstpipewiresink.h @@ -70,6 +70,8 @@ struct _GstPipeWireSink { gboolean rate_match; gint rate; gboolean is_rawvideo; + gboolean first_buffer; + GstClockTime first_buffer_pts; GstPipeWireSinkMode mode; GstPipeWireSinkSlaveMethod slave_method; diff --git a/src/gst/gstpipewirestream.h b/src/gst/gstpipewirestream.h index a301375c7..23dc996a9 100644 --- a/src/gst/gstpipewirestream.h +++ b/src/gst/gstpipewirestream.h @@ -31,6 +31,7 @@ struct _GstPipeWireStream { GstClock *clock; guint64 position; + guint64 buf_duration; struct spa_dll dll; double err_avg, err_var, err_wdw; guint64 last_ts; @@ -41,6 +42,8 @@ struct _GstPipeWireStream { struct pw_stream *pwstream; struct spa_hook pwstream_listener; + struct spa_io_position *io_position; + /* common properties */ int fd; gchar *path; From b2695f86cfe5e30644f0ce565e22c1e6da303655 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 16:01:18 +0200 Subject: [PATCH 0242/1014] alsa-pcm: handle and warn about a driver bug wrt channels If the driver returns a larger min than max channel count, log a warning and swap the two numbers. See #4687 --- spa/plugins/alsa/alsa-pcm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 36834eaed..e71d28093 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1563,6 +1563,12 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", min, max, state->default_channels, all); + if (min > max) { + spa_log_warn(state->log, "driver bug! min > max channels: (%d > %d)", + min, max); + SPA_SWAP(min, max); + } + if (state->default_channels != 0 && !all) { if (min < state->default_channels) min = state->default_channels; From ff0d6d5677ba576ff3d51086638f4dd1966c7c84 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 18:14:02 +0200 Subject: [PATCH 0243/1014] alsa: clamp audio.channels to MAX_CHANNELS So that we don't end up trying to use too many channels later on. --- spa/plugins/alsa/alsa-pcm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index e71d28093..d61d1f333 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -162,6 +162,11 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) int fmt_change = 0; if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { state->default_channels = atoi(s); + if (state->default_channels > SPA_AUDIO_MAX_CHANNELS) { + spa_log_warn(state->log, "%p: %s: %s > %d, clamping", + state, k, s, SPA_AUDIO_MAX_CHANNELS); + state->default_channels = SPA_AUDIO_MAX_CHANNELS; + } fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { state->default_rate = atoi(s); From 2d8080cbdefe553014c739b8041fd83c7739605b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 18:16:53 +0200 Subject: [PATCH 0244/1014] alsa: only use default rate and channels when valid Check the user provided rate and channels and only use them to limit the rate and channels when they are valid. --- spa/plugins/alsa/alsa-pcm.c | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index d61d1f333..4c159e020 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1568,21 +1568,18 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", min, max, state->default_channels, all); - if (min > max) { - spa_log_warn(state->log, "driver bug! min > max channels: (%d > %d)", - min, max); - SPA_SWAP(min, max); - } - - if (state->default_channels != 0 && !all) { - if (min < state->default_channels) - min = state->default_channels; - if (max > state->default_channels) - max = state->default_channels; - } min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS); max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS); + if (state->default_channels != 0 && !all) { + if (min > state->default_channels || + max < state->default_channels) + spa_log_warn(state->log, "given audio.channels %d out of range:%d-%d", + state->default_channels, min, max); + else + min = max = state->default_channels; + } + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_channels, 0); if (state->props.use_chmap && (maps = snd_pcm_query_chmaps(hndl)) != NULL) { @@ -1853,10 +1850,12 @@ static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *ne spa_log_debug(state->log, "rate (%d %d)", rmin, rmax); if (state->default_rate != 0) { - if (rmin < state->default_rate) - rmin = state->default_rate; - if (rmax > state->default_rate) - rmax = state->default_rate; + if (rmin > state->default_rate || + rmax < state->default_rate) + spa_log_warn(state->log, "given audio.rate %d out of range:%d-%d", + state->default_rate, rmin, rmax); + else + rmin = rmax = state->default_rate; } spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_iec958Codec, 0); From f068e4ba8513e696a8547c323379935ccd92fe4c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 8 May 2025 18:17:45 +0200 Subject: [PATCH 0245/1014] alsa: rework the channel params Avoid adding a None choice, just add one single value without a choice when the min == max. In that case we can also add a channel position. --- spa/plugins/alsa/alsa-pcm.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 4c159e020..bc5a0ae63 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1617,23 +1617,18 @@ skip_channels: snd_pcm_free_chmaps(maps); } else { - const struct channel_map *map = NULL; - struct spa_pod_choice *choice; - if (index > 0) return 0; - spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_None, 0); - choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[0]); - spa_pod_builder_int(b, max); if (min != max) { + spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_Range, 0); + spa_pod_builder_int(b, max); spa_pod_builder_int(b, min); spa_pod_builder_int(b, max); - choice->body.type = SPA_CHOICE_Range; - } - spa_pod_builder_pop(b, &f[0]); - - if (min == max) { + spa_pod_builder_pop(b, &f[0]); + } else { + const struct channel_map *map = NULL; + spa_pod_builder_int(b, min); if (state->default_pos.channels == min) { map = &state->default_pos; spa_log_debug(state->log, "%p: using provided default", state); @@ -1641,15 +1636,15 @@ skip_channels: map = &default_map[min]; spa_log_debug(state->log, "%p: using default %d channel map", state, min); } - } - if (map) { - spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); - spa_pod_builder_push_array(b, &f[0]); - for (i = 0; i < map->channels; i++) { - spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]); - spa_pod_builder_id(b, map->pos[i]); + if (map) { + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); + spa_pod_builder_push_array(b, &f[0]); + for (i = 0; i < map->channels; i++) { + 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_pop(b, &f[0]); } - spa_pod_builder_pop(b, &f[0]); } } return 1; From a5a69734720aea53ece5d61c9ce82e8df9c5a22b Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Wed, 7 May 2025 20:04:58 +0530 Subject: [PATCH 0246/1014] bluez5-dbus: Fix another build failure on 32-bit system --- spa/plugins/bluez5/bluez5-dbus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 231815773..90a183b73 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -2811,7 +2811,7 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->hisyncid = *(uint64_t *)value; - spa_log_debug(monitor->log, "remote_endpoint %p: %s=%zd", remote_endpoint, key, remote_endpoint->hisyncid); + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid); } else spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); From 608bf93f7ad7bb8bbedb1a97e98fd629cd0ec7f2 Mon Sep 17 00:00:00 2001 From: Taruntej Kanakamalla Date: Thu, 8 May 2025 21:16:48 +0530 Subject: [PATCH 0247/1014] gst: sink: minor formatting fixes follow up of !2337 --- src/gst/gstpipewiresink.c | 42 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index ac8985c29..9a9efde8c 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -40,8 +40,8 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); #define DEFAULT_PROP_SLAVE_METHOD GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE #define DEFAULT_PROP_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO -#define MAX_ERROR_MS 1 -#define RESYNC_TIMEOUT_MS 10 +#define MAX_ERROR_MS 1 +#define RESYNC_TIMEOUT_MS 10 enum { @@ -694,15 +694,18 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) GST_WARNING_OBJECT (pwsink, "can't send buffer %s", spa_strerror(res)); } else { data->queued = TRUE; - GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p size: %"PRIu64"; gstbuffer %p ",data->b, data->b->size, buffer); + GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p size: %"PRIu64"; gstbuffer %p", + data->b, data->b->size, buffer); if (pwsink->first_buffer) { pwsink->first_buffer = false; pwsink->first_buffer_pts = GST_BUFFER_PTS(buffer); } - stream->position = gst_util_uint64_scale_int(GST_BUFFER_PTS(buffer) - pwsink->first_buffer_pts, pwsink->rate, 1 * GST_SECOND); + stream->position = gst_util_uint64_scale_int(GST_BUFFER_PTS(buffer) - pwsink->first_buffer_pts, + pwsink->rate, 1 * GST_SECOND); // have the buffer duration value minimum as 1, in case of video where rate is 0 (not applicable) - stream->buf_duration = SPA_MAX((uint64_t)1, gst_util_uint64_scale_int(GST_BUFFER_DURATION(buffer), pwsink->rate, 1 * GST_SECOND)); + stream->buf_duration = SPA_MAX((uint64_t)1, gst_util_uint64_scale_int(GST_BUFFER_DURATION(buffer), + pwsink->rate, 1 * GST_SECOND)); } switch (pwsink->slave_method) { @@ -725,19 +728,22 @@ static void update_time (GstPipeWireSink *pwsink) if (pwsink->first_buffer) { // use the target duration before the first buffer pwsink->stream->buf_duration = p->clock.target_duration; - spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); + spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, + pwsink->rate); } now = pw_stream_get_nsec(pwsink->stream->pwstream); - err = (double)gst_util_uint64_scale(now, pwsink->rate, 1 *GST_SECOND) - - (double)gst_util_uint64_scale(p->clock.next_nsec, pwsink->rate, 1 *GST_SECOND); + err = (double)gst_util_uint64_scale(now, pwsink->rate, 1 * GST_SECOND) - + (double)gst_util_uint64_scale(p->clock.next_nsec, pwsink->rate, 1 * GST_SECOND); - GST_LOG_OBJECT(pwsink, "err is %f max err is %f now %lu next is %lu", err, max_err, now, p->clock.next_nsec); + GST_LOG_OBJECT(pwsink, "err is %f max err is %f now %"PRIu64" next is %"PRIu64"", err, max_err, now, + p->clock.next_nsec); if (fabs(err) > max_err) { if (fabs(err) > resync_timeout) { GST_WARNING_OBJECT(pwsink, "err %f exceeds resync timeout, resetting", err); - spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); + spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, + pwsink->rate); err = 0.0; } else { err = SPA_CLAMPD(err, -max_err, max_err); @@ -757,8 +763,9 @@ static void update_time (GstPipeWireSink *pwsink) p->clock.next_nsec = now + (uint64_t)(p->clock.duration / corr * GST_SECOND / pwsink->rate); p->clock.rate_diff = corr; - GST_DEBUG_OBJECT(pwsink, "now %lu, position %lu, duration %lu, rate :%d, next : %lu, delay is %ld, rate_diff is %f", - p->clock.nsec, p->clock.position, p->clock.duration, pwsink->rate, p->clock.next_nsec, p->clock.delay,p->clock.rate_diff); + GST_DEBUG_OBJECT(pwsink, "now %"PRIu64", position %"PRIu64", duration %"PRIu64", rate :%d," + "next : %"PRIu64", delay is %"PRIi64", rate_diff is %f", p->clock.nsec, p->clock.position, + p->clock.duration, pwsink->rate, p->clock.next_nsec, p->clock.delay,p->clock.rate_diff); } static void @@ -775,7 +782,6 @@ static int invoke_trigger_process(struct spa_loop *loop, GstPipeWireSink *pwsink = user_data; - /* Note: We cannot use the rate for computation of other clock params * in case of video because the rate for video is set as 0 in the _setcaps. * So skip update time for video (i.e. when rate is 0). The video buffers @@ -802,7 +808,8 @@ on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state sta break; case PW_STREAM_STATE_STREAMING: if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), + invoke_trigger_process, 1, NULL, 0 , false, pwsink); break; case PW_STREAM_STATE_ERROR: /* make the error permanent, if it is not already; @@ -1099,7 +1106,8 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) gst_buffer_unref (b); if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), + invoke_trigger_process, 1, NULL, 0 , false, pwsink); } } else { GST_TRACE_OBJECT(pwsink, "Buffer is from pipewirepool"); @@ -1107,7 +1115,8 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) do_send_buffer (pwsink, buffer); if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); + pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), + invoke_trigger_process, 1, NULL, 0 , false, pwsink); } done_unlock: @@ -1128,7 +1137,6 @@ on_io_changed(void *data, uint32_t id, void *area, uint32_t size) switch (id) { case SPA_IO_Position: - GST_DEBUG_OBJECT(pwsink, "got io position %p", area); pwsink->stream->io_position = area; break; } From 436195f05fc7a970bd7d72e68af8919feae87ad7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 9 May 2025 09:33:05 +0200 Subject: [PATCH 0248/1014] control: only convert midi/UMP, pass other control types We should only try to convert MIDI/UMP when the port doesn't support it but let all the other control types pass through. Fixes #4692 --- spa/plugins/control/mixer.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 439bea76b..e97072d45 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -671,6 +671,14 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * } } +static inline bool control_needs_conversion(struct port *port, uint32_t type) +{ + /* we only converter between midi and UMP and only when the port + * does not support the current type */ + return (type == SPA_CONTROL_Midi || type == SPA_CONTROL_UMP) && + port->types && (port->types & (1u << type)) == 0; +} + static int impl_node_process(void *object) { struct impl *this = object; @@ -782,7 +790,7 @@ static int impl_node_process(void *object) if (next == NULL) break; - if (outport->types && (outport->types & (1u << next->type)) == 0) { + if (control_needs_conversion(outport, next->type)) { uint8_t *data = SPA_POD_BODY(&next->value); size_t size = SPA_POD_BODY_SIZE(&next->value); From e5d0dd202094db01099d911c984fc1002a99a8eb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 11 May 2025 16:13:25 +0300 Subject: [PATCH 0249/1014] bluez5: enable asha device set only if there's a device set Now that ASHA uses device set, no need to special case here. --- spa/plugins/bluez5/bluez5-device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 8629eaa4c..e14621b83 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -684,7 +684,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.a2dp-duplex", "true"); n_items++; } - if (in_device_set || t->media_codec->asha) { + if (in_device_set) { items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.set", this->device_set.path); n_items++; items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.internal", "true"); From 8dfa086c3c4c22b977f66ba09cbcfde97f1d18bb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 9 May 2025 17:45:08 +0200 Subject: [PATCH 0250/1014] audioconvert: refactor params Move the param enumeration code out of the main enum function. Emit node events after completion of the set_param functions to ensure we only emit things once. --- spa/plugins/audioconvert/audioconvert.c | 1267 ++++++++++++----------- 1 file changed, 662 insertions(+), 605 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index b37e15613..c227ef1e1 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -392,6 +392,22 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) } } +static void emit_info(struct impl *this, bool full) +{ + struct port *p; + uint32_t i; + + emit_node_info(this, full); + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + if ((p = GET_IN_PORT(this, i)) && p->valid) + emit_port_info(this, p, full); + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + if ((p = GET_OUT_PORT(this, i)) && p->valid) + emit_port_info(this, p, full); + } +} + static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t position, bool is_dsp, bool is_monitor, bool is_control) { @@ -414,10 +430,10 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p name = spa_debug_type_find_short_name(spa_type_audio_channel, position); snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + port->info = SPA_PORT_INFO_INIT(); + port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); @@ -454,8 +470,6 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p spa_log_debug(this->log, "%p: add port %d:%d position:%s %d %d %d", this, direction, port_id, port->position, is_dsp, is_monitor, is_control); - emit_port_info(this, port, true); - return 0; } @@ -469,43 +483,15 @@ static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t return 0; } -static int impl_node_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) + +static int node_param_enum_port_config(struct impl *this, uint32_t id, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) { - struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[4096]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumPortConfig: + switch (index) { + case 0 ... 1: { - struct dir *dir; - switch (result.index) { - case 0: - dir = &this->dir[SPA_DIRECTION_INPUT];; - break; - case 1: - dir = &this->dir[SPA_DIRECTION_OUTPUT];; - break; - default: - return 0; - } - param = spa_pod_builder_add_object(&b, + struct dir *dir = &this->dir[index]; + *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, @@ -517,23 +503,23 @@ static int impl_node_enum_params(void *object, int seq, SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); break; } - case SPA_PARAM_PortConfig: + default: + return 0; + } + return 1; +} + +static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: { - struct dir *dir; + struct dir *dir = &this->dir[index]; struct spa_pod_frame f[1]; - switch (result.index) { - case 0: - dir = &this->dir[SPA_DIRECTION_INPUT];; - break; - case 1: - dir = &this->dir[SPA_DIRECTION_OUTPUT];; - break; - default: - return 0; - } - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); - spa_pod_builder_add(&b, + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); + spa_pod_builder_add(b, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), @@ -541,406 +527,456 @@ static int impl_node_enum_params(void *object, int seq, 0); if (dir->have_format) { - spa_pod_builder_prop(&b, SPA_PARAM_PORT_CONFIG_format, 0); - spa_format_audio_raw_build(&b, SPA_PARAM_PORT_CONFIG_format, + spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); + spa_format_audio_raw_build(b, SPA_PARAM_PORT_CONFIG_format, &dir->format.info.raw); } - param = spa_pod_builder_pop(&b, &f[0]); - break; - } - case SPA_PARAM_PropInfo: - { - struct props *p = &this->props; - struct spa_pod_frame f[2]; - - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), - SPA_PROP_INFO_description, SPA_POD_String("Volume"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME)); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), - SPA_PROP_INFO_description, SPA_POD_String("Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); - break; - case 2: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 3: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), - SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), - SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 4: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), - SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); - break; - case 5: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 6: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), - SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); - break; - case 7: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), - SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), - SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); - break; - case 8: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("monitor.channel-volumes"), - SPA_PROP_INFO_description, SPA_POD_String("Monitor channel volume"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - this->monitor_channel_volumes), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 9: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mix_disabled), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 10: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.min-volume"), - SPA_PROP_INFO_description, SPA_POD_String("Minimum volume level"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->min_volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 11: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.max-volume"), - SPA_PROP_INFO_description, SPA_POD_String("Maximum volume level"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->max_volume, - DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 12: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), - SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 13: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), - SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 14: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), - SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 15: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), - SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.lfe_cutoff, 0.0, 1000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 16: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), - SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.fc_cutoff, 0.0, 48000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 17: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), - SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.rear_delay, 0.0, 1000.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 18: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), - SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - this->mix.widen, 0.0, 1.0), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 19: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), - SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - this->mix.hilbert_taps, 0, MAX_TAPS), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 20: - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); - spa_pod_builder_add(&b, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), - SPA_PROP_INFO_description, SPA_POD_String("Upmix method to use"), - SPA_PROP_INFO_type, SPA_POD_String( - channelmix_upmix_info[this->mix.upmix].label), - SPA_PROP_INFO_params, SPA_POD_Bool(true), - 0); - - spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); - spa_pod_builder_push_struct(&b, &f[1]); - SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { - spa_pod_builder_string(&b, i->label); - spa_pod_builder_string(&b, i->description); - } - spa_pod_builder_pop(&b, &f[1]); - param = spa_pod_builder_pop(&b, &f[0]); - break; - case 21: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), - SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); - break; - case 22: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), - SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), - SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 23: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 24: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), - SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 25: - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); - spa_pod_builder_add(&b, - SPA_PROP_INFO_name, SPA_POD_String("dither.method"), - SPA_PROP_INFO_description, SPA_POD_String("The dithering method"), - SPA_PROP_INFO_type, SPA_POD_String( - dither_method_info[this->dir[1].conv.method].label), - SPA_PROP_INFO_params, SPA_POD_Bool(true), - 0); - spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); - spa_pod_builder_push_struct(&b, &f[1]); - SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { - spa_pod_builder_string(&b, i->label); - spa_pod_builder_string(&b, i->description); - } - spa_pod_builder_pop(&b, &f[1]); - param = spa_pod_builder_pop(&b, &f[0]); - break; - case 26: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), - SPA_PROP_INFO_description, SPA_POD_String("Path to WAV file"), - SPA_PROP_INFO_type, SPA_POD_String(p->wav_path), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 27: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"), - SPA_PROP_INFO_description, SPA_POD_String("Disable volume updates"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 28: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), - SPA_PROP_INFO_description, SPA_POD_String("Disable Filter graph updates"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - case 29: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph"), - SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"), - SPA_PROP_INFO_type, SPA_POD_String(""), - SPA_PROP_INFO_params, SPA_POD_Bool(true)); - break; - default: - if (this->filter_graph[0] && this->filter_graph[0]->graph) { - res = spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, - result.index - 30, &b, ¶m); - if (res <= 0) - return res; - } else - return 0; - } - break; - } - - case SPA_PARAM_Props: - { - struct props *p = &this->props; - struct spa_pod_frame f[2]; - - switch (result.index) { - case 0: - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Props, id); - spa_pod_builder_add(&b, - SPA_PROP_volume, SPA_POD_Float(p->volume), - SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), - SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->channel.n_volumes, - p->channel.volumes), - SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), - SPA_TYPE_Id, - p->n_channels, - p->channel_map), - SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), - SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->soft.n_volumes, - p->soft.volumes), - SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), - SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, - p->monitor.n_volumes, - p->monitor.volumes), - 0); - spa_pod_builder_prop(&b, SPA_PROP_params, 0); - spa_pod_builder_push_struct(&b, &f[1]); - spa_pod_builder_string(&b, "monitor.channel-volumes"); - spa_pod_builder_bool(&b, this->monitor_channel_volumes); - spa_pod_builder_string(&b, "channelmix.disable"); - spa_pod_builder_bool(&b, this->props.mix_disabled); - spa_pod_builder_string(&b, "channelmix.min-volume"); - spa_pod_builder_float(&b, this->props.min_volume); - spa_pod_builder_string(&b, "channelmix.max-volume"); - spa_pod_builder_float(&b, this->props.max_volume); - spa_pod_builder_string(&b, "channelmix.normalize"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_NORMALIZE)); - spa_pod_builder_string(&b, "channelmix.mix-lfe"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_MIX_LFE)); - spa_pod_builder_string(&b, "channelmix.upmix"); - spa_pod_builder_bool(&b, SPA_FLAG_IS_SET(this->mix.options, - CHANNELMIX_OPTION_UPMIX)); - spa_pod_builder_string(&b, "channelmix.lfe-cutoff"); - spa_pod_builder_float(&b, this->mix.lfe_cutoff); - spa_pod_builder_string(&b, "channelmix.fc-cutoff"); - spa_pod_builder_float(&b, this->mix.fc_cutoff); - spa_pod_builder_string(&b, "channelmix.rear-delay"); - spa_pod_builder_float(&b, this->mix.rear_delay); - spa_pod_builder_string(&b, "channelmix.stereo-widen"); - spa_pod_builder_float(&b, this->mix.widen); - spa_pod_builder_string(&b, "channelmix.hilbert-taps"); - spa_pod_builder_int(&b, this->mix.hilbert_taps); - spa_pod_builder_string(&b, "channelmix.upmix-method"); - spa_pod_builder_string(&b, channelmix_upmix_info[this->mix.upmix].label); - spa_pod_builder_string(&b, "resample.quality"); - spa_pod_builder_int(&b, p->resample_quality); - spa_pod_builder_string(&b, "resample.disable"); - spa_pod_builder_bool(&b, p->resample_disabled); - spa_pod_builder_string(&b, "dither.noise"); - spa_pod_builder_int(&b, this->dir[1].conv.noise_bits); - spa_pod_builder_string(&b, "dither.method"); - spa_pod_builder_string(&b, dither_method_info[this->dir[1].conv.method].label); - spa_pod_builder_string(&b, "debug.wav-path"); - spa_pod_builder_string(&b, p->wav_path); - spa_pod_builder_string(&b, "channelmix.lock-volumes"); - spa_pod_builder_bool(&b, p->lock_volumes); - spa_pod_builder_string(&b, "audioconvert.filter-graph.disable"); - spa_pod_builder_bool(&b, p->filter_graph_disabled); - spa_pod_builder_string(&b, "audioconvert.filter-graph"); - spa_pod_builder_string(&b, ""); - spa_pod_builder_pop(&b, &f[1]); - param = spa_pod_builder_pop(&b, &f[0]); - break; - default: - if (result.index-1 >= this->n_graph) - return 0; - - if (this->filter_graph[result.index-1]->graph == NULL) - goto next; - - res = spa_filter_graph_get_props(this->filter_graph[result.index-1]->graph, - &b, ¶m); - if (res < 0) - return res; - if (res == 0) - goto next; - break; - } + *param = spa_pod_builder_pop(b, &f[0]); break; } default: return 0; } + return 1; +} - if (spa_pod_filter(&b, &result.param, param, filter) < 0) +static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) +{ + struct props *p = &this->props; + struct spa_pod_frame f[2]; + + switch (index) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), + SPA_PROP_INFO_description, SPA_POD_String("Volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME)); + break; + case 1: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), + SPA_PROP_INFO_description, SPA_POD_String("Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); + break; + case 2: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 3: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), + SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), + SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 4: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); + break; + case 5: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 6: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), + SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); + break; + case 7: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), + SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; + case 8: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("monitor.channel-volumes"), + SPA_PROP_INFO_description, SPA_POD_String("Monitor channel volume"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + this->monitor_channel_volumes), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 9: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mix_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 10: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.min-volume"), + SPA_PROP_INFO_description, SPA_POD_String("Minimum volume level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->min_volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 11: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.max-volume"), + SPA_PROP_INFO_description, SPA_POD_String("Maximum volume level"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->max_volume, + DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 12: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), + SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 13: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), + SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 14: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), + SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 15: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.lfe_cutoff, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 16: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), + SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.fc_cutoff, 0.0, 48000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 17: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), + SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.rear_delay, 0.0, 1000.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 18: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), + SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + this->mix.widen, 0.0, 1.0), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 19: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), + SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + this->mix.hilbert_taps, 0, MAX_TAPS), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 20: + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(b, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), + SPA_PROP_INFO_description, SPA_POD_String("Upmix method to use"), + SPA_PROP_INFO_type, SPA_POD_String( + channelmix_upmix_info[this->mix.upmix].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + + spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(b, &f[1]); + SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { + spa_pod_builder_string(b, i->label); + spa_pod_builder_string(b, i->description); + } + spa_pod_builder_pop(b, &f[1]); + *param = spa_pod_builder_pop(b, &f[0]); + break; + case 21: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), + SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); + break; + case 22: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), + SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), + SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 23: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 24: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), + SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 25: + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_add(b, + SPA_PROP_INFO_name, SPA_POD_String("dither.method"), + SPA_PROP_INFO_description, SPA_POD_String("The dithering method"), + SPA_PROP_INFO_type, SPA_POD_String( + dither_method_info[this->dir[1].conv.method].label), + SPA_PROP_INFO_params, SPA_POD_Bool(true), + 0); + spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(b, &f[1]); + SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { + spa_pod_builder_string(b, i->label); + spa_pod_builder_string(b, i->description); + } + spa_pod_builder_pop(b, &f[1]); + *param = spa_pod_builder_pop(b, &f[0]); + break; + case 26: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), + SPA_PROP_INFO_description, SPA_POD_String("Path to WAV file"), + SPA_PROP_INFO_type, SPA_POD_String(p->wav_path), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 27: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"), + SPA_PROP_INFO_description, SPA_POD_String("Disable volume updates"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 28: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Filter graph updates"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 29: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph"), + SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"), + SPA_PROP_INFO_type, SPA_POD_String(""), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + default: + if (this->filter_graph[0] && this->filter_graph[0]->graph) { + return spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, + index - 30, b, param); + } + return 0; + } + return 1; +} + +static int node_param_props(struct impl *this, uint32_t id, uint32_t index, + struct spa_pod **param, struct spa_pod_builder *b) +{ + struct props *p = &this->props; + struct spa_pod_frame f[2]; + + switch (index) { + case 0: + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_Props, id); + spa_pod_builder_add(b, + SPA_PROP_volume, SPA_POD_Float(p->volume), + SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), + SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->channel.n_volumes, + p->channel.volumes), + SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, + p->n_channels, + p->channel_map), + SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->soft.n_volumes, + p->soft.volumes), + SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), + SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->monitor.n_volumes, + p->monitor.volumes), + 0); + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f[1]); + spa_pod_builder_string(b, "monitor.channel-volumes"); + spa_pod_builder_bool(b, this->monitor_channel_volumes); + spa_pod_builder_string(b, "channelmix.disable"); + spa_pod_builder_bool(b, this->props.mix_disabled); + spa_pod_builder_string(b, "channelmix.min-volume"); + spa_pod_builder_float(b, this->props.min_volume); + spa_pod_builder_string(b, "channelmix.max-volume"); + spa_pod_builder_float(b, this->props.max_volume); + spa_pod_builder_string(b, "channelmix.normalize"); + spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_NORMALIZE)); + spa_pod_builder_string(b, "channelmix.mix-lfe"); + spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_MIX_LFE)); + spa_pod_builder_string(b, "channelmix.upmix"); + spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, + CHANNELMIX_OPTION_UPMIX)); + spa_pod_builder_string(b, "channelmix.lfe-cutoff"); + spa_pod_builder_float(b, this->mix.lfe_cutoff); + spa_pod_builder_string(b, "channelmix.fc-cutoff"); + spa_pod_builder_float(b, this->mix.fc_cutoff); + spa_pod_builder_string(b, "channelmix.rear-delay"); + spa_pod_builder_float(b, this->mix.rear_delay); + spa_pod_builder_string(b, "channelmix.stereo-widen"); + spa_pod_builder_float(b, this->mix.widen); + spa_pod_builder_string(b, "channelmix.hilbert-taps"); + spa_pod_builder_int(b, this->mix.hilbert_taps); + spa_pod_builder_string(b, "channelmix.upmix-method"); + spa_pod_builder_string(b, channelmix_upmix_info[this->mix.upmix].label); + spa_pod_builder_string(b, "resample.quality"); + spa_pod_builder_int(b, p->resample_quality); + spa_pod_builder_string(b, "resample.disable"); + spa_pod_builder_bool(b, p->resample_disabled); + spa_pod_builder_string(b, "dither.noise"); + spa_pod_builder_int(b, this->dir[1].conv.noise_bits); + spa_pod_builder_string(b, "dither.method"); + spa_pod_builder_string(b, dither_method_info[this->dir[1].conv.method].label); + spa_pod_builder_string(b, "debug.wav-path"); + spa_pod_builder_string(b, p->wav_path); + spa_pod_builder_string(b, "channelmix.lock-volumes"); + spa_pod_builder_bool(b, p->lock_volumes); + spa_pod_builder_string(b, "audioconvert.filter-graph.disable"); + spa_pod_builder_bool(b, p->filter_graph_disabled); + spa_pod_builder_string(b, "audioconvert.filter-graph"); + spa_pod_builder_string(b, ""); + spa_pod_builder_pop(b, &f[1]); + *param = spa_pod_builder_pop(b, &f[0]); + break; + default: + { + struct spa_filter_graph *graph; + int res; + + if (index-1 >= this->n_graph) + return 0; + + graph = this->filter_graph[index-1]->graph; + if (graph == NULL) + return 1; + + res = spa_filter_graph_get_props(graph, b, param); + if (res == 0) { + *param = NULL; + return 1; + } + return res; + } + } + return 1; +} + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + struct spa_result_node_params result; + uint32_t count = 0; + int res = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + param = NULL; + switch (id) { + case SPA_PARAM_EnumPortConfig: + res = node_param_enum_port_config(this, id, result.index, ¶m, &b); + break; + case SPA_PARAM_PortConfig: + res = node_param_port_config(this, id, result.index, ¶m, &b); + break; + case SPA_PARAM_PropInfo: + res = node_param_prop_info(this, id, result.index, ¶m, &b); + break; + case SPA_PARAM_Props: + res = node_param_props(this, id, result.index, ¶m, &b); + break; + default: + return 0; + } + if (res <= 0) + return res; + + if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -1004,8 +1040,9 @@ static void graph_apply_props(void *object, enum spa_direction direction, const struct impl *impl = g->impl; if (g->removing) return; - if (apply_props(impl, props) > 0) - emit_node_info(impl, false); + apply_props(impl, props); + + emit_info(impl, false); } static void graph_props_changed(void *object, enum spa_direction direction) @@ -1016,6 +1053,7 @@ static void graph_props_changed(void *object, enum spa_direction direction) return; impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[IDX_Props].user++; + emit_info(impl, false); } struct spa_filter_graph_events graph_events = { @@ -1694,6 +1732,10 @@ static int apply_props(struct impl *this, const struct spa_pod *param) this->vol_ramp_offset = 0; this->recalc = true; } + if (changed) { + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + this->params[IDX_Props].user++; + } return changed; } @@ -1846,8 +1888,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) return res; - - emit_node_info(this, false); break; } case SPA_PARAM_Props: @@ -1867,8 +1907,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, this->filter_props_count++; this->in_filter_props--; } - if (!have_graph && apply_props(this, param) > 0) - emit_node_info(this, false); + if (!have_graph) + apply_props(this, param); clean_filter_handles(this, false); break; @@ -1876,6 +1916,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, default: return -ENOENT; } + emit_info(this, false); return 0; } @@ -2376,8 +2417,6 @@ static int setup_convert(struct impl *this) this->setup = true; this->recalc = true; - emit_node_info(this, false); - return 0; } @@ -2434,24 +2473,15 @@ impl_node_add_listener(void *object, void *data) { struct impl *this = object; - uint32_t i; struct spa_hook_list save; - struct port *p; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - emit_node_info(this, true); - for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { - if ((p = GET_IN_PORT(this, i)) && p->valid) - emit_port_info(this, p, true); - } - for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { - if ((p = GET_OUT_PORT(this, i)) && p->valid) - emit_port_info(this, p, true); - } + emit_info(this, true); + spa_hook_list_join(&this->hooks, &save); return 0; @@ -2477,36 +2507,30 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return -ENOTSUP; } -static int port_enum_formats(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t index, - struct spa_pod **param, - struct spa_pod_builder *builder) +static int port_param_enum_formats(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { - struct impl *this = object; - switch (index) { case 0: - if (PORT_IS_DSP(this, direction, port_id)) { + if (port->is_dsp) { struct spa_audio_info_dsp info; info.format = SPA_AUDIO_FORMAT_DSP_F32; - *param = spa_format_audio_dsp_build(builder, - SPA_PARAM_EnumFormat, &info); - } else if (PORT_IS_CONTROL(this, direction, port_id)) { - *param = spa_pod_builder_add_object(builder, - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + *param = spa_format_audio_dsp_build(b, id, &info); + } else if (port->is_control) { + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( (1u<io_position ? - this->io_position->clock.target_rate.denom : DEFAULT_RATE; + uint32_t rate = impl->io_position ? + impl->io_position->clock.target_rate.denom : DEFAULT_RATE; - spa_pod_builder_push_object(builder, &f[0], - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(builder, + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, @@ -2536,17 +2560,17 @@ static int port_enum_formats(void *object, SPA_AUDIO_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW), 0); - if (!this->props.resample_disabled) { - spa_pod_builder_add(builder, + if (!impl->props.resample_disabled) { + spa_pod_builder_add(b, SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( rate, 1, INT32_MAX), 0); } - spa_pod_builder_add(builder, + spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( DEFAULT_CHANNELS, 1, SPA_AUDIO_MAX_CHANNELS), 0); - *param = spa_pod_builder_pop(builder, &f[0]); + *param = spa_pod_builder_pop(b, &f[0]); } break; default: @@ -2555,6 +2579,138 @@ static int port_enum_formats(void *object, return 1; } +static int port_param_format(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + if (!port->have_format) + return -EIO; + if (index > 0) + return 0; + + if (port->is_dsp) + *param = spa_format_audio_dsp_build(b, id, &port->format.info.dsp); + else if (port->is_control) + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, id, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_Int( + (1u<format.info.raw); + + return 1; +} + +static int port_param_buffers(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + uint32_t size; + + if (!port->have_format) + return -EIO; + if (index > 0) + return 0; + + size = impl->quantum_limit; + + if (!port->is_dsp) { + uint32_t irate, orate; + struct dir *dir = &impl->dir[port->direction]; + + /* Convert ports are scaled so that they can always + * provide one quantum of data. irate is the rate of the + * data before it goes into the resampler. */ + irate = dir->format.info.raw.rate; + /* scale the size for adaptive resampling */ + size += size/2; + + /* collect the other port rate. This is the output of the resampler + * and is usually one quantum. */ + dir = &impl->dir[SPA_DIRECTION_REVERSE(port->direction)]; + if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) + orate = impl->io_position ? impl->io_position->clock.target_rate.denom : DEFAULT_RATE; + else + orate = dir->format.info.raw.rate; + + /* scale the buffer size when we can. Only do this when we downsample because + * then we need to ask more input data for one quantum. */ + if (irate != 0 && orate != 0 && irate > orate) + size = SPA_SCALE32_UP(size, irate, orate); + } + + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + size * port->stride, + 16 * port->stride, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + + return 1; +} + +static int port_param_meta(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + return 1; +} +static int port_param_io(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + default: + return 0; + } + return 1; +} + +static int port_param_latency(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + *param = spa_latency_build(b, id, &port->latency[index]); + break; + default: + return 0; + } + return 1; +} + +static int port_param_tag(struct impl *impl, struct port *port, uint32_t id, + uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) +{ + switch (index) { + case 0 ... 1: + if (port->is_monitor) + index = index ^ 1; + *param = impl->dir[index].tag; + break; + default: + return 0; + } + return 1; +} + static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -2587,133 +2743,36 @@ impl_node_port_enum_params(void *object, int seq, spa_pod_builder_init(&b, buffer, sizeof(buffer)); + param = NULL; switch (id) { case SPA_PARAM_EnumFormat: - if ((res = port_enum_formats(object, direction, port_id, result.index, ¶m, &b)) <= 0) - return res; + res = port_param_enum_formats(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - if (PORT_IS_DSP(this, direction, port_id)) - param = spa_format_audio_dsp_build(&b, id, &port->format.info.dsp); - else if (PORT_IS_CONTROL(this, direction, port_id)) - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Format, id, - SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_Int( - (1u<format.info.raw); + res = port_param_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Buffers: - { - uint32_t size; - - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - size = this->quantum_limit; - - if (!PORT_IS_DSP(this, direction, port_id)) { - uint32_t irate, orate; - struct dir *dir = &this->dir[direction]; - - /* Convert ports are scaled so that they can always - * provide one quantum of data. irate is the rate of the - * data before it goes into the resampler. */ - irate = dir->format.info.raw.rate; - /* scale the size for adaptive resampling */ - size += size/2; - - /* collect the other port rate. This is the output of the resampler - * and is usually one quantum. */ - dir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; - if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) - orate = this->io_position ? this->io_position->clock.target_rate.denom : DEFAULT_RATE; - else - orate = dir->format.info.raw.rate; - - /* scale the buffer size when we can. Only do this when we downsample because - * then we need to ask more input data for one quantum. */ - if (irate != 0 && orate != 0 && irate > orate) - size = SPA_SCALE32_UP(size, irate, orate); - } - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - size * port->stride, - 16 * port->stride, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); + res = port_param_buffers(this, port, id, result.index, ¶m, &b); break; - } case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } + res = port_param_meta(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - default: - return 0; - } + res = port_param_io(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Latency: - switch (result.index) { - case 0: case 1: - { - uint32_t idx = result.index; - param = spa_latency_build(&b, id, &port->latency[idx]); - break; - } - default: - return 0; - } + res = port_param_latency(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Tag: - switch (result.index) { - case 0: case 1: - { - uint32_t idx = result.index; - if (port->is_monitor) - idx = idx ^ 1; - param = this->dir[idx].tag; - if (param == NULL) - goto next; - break; - } - default: - return 0; - } + res = port_param_tag(this, port, id, result.index, ¶m, &b); break; default: return -ENOENT; } + if (res <= 0) + return res; - if (spa_pod_filter(&b, &result.param, param, filter) < 0) + if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); @@ -2799,7 +2858,6 @@ static int port_set_latency(void *object, oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; - emit_port_info(this, oport, false); } } else { spa_latency_info_combine_start(&info, other); @@ -2827,14 +2885,12 @@ static int port_set_latency(void *object, oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; - emit_port_info(this, oport, false); } } } if (emit) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; - emit_port_info(this, port, false); } return 0; } @@ -2872,12 +2928,10 @@ static int port_set_tag(void *object, oport = GET_PORT(this, other, i); oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Tag].user++; - emit_port_info(this, oport, false); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Tag].user++; - emit_port_info(this, port, false); return 0; } @@ -2980,12 +3034,9 @@ static int port_set_format(void *object, port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } - emit_port_info(this, port, false); - return 0; } - static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, @@ -2993,6 +3044,7 @@ impl_node_port_set_param(void *object, const struct spa_pod *param) { struct impl *this = object; + int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -3003,14 +3055,19 @@ impl_node_port_set_param(void *object, switch (id) { case SPA_PARAM_Latency: - return port_set_latency(this, direction, port_id, flags, param); + res = port_set_latency(this, direction, port_id, flags, param); + break; case SPA_PARAM_Tag: - return port_set_tag(this, direction, port_id, flags, param); + res = port_set_tag(this, direction, port_id, flags, param); + break; case SPA_PARAM_Format: - return port_set_format(this, direction, port_id, flags, param); + res = port_set_format(this, direction, port_id, flags, param); + break; default: return -ENOENT; } + emit_info(this, false); + return res; } static inline void queue_buffer(struct impl *this, struct port *port, uint32_t id) From 2420c3a8c87ad880ca9c482ad3820363292ddb36 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 12 May 2025 10:31:01 +0200 Subject: [PATCH 0251/1014] convert: refactor node_set_param functions Move them to their own function to make the main function shorter. Also make sure we emit the new ports first and then the node info. --- spa/plugins/audioconvert/audioconvert.c | 145 ++++++++++-------- .../videoconvert/videoconvert-ffmpeg.c | 87 ++++++----- 2 files changed, 130 insertions(+), 102 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index c227ef1e1..422c6f1a6 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1828,6 +1828,8 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m i = dir->n_ports++; init_port(this, direction, i, 0, false, false, true); } + /* emit all port changes */ + emit_info(this, false); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; @@ -1836,88 +1838,99 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m return 0; } -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) +static int node_set_param_port_config(struct impl *this, uint32_t flags, + const struct spa_pod *param) { - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); + struct spa_audio_info info = { 0, }, *infop = NULL; + struct spa_pod *format = NULL; + enum spa_direction direction; + enum spa_param_port_config_mode mode; + bool monitor = false, control = false; + int res; if (param == NULL) return 0; - switch (id) { - case SPA_PARAM_PortConfig: - { - struct spa_audio_info info = { 0, }, *infop = NULL; - struct spa_pod *format = NULL; - enum spa_direction direction; - enum spa_param_port_config_mode mode; - bool monitor = false, control = false; - int res; + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; - if (spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamPortConfig, NULL, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), - SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + if (format) { + if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) return -EINVAL; - if (format) { - if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) - return -EINVAL; - - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return res; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) - return -EINVAL; - - infop = &info; - } - - if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; - break; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if (info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + + infop = &info; } + return reconfigure_mode(this, mode, direction, monitor, control, infop); +} + +static int node_set_param_props(struct impl *this, uint32_t flags, + const struct spa_pod *param) +{ + bool have_graph = false; + struct filter_graph *g, *t; + + if (param == NULL) + return 0; + + this->filter_props_count = 0; + + spa_list_for_each_safe(g, t, &this->active_graphs, link) { + if (g->removing) + continue; + + have_graph = true; + this->in_filter_props++; + spa_filter_graph_set_props(g->graph, SPA_DIRECTION_INPUT, param); + this->filter_props_count++; + this->in_filter_props--; + } + if (!have_graph) + apply_props(this, param); + + clean_filter_handles(this, false); + return 0; +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_PortConfig: + res = node_set_param_port_config(this, flags, param); + break; case SPA_PARAM_Props: - { - bool have_graph = false; - struct filter_graph *g, *t; - this->filter_props_count = 0; - - spa_list_for_each_safe(g, t, &this->active_graphs, link) { - if (g->removing) - continue; - - have_graph = true; - this->in_filter_props++; - spa_filter_graph_set_props(g->graph, - SPA_DIRECTION_INPUT, param); - this->filter_props_count++; - this->in_filter_props--; - } - if (!have_graph) - apply_props(this, param); - - clean_filter_handles(this, false); + res = node_set_param_props(this, flags, param); break; - } default: return -ENOENT; } emit_info(this, false); - return 0; + return res; } static int int32_cmp(const void *v1, const void *v2) diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index bdf4ba589..fa4e63849 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -472,10 +472,10 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); @@ -788,6 +788,9 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m init_port(this, direction, i, false, false, true); } + /* emit all port info first, then the node props and flags */ + emit_info(this, false); + this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_PortConfig].user++; @@ -795,57 +798,69 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m return 0; } -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) +static int node_set_param_port_config(struct impl *this, uint32_t flags, + const struct spa_pod *param) { - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); + struct spa_video_info info = { 0, }, *infop = NULL; + struct spa_pod *format = NULL; + enum spa_direction direction; + enum spa_param_port_config_mode mode; + bool monitor = false, control = false; + int res; if (param == NULL) return 0; - switch (id) { - case SPA_PARAM_PortConfig: - { - struct spa_video_info info = { 0, }, *infop = NULL; - struct spa_pod *format = NULL; - enum spa_direction direction; - enum spa_param_port_config_mode mode; - bool monitor = false, control = false; - int res; + if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_ParamPortConfig, NULL, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), + SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + return -EINVAL; - if (spa_pod_parse_object(param, - SPA_TYPE_OBJECT_ParamPortConfig, NULL, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), - SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), - SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), - SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) + if (format) { + if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) return -EINVAL; - if (format) { - if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) - return -EINVAL; - - if ((res = spa_format_video_parse(format, &info)) < 0) - return res; - - infop = &info; - } - - if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) + if ((res = spa_format_video_parse(format, &info)) < 0) return res; - break; + + infop = &info; } + return reconfigure_mode(this, mode, direction, monitor, control, infop); +} + +static int node_set_param_props(struct impl *this, uint32_t flags, + const struct spa_pod *param) +{ + if (param == NULL) + return 0; + + apply_props(this, param); + return 0; +} +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_PortConfig: + res = node_set_param_port_config(this, flags, param); + break; case SPA_PARAM_Props: - apply_props(this, param); + res = node_set_param_props(this, flags, param); break; default: return -ENOENT; } emit_info(this, false); - return 0; + return res; } static inline void free_decoder(struct impl *this) From 09a5b7ee35db9dc61fa23d56e99631b31f34aeeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 10 May 2025 17:08:08 +0200 Subject: [PATCH 0252/1014] ci: do not install `libv4l` `libv4l` has not been necessary for a long time, so drop it. --- .gitlab-ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 118b06887..07a91cc01 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-04-06.1' + FDO_DISTRIBUTION_TAG: '2025-05-10.0' FDO_DISTRIBUTION_VERSION: '41' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -64,7 +64,6 @@ include: libubsan libusb1-devel lilv-devel - libv4l-devel libva-devel libX11-devel ModemManager-devel @@ -101,7 +100,7 @@ include: .ubuntu: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-03-25.0' + FDO_DISTRIBUTION_TAG: '2025-05-10.0' FDO_DISTRIBUTION_VERSION: '22.04' FDO_DISTRIBUTION_PACKAGES: >- debhelper-compat @@ -122,7 +121,6 @@ include: libsnapd-glib-dev libudev-dev libva-dev - libv4l-dev libx11-dev meson ninja-build From 1445843cedcf11e22b38391f3741e7cdb5a0e3e6 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 11 May 2025 13:45:34 +0300 Subject: [PATCH 0253/1014] modules: get also instance id for flatpak apps Add "pipewire.access.portal.instance_id" property for distinguishing Flatpak application instances from each other. --- src/modules/flatpak-utils.h | 18 ++++++++++++------ src/modules/module-access.c | 7 +++++-- src/modules/module-protocol-pulse/server.c | 6 ++++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/modules/flatpak-utils.h b/src/modules/flatpak-utils.h index 9b839e210..76b14491c 100644 --- a/src/modules/flatpak-utils.h +++ b/src/modules/flatpak-utils.h @@ -16,8 +16,6 @@ #include #endif -#include "config.h" - #ifdef HAVE_GLIB2 #include #endif @@ -26,7 +24,7 @@ #include #include -static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char **app_id, char **devices) +static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char **app_id, char **instance_id, char **devices) { #ifdef HAVE_GLIB2 /* @@ -53,13 +51,19 @@ static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char ** g_free(s); } + if (instance_id) { + s = g_key_file_get_value(metadata, "Instance", "instance-id", NULL); + *instance_id = s ? strdup(s) : NULL; + g_free(s); + } + return 0; #else return -ENOTSUP; #endif } -static int pw_check_flatpak(pid_t pid, char **app_id, char **devices) +static int pw_check_flatpak(pid_t pid, char **app_id, char **instance_id, char **devices) { #if defined(__linux__) char root_path[2048]; @@ -68,6 +72,8 @@ static int pw_check_flatpak(pid_t pid, char **app_id, char **devices) if (app_id) *app_id = NULL; + if (instance_id) + *instance_id = NULL; if (devices) *devices = NULL; @@ -107,14 +113,14 @@ static int pw_check_flatpak(pid_t pid, char **app_id, char **devices) if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { /* Some weird fd => failure, assume sandboxed */ pw_log_error("error fstat .flatpak-info: %m"); - } else if (app_id || devices) { + } else if (app_id || instance_id || devices) { /* Parse the application ID if needed */ const size_t size = stat_buf.st_size; if (size > 0) { void *buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, info_fd, 0); if (buf != MAP_FAILED) { - res = pw_check_flatpak_parse_metadata(buf, size, app_id, devices); + res = pw_check_flatpak_parse_metadata(buf, size, app_id, instance_id, devices); munmap(buf, size); } else { res = -errno; diff --git a/src/modules/module-access.c b/src/modules/module-access.c index 2e246ae91..05bc14483 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -167,12 +167,13 @@ context_check_access(void *data, struct pw_impl_client *client) { struct impl *impl = data; struct pw_permission permissions[1]; - struct spa_dict_item items[3]; + struct spa_dict_item items[4]; const struct pw_properties *props; const char *str; const char *access; const char *socket; spa_autofree char *flatpak_app_id = NULL; + spa_autofree char *flatpak_instance_id = NULL; int nitems = 0; bool sandbox_flatpak; int pid, res; @@ -197,7 +198,7 @@ context_check_access(void *data, struct pw_impl_client *client) } else { pw_log_info("client %p has trusted pid %d", client, pid); - res = pw_check_flatpak(pid, &flatpak_app_id, NULL); + res = pw_check_flatpak(pid, &flatpak_app_id, &flatpak_instance_id, NULL); if (res != 0) { if (res < 0) pw_log_warn("%p: client %p flatpak check failed: %s", @@ -233,6 +234,8 @@ context_check_access(void *data, struct pw_impl_client *client) if (sandbox_flatpak) { items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.app_id", flatpak_app_id); + items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.instance_id", + flatpak_instance_id); items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.sec.flatpak", "true"); } diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index fe7d47ab0..e1ffafffa 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -430,7 +430,7 @@ on_connect(void *data, int fd, uint32_t mask) client_access = server->client_access; if (server->addr.ss_family == AF_UNIX) { - spa_autofree char *app_id = NULL, *snap_app_id = NULL, *devices = NULL; + spa_autofree char *app_id = NULL, *snap_app_id = NULL, *devices = NULL, *instance_id = NULL; #ifdef HAVE_SNAP pw_sandbox_access_t snap_access; #endif @@ -441,7 +441,7 @@ on_connect(void *data, int fd, uint32_t mask) pw_log_warn("setsockopt(SO_PRIORITY) failed: %m"); #endif pid = get_client_pid(client, client_fd); - if (pid != 0 && pw_check_flatpak(pid, &app_id, &devices) == 1) { + if (pid != 0 && pw_check_flatpak(pid, &app_id, &instance_id, &devices) == 1) { /* * XXX: we should really use Portal client access here * @@ -464,6 +464,8 @@ on_connect(void *data, int fd, uint32_t mask) client_access = "flatpak"; pw_properties_set(client->props, "pipewire.access.portal.app_id", app_id); + pw_properties_set(client->props, "pipewire.access.portal.instance_id", + instance_id); if (devices && (spa_streq(devices, "all") || spa_strstartswith(devices, "all;") || From 4f531368910d04fbdfe8f9bd4d32cc7825c8faa4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 12 May 2025 12:57:11 +0200 Subject: [PATCH 0254/1014] netjack2: reverse send/recv roles in driver/manager The params contain the send/recv streams from the point of view of the manager (and not the driver as was assumed before). This means we need to swap send/recv in the driver, not the manager. This makes things interoperate with JACK/netjack2. See #4666 --- src/modules/module-netjack2-driver.c | 14 +++++++++---- src/modules/module-netjack2-manager.c | 29 +++++++++++---------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index e1f5eb6ca..0aa9a85cd 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -848,6 +848,10 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p pw_log_warn("invalid follower setup"); return -EINVAL; } + /* the params are from the perspective of the manager, so send is our + * receive (source) and recv is our send (sink) */ + SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); + SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); pw_loop_update_io(impl->main_loop, impl->setup_socket, 0); @@ -1005,10 +1009,12 @@ static int send_follower_available(struct impl *impl) snprintf(params.follower_name, sizeof(params.follower_name), "%s", pw_get_host_name()); params.mtu = htonl(impl->mtu); params.transport_sync = htonl(0); - params.send_audio_channels = htonl(impl->sink.wanted_n_audio); - params.recv_audio_channels = htonl(impl->source.wanted_n_audio); - params.send_midi_channels = htonl(impl->sink.wanted_n_midi); - params.recv_midi_channels = htonl(impl->source.wanted_n_midi); + /* send/recv is from the perspective of the manager, so what we send (sink) + * is recv on the manager and vice versa. */ + params.recv_audio_channels = htonl(impl->sink.wanted_n_audio); + params.send_audio_channels = htonl(impl->source.wanted_n_audio); + params.recv_midi_channels = htonl(impl->sink.wanted_n_midi); + params.send_midi_channels = htonl(impl->source.wanted_n_midi); params.sample_encoder = htonl(NJ2_ENCODER_FLOAT); params.follower_sync_mode = htonl(1); params.network_latency = htonl(impl->latency); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 634a42d58..2cef87218 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -1012,29 +1012,28 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param peer->params.sample_encoder = impl->encoding; peer->params.kbps = impl->kbps; - /* params send and recv are from the client point of view and reversed for the - * manager, so when the client sends, we receive in a source etc. We swap the params - * later after we replied to the client. */ + /* params send and recv are from the manager point of view and reversed for the + * driver. So, for us send = sink and recv = source */ if (peer->params.send_audio_channels < 0) - peer->params.send_audio_channels = follower->source.n_audio; + peer->params.send_audio_channels = follower->sink.n_audio; if (peer->params.recv_audio_channels < 0) - peer->params.recv_audio_channels = follower->sink.n_audio; + peer->params.recv_audio_channels = follower->source.n_audio; if (peer->params.send_midi_channels < 0) - peer->params.send_midi_channels = follower->source.n_midi; + peer->params.send_midi_channels = follower->sink.n_midi; if (peer->params.recv_midi_channels < 0) - peer->params.recv_midi_channels = follower->sink.n_midi; + peer->params.recv_midi_channels = follower->source.n_midi; - follower->source.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; + follower->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; follower->source.info.rate = peer->params.sample_rate; - if ((uint32_t)peer->params.send_audio_channels != follower->source.info.channels) { - follower->source.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { + follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); for (i = 0; i < follower->source.info.channels; i++) follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } - follower->sink.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; + follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.info.rate = peer->params.sample_rate; - if ((uint32_t)peer->params.recv_audio_channels != follower->sink.info.channels) { - follower->sink.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { + follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); for (i = 0; i < follower->sink.info.channels; i++) follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1125,10 +1124,6 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param nj2_dump_session_params(params); send(follower->socket->fd, params, sizeof(*params), 0); - /* now swap send and recv to make it match our point of view */ - SPA_SWAP(peer->params.send_audio_channels, peer->params.recv_audio_channels); - SPA_SWAP(peer->params.send_midi_channels, peer->params.recv_midi_channels); - return 0; create_failed: From b47d28bad65fbb8d47ae3f9b007106484db5a069 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 12 May 2025 17:23:35 +0200 Subject: [PATCH 0255/1014] profiler: use doubles in profiler.log Don't truncate the measurements when we convert to usec but keep the fractional part to get a more accurate graph. --- src/tools/pw-profiler.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index a73cc7ec7..38e34b68d 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -307,24 +307,24 @@ static int process_follower_clock(struct data *d, const struct spa_pod *pod, str static void dump_point(struct data *d, struct point *point) { int i; - int64_t d1, d2; - int64_t delay, period_usecs; + double d1, d2; + double delay, period_usecs; -#define CLOCK_AS_USEC(cl,val) (int64_t)(val * (float)SPA_USEC_PER_SEC / (cl)->rate.denom) -#define CLOCK_AS_SUSEC(cl,val) (int64_t)(val * (float)SPA_USEC_PER_SEC / ((cl)->rate.denom * (cl)->rate_diff)) +#define CLOCK_AS_USEC(cl,val) (double)(val * (double)SPA_USEC_PER_SEC / (cl)->rate.denom) +#define CLOCK_AS_SUSEC(cl,val) (double)(val * (double)SPA_USEC_PER_SEC / ((cl)->rate.denom * (cl)->rate_diff)) delay = CLOCK_AS_USEC(&point->clock, point->clock.delay); period_usecs = CLOCK_AS_SUSEC(&point->clock, point->clock.duration); - d1 = (point->driver.signal - point->driver.prev_signal) / 1000; - d2 = (point->driver.finish - point->driver.signal) / 1000; + d1 = (point->driver.signal - point->driver.prev_signal) / 1000.0; + d2 = (point->driver.finish - point->driver.signal) / 1000.0; if (d1 > period_usecs * 1.3 || d2 > period_usecs * 1.3) - d1 = d2 = (int64_t)(period_usecs * 1.4); + d1 = d2 = (double)(period_usecs * 1.4); /* 4 columns for the driver */ - fprintf(d->output, "%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t", + fprintf(d->output, "%.3f\t%.3f\t%.3f\t%.3f\t", d1 > 0 ? d1 : 0, d2 > 0 ? d2 : 0, delay, period_usecs); for (i = 0; i < MAX_FOLLOWERS; i++) { @@ -332,11 +332,11 @@ static void dump_point(struct data *d, struct point *point) if (point->follower[i].status == 0) { fprintf(d->output, " \t \t \t \t \t \t \t \t"); } else { - int64_t d4 = (point->follower[i].signal - point->driver.signal) / 1000; - int64_t d5 = (point->follower[i].awake - point->driver.signal) / 1000; - int64_t d6 = (point->follower[i].finish - point->driver.signal) / 1000; + double d4 = (point->follower[i].signal - point->driver.signal) / 1000.0; + double d5 = (point->follower[i].awake - point->driver.signal) / 1000.0; + double d6 = (point->follower[i].finish - point->driver.signal) / 1000.0; - fprintf(d->output, "%u\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%d\t0\t", + fprintf(d->output, "%u\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%d\t0\t", d->followers[i].id, d4 > 0 ? d4 : 0, d5 > 0 ? d5 : 0, From 882542e0011eeccd0cda386396ce0bcc33be3d16 Mon Sep 17 00:00:00 2001 From: Taruntej Kanakamalla Date: Mon, 12 May 2025 17:06:01 +0530 Subject: [PATCH 0256/1014] gst: sink: minor type fix --- src/gst/gstpipewiresink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index 9a9efde8c..c7ac851ae 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -722,8 +722,8 @@ static void update_time (GstPipeWireSink *pwsink) struct spa_io_position *p = pwsink->stream->io_position; double err = 0.0, corr = 1.0; guint64 now; - double_t max_err = pwsink->rate * MAX_ERROR_MS/1000; - double_t resync_timeout = pwsink->rate * RESYNC_TIMEOUT_MS/1000; + double max_err = pwsink->rate * MAX_ERROR_MS/1000.0; + double resync_timeout = pwsink->rate * RESYNC_TIMEOUT_MS/1000.0; if (pwsink->first_buffer) { // use the target duration before the first buffer From d599936925e8222ea0a7ae68c32ada0e44541e96 Mon Sep 17 00:00:00 2001 From: dartvader316 Date: Mon, 12 May 2025 14:14:11 +0300 Subject: [PATCH 0257/1014] pw-dump: add raw json elements output option --- src/tools/pw-dump.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index 179e0c673..7deb37d19 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -31,6 +31,7 @@ #define INDENT 2 static bool colors = false; +static bool raw = false; #define NORMAL (colors ? SPA_ANSI_RESET : "") #define LITERAL (colors ? SPA_ANSI_BRIGHT_MAGENTA : "") @@ -221,7 +222,7 @@ static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const put_key(d, key); fprintf(d->out, "%s%s%*s", d->state & STATE_COMMA ? "," : "", - d->state & (STATE_MASK | STATE_KEY) ? " " : d->state & STATE_FIRST ? "" : "\n", + d->state & (STATE_MASK | STATE_KEY) ? " " : (d->state & STATE_FIRST) | raw ? "" : "\n", d->state & (STATE_MASK | STATE_KEY) ? 0 : d->level, ""); va_start(va, fmt); vfprintf(d->out, fmt, va); @@ -1512,7 +1513,8 @@ static void show_help(struct data *data, const char *name, bool error) " -r, --remote Remote daemon name\n" " -m, --monitor monitor changes\n" " -N, --no-colors disable color output\n" - " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n", + " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" + " -R, --raw force raw output\n", name); } @@ -1529,6 +1531,7 @@ int main(int argc, char *argv[]) { "monitor", no_argument, NULL, 'm' }, { "no-colors", no_argument, NULL, 'N' }, { "color", optional_argument, NULL, 'C' }, + { "raw", no_argument, NULL, 'R' }, { NULL, 0, NULL, 0} }; int c; @@ -1537,11 +1540,13 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); data.out = stdout; + if (!isatty(fileno(data.out))) + raw = true; if (getenv("NO_COLOR") == NULL && isatty(fileno(data.out))) colors = true; setlinebuf(data.out); - while ((c = getopt_long(argc, argv, "hVr:mNC", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:mNCR", long_options, NULL)) != -1) { switch (c) { case 'h' : show_help(&data, argv[0], false); @@ -1563,6 +1568,9 @@ int main(int argc, char *argv[]) case 'N' : colors = false; break; + case 'R' : + raw = true; + break; case 'C' : if (optarg == NULL || !strcmp(optarg, "auto")) break; /* nothing to do, tty detection was done From 1f16f949ec705667086b2f8775ac1098fed7f6b3 Mon Sep 17 00:00:00 2001 From: Kirill Date: Mon, 12 May 2025 12:22:36 +0000 Subject: [PATCH 0258/1014] Apply 1 suggestion(s) to 1 file(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Barnabás Pőcze --- src/tools/pw-dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index 7deb37d19..dd1e2cecb 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -222,7 +222,7 @@ static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const put_key(d, key); fprintf(d->out, "%s%s%*s", d->state & STATE_COMMA ? "," : "", - d->state & (STATE_MASK | STATE_KEY) ? " " : (d->state & STATE_FIRST) | raw ? "" : "\n", + d->state & (STATE_MASK | STATE_KEY) ? " " : (d->state & STATE_FIRST) || raw ? "" : "\n", d->state & (STATE_MASK | STATE_KEY) ? 0 : d->level, ""); va_start(va, fmt); vfprintf(d->out, fmt, va); From 08efc1171c975399f6f0f86b316a02d371e6929e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 13 May 2025 10:37:00 +0200 Subject: [PATCH 0259/1014] pw-dump: disable indent in raw mode as well --- src/tools/pw-dump.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index dd1e2cecb..fb891c45a 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -57,6 +57,7 @@ struct data { FILE *out; int level; + int indent; #define STATE_KEY (1<<0) #define STATE_COMMA (1<<1) #define STATE_FIRST (1<<2) @@ -242,13 +243,13 @@ static void put_key(struct data *d, const char *key) static void put_begin(struct data *d, const char *key, const char *type, uint32_t flags) { put_fmt(d, key, "%s", type); - d->level += INDENT; + d->level += d->indent; d->state = (d->state & STATE_MASK) + (flags & STATE_SIMPLE); } static void put_end(struct data *d, const char *type, uint32_t flags) { - d->level -= INDENT; + d->level -= d->indent; d->state = d->state & STATE_MASK; put_fmt(d, NULL, "%s", type); d->state = (d->state & STATE_MASK) + STATE_COMMA - (flags & STATE_SIMPLE); @@ -1590,6 +1591,7 @@ int main(int argc, char *argv[]) return -1; } } + data.indent = raw ? 0 : INDENT; if (optind < argc) data.pattern = argv[optind++]; From 6b3681938b09aef915815fc91409b7d42245bee8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 13 May 2025 14:59:51 +0200 Subject: [PATCH 0260/1014] pw-dump: don't do raw mode when streaming pw-dump | less -RS is a common thing and raw mode is not helpful. --- src/tools/pw-dump.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index fb891c45a..bf6d5c3a4 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -1541,8 +1541,6 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); data.out = stdout; - if (!isatty(fileno(data.out))) - raw = true; if (getenv("NO_COLOR") == NULL && isatty(fileno(data.out))) colors = true; setlinebuf(data.out); From b71216af705658e1e11d011f2f88209a9ed04dbf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 13 May 2025 15:01:00 +0200 Subject: [PATCH 0261/1014] doc: add document about latency handling --- doc/dox/internals/index.dox | 1 + doc/dox/internals/latency.dox | 233 ++++++++++++++++++++++++++++++++++ doc/meson.build | 1 + 3 files changed, 235 insertions(+) create mode 100644 doc/dox/internals/latency.dox diff --git a/doc/dox/internals/index.dox b/doc/dox/internals/index.dox index f7152d61c..84e0ce295 100644 --- a/doc/dox/internals/index.dox +++ b/doc/dox/internals/index.dox @@ -11,6 +11,7 @@ - \subpage page_library - \subpage page_dma_buf - \subpage page_scheduling +- \subpage page_latency - \subpage page_native_protocol diff --git a/doc/dox/internals/latency.dox b/doc/dox/internals/latency.dox new file mode 100644 index 000000000..4aec05213 --- /dev/null +++ b/doc/dox/internals/latency.dox @@ -0,0 +1,233 @@ +/** \page page_latency Latency support + +This document explains how the latency in the PipeWire graph is implemented. + +# Use Cases + +## A node port has a latency + +Applications need to be able to query the latency of a port. + +Linked Nodes need to be informed of the latency of a port. + +## dynamically update port latencies + +It needs to be possible to dynamically update the latency of a port and this +should inform all linked ports/nodes of the updated latency. + +## Linked nodes add latency to the signal + +When two nodes/ports have a latency, the signal is delayed by the sum of +the nodes latencies. + +## Calculate the signal delay upstream and downstream + +A node might need to know how much a signal was delayed since it arrived +in the node. + +A node might need to know how much the signal will be delayed before it +exists the graph. + +## Detect latency mismatch + +When a signal travels through 2 different parts of the graph with different +latencies and then eventually join, there is a latency mismatch. It should +be possible to detect this mismatch. + + +# Concepts + +## Port Latency + +The fundamental object for implementing latency reporting in PipeWire is the +Latency object. + +It consists of a direction (input/output) and min/max latency values. The latency +values can express a latency relative to the graph quantum, the samplerate or in +nanoseconds. There is a mininum and maximum latency value in the Latency object. + +The direction of the latency object determines how the latency object propagates. + +- SPA_DIRECTION_OUTPUT Latency objects move from output ports downstream and contain + the latency from all nodes upstream. An output latency received on an input port + should instruct the node to update the output latency on its output ports related + to this input port. This corresponds to the JackCaptureLatency. + +- SPA_DIRECTION_INPUT Latency objects move from input ports upstream and contain + the latency from all nodes downstream. An input latency received on an output port + should instruct the node to update the input latency on its input ports related + to this output port. This corresponds to the JackPlaybackLatency. + +PipeWire will automatically propagate Latency objects from ports to all linked ports +in the graph. Output Latency objects on output ports are propagated to linked input +ports and input Latency objects on input ports are propagated to linked output ports. + +If a port has links with multiple other ports, the Latency objects are merged by +taking the min of the min values and the max of the max values of all latency objects +on the other ports. + +This way, Output Latency always describes the aggragated total upstream latency of +signal up to the port and Input latency describes the aggregated downstream latency +of the signal from the port. + + +## Node ProcessLatency + +This is a per node property and applies to the latency introduced by the node +logic itself. + +This mostly works if (almost) all data processing ports (input/output) participate in +the same data processing with the same latency, which is a common use case. + +ProcessLatency is mostly used to easily calculate Output and Input Latency on ports. +We can simply add the ProcessLatency to all latency values in the ports Latency objects +to obtain the corresponding Latency object for the other ports. + + +# Latency updates + +Latency params on the ports can be updated as needed. This can happen because some upstream or +downstream latency changed or when the ProcessLatency of a node changes. + +When the ProcessLatency changes, the procedure to notify of latency changes is: + +- Take output latency on input port, add new ProcessLatency, set new latency on output + port. This propagates the new latency downstream. + +- Take input latency on output port, add new ProcessLatency, set new latency on input + port. This propagates the new latency upstream. + +PipeWire will automatically aggregate latency from links and propagate the new latencies +down and upstream. + +# Examples + +## A source node with a given ProcessLatency + +When we have a source with a ProcessLatency, for example, of 1024 samples: + + +----------+ + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + | FL ---+ Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] + | source + + | FR ---+ Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + +----------+ + Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] + ^ + | + ProcessLatency: [ { "quantum": 0, "rate": 1024, "ns": 0 } ] + +Both output ports have an output latency of 1024 samples and no input latency. + +## A sink node with a given ProcessLatency + +When we have a sink with a ProcessLatency, for example, of 512 samples: + + Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] + Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + ^ + | +----------+ + +---- FL | + | sink | <- ProcessLatency: [ { "quantum": 0, "rate": 1024, "ns": 0 } ] + +---- FR | + | +----------+ + v + Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] + Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + +Both input ports have an input latency of 512 samples and no output latency. + +## A source and sink node linked together + +With the source and sink from above, if we link the FL channels, the input latency +from the input port of the sink is propagated to the output port of the source +and the output latency of the output port is propagated to the input port of the +sink: + + + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + ^ + | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + | | + +----------+v v+--------+ + | FL ------------ FL | + | source + | sink | + | FR --+ FR | + +----------+ | +--------+ + v + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] + + +## Insert a latency node + +If we place a node with a 256 sample latency in the above source-sink graph: + + + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 768 } ] + ^ + | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + | Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 768 } ] + | ^ + | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] + | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + | | ^ + | | | + +----------+v v+--------+v +-------+ + | FL ------------ FL FL --------- FL | + | source + | node | ^ | sink | + | . | . | . | + +----------+ +--------+ | +-------+ + v + Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] + Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + + +See how the output latency propagates and is incremented going downstream and the +input latency is incremented and traveling upstream. + + +## Link a port to two port with different latencies + +When we introduce a sink2 with different input latency and link this to +the node FL output port, it will aggregate the two input latencies by +taking the min of min and max of max. + + + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 2304 } ] + ^ + | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] + | Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 2304 } ] + | ^ + | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] + | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 2048 } ] + | | ^ + | | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] + | | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] + | | | ^ + | | | | + +----------+v v+--------+v v +-------+ + | FL ------------ FL FL --------- FL | + | source + | node | \ | sink | + | . | . \ . | + +----------+ +--------+ \ +-------+ + \ + \ +-------+ + +--- FL | + ^ | sink2 | + | . . + | +-------+ + v + Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] + Latency: [{ "direction": "input", "min-rate": 2048, "max-rate": 2048 } ] + + +The source node now knows that its output signal will be delayed between 768 amd 2304 samples +depending on the path in the graph. + +We also see that node.FL has different min/max-rate input latencies. This information can be +used to insert a delay node to align the latencies again. For example, if we delay the signal +between node.FL and FL.sink with 1536 samples, the latencies will be aligned again. + diff --git a/doc/meson.build b/doc/meson.build index 5f2e28fb8..2e221fb06 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -58,6 +58,7 @@ extra_docs = [ 'dox/internals/index.dox', 'dox/internals/design.dox', 'dox/internals/access.dox', + 'dox/internals/latency.dox', 'dox/internals/midi.dox', 'dox/internals/portal.dox', 'dox/internals/daemon.dox', From ca032152b1d3bc776ad370f89402dd85d8be9c94 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 13 May 2025 15:17:56 +0200 Subject: [PATCH 0262/1014] doc: improve formatting --- doc/dox/internals/latency.dox | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/dox/internals/latency.dox b/doc/dox/internals/latency.dox index 4aec05213..af3b5acf9 100644 --- a/doc/dox/internals/latency.dox +++ b/doc/dox/internals/latency.dox @@ -106,6 +106,7 @@ down and upstream. When we have a source with a ProcessLatency, for example, of 1024 samples: +``` +----------+ + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] | FL ---+ Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] | source + @@ -114,6 +115,7 @@ When we have a source with a ProcessLatency, for example, of 1024 samples: ^ | ProcessLatency: [ { "quantum": 0, "rate": 1024, "ns": 0 } ] +``` Both output ports have an output latency of 1024 samples and no input latency. @@ -121,6 +123,7 @@ Both output ports have an output latency of 1024 samples and no input latency. When we have a sink with a ProcessLatency, for example, of 512 samples: +``` Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ^ @@ -132,6 +135,7 @@ When we have a sink with a ProcessLatency, for example, of 512 samples: v Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] +``` Both input ports have an input latency of 512 samples and no output latency. @@ -143,6 +147,7 @@ and the output latency of the output port is propagated to the input port of the sink: +``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ^ @@ -157,6 +162,7 @@ sink: v Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] +``` ## Insert a latency node @@ -164,6 +170,7 @@ sink: If we place a node with a 256 sample latency in the above source-sink graph: +``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 768 } ] ^ @@ -182,6 +189,7 @@ If we place a node with a 256 sample latency in the above source-sink graph: v Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] +``` See how the output latency propagates and is incremented going downstream and the @@ -195,6 +203,7 @@ the node FL output port, it will aggregate the two input latencies by taking the min of min and max of max. +``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 2304 } ] ^ @@ -222,7 +231,7 @@ taking the min of min and max of max. v Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] Latency: [{ "direction": "input", "min-rate": 2048, "max-rate": 2048 } ] - +``` The source node now knows that its output signal will be delayed between 768 amd 2304 samples depending on the path in the graph. @@ -231,3 +240,4 @@ We also see that node.FL has different min/max-rate input latencies. This inform used to insert a delay node to align the latencies again. For example, if we delay the signal between node.FL and FL.sink with 1536 samples, the latencies will be aligned again. +*/ From 37bf571db37d22f76ec9ec7e723dd6366c0ba439 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 13 May 2025 15:27:17 +0200 Subject: [PATCH 0263/1014] audioconvert: keep the graph latency around --- spa/plugins/audioconvert/audioconvert.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 422c6f1a6..20a445f8a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -235,6 +235,7 @@ struct filter_graph { uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; + struct spa_process_latency_info latency; bool removing; bool setup; }; @@ -1029,6 +1030,11 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) else if (spa_streq(k, "outputs.audio.position")) spa_audio_parse_position(s, strlen(s), g->outputs_position, &g->n_outputs); + else if (spa_streq(k, "latency")) { + double latency; + if (spa_atod(s, &latency)) + g->latency.rate = (uint32_t)latency; + } } } From c334bfb0bb69ba7a9b08359026196a18421880cc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 09:19:17 +0200 Subject: [PATCH 0264/1014] audioconvert: do params after init of the node First do the essential properties to set up the node, then set up the node and then parse the params. The params might do some setup that relies on a completely configured node, such as emit events. --- spa/plugins/audioconvert/audioconvert.c | 34 +++++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 20a445f8a..816bbd488 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1331,10 +1331,9 @@ error: return -ENOTSUP; } -static int audioconvert_set_param(struct impl *this, const char *k, const char *s) +static int audioconvert_set_param(struct impl *this, const char *k, const char *s, bool *disable_filter) { int res; - if (spa_streq(k, "monitor.channel-volumes")) this->monitor_channel_volumes = spa_atob(s); else if (spa_streq(k, "channelmix.disable")) @@ -1382,6 +1381,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char * order, spa_strerror(res)); } } + else if (spa_streq(k, "audioconvert.filter-graph.disable")) { + if (!*disable_filter) + *disable_filter = spa_atob(s); + } else return 0; return 1; @@ -1392,6 +1395,7 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) struct spa_pod_parser prs; struct spa_pod_frame f; int changed = 0; + bool filter_graph_disabled = this->props.filter_graph_disabled; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) @@ -1429,9 +1433,10 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) continue; spa_log_info(this->log, "key:'%s' val:'%s'", name, value); - changed += audioconvert_set_param(this, name, value); + changed += audioconvert_set_param(this, name, value, &filter_graph_disabled); } if (changed) { + this->props.filter_graph_disabled = filter_graph_disabled; channelmix_init(&this->mix); } return changed; @@ -4180,8 +4185,7 @@ impl_init(const struct spa_handle_factory *factory, { struct impl *this; uint32_t i; - const char *str; - bool filter_graph_disabled; + bool filter_graph_disabled = false; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -4223,13 +4227,12 @@ impl_init(const struct spa_handle_factory *factory, this->mix.rear_delay = 0.0f; this->mix.widen = 0.0f; - if (info && (str = spa_dict_lookup(info, "clock.quantum-limit")) != NULL) - spa_atou32(str, &this->quantum_limit, 0); - for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; - if (spa_streq(k, "resample.peaks")) + if (spa_streq(k, "clock.quantum-limit")) + spa_atou32(s, &this->quantum_limit, 0); + else if (spa_streq(k, "resample.peaks")) this->resample_peaks = spa_atob(s); else if (spa_streq(k, "resample.prefill")) SPA_FLAG_UPDATE(this->resample.options, @@ -4251,12 +4254,7 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); else if (spa_streq(k, "monitor.passthrough")) this->monitor_passthrough = spa_atob(s); - else if (spa_streq(k, "audioconvert.filter-graph.disable")) - filter_graph_disabled = spa_atob(s); - else - audioconvert_set_param(this, k, s); } - this->props.filter_graph_disabled = filter_graph_disabled; this->props.channel.n_volumes = this->props.n_channels; this->props.soft.n_volumes = this->props.n_channels; this->props.monitor.n_volumes = this->props.n_channels; @@ -4294,6 +4292,14 @@ impl_init(const struct spa_handle_factory *factory, reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, false, NULL); reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, false, NULL); + filter_graph_disabled = this->props.filter_graph_disabled; + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + audioconvert_set_param(this, k, s, &filter_graph_disabled); + } + this->props.filter_graph_disabled = filter_graph_disabled; + return 0; } From 093b3eea21b214f41f56de2a72a107749f540ef1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 09:21:31 +0200 Subject: [PATCH 0265/1014] audioconvert: implement graph latency reporting Keep per graph latency. Sum all the graph latencies together and keep this around as the process-latency. Refactor the port latency setter. Make a function to recalculate the latency of all other ports. Take into account the graph latencies. Update the port latencies when the total graph latency changes. --- spa/plugins/audioconvert/audioconvert.c | 125 +++++++++++++++--------- 1 file changed, 79 insertions(+), 46 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 816bbd488..75f6f01b7 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -235,7 +235,7 @@ struct filter_graph { uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_outputs; uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; - struct spa_process_latency_info latency; + uint32_t latency; bool removing; bool setup; }; @@ -255,6 +255,7 @@ struct impl { struct spa_list free_graphs; struct spa_list active_graphs; struct filter_graph graphs[MAX_GRAPH]; + struct spa_process_latency_info latency; int in_filter_props; int filter_props_count; @@ -1006,6 +1007,75 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } +static void port_update_latency(struct port *port, + const struct spa_latency_info *info, bool valid) +{ + if (spa_latency_info_compare(info, &port->latency[info->direction]) != 0) { + port->latency[info->direction] = *info; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Latency].user++; + } + port->have_latency = valid; +} + +static void recalc_latencies(struct impl *this, enum spa_direction direction) +{ + struct spa_latency_info info; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + struct port *port; + uint32_t i; + bool have_latency = false; + + spa_latency_info_combine_start(&info, other); + for (i = 0; i < this->dir[direction].n_ports; i++) { + port = GET_PORT(this, direction, i); + if ((port->is_monitor) || !port->have_latency) + continue; + spa_log_debug(this->log, "%p: combine %d", this, i); + spa_latency_info_combine(&info, &port->latency[other]); + have_latency = true; + } + spa_latency_info_combine_finish(&info); + + spa_process_latency_info_add(&this->latency, &info); + + spa_log_debug(this->log, "%p: combined %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + for (i = 0; i < this->dir[other].n_ports; i++) { + port = GET_PORT(this, other, i); + if (port->is_monitor) + continue; + port_update_latency(port, &info, have_latency); + } +} + +static void recalc_graph_latency(struct impl *impl) +{ + struct filter_graph *g; + int32_t latency = 0; + + spa_list_for_each(g, &impl->active_graphs, link) + latency += g->latency; + + if (latency != impl->latency.rate) { + impl->latency.rate = latency; + recalc_latencies(impl, SPA_DIRECTION_INPUT); + recalc_latencies(impl, SPA_DIRECTION_OUTPUT); + } +} + +static void update_graph_latency(struct filter_graph *g, uint32_t latency) +{ + if (g->latency != latency) { + g->latency = latency; + recalc_graph_latency(g->impl); + } +} + static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct filter_graph *g = object; @@ -1033,9 +1103,10 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) else if (spa_streq(k, "latency")) { double latency; if (spa_atod(s, &latency)) - g->latency.rate = (uint32_t)latency; + update_graph_latency(g, (uint32_t)latency); } } + emit_info(g->impl, false); } static int apply_props(struct impl *impl, const struct spa_pod *props); @@ -1245,6 +1316,7 @@ static void clean_filter_handles(struct impl *impl, bool force) spa_zero(*g); spa_list_append(&impl->free_graphs, &g->link); } + recalc_graph_latency(impl); } static inline void insert_graph(struct spa_list *graphs, struct filter_graph *pending) @@ -2841,8 +2913,7 @@ static int port_set_latency(void *object, struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; - bool have_latency, emit = false;; - uint32_t i; + bool have_latency;; spa_log_debug(this->log, "%p: set latency direction:%d id:%d %p", this, direction, port_id, latency); @@ -2857,11 +2928,8 @@ static int port_set_latency(void *object, return -EINVAL; have_latency = true; } - emit = spa_latency_info_compare(&info, &port->latency[other]) != 0 || - port->have_latency == have_latency; - port->latency[other] = info; - port->have_latency = have_latency; + port_update_latency(port, &info, have_latency); spa_log_debug(this->log, "%p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", @@ -2877,45 +2945,10 @@ static int port_set_latency(void *object, else return 0; - if (oport != NULL && - spa_latency_info_compare(&info, &oport->latency[other]) != 0) { - oport->latency[other] = info; - oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - oport->params[IDX_Latency].user++; - } - } else { - spa_latency_info_combine_start(&info, other); - for (i = 0; i < this->dir[direction].n_ports; i++) { - oport = GET_PORT(this, direction, i); - if ((oport->is_monitor) || !oport->have_latency) - continue; - spa_log_debug(this->log, "%p: combine %d", this, i); - spa_latency_info_combine(&info, &oport->latency[other]); - } - spa_latency_info_combine_finish(&info); - - spa_log_debug(this->log, "%p: combined %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, - info.direction == SPA_DIRECTION_INPUT ? "input" : "output", - info.min_quantum, info.max_quantum, - info.min_rate, info.max_rate, - info.min_ns, info.max_ns); - - for (i = 0; i < this->dir[other].n_ports; i++) { - oport = GET_PORT(this, other, i); - if (oport->is_monitor) - continue; - spa_log_debug(this->log, "%p: change %d", this, i); - if (spa_latency_info_compare(&info, &oport->latency[other]) != 0) { - oport->latency[other] = info; - oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - oport->params[IDX_Latency].user++; - } - } - } - if (emit) { - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].user++; + if (oport != NULL) + port_update_latency(oport, &info, have_latency); } + recalc_latencies(this, direction); return 0; } From 26099f4c29d294ae7ba0f3d1bbbeb3a8813bace1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 11:28:16 +0200 Subject: [PATCH 0266/1014] doc: add doc about Tag params --- doc/dox/internals/index.dox | 1 + doc/dox/internals/tag,dox | 68 +++++++++++++++++++++++++++++++++++++ doc/meson.build | 1 + 3 files changed, 70 insertions(+) create mode 100644 doc/dox/internals/tag,dox diff --git a/doc/dox/internals/index.dox b/doc/dox/internals/index.dox index 84e0ce295..64f548ccc 100644 --- a/doc/dox/internals/index.dox +++ b/doc/dox/internals/index.dox @@ -12,6 +12,7 @@ - \subpage page_dma_buf - \subpage page_scheduling - \subpage page_latency +- \subpage page_tag - \subpage page_native_protocol diff --git a/doc/dox/internals/tag,dox b/doc/dox/internals/tag,dox new file mode 100644 index 000000000..7adf0cdc4 --- /dev/null +++ b/doc/dox/internals/tag,dox @@ -0,0 +1,68 @@ +/** \page page_tag Tag support + +This document explains how stream specific metadata is transported in +the PipeWire graph. + +The metadata is a dictionary of string key/value pairs with information +such as the author, track, copyright, album information etc. + +# Use Cases + +## A stream/node/port has some metadata + +Applications need to be able to query the Tag of a port/node/stream. + +Linked Nodes need to be informed of the upstream and downstream tags. + +## dynamically update tags + +It needs to be possible to dynamically update the tags of a port/node/stream +and this should inform all linked ports/nodes of the updated tags. + +## Aggregate tags upstream and downstream + +A node might need to know all the upstream and downstream tags. Each node can +add or remove metadata in the Tag param. + +A mixer node might need to combine the Tags of the two input streams and +generate a combined tag. + +# Concepts + +## Port Tags + +The fundamental object for implementing metadata reporting in PipeWire is the +Tag object. + +It consists of a direction (input/output) and one or more generic dictionaries +with string key/value pairs. + +The direction of the tag object determines how the object propagates in the graph. + +- SPA_DIRECTION_OUTPUT Tag objects move from output ports downstream and contain + the metadata from all nodes upstream. An output tag received on an input port + should instruct the node to update the output tag on its output ports related + to this input port. + +- SPA_DIRECTION_INPUT Tag objects move from input ports upstream and contain + the metadata from all nodes downstream. An input tag received on an output port + should instruct the node to update the input tag on its input ports related + to this output port. + +PipeWire will automatically propagate Tag objects from ports to all linked ports +in the graph. Output Tag objects on output ports are propagated to linked input +ports and input Tag objects on input ports are propagated to linked output ports. + +If a port has links with multiple other ports, the Tag objects are merged by +appending the dictionaties to the Tag param. Intermediate nodes or sinks are allowed +to take the multiple dictionaries in a Tag and combine them into one dictionary if +they would like to do so. + +This way, Output Tag always describes the aggragated total upstream metadata of +signal up to the port and Input tag describes the aggregated downstream metadata +of the signal from the port. + +# Tag updates + +Tag params on the ports can be updated as needed. This can happen because some upstream or +downstream Tag changed or when the metadata of a node/port/stream changes. diff --git a/doc/meson.build b/doc/meson.build index 2e221fb06..dc90e122b 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -59,6 +59,7 @@ extra_docs = [ 'dox/internals/design.dox', 'dox/internals/access.dox', 'dox/internals/latency.dox', + 'dox/internals/tag.dox', 'dox/internals/midi.dox', 'dox/internals/portal.dox', 'dox/internals/daemon.dox', From b957d74eb68bdca6d1e05f4160dd3cbc5db93234 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 11:40:32 +0200 Subject: [PATCH 0267/1014] doc: fix bad filename --- doc/dox/internals/{tag,dox => tag.dox} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/dox/internals/{tag,dox => tag.dox} (100%) diff --git a/doc/dox/internals/tag,dox b/doc/dox/internals/tag.dox similarity index 100% rename from doc/dox/internals/tag,dox rename to doc/dox/internals/tag.dox From a0eb12e4be12a43845aa99d0c7c2c0f3b90eaf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 10 May 2025 12:59:36 +0200 Subject: [PATCH 0268/1014] ci: specify explicit dependencies between jobs Use `needs` to specify job dependencies explicitly instead of relying on stages for ordering. This allows jobs from multiple stages to run concurrently without having to wait for unrelated jobs in earlier stages. See https://docs.gitlab.com/ci/yaml/#needs for more information. --- .gitlab-ci.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 07a91cc01..8319a1626 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -256,6 +256,9 @@ build_on_ubuntu: - .fdo.distribution-image@ubuntu - .build stage: build + needs: + - job: container_ubuntu + artifacts: false variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=enabled" @@ -266,6 +269,9 @@ build_on_ubuntu: - .fdo.distribution-image@fedora - .build stage: build + needs: + - job: container_fedora + artifacts: false build_on_fedora: extends: @@ -358,6 +364,9 @@ build_on_alpine: - .fdo.distribution-image@alpine - .build stage: build + needs: + - job: container_alpine + artifacts: false variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled -Dlogind=enabled -Dlogind-provider=libelogind" @@ -493,6 +502,9 @@ build_with_coverity: - .fdo.suffixed-image@fedora - .build stage: analysis + needs: + - job: container_coverity + artifacts: false script: - export PATH=/opt/coverity/bin:$PATH - meson setup "$BUILD_DIR" --prefix="$PREFIX" @@ -568,8 +580,9 @@ check_missing_headers: - .not_coverity - .fdo.distribution-image@fedora stage: analysis - dependencies: - - build_on_fedora + needs: + - job: build_on_fedora + artifacts: true script: - export PREFIX=`find -name prefix-*` - ./.gitlab/ci/check_missing_headers.sh @@ -578,8 +591,9 @@ pages: extends: - .not_coverity stage: pages - dependencies: - - build_on_fedora_html_docs + needs: + - job: build_on_fedora_html_docs + artifacts: true script: - mkdir public public/1.0 public/1.2 public/1.4 public/devel - cp -R branch-1.0/builddir/doc/html/* public/1.0/ From 9b316f6deb8f4c718d16a566f6e80a513833097b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 17:02:46 +0200 Subject: [PATCH 0269/1014] filter-graph: remove port check Remove the port number check, we never call this with an invalid port number and the check was wrong for the sofa_plugin. Fixes #4700 --- spa/plugins/filter-graph/ebur128_plugin.c | 6 ++---- spa/plugins/filter-graph/sofa_plugin.c | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c index a7aeeef99..0c94449ff 100644 --- a/spa/plugins/filter-graph/ebur128_plugin.c +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -222,8 +222,7 @@ static void ebur128_connect_port(void * Instance, unsigned long Port, float * DataLocation) { struct ebur128_impl *impl = Instance; - if (Port < PORT_MAX) - impl->port[Port] = DataLocation; + impl->port[Port] = DataLocation; } static void ebur128_cleanup(void * Instance) @@ -446,8 +445,7 @@ static void lufs2gain_connect_port(void * Instance, unsigned long Port, float * DataLocation) { struct lufs2gain_impl *impl = Instance; - if (Port < 3) - impl->port[Port] = DataLocation; + impl->port[Port] = DataLocation; } static void lufs2gain_run(void * Instance, unsigned long SampleCount) diff --git a/spa/plugins/filter-graph/sofa_plugin.c b/spa/plugins/filter-graph/sofa_plugin.c index 2942ed3bc..aea14278f 100644 --- a/spa/plugins/filter-graph/sofa_plugin.c +++ b/spa/plugins/filter-graph/sofa_plugin.c @@ -319,8 +319,6 @@ static void spatializer_connect_port(void * Instance, unsigned long Port, float * DataLocation) { struct spatializer_impl *impl = Instance; - if (Port > 5) - return; impl->port[Port] = DataLocation; } From 6712d9c3dbc2f53c0d4197eeb3e9e3a8b3def95e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 15 Dec 2024 00:07:45 +0100 Subject: [PATCH 0270/1014] pipewire: module-raop-discover: fix typo in documentation roap -> raop --- src/modules/module-raop-discover.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 9a1079a4e..9782eb458 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -45,7 +45,7 @@ * * Options specific to the behavior of this module * - * - `roap.discover-local` = allow discovery of local services as well. + * - `raop.discover-local` = allow discovery of local services as well. * false by default. * - `raop.latency.ms` = latency for all streams in microseconds. This * can be overwritten in the stream rules. @@ -60,7 +60,7 @@ * context.modules = [ * { name = libpipewire-module-raop-discover * args = { - * #roap.discover-local = false; + * #raop.discover-local = false; * #raop.latency.ms = 1000 * stream.rules = [ * { matches = [ From 218f25088caa24b8d82103041a9e18943daeb96c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 18:21:02 +0200 Subject: [PATCH 0271/1014] convert: place the right id in the portconfig format --- spa/plugins/audioconvert/audioconvert.c | 3 +-- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 75f6f01b7..f30de33ae 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -530,8 +530,7 @@ static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index if (dir->have_format) { spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); - spa_format_audio_raw_build(b, SPA_PARAM_PORT_CONFIG_format, - &dir->format.info.raw); + spa_format_audio_raw_build(b, id, &dir->format.info.raw); } *param = spa_pod_builder_pop(b, &f[0]); break; diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index fa4e63849..889117820 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -573,8 +573,7 @@ static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index if (dir->have_format) { spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); - spa_format_video_build(b, SPA_PARAM_PORT_CONFIG_format, - &dir->format); + spa_format_video_build(b, id, &dir->format); } *param = spa_pod_builder_pop(b, &f[0]); return 1; From 2771c435fdc9b751c00c57dc48d83069e3fe7396 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 14 May 2025 18:23:41 +0200 Subject: [PATCH 0272/1014] doc: fix end of page for tag doc --- doc/dox/internals/tag.dox | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/dox/internals/tag.dox b/doc/dox/internals/tag.dox index 7adf0cdc4..9374d10c0 100644 --- a/doc/dox/internals/tag.dox +++ b/doc/dox/internals/tag.dox @@ -66,3 +66,5 @@ of the signal from the port. Tag params on the ports can be updated as needed. This can happen because some upstream or downstream Tag changed or when the metadata of a node/port/stream changes. + +*/ From 01d1e29402a03b25ec4d61abdb6d6a2821fe5838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 15 Dec 2024 00:21:45 +0100 Subject: [PATCH 0273/1014] spa: vulkan: simplify kernel version parsing Use a single `sscanf()` instead of multiple `strtok()`, `atoi()` calls. After this change all three components are required to be present. --- spa/plugins/vulkan/dmabuf_linux.c | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/spa/plugins/vulkan/dmabuf_linux.c b/spa/plugins/vulkan/dmabuf_linux.c index f3b9c6f13..1efddb22b 100644 --- a/spa/plugins/vulkan/dmabuf_linux.c +++ b/spa/plugins/vulkan/dmabuf_linux.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -51,29 +52,9 @@ bool dmabuf_check_sync_file_import_export(struct spa_log *log) { return false; } - // Trim release suffix if any, e.g. "-arch1-1" - for (size_t i = 0; utsname.release[i] != '\0'; i++) { - char ch = utsname.release[i]; - if ((ch < '0' || ch > '9') && ch != '.') { - utsname.release[i] = '\0'; - break; - } - } - - char *rel = strtok(utsname.release, "."); - int major = atoi(rel); - - int minor = 0; - rel = strtok(NULL, "."); - if (rel != NULL) { - minor = atoi(rel); - } - - int patch = 0; - rel = strtok(NULL, "."); - if (rel != NULL) { - patch = atoi(rel); - } + unsigned int major, minor, patch; + if (sscanf(utsname.release, "%u.%u.%u", &major, &minor, &patch) != 3) + return false; return KERNEL_VERSION(major, minor, patch) >= KERNEL_VERSION(5, 20, 0); } From e375364c87b27e7ec678dbcea3ff4eaff829f9a2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 15 May 2025 19:42:56 +0300 Subject: [PATCH 0274/1014] bluez5: fix device set profile/route enumerations Now that we have ASHA & BAP, this->device_set now refers to the device set of the active profile. It cannot be used to produce EnumProfile/EnumRoute. Fix this by computing the device set for given profile(s) as needed. --- spa/plugins/bluez5/bluez5-device.c | 74 +++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index e14621b83..673dd1401 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -1135,12 +1135,48 @@ static void device_set_update_bap(struct impl *this, struct device_set *dset) dset->source_enabled = dset->path && (dset->sources > 1); } -static void device_set_update(struct impl *this, struct device_set *dset) +static void device_set_update(struct impl *this, struct device_set *dset, int profile) { - if (this->profile == DEVICE_PROFILE_BAP) + if (profile == DEVICE_PROFILE_BAP) device_set_update_bap(this, dset); - else if (this->profile == DEVICE_PROFILE_ASHA) + else if (profile == DEVICE_PROFILE_ASHA) device_set_update_asha(this, dset); + else + device_set_clear(this, dset); +} + +static void device_set_get_dset_info(const struct device_set *dset, + int *n_set_sink, int *n_set_source) +{ + if (dset->sink_enabled) + *n_set_sink = dset->leader ? 1 : 0; + if (dset->source_enabled) + *n_set_source = dset->leader ? 1 : 0; +} + +static void device_set_get_info(struct impl *this, uint32_t profile, + int *n_set_sink, int *n_set_source) +{ + struct device_set dset = { .impl = this }; + + *n_set_sink = -1; + *n_set_source = -1; + + if (profile == this->profile) { + device_set_get_dset_info(&this->device_set, n_set_sink, n_set_source); + } else if (profile != SPA_ID_INVALID) { + device_set_update(this, &dset, profile); + device_set_get_dset_info(&dset, n_set_sink, n_set_source); + device_set_clear(this, &dset); + } else { + device_set_update(this, &dset, DEVICE_PROFILE_BAP); + device_set_get_dset_info(&dset, n_set_sink, n_set_source); + device_set_clear(this, &dset); + + device_set_update(this, &dset, DEVICE_PROFILE_ASHA); + device_set_get_dset_info(&dset, n_set_sink, n_set_source); + device_set_clear(this, &dset); + } } static bool device_set_equal(struct device_set *a, struct device_set *b) @@ -1166,7 +1202,7 @@ static int emit_nodes(struct impl *this) this->props.codec = 0; - device_set_update(this, &this->device_set); + device_set_update(this, &this->device_set, this->profile); switch (this->profile) { case DEVICE_PROFILE_OFF: @@ -1485,7 +1521,7 @@ static bool device_set_needs_update(struct impl *this) this->profile != DEVICE_PROFILE_ASHA) return false; - device_set_update(this, &dset); + device_set_update(this, &dset, this->profile); changed = !device_set_equal(&dset, &this->device_set); device_set_clear(this, &dset); return changed; @@ -1910,6 +1946,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * case DEVICE_PROFILE_ASHA: { uint32_t profile = device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; + int n_set_sink, n_set_source; if (codec == 0) return NULL; @@ -1925,9 +1962,9 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * n_sink++; priority = 1; - if (this->device_set.sink_enabled) - n_sink = this->device_set.leader ? 1 : 0; - + device_set_get_info(this, DEVICE_PROFILE_ASHA, &n_set_sink, &n_set_source); + if (n_set_sink >= 0) + n_sink = n_set_sink; break; } case DEVICE_PROFILE_A2DP: @@ -1990,6 +2027,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * | SPA_BT_PROFILE_BAP_BROADCAST_SINK); size_t idx; const struct media_codec *media_codec; + int n_set_sink, n_set_source; /* BAP will only enlist codec profiles */ if (codec == 0) @@ -2056,10 +2094,11 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * priority = 128; } - if (this->device_set.sink_enabled) - n_sink = this->device_set.leader ? 1 : 0; - if (this->device_set.source_enabled) - n_source = this->device_set.leader ? 1 : 0; + device_set_get_info(this, DEVICE_PROFILE_BAP, &n_set_sink, &n_set_source); + if (n_set_sink >= 0) + n_sink = n_set_sink; + if (n_set_source >= 0) + n_source = n_set_source; break; } case DEVICE_PROFILE_HSP_HFP: @@ -2227,6 +2266,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, char name[128]; uint32_t i, j, mask, next; uint32_t dev; + int n_set_sink, n_set_source; ff = spa_bt_form_factor_from_class(device->bluetooth_class); @@ -2294,12 +2334,14 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, break; } + device_set_get_info(this, profile, &n_set_sink, &n_set_source); + switch (route) { case ROUTE_INPUT: direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-input", name_prefix); dev = DEVICE_ID_SOURCE; - available = this->device_set.source_enabled ? + available = (n_set_source >= 0) ? SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; if ((this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) && @@ -2312,7 +2354,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-output", name_prefix); dev = DEVICE_ID_SINK; - available = this->device_set.sink_enabled ? + available = (n_set_sink >= 0) ? SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; if (device_has_route(this, ROUTE_HF_OUTPUT)) { @@ -2339,7 +2381,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADSET); break; case ROUTE_SET_INPUT: - if (!(this->device_set.source_enabled && this->device_set.leader)) + if (n_set_source < 1) return NULL; direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-set-input", name_prefix); @@ -2347,7 +2389,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, available = SPA_PARAM_AVAILABILITY_yes; break; case ROUTE_SET_OUTPUT: - if (!(this->device_set.sink_enabled && this->device_set.leader)) + if (n_set_sink < 1) return NULL; direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-set-output", name_prefix); From 897748759e826105e8c58ffb0c104b5f431bd7bb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 May 2024 12:46:31 +0300 Subject: [PATCH 0275/1014] bluez5: update timestamping code to match kernel & bluez Update code to match the final version landed in mainline Linux 6.15 --- spa/plugins/bluez5/bluez5-dbus.c | 24 ++++++++- spa/plugins/bluez5/bt-latency.h | 88 ++++++++++++++++++++++---------- spa/plugins/bluez5/defs.h | 1 + spa/plugins/bluez5/iso-io.c | 2 +- 4 files changed, 84 insertions(+), 31 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 90a183b73..04f029ca4 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -1297,8 +1297,28 @@ static int adapter_media_update_props(struct spa_bt_adapter *adapter, dbus_message_iter_next(&iter); } - } - else + } else if (spa_streq(key, "SupportedFeatures")) { + DBusMessageIter iter; + + if (!check_iter_signature(&it[1], "as")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + + while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { + const char *feature; + + dbus_message_iter_get_basic(&iter, &feature); + + if (spa_streq(feature, "tx-timestamping")) { + adapter->tx_timestamping_supported = true; + spa_log_info(monitor->log, "Adapter %s: TX timestamping supported", + adapter->path); + } + + dbus_message_iter_next(&iter); + } + } else spa_log_debug(monitor->log, "media: unhandled key %s", key); next: diff --git a/spa/plugins/bluez5/bt-latency.h b/spa/plugins/bluez5/bt-latency.h index 1df17f9a2..56d517ab3 100644 --- a/spa/plugins/bluez5/bt-latency.h +++ b/spa/plugins/bluez5/bt-latency.h @@ -14,15 +14,16 @@ #include #include +#include "defs.h" #include "rate-control.h" /* New kernel API */ #ifndef BT_SCM_ERROR #define BT_SCM_ERROR 0x04 #endif -#ifndef BT_POLL_ERRQUEUE -#define BT_POLL_ERRQUEUE 21 -#endif + +#define NEW_SOF_TIMESTAMPING_TX_COMPLETION (1 << 18) +#define NEW_SCM_TSTAMP_COMPLETION (SCM_TSTAMP_ACK + 1) /** * Bluetooth latency tracking. @@ -32,46 +33,45 @@ struct spa_bt_latency uint64_t value; struct spa_bt_ptp ptp; bool valid; - bool disabled; + bool enabled; + uint32_t queue; + uint32_t kernel_queue; + size_t unsent; struct { int64_t send[64]; + size_t size[64]; uint32_t pos; int64_t prev_tx; } impl; }; -static inline void spa_bt_latency_init(struct spa_bt_latency *lat, int fd, +static inline void spa_bt_latency_init(struct spa_bt_latency *lat, struct spa_bt_transport *transport, uint32_t period, struct spa_log *log) { - int so_timestamping = (SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE | - SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY); - uint32_t flag; + int so_timestamping = (NEW_SOF_TIMESTAMPING_TX_COMPLETION | SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY); int res; spa_zero(*lat); - flag = 0; - res = setsockopt(fd, SOL_BLUETOOTH, BT_POLL_ERRQUEUE, &flag, sizeof(flag)); - if (res < 0) { - spa_log_warn(log, "setsockopt(BT_POLL_ERRQUEUE) failed (kernel feature not enabled?): %d (%m)", errno); - lat->disabled = true; + if (!transport->device->adapter->tx_timestamping_supported) return; - } - res = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping)); + res = setsockopt(transport->fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping)); if (res < 0) { - spa_log_warn(log, "setsockopt(SO_TIMESTAMPING) failed (kernel feature not enabled?): %d (%m)", errno); - lat->disabled = true; + spa_log_info(log, "setsockopt(SO_TIMESTAMPING) failed (kernel feature not enabled?): %d (%m)", errno); return; } /* Flush errqueue on start */ do { - res = recv(fd, NULL, 0, MSG_ERRQUEUE | MSG_DONTWAIT | MSG_TRUNC); + res = recv(transport->fd, NULL, 0, MSG_ERRQUEUE | MSG_DONTWAIT | MSG_TRUNC); } while (res == 0); spa_bt_ptp_init(&lat->ptp, period, period / 2); + + lat->enabled = true; } static inline void spa_bt_latency_reset(struct spa_bt_latency *lat) @@ -81,16 +81,27 @@ static inline void spa_bt_latency_reset(struct spa_bt_latency *lat) spa_bt_ptp_init(&lat->ptp, lat->ptp.period, lat->ptp.period / 2); } -static inline void spa_bt_latency_sent(struct spa_bt_latency *lat, uint64_t now) +static inline ssize_t spa_bt_send(int fd, const void *buf, size_t size, + struct spa_bt_latency *lat, uint64_t now) { - const unsigned int n = SPA_N_ELEMENTS(lat->impl.send); + ssize_t res = send(fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL); - if (lat->disabled) - return; + if (!lat || !lat->enabled) + return res; - lat->impl.send[lat->impl.pos++] = now; - if (lat->impl.pos >= n) - lat->impl.pos = 0; + if (res >= 0) { + lat->impl.send[lat->impl.pos] = now; + lat->impl.size[lat->impl.pos] = size; + lat->impl.pos++; + if (lat->impl.pos >= SPA_N_ELEMENTS(lat->impl.send)) + lat->impl.pos = 0; + + lat->queue++; + lat->kernel_queue++; + lat->unsent += size; + } + + return res; } static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int fd, struct spa_log *log) @@ -100,7 +111,7 @@ static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int f char control[512]; } control; - if (lat->disabled) + if (!lat->enabled) return -EOPNOTSUPP; do { @@ -137,20 +148,38 @@ static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int f if (!tss || !serr || serr->ee_errno != ENOMSG || serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) return -EINVAL; - if (serr->ee_info != SCM_TSTAMP_SND) + + switch (serr->ee_info) { + case SCM_TSTAMP_SND: + if (lat->kernel_queue) + lat->kernel_queue--; continue; + case NEW_SCM_TSTAMP_COMPLETION: + break; + default: + continue; + } struct timespec *ts = &tss->ts[0]; int64_t tx_time = SPA_TIMESPEC_TO_NSEC(ts); uint32_t tx_pos = serr->ee_data % SPA_N_ELEMENTS(lat->impl.send); lat->value = tx_time - lat->impl.send[tx_pos]; + if (lat->unsent > lat->impl.size[tx_pos]) + lat->unsent -= lat->impl.size[tx_pos]; + else + lat->unsent = 0; if (lat->impl.prev_tx && tx_time > lat->impl.prev_tx) spa_bt_ptp_update(&lat->ptp, lat->value, tx_time - lat->impl.prev_tx); lat->impl.prev_tx = tx_time; + if (lat->queue > 0) + lat->queue--; + if (!lat->queue) + lat->unsent = 0; + spa_log_trace(log, "fd:%d latency[%d] nsec:%"PRIu64" range:%d..%d ms", fd, tx_pos, lat->value, (int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.min / SPA_NSEC_PER_MSEC : -1), @@ -166,11 +195,14 @@ static inline void spa_bt_latency_flush(struct spa_bt_latency *lat, int fd, stru { int so_timestamping = 0; + if (!lat->enabled) + return; + /* Disable timestamping and flush errqueue */ setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping)); spa_bt_latency_recv_errqueue(lat, fd, log); - lat->disabled = true; + lat->enabled = false; } #endif diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index dd092a626..148b00bbf 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -385,6 +385,7 @@ struct spa_bt_adapter { unsigned int has_adapter1_interface:1; unsigned int has_media1_interface:1; unsigned int le_audio_bcast_supported:1; + unsigned int tx_timestamping_supported:1; }; enum spa_bt_form_factor { diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 802961bb6..120f02680 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -434,7 +434,7 @@ static struct stream *stream_create(struct spa_bt_transport *t, struct group *gr stream->this.format = format; stream->block_size = block_size; - spa_bt_latency_init(&stream->tx_latency, stream->fd, LATENCY_PERIOD, group->log); + spa_bt_latency_init(&stream->tx_latency, t, LATENCY_PERIOD, group->log); if (sink) stream_silence(stream); From 489e4b6bd21da5ab8461be1daaf3d247d0625a8e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 2 Mar 2024 19:28:22 +0200 Subject: [PATCH 0276/1014] bluez5: latency tracking also for A2DP, use it for unsent size Use TX timestamping to figure out the accurate amount of unsent data, including controller buffers. SIOCOUTQ does not report accurate data size as it includes overheads. --- spa/plugins/bluez5/media-sink.c | 65 ++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 5bc1811dd..3020edfd9 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -208,6 +208,8 @@ struct impl { struct spa_bt_asha *asha; struct spa_list asha_link; + + struct spa_bt_latency tx_latency; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) @@ -672,30 +674,41 @@ static int setup_matching(struct impl *this) return 0; } -static int get_transport_unused_size(struct impl *this) +static int get_transport_unsent_size(struct impl *this) { int res, value; - res = ioctl(this->flush_source.fd, TIOCOUTQ, &value); - if (res < 0) { - spa_log_error(this->log, "%p: ioctl fail: %m", this); - return -errno; + + if (this->tx_latency.enabled) { + res = 0; + value = this->tx_latency.unsent; + } else { + res = ioctl(this->flush_source.fd, TIOCOUTQ, &value); + if (res < 0) { + spa_log_error(this->log, "%p: ioctl fail: %m", this); + return -errno; + } + if ((unsigned int)value > this->fd_buffer_size) + return -EIO; + value = this->fd_buffer_size - value; } - spa_log_trace(this->log, "%p: fd unused buffer size:%d/%d", this, value, this->fd_buffer_size); + + spa_log_trace(this->log, "%p: fd unsent size:%d/%d", this, value, this->fd_buffer_size); return value; } static int send_buffer(struct impl *this) { int written, unsent; + struct timespec ts_pre; - unsent = get_transport_unused_size(this); - if (unsent >= 0) { - unsent = this->fd_buffer_size - unsent; + unsent = get_transport_unsent_size(this); + if (unsent >= 0) this->codec->abr_process(this->codec_data, unsent); - } - written = send(this->flush_source.fd, this->buffer, - this->buffer_used, MSG_DONTWAIT | MSG_NOSIGNAL); + spa_system_clock_gettime(this->data_system, CLOCK_REALTIME, &ts_pre); + + written = spa_bt_send(this->flush_source.fd, this->buffer, this->buffer_used, + &this->tx_latency, SPA_TIMESPEC_TO_NSEC(&ts_pre)); if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { struct timespec ts; @@ -855,7 +868,7 @@ static int flush_data(struct impl *this, uint64_t now_time) bool is_asha = this->codec->asha; uint32_t total_frames; int written; - int unused_buffer; + int unsent_buffer; spa_assert(this->transport_started); @@ -994,11 +1007,10 @@ again: } /* - * Get socket queue size before writing to it. - * This should be the same as buffer size to increase bitpool - * Bitpool shouldn't be increased when data is left over in the buffer + * Get packet queue size before writing to it. This should be zero to increase + * bitpool. Bitpool shouldn't be increased when there is unsent data. */ - unused_buffer = get_transport_unused_size(this); + unsent_buffer = get_transport_unsent_size(this); written = flush_buffer(this); @@ -1075,7 +1087,7 @@ again: } if (now_time - this->last_error > SPA_NSEC_PER_SEC) { - if (unused_buffer == (int)this->fd_buffer_size) { + if (unsent_buffer == 0) { int res = this->codec->increase_bitpool(this->codec_data); spa_log_debug(this->log, "%p: increase bitpool: %i", this, res); @@ -1209,9 +1221,13 @@ static void media_on_flush_error(struct spa_source *source) if (source->rmask & SPA_IO_ERR) { /* TX timestamp info? */ - if (this->transport && this->transport->iso_io) + if (this->transport && this->transport->iso_io) { if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0) return; + } else { + if (spa_bt_latency_recv_errqueue(&this->tx_latency, this->flush_source.fd, this->log) == 0) + return; + } /* Otherwise: actual error */ } @@ -1220,8 +1236,10 @@ static void media_on_flush_error(struct spa_source *source) if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_warn(this->log, "%p: error %d", this, source->rmask); - if (this->flush_source.loop) + if (this->flush_source.loop) { + spa_bt_latency_flush(&this->tx_latency, this->flush_source.fd, this->log); spa_loop_remove_source(this->data_loop, &this->flush_source); + } enable_flush_timer(this, false); if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); @@ -1510,6 +1528,8 @@ static int transport_start(struct impl *this) this->flush_timer_source.mask = SPA_IO_IN; this->flush_timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_timer_source); + + spa_bt_latency_init(&this->tx_latency, this->transport, LATENCY_PERIOD, this->log); } if (!is_asha) { @@ -1626,8 +1646,11 @@ static int do_remove_transport_source(struct spa_loop *loop, this->transport_started = false; - if (this->flush_source.loop) + if (this->flush_source.loop) { + spa_bt_latency_flush(&this->tx_latency, this->flush_source.fd, this->log); spa_loop_remove_source(this->data_loop, &this->flush_source); + } + if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); if (this->codec->asha) { From 1e18cded7580e3c51cfd7aef8a2221f2aa6ea234 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 31 May 2024 20:31:59 +0300 Subject: [PATCH 0277/1014] bluez5: iso-io: improve latency logic If kernel socket queues for different streams get out of sync, it will mess up time alignment of different streams. If that happens, flush to resync. If total latency becomes too large, flush queue. Get accurate queue sizes from tx timestamping. --- spa/plugins/bluez5/iso-io.c | 104 ++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 120f02680..bb52575bd 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -30,8 +30,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.iso"); #define IDLE_TIME (500 * SPA_NSEC_PER_MSEC) #define EMPTY_BUF_SIZE 65536 -#define LATENCY_PERIOD (200 * SPA_NSEC_PER_MSEC) -#define MAX_PACKET_QUEUE 3 +#define LATENCY_PERIOD (1000 * SPA_NSEC_PER_MSEC) +#define MAX_LATENCY (50 * SPA_NSEC_PER_MSEC) struct group { struct spa_log *log; @@ -43,6 +43,7 @@ struct group { uint8_t id; uint64_t next; uint64_t duration; + bool flush; bool started; }; @@ -164,6 +165,57 @@ static void drop_rx(int fd) } while (res >= 0); } +static bool group_latency_check(struct group *group) +{ + struct stream *stream; + int32_t min_latency = INT32_MAX, max_latency = INT32_MIN; + unsigned int kernel_queue = UINT_MAX; + + spa_list_for_each(stream, &group->streams, link) { + if (!stream->sink) + continue; + if (!stream->tx_latency.enabled) + return false; + + if (kernel_queue == UINT_MAX) + kernel_queue = stream->tx_latency.kernel_queue; + + if (group->flush && stream->tx_latency.queue) { + spa_log_debug(group->log, "%p: ISO group:%d latency skip: flushing", + group, group->id); + return true; + } + if (stream->tx_latency.kernel_queue != kernel_queue) { + /* Streams out of sync, try to correct if it persists */ + spa_log_debug(group->log, "%p: ISO group:%d latency skip: imbalance", + group, group->id); + group->flush = true; + return true; + } + } + + group->flush = false; + + spa_list_for_each(stream, &group->streams, link) { + if (!stream->sink) + continue; + if (!stream->tx_latency.valid) + return false; + + min_latency = SPA_MIN(min_latency, stream->tx_latency.ptp.min); + max_latency = SPA_MAX(max_latency, stream->tx_latency.ptp.max); + } + + if (max_latency > MAX_LATENCY) { + spa_log_debug(group->log, "%p: ISO group:%d latency skip: latency %d ms", + group, group->id, (int)(max_latency / SPA_NSEC_PER_MSEC)); + group->flush = true; + return true; + } + + return false; +} + static void group_on_timeout(struct spa_source *source) { struct group *group = source->data; @@ -179,6 +231,8 @@ static void group_on_timeout(struct spa_source *source) group, group->id, spa_strerror(res)); return; } + if (!exp) + return; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) { @@ -200,12 +254,16 @@ static void group_on_timeout(struct spa_source *source) group->started = true; } + if (group_latency_check(group)) { + spa_list_for_each(stream, &group->streams, link) + spa_bt_latency_reset(&stream->tx_latency); + goto done; + } + /* Produce output */ spa_list_for_each(stream, &group->streams, link) { int res = 0; uint64_t now; - int32_t min_latency = INT32_MAX, max_latency = INT32_MIN; - struct stream *other; if (!stream->sink) continue; @@ -223,44 +281,21 @@ static void group_on_timeout(struct spa_source *source) } } - spa_list_for_each(other, &group->streams, link) { - if (!other->sink || stream == other || !other->tx_latency.valid) - continue; - min_latency = SPA_MIN(min_latency, other->tx_latency.ptp.min); - max_latency = SPA_MAX(max_latency, other->tx_latency.ptp.max); - } - - if (stream->tx_latency.valid && min_latency <= max_latency && - stream->tx_latency.ptp.min > min_latency + (int64_t)group->duration/2 && - stream->tx_latency.ptp.max > max_latency + (int64_t)group->duration/2) { - spa_log_debug(group->log, "%p: ISO group:%u latency skip align fd:%d", group, group->id, stream->fd); - spa_bt_latency_reset(&stream->tx_latency); - goto stream_done; - } - - /* TODO: this should use rate match */ - if (stream->tx_latency.valid && - stream->tx_latency.ptp.min > MAX_PACKET_QUEUE * (int64_t)group->duration) { - spa_log_debug(group->log, "%p: ISO group:%u latency skip fd:%d", group, group->id, stream->fd); - spa_bt_latency_reset(&stream->tx_latency); - goto stream_done; - } - now = get_time_ns(group->data_system, CLOCK_REALTIME); - res = send(stream->fd, stream->this.buf, stream->this.size, MSG_DONTWAIT | MSG_NOSIGNAL); + res = spa_bt_send(stream->fd, stream->this.buf, stream->this.size, + &stream->tx_latency, now); if (res < 0) { res = -errno; fail = true; - } else { - spa_bt_latency_sent(&stream->tx_latency, now); + group->flush = true; } - stream_done: - spa_log_trace(group->log, "%p: ISO group:%u sent fd:%d size:%u ts:%u idle:%d res:%d latency:%d..%d us", + spa_log_trace(group->log, "%p: ISO group:%u sent fd:%d size:%u ts:%u idle:%d res:%d latency:%d..%d%sus queue:%u", group, group->id, stream->fd, (unsigned)stream->this.size, (unsigned)stream->this.timestamp, stream->idle, res, - stream->tx_latency.valid ? stream->tx_latency.ptp.min/1000 : -1, - stream->tx_latency.valid ? stream->tx_latency.ptp.max/1000 : -1); + stream->tx_latency.ptp.min/1000, stream->tx_latency.ptp.max/1000, + stream->tx_latency.valid ? " " : "* ", + stream->tx_latency.queue); stream->this.size = 0; } @@ -268,6 +303,7 @@ static void group_on_timeout(struct spa_source *source) if (fail) spa_log_debug(group->log, "%p: ISO group:%d send failure", group, group->id); +done: /* Pull data for the next interval */ group->next += exp * group->duration; From e387772dc2a4316d88d41b528b6b2b16eccf32ba Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Thu, 15 May 2025 21:38:35 +0200 Subject: [PATCH 0278/1014] v4l2: Re-enable first buffer skip for jpeg types The workaround is typically needed with usb-cameras using jpeg streams. Re-enabling the skip shouldn't affect what the commit was trying to do, i.e. fix encoded streams like h264 etc. Fixes: bcf0c0cf8 (v4l2: only skip buffer for raw formats) --- spa/plugins/v4l2/v4l2-utils.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 96ce68ff7..887094eff 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1873,7 +1873,9 @@ static int spa_v4l2_stream_on(struct impl *this) spa_log_debug(this->log, "starting"); - if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw) + if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw || + port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_mjpg || + port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_jpeg) port->first_buffer = true; else port->first_buffer = false; From e5afc939e8d053e3331e401f29bdc9913bf200f0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sat, 17 May 2025 14:05:23 +0200 Subject: [PATCH 0279/1014] libcemara: take care of index offset when enumerating controls Add an index offset when enumerating controls. We insert 2 properties before enumerating the controls so the index of the first control needs to have an offset of 2. --- spa/plugins/libcamera/libcamera-source.cpp | 4 ++-- spa/plugins/libcamera/libcamera-utils.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f570499d1..0dffbc92b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -273,7 +273,7 @@ next: default: return spa_libcamera_enum_controls(impl, GET_OUT_PORT(impl, 0), - seq, result.index - 2, num, filter); + seq, result.index, 2, num, filter); } break; } @@ -535,7 +535,7 @@ next: switch (id) { case SPA_PARAM_PropInfo: - return spa_libcamera_enum_controls(impl, port, seq, start, num, filter); + return spa_libcamera_enum_controls(impl, port, seq, start, 0, num, filter); case SPA_PARAM_EnumFormat: return spa_libcamera_enum_format(impl, port, seq, start, num, filter); diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 92185c4b0..bbd351afe 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -495,7 +495,7 @@ static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) static int spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, - uint32_t start, uint32_t num, + uint32_t start, uint32_t offset, uint32_t num, const struct spa_pod *filter) { const ControlInfoMap &info = impl->camera->controls(); @@ -513,7 +513,7 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, result.next = start; auto it = info.begin(); - for (skip = result.next; skip; skip--) + for (skip = result.next - offset; skip; skip--) it++; if (false) { From 9ff1c93ab1203a104a1593d9e8dfab10ebe3ad7a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Sat, 17 May 2025 09:16:24 -0400 Subject: [PATCH 0280/1014] media-sink: Set up ASHA source after setting transport state We need to make sure the state is available when the source starts, so that it does not assert in flush_data() --- spa/plugins/bluez5/media-sink.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 3020edfd9..f7748630b 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1541,6 +1541,14 @@ static int transport_start(struct impl *this) spa_loop_add_source(this->data_loop, &this->flush_source); } + this->resync = RESYNC_CYCLES; + this->flush_pending = false; + this->iso_pending = false; + + this->transport_started = true; + + if (this->transport->iso_io) + spa_loop_invoke(this->data_loop, do_start_iso_io, 0, NULL, 0, true, this); if (is_asha) { struct spa_bt_asha *asha = this->asha; @@ -1564,14 +1572,6 @@ static int transport_start(struct impl *this) spa_list_append(&asha_sinks, &this->asha_link); } - this->resync = RESYNC_CYCLES; - this->flush_pending = false; - this->iso_pending = false; - - this->transport_started = true; - - if (this->transport->iso_io) - spa_loop_invoke(this->data_loop, do_start_iso_io, 0, NULL, 0, true, this); return 0; } From b9a8bb15a4094e02108212e80bdfc83e65aec4be Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 19 May 2025 09:45:05 +0200 Subject: [PATCH 0281/1014] impl-link: fix feedback handling If a link is a feedback link, we just need to swap the input/output scheduling dependency. Don't swap input/output nodes (without swapping ports) because then we will be querying the wrong node/port pair when negotiating and cause errors. See #4706 --- src/pipewire/impl-link.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 5b38117e4..c764f445e 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -1536,14 +1536,8 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, impl->input.port = input; impl->output.port = output; - if (this->feedback) { - impl->input.node = output_node; - impl->output.node = input_node; - } - else { - impl->output.node = output_node; - impl->input.node = input_node; - } + impl->output.node = output_node; + impl->input.node = input_node; impl->input.mix = &this->rt.in_mix; impl->output.mix = &this->rt.out_mix; @@ -1569,8 +1563,12 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, pw_impl_port_recalc_tag(output); pw_impl_port_recalc_tag(input); - if (impl->output.node != impl->input.node) - this->peer = pw_node_peer_ref(impl->output.node, impl->input.node); + if (impl->output.node != impl->input.node) { + if (this->feedback) + this->peer = pw_node_peer_ref(impl->input.node, impl->output.node); + else + this->peer = pw_node_peer_ref(impl->output.node, impl->input.node); + } return this; From b80189c3dd13224d78af08c7f048fd89a9708cd9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 19 May 2025 16:48:04 +0200 Subject: [PATCH 0282/1014] adapter: handle -ENOTSUP for commands When using custom commands, the converter might return -ENOTSUP and we should ignore this. --- spa/plugins/audioconvert/audioadapter.c | 5 ++++- spa/plugins/videoconvert/videoadapter.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index ca44f7d64..025c5a661 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1058,7 +1058,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; } - if ((res = spa_node_send_command(this->target, command)) < 0) { + res = spa_node_send_command(this->target, command); + if (res == -ENOTSUP) + res = 0; + if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 55fa9f669..c0d651c24 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1109,7 +1109,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; } - if ((res = spa_node_send_command(this->target, command)) < 0) { + res = spa_node_send_command(this->target, command); + if (res == -ENOTSUP) + res = 0; + if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); From 5a54ecd8df4c5e75956b033e34d9142682622686 Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Mon, 19 May 2025 17:24:13 +0200 Subject: [PATCH 0283/1014] daemon/pipewire.conf.avail: Add snippet enabling module-raop --- src/daemon/pipewire.conf.avail/50-raop.conf.in | 4 ++++ src/daemon/pipewire.conf.avail/meson.build | 1 + 2 files changed, 5 insertions(+) create mode 100644 src/daemon/pipewire.conf.avail/50-raop.conf.in diff --git a/src/daemon/pipewire.conf.avail/50-raop.conf.in b/src/daemon/pipewire.conf.avail/50-raop.conf.in new file mode 100644 index 000000000..088f4a597 --- /dev/null +++ b/src/daemon/pipewire.conf.avail/50-raop.conf.in @@ -0,0 +1,4 @@ +context.modules = [ + # Use mDNS to detect and load module-raop-sink + { name = libpipewire-module-raop-discover } +] diff --git a/src/daemon/pipewire.conf.avail/meson.build b/src/daemon/pipewire.conf.avail/meson.build index 8b071ba42..b936a217b 100644 --- a/src/daemon/pipewire.conf.avail/meson.build +++ b/src/daemon/pipewire.conf.avail/meson.build @@ -1,6 +1,7 @@ conf_files = [ '10-rates.conf', '20-upmix.conf', + '50-raop.conf', ] foreach c : conf_files From 7dc1d28b2c9686e23eafae23c5b66a46f6b9845c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 20 May 2025 10:32:10 +0200 Subject: [PATCH 0284/1014] node: add User command and event Add a User command and event id with a String property called 'extra' to make it possible to send arbitrary User defined commands and events. Also makes it possible to make User commands in pw-cli. pw-cli c 86 User '{ extra="{ test: foo }" }' --- spa/include/spa/node/command.h | 12 ++++++++++++ spa/include/spa/node/event.h | 6 ++++++ spa/include/spa/node/type-info.h | 12 ++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/node/command.h b/spa/include/spa/node/command.h index 24c81a53e..e9e482d7a 100644 --- a/spa/include/spa/node/command.h +++ b/spa/include/spa/node/command.h @@ -36,12 +36,24 @@ enum spa_node_command { SPA_NODE_COMMAND_ParamEnd, /**< end a transaction */ SPA_NODE_COMMAND_RequestProcess,/**< Sent to a driver when some other node emitted * the RequestProcess event. */ + SPA_NODE_COMMAND_User, /**< User defined command */ }; #define SPA_NODE_COMMAND_ID(cmd) SPA_COMMAND_ID(cmd, SPA_TYPE_COMMAND_Node) #define SPA_NODE_COMMAND_INIT(id) SPA_COMMAND_INIT(SPA_TYPE_COMMAND_Node, id) +/* properties for SPA_TYPE_COMMAND_Node */ +enum spa_command_node { + SPA_COMMAND_NODE_START, + + SPA_COMMAND_NODE_START_User = 0x1000, + SPA_COMMAND_NODE_extra, /** extra info (String) */ + + SPA_COMMAND_NODE_START_CUSTOM = 0x1000000, +}; + + /** * \} */ diff --git a/spa/include/spa/node/event.h b/spa/include/spa/node/event.h index b975a7bfc..e70aad75f 100644 --- a/spa/include/spa/node/event.h +++ b/spa/include/spa/node/event.h @@ -23,6 +23,7 @@ enum spa_node_event { SPA_NODE_EVENT_RequestRefresh, SPA_NODE_EVENT_RequestProcess, /*< Ask the driver to start processing * the graph */ + SPA_NODE_EVENT_User, /* User defined event */ }; #define SPA_NODE_EVENT_ID(ev) SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Node) @@ -31,6 +32,11 @@ enum spa_node_event { /* properties for SPA_TYPE_EVENT_Node */ enum spa_event_node { SPA_EVENT_NODE_START, + + SPA_EVENT_NODE_START_User = 0x1000, + SPA_EVENT_NODE_extra, /** extra info (String) */ + + SPA_EVENT_NODE_START_CUSTOM = 0x1000000, }; /** diff --git a/spa/include/spa/node/type-info.h b/spa/include/spa/node/type-info.h index 5b956348b..4b92a4f9f 100644 --- a/spa/include/spa/node/type-info.h +++ b/spa/include/spa/node/type-info.h @@ -46,16 +46,20 @@ static const struct spa_type_info spa_type_node_event_id[] = { { SPA_NODE_EVENT_Buffering, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "Buffering", NULL }, { SPA_NODE_EVENT_RequestRefresh, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "RequestRefresh", NULL }, { SPA_NODE_EVENT_RequestProcess, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "RequestProcess", NULL }, + { SPA_NODE_EVENT_User, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "User", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_node_event[] = { { SPA_EVENT_NODE_START, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_EVENT_BASE, spa_type_node_event_id }, + + { SPA_EVENT_NODE_extra, SPA_TYPE_String, SPA_TYPE_INFO_NODE_EVENT_BASE "extra", NULL }, + { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_NodeCommand SPA_TYPE_INFO_COMMAND_BASE "Node" -#define SPA_TYPE_INFO_NODE_COMMAND_BASE SPA_TYPE_INFO_NodeCommand ":" +#define SPA_TYPE_INFO_NODE_COMMAND_BASE SPA_TYPE_INFO_NodeCommand ":" static const struct spa_type_info spa_type_node_command_id[] = { { SPA_NODE_COMMAND_Suspend, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Suspend", NULL }, @@ -69,11 +73,15 @@ static const struct spa_type_info spa_type_node_command_id[] = { { SPA_NODE_COMMAND_ParamBegin, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "ParamBegin", NULL }, { SPA_NODE_COMMAND_ParamEnd, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "ParamEnd", NULL }, { SPA_NODE_COMMAND_RequestProcess, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "RequestProcess", NULL }, + { SPA_NODE_COMMAND_User, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "User", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_node_command[] = { - { 0, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_COMMAND_BASE, spa_type_node_command_id }, + { SPA_COMMAND_NODE_START, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_COMMAND_BASE, spa_type_node_command_id }, + + { SPA_COMMAND_NODE_extra, SPA_TYPE_String, SPA_TYPE_INFO_NODE_COMMAND_BASE "extra", NULL }, + { 0, 0, NULL, NULL }, }; From 12f8ca664b91369f411c0037512a76e8713a2c7a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 20 May 2025 10:41:59 +0200 Subject: [PATCH 0285/1014] adapter: log command errors when no converter --- spa/plugins/audioconvert/audioadapter.c | 2 +- spa/plugins/videoconvert/videoadapter.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 025c5a661..e9ea63247 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1059,7 +1059,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman } res = spa_node_send_command(this->target, command); - if (res == -ENOTSUP) + if (res == -ENOTSUP && this->target != this->follower) res = 0; if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index c0d651c24..568e72cab 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1110,7 +1110,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman } res = spa_node_send_command(this->target, command); - if (res == -ENOTSUP) + if (res == -ENOTSUP && this->target != this->follower) res = 0; if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", From f7eab4172ef9dc3f478ce1b69bb238cd981136a1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 20 May 2025 11:47:10 +0200 Subject: [PATCH 0286/1014] client-node: let all events go to the node Just pass all events to the node and only error out when they return an error value. Make sure we pass the result values of the command around. --- src/modules/module-client-node/remote-node.c | 17 +++++++++-------- src/pipewire/impl-node.c | 7 ++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/modules/module-client-node/remote-node.c b/src/modules/module-client-node/remote-node.c index 8b5c7daf3..52134eaa4 100644 --- a/src/modules/module-client-node/remote-node.c +++ b/src/modules/module-client-node/remote-node.c @@ -481,15 +481,16 @@ static int client_node_command(void *_data, const struct spa_command *command) pw_proxy_error(proxy, res, "suspend failed"); } break; - case SPA_NODE_COMMAND_RequestProcess: - res = pw_impl_node_send_command(node, command); - break; default: - pw_log_warn("unhandled node command %d (%s)", id, - spa_debug_type_find_name(spa_type_node_command_id, id)); - res = -ENOTSUP; - pw_proxy_errorf(proxy, res, "command %d (%s) not supported", id, - spa_debug_type_find_name(spa_type_node_command_id, id)); + res = pw_impl_node_send_command(node, command); + if (res < 0) { + pw_log_warn("node command %d (%s) error: %s (%d)", id, + spa_debug_type_find_name(spa_type_node_command_id, id), + spa_strerror(res), res); + pw_proxy_errorf(proxy, res, "command %d (%s) error: %s (%d)", id, + spa_debug_type_find_name(spa_type_node_command_id, id), + spa_strerror(res), res); + } } return res; } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 351f98b06..e5636d561 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -660,19 +660,20 @@ static int node_send_command(void *object, const struct spa_command *command) struct resource_data *data = object; struct pw_impl_node *node = data->node; uint32_t id = SPA_NODE_COMMAND_ID(command); + int res; pw_log_debug("%p: got command %d (%s)", node, id, spa_debug_type_find_name(spa_type_node_command_id, id)); switch (id) { case SPA_NODE_COMMAND_Suspend: - suspend_node(node); + res = suspend_node(node); break; default: - spa_node_send_command(node->node, command); + res = spa_node_send_command(node->node, command); break; } - return 0; + return res; } static const struct pw_node_methods node_methods = { From ccb7a51c3a8f1c7b3e073bd026aa1f2513eaccdb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 21 May 2025 15:19:23 +0200 Subject: [PATCH 0287/1014] filter-graph: add support for lv2 State If the lv2 plugin supports State restore, use it to load state from the config section in the lv2 plugin definition. --- spa/plugins/filter-graph/lv2_plugin.c | 80 +++++++++++++++++++++++++-- src/modules/module-filter-chain.c | 49 ++++++++++++++++ 2 files changed, 124 insertions(+), 5 deletions(-) diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 47566bc9f..0173ab4b4 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ #include #include #include + #include #include #include @@ -27,6 +29,7 @@ #include #include #include + #include #include #include @@ -101,6 +104,7 @@ struct context { LilvNode *boundedBlockLength; LilvNode* worker_schedule; LilvNode* worker_iface; + LilvNode* state_iface; URITable uri_table; LV2_URID_Map map; @@ -169,6 +173,7 @@ static struct context *context_new(void) c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength); c->worker_schedule = lilv_new_uri(c->world, LV2_WORKER__schedule); c->worker_iface = lilv_new_uri(c->world, LV2_WORKER__interface); + c->state_iface = lilv_new_uri(c->world, LV2_STATE__interface); c->map.handle = &c->uri_table; c->map.map = uri_table_map; @@ -234,9 +239,10 @@ struct instance { LV2_Options_Option options[6]; LV2_Feature options_feature; - const LV2_Feature *features[7]; + const LV2_Feature *features[10]; const LV2_Worker_Interface *work_iface; + const LV2_State_Interface *state_iface; int32_t block_length; LV2_Atom empty_atom; @@ -278,6 +284,57 @@ work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data return LV2_WORKER_SUCCESS; } +struct state_data { + struct instance *i; + const char *config; + char *tmp; +}; + +static const void *state_retrieve_function(LV2_State_Handle handle, + uint32_t key, size_t *size, uint32_t *type, uint32_t *flags) +{ + struct state_data *sd = (struct state_data*)handle; + struct plugin *p = sd->i->p; + struct context *c = p->c; + const char *uri = c->unmap.unmap(c->unmap.handle, key), *val; + struct spa_json it[3]; + char k[strlen(uri)+3]; + int len; + + if (sd->config == NULL) { + spa_log_info(p->log, "lv2: restore %d %s without a config", key, uri); + return NULL; + } + + if (spa_json_begin_object(&it[0], sd->config, strlen(sd->config)) <= 0) { + spa_log_error(p->log, "lv2: config must be an object"); + return NULL; + } + + while ((len = spa_json_object_next(&it[0], k, sizeof(k), &val)) > 0) { + if (!spa_streq(k, uri)) + continue; + + if (spa_json_is_container(val, len)) + if ((len = spa_json_container_len(&it[0], val, len)) <= 0) + return NULL; + + sd->tmp = realloc(sd->tmp, len+1); + spa_json_parse_stringn(val, len, sd->tmp, len+1); + + spa_log_info(p->log, "lv2: restore %d %s %s", key, uri, sd->tmp); + if (size) + *size = strlen(sd->tmp); + if (type) + *type = 0; + if (flags) + *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE; + return sd->tmp; + } + spa_log_info(p->log, "lv2: restore %d %s not found in config", key, uri); + return NULL; +} + static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { @@ -328,9 +385,11 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s c->atom_Float, &fsample_rate }; i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }; - i->options_feature.URI = LV2_OPTIONS__options; - i->options_feature.data = i->options; - i->features[n_features++] = &i->options_feature; + i->options_feature.URI = LV2_OPTIONS__options; + i->options_feature.data = i->options; + i->features[n_features++] = &i->options_feature; + i->features[n_features++] = NULL; + spa_assert(n_features < SPA_N_ELEMENTS(i->features)); i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features); if (i->instance == NULL) { @@ -341,19 +400,30 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s i->work_iface = (const LV2_Worker_Interface*) lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface); } + if (lilv_plugin_has_extension_data(p->p, c->state_iface)) { + i->state_iface = (const LV2_State_Interface*) + lilv_instance_get_extension_data(i->instance, LV2_STATE__interface); + } for (n = 0; n < desc->n_ports; n++) { const LilvPort *port = lilv_plugin_get_port_by_index(p->p, n); if (lilv_port_is_a(p->p, port, c->atom_AtomPort)) { lilv_instance_connect_port(i->instance, n, &i->empty_atom); } } - + if (i->state_iface && i->state_iface->restore) { + struct state_data sd = { .i = i, .config = config, .tmp = NULL }; + i->state_iface->restore(i->instance->lv2_handle, state_retrieve_function, + &sd, 0, i->features); + free(sd.tmp); + } return i; } static void lv2_cleanup(void *instance) { struct instance *i = instance; + spa_loop_invoke(i->p->data_loop, NULL, 0, NULL, 0, true, NULL); + spa_loop_invoke(i->p->main_loop, NULL, 0, NULL, 0, true, NULL); lilv_instance_free(i->instance); free(i); } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 8b4e54ac0..6b72ea025 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -110,10 +110,59 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * - `config` contains a filter specific configuration section. Some plugins need * this. (convolver, sofa, delay, ...) + * - For lv2, the config can contain a set of state key/value pairs. If the lv2 + * plugin supports the LV2_STATE__interface, these values will be provided for + * the given keys. * - `control` contains the initial values for the control ports of the filter. * normally these are given with the port name but it is also possible * to give the control index as the key. * + * Some examples ladspa and lv2 plugins: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * # an example ladspa plugin + * type = ladspa + * name = pitch + * plugin = "/usr/lib64/ladspa/ladspa-rubberband.so" + * label = "rubberband-r3-pitchshifter-mono" + * control = { + * # controls are using the ladspa port names as seen in analyseplugin + * "Semitones" = -3 + * } + * } + * { + * # an example lv2 plugin + * type = lv2 + * name = pitch + * plugin = "http://breakfastquay.com/rdf/lv2-rubberband#mono" + * control = { + * # controls are using the lv2 symbol as seen with lv2info + * "semitones" = -3 + * } + * } + * { + * # an example lv2 plugin with a state + * type = lv2 + * name = neural + * plugin = "http://aidadsp.cc/plugins/aidadsp-bundle/rt-neural-generic" + * control = { + * # use the port symbols as seen with lv2info + * PRESENCE = 1.0 + * } + * config = { + * # the config contains state keys and values + * "http://aidadsp.cc/plugins/aidadsp-bundle/rt-neural-generic#json" = + * "/usr/lib64/lv2/rt-neural-generic.lv2/models/deer ink studios/tw40_blues_solo_deerinkstudios.json" + * } + * } + * } + * ... + * } + *\endcode + * * ### Links * * Links can be made between ports of nodes. The `portname` is given as From 5e81ff8ede7d708ecf8c4bdad269a168d91d192e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 21 May 2025 15:23:00 +0200 Subject: [PATCH 0288/1014] filter-chain: manage graph from source only Only react to the capture stream state change and format changes. The playback and capture streams change state somewhat concurrently and so the graph state might not be consistent. Because only the capture stream will trigger the playback stream and start the processing, we can safely react to capture stream changes only. --- src/modules/module-filter-chain.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 6b72ea025..e04bb1590 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1084,7 +1084,7 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param, pw_stream_update_params(impl->playback, params, 1); } -static void state_changed(void *data, enum pw_stream_state old, +static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; @@ -1159,7 +1159,9 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod * struct spa_audio_info_raw info; spa_zero(info); if (param == NULL) { - spa_filter_graph_deactivate(graph); + pw_log_info("module %p: filter deactivate", impl); + if (direction == SPA_DIRECTION_INPUT) + spa_filter_graph_deactivate(graph); impl->rate = 0; } else { if ((res = spa_format_audio_raw_parse(param, &info)) < 0) @@ -1200,7 +1202,7 @@ static const struct pw_stream_events in_stream_events = { .destroy = capture_destroy, .process = capture_process, .io_changed = io_changed, - .state_changed = state_changed, + .state_changed = capture_state_changed, .param_changed = capture_param_changed }; @@ -1222,7 +1224,6 @@ static const struct pw_stream_events out_stream_events = { .destroy = playback_destroy, .process = playback_process, .io_changed = io_changed, - .state_changed = state_changed, .param_changed = playback_param_changed, }; From d37f21323682c14563fb849f2a37be1b66ad025c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 23 May 2025 16:40:20 +0200 Subject: [PATCH 0289/1014] docs: fix some typos --- doc/dox/internals/midi.dox | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/dox/internals/midi.dox b/doc/dox/internals/midi.dox index 5a266b2b2..4c86c516b 100644 --- a/doc/dox/internals/midi.dox +++ b/doc/dox/internals/midi.dox @@ -105,11 +105,11 @@ filtering out the \ref SPA_CONTROL_Midi, \ref SPA_CONTROL_OSC and converted to control messages in a similar way. Normally, all MIDI and UMP messages are converted to MIDI1 jack events unless -the JACK port was created with an explcit "32 bits raw UMP" format or with +the JACK port was created with an explcit "32 bit raw UMP" format or with the JackPortIsMIDI2 flag, in which case the raw UMP is passed to the JACK application directly. For output ports, the JACK events are assumed to be MIDI1 and converted to UMP unless the port -has the "32 bit raw UMP" formati or the JackPortIsMIDI2 flag, in which case +has the "32 bit raw UMP" format or the JackPortIsMIDI2 flag, in which case the UMP messages are simply passed on. There is a 1 to 1 mapping between the JACK events and control From 617f1b8a38b0b20f43b3e5ce4b69bf8ef4198fbd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 23 May 2025 16:41:00 +0200 Subject: [PATCH 0290/1014] pod: add bytes start/append/end functions Add functions to dynamically start and build a bytes pod. --- spa/include/spa/pod/builder.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 231c1ebf2..cac474540 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -334,6 +334,31 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); } +SPA_API_POD_BUILDER uint32_t +spa_pod_builder_bytes_start(struct spa_pod_builder *builder) +{ + uint32_t offset = builder->state.offset; + const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(0); + spa_pod_builder_raw(builder, &p, sizeof(p)); + return offset; +} +SPA_API_POD_BUILDER int +spa_pod_builder_bytes_append(struct spa_pod_builder *builder, uint32_t offset, + const void *data, uint32_t size) +{ + int res = spa_pod_builder_raw(builder, data, size); + struct spa_pod *pod = spa_pod_builder_deref(builder, offset); + if (pod) + pod->size += size; + return res; +} + +SPA_API_POD_BUILDER int +spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset) +{ + return spa_pod_builder_pad(builder, builder->state.offset); +} + #define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) SPA_API_POD_BUILDER int From 564c9b1ba508f350b51a565c13c0cfc2ed2ea94f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 23 May 2025 16:46:13 +0200 Subject: [PATCH 0291/1014] Use "8 bit raw midi" for control ports again There is no need to encode the potential format in the format.dsp of control ports, this is just for legacy compatibility with JACK apps. The actual format can be negotiated with the types field. Fixes midi port visibility with apps compiled against 1.2, such as JACK apps in flatpaks. --- pipewire-jack/src/pipewire-jack.c | 20 +------------------ spa/plugins/alsa/alsa-seq-bridge.c | 2 +- spa/plugins/audioconvert/audioconvert.c | 2 +- spa/plugins/bluez5/midi-node.c | 4 ++-- .../videoconvert/videoconvert-ffmpeg.c | 2 +- src/examples/midi-src.c | 2 +- src/modules/module-ffado-driver.c | 2 +- src/modules/module-jack-tunnel.c | 2 +- src/modules/module-netjack2-driver.c | 2 +- src/modules/module-netjack2-manager.c | 2 +- src/modules/module-rtp/stream.c | 2 +- src/modules/module-vban/stream.c | 2 +- src/pipewire/filter.c | 4 +++- src/pipewire/stream.c | 2 +- src/tools/pw-cat.c | 2 +- 15 files changed, 18 insertions(+), 34 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 3ba2d7e3c..fc5d28bc2 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -3466,24 +3466,6 @@ static const char* type_to_string(jack_port_type_id_t type_id) } } -static const char* type_to_format_dsp(jack_port_type_id_t type_id) -{ - switch(type_id) { - case TYPE_ID_AUDIO: - return JACK_DEFAULT_AUDIO_TYPE; - case TYPE_ID_VIDEO: - return JACK_DEFAULT_VIDEO_TYPE; - case TYPE_ID_OSC: - return JACK_DEFAULT_OSC_TYPE; - case TYPE_ID_MIDI: - return JACK_DEFAULT_MIDI_TYPE; - case TYPE_ID_UMP: - return JACK_DEFAULT_UMP_TYPE; - default: - return NULL; - } -} - static bool type_is_dsp(jack_port_type_id_t type_id) { switch(type_id) { @@ -5546,7 +5528,7 @@ jack_port_t * jack_port_register (jack_client_t *client, spa_list_init(&p->mix); - pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_format_dsp(type_id)); + pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_string(type_id)); pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); if (flags > 0x1f) { pw_properties_setf(p->props, PW_KEY_PORT_EXTRA, diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 7ec39321c..68e6c91a8 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -275,7 +275,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f snprintf(alias, sizeof(alias), "%s:%s", client_name, port_name); clean_name(alias); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index f30de33ae..e5c0daf9f 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -375,7 +375,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index b5fd179ea..cfba2bfcf 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -2024,13 +2024,13 @@ impl_init(const struct spa_handle_factory *factory, for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; static const struct spa_dict_item in_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), }; static const struct spa_dict_item out_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 889117820..a53591010 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -240,7 +240,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); diff --git a/src/examples/midi-src.c b/src/examples/midi-src.c index edcaa0f08..12f86d6f2 100644 --- a/src/examples/midi-src.c +++ b/src/examples/midi-src.c @@ -213,7 +213,7 @@ int main(int argc, char *argv[]) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 4345e5a19..1c9c7c590 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -772,7 +772,7 @@ static int make_stream_ports(struct stream *s) break; case ffado_stream_type_midi: props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, port->name, PW_KEY_PORT_PHYSICAL, "true", PW_KEY_PORT_TERMINAL, "true", diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index c8dabf04e..b69ec7704 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -540,7 +540,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "midi_%s_%d", prefix, i - s->info.channels + 1); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, name, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 0aa9a85cd..011052e7b 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -452,7 +452,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_AUDIO_CHANNEL, name, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 2cef87218..fe482fd94 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -614,7 +614,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_PHYSICAL, "true", PW_KEY_AUDIO_CHANNEL, name, NULL); diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index f4d3fa3b8..cbd3ee994 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -390,7 +390,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index efa5af370..10eb34a82 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -307,7 +307,7 @@ struct vban_stream *vban_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index c4130322a..4c1bb6870 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1855,8 +1855,10 @@ void *pw_filter_add_port(struct pw_filter *filter, add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi); else if (spa_streq(str, "8 bit raw control")) add_control_dsp_port_params(impl, p, 0); - else if (spa_streq(str, "32 bit raw UMP")) + else if (spa_streq(str, "32 bit raw UMP")) { add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + } } /* then override with user provided if any */ if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 29aa9afe8..2ee63fae1 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -2055,7 +2055,7 @@ pw_stream_connect(struct pw_stream *stream, pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, str); else if (impl->media_type == SPA_MEDIA_TYPE_application && impl->media_subtype == SPA_MEDIA_SUBTYPE_control) - pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); if (pw_properties_get(impl->port_props, PW_KEY_PORT_GROUP) == NULL) pw_properties_set(impl->port_props, PW_KEY_PORT_GROUP, "stream.0"); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 36a78981e..928afed0e 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1985,7 +1985,7 @@ int main(int argc, char *argv[]) SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); + pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); break; case TYPE_DSD: { From 737ca4ee7befd47130d9b8181a231fdaa0d69ed4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 23 May 2025 16:49:17 +0200 Subject: [PATCH 0292/1014] tools: add sysex play option in pw-cat This makes it possible to upload raw midi (sysex) into the graph. --- src/tools/meson.build | 1 + src/tools/pw-cat.c | 62 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/tools/meson.build b/src/tools/meson.build index 8fbc8e210..103b48adc 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -59,6 +59,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found() 'pw-midirecord', 'pw-dsdplay', 'pw-encplay', + 'pw-sysex', ] pw_cat = executable('pw-cat', diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 928afed0e..5fdb9aa93 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -102,6 +102,7 @@ struct data { #define TYPE_DSD 2 #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #define TYPE_ENCODED 3 +#define TYPE_SYSEX 4 #endif int data_type; bool raw; @@ -162,6 +163,9 @@ struct data { int64_t accumulated_excess_playtime; } encoded; #endif + struct { + FILE *file; + } sysex; }; #define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)" @@ -942,6 +946,7 @@ static const struct option long_options[] = { { "midi", no_argument, NULL, 'm' }, { "dsd", no_argument, NULL, 'd' }, { "encoded", no_argument, NULL, 'o' }, + { "sysex", no_argument, NULL, 's' }, { "remote", required_argument, NULL, 'R' }, @@ -1020,6 +1025,7 @@ static void show_usage(const char *name, bool is_error) #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION " -o, --encoded Encoded mode\n" #endif + " -s, --sysex SysEx mode\n" "\n"), fp); } } @@ -1163,6 +1169,47 @@ static int setup_midifile(struct data *data) return 0; } +static int sysex_play(struct data *d, void *dst, unsigned int n_frames, bool *null_frame) +{ + struct spa_pod_builder b; + struct spa_pod_frame f; + size_t size, to_read = n_frames - 64; + uint8_t bytes[to_read]; + + spa_zero(b); + spa_pod_builder_init(&b, dst, n_frames); + + spa_pod_builder_push_sequence(&b, &f, 0); + spa_pod_builder_control(&b, 0, SPA_CONTROL_Midi); + + size = fread(bytes, 1, to_read, d->sysex.file); + + spa_pod_builder_bytes(&b, bytes, size); + spa_pod_builder_pop(&b, &f); + + return b.state.offset; +} + +static int setup_sysex(struct data *data) +{ + if (data->mode == mode_record) + return -ENOTSUP; + + data->sysex.file = fopen(data->filename, "r"); + if (data->sysex.file == NULL) { + fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename); + return -errno; + } + + if (data->verbose) + fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename); + + data->fill = sysex_play; + data->stride = 1; + + return 0; +} + struct dsd_layout_info { uint32_t type; struct spa_audio_layout_info info; @@ -1672,6 +1719,9 @@ int main(int argc, char *argv[]) } else if (spa_streq(prog, "pw-midirecord")) { data.mode = mode_record; data.data_type = TYPE_MIDI; + } else if (spa_streq(prog, "pw-sysex")) { + data.mode = mode_playback; + data.data_type = TYPE_SYSEX; } else if (spa_streq(prog, "pw-dsdplay")) { data.mode = mode_playback; data.data_type = TYPE_DSD; @@ -1697,9 +1747,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:a", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:a", long_options, NULL)) != -1) { #endif switch (c) { @@ -1742,6 +1792,9 @@ int main(int argc, char *argv[]) data.data_type = TYPE_ENCODED; break; #endif + case 's': + data.data_type = TYPE_SYSEX; + break; case 'R': data.remote_name = optarg; @@ -1831,6 +1884,7 @@ int main(int argc, char *argv[]) if (!data.media_type) { switch (data.data_type) { case TYPE_MIDI: + case TYPE_SYSEX: data.media_type = DEFAULT_MIDI_MEDIA_TYPE; break; default: @@ -1918,6 +1972,9 @@ int main(int argc, char *argv[]) ret = setup_encodedfile(&data); break; #endif + case TYPE_SYSEX: + ret = setup_sysex(&data); + break; default: ret = -ENOTSUP; break; @@ -1980,6 +2037,7 @@ int main(int argc, char *argv[]) break; } case TYPE_MIDI: + case TYPE_SYSEX: params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), From e81fb773224fb5cd4b9399393ca2d2bfab7f4273 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sun, 25 May 2025 03:57:38 +0200 Subject: [PATCH 0293/1014] gst: src: Change DEFAULT_MIN_BUFFERS back to 1 The change from 1 to 8 was done without justification in the commit message and possibly for debug purposes. Unfortunately it breaks negotiation with the libcamera virtual pipeline, which defaults to 4 buffers. Set the the value to 1 again as successful negotiation - even with an unusually low number of buffers - is usually more desirable than an error. Fixes: 98b7dc7c0 ("gst: don't do set_caps from the pipewire callback") --- src/gst/gstpipewiresrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 14592f0d5..95a724f46 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -41,7 +41,7 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define GST_CAT_DEFAULT pipewire_src_debug #define DEFAULT_ALWAYS_COPY false -#define DEFAULT_MIN_BUFFERS 8 +#define DEFAULT_MIN_BUFFERS 1 #define DEFAULT_MAX_BUFFERS INT32_MAX #define DEFAULT_RESEND_LAST false #define DEFAULT_KEEPALIVE_TIME 0 From 96007dc5760a43692440a67451e18ad92136be71 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 26 May 2025 19:24:23 +0200 Subject: [PATCH 0294/1014] pw-cat: mode SYSEX mode out of ffmpeg define --- src/tools/pw-cat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 5fdb9aa93..4e7e6954a 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -102,8 +102,8 @@ struct data { #define TYPE_DSD 2 #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #define TYPE_ENCODED 3 -#define TYPE_SYSEX 4 #endif +#define TYPE_SYSEX 4 int data_type; bool raw; const char *remote_name; From 417a72365e3f26ac94ed08caeba708550e5bee95 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 27 May 2025 09:30:52 +0200 Subject: [PATCH 0295/1014] adapter: negotiate from target to follower Now that the filter functions prefer the filter default value, use the target object as a filter for buffer allocation as well. --- spa/plugins/audioconvert/audioadapter.c | 28 ++++++++++++------------- spa/plugins/videoconvert/videoadapter.c | 28 ++++++++++++------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index e9ea63247..971e0f765 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -449,27 +449,27 @@ static int negotiate_buffers(struct impl *this) state = 0; param = NULL; - if ((res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, + if ((res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); + debug_params(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; - if ((res = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + if ((res = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); return -ENOTSUP; } if (param == NULL) @@ -503,7 +503,7 @@ static int negotiate_buffers(struct impl *this) if (this->async) buffers = SPA_MAX(2u, buffers); - spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); @@ -966,11 +966,11 @@ static int negotiate_format(struct impl *this) /* The target has been negotiated on its other ports and so it can propose * a passthrough format or an ideal conversion. We use the suggestions of the * target to find the best follower format */ - for (fstate = 0;;) { + for (tstate = 0;;) { format = NULL; res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &fstate, + SPA_PARAM_EnumFormat, &tstate, NULL, &format, &b); if (res == -ENOENT) @@ -981,10 +981,10 @@ static int negotiate_format(struct impl *this) if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); - tstate = 0; + fstate = 0; fres = node_port_enum_params_sync(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, &tstate, + SPA_PARAM_EnumFormat, &fstate, format, &format, &b); if (fres == 0 && res == 1) continue; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 568e72cab..1f1585e30 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -451,27 +451,27 @@ static int negotiate_buffers(struct impl *this) state = 0; param = NULL; - if ((res = node_port_enum_params_sync(this, this->follower, - this->direction, 0, + if ((res = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_Buffers, param, "follower buffers", res); + debug_params(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; - if ((res = node_port_enum_params_sync(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, + if ((res = node_port_enum_params_sync(this, this->follower, + this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->target, - SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_Buffers, param, "convert buffers", res); + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_Buffers, param, "follower buffers", res); return -ENOTSUP; } if (param == NULL) @@ -507,7 +507,7 @@ static int negotiate_buffers(struct impl *this) if (this->async) buffers = SPA_MAX(2u, buffers); - spa_log_debug(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", + spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); @@ -1017,11 +1017,11 @@ static int negotiate_format(struct impl *this) /* The target has been negotiated on its other ports and so it can propose * a passthrough format or an ideal conversion. We use the suggestions of the * target to find the best follower format */ - for (fstate = 0;;) { + for (tstate = 0;;) { format = NULL; res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_PARAM_EnumFormat, &fstate, + SPA_PARAM_EnumFormat, &tstate, NULL, &format, &b); if (res == -ENOENT) @@ -1032,10 +1032,10 @@ static int negotiate_format(struct impl *this) if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); - tstate = 0; + fstate = 0; fres = node_port_enum_params_sync(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, &tstate, + SPA_PARAM_EnumFormat, &fstate, format, &format, &b); if (fres == 0 && res == 1) continue; From 548fa0ec48912ae7d6aa5103963cdd0b155f7a1d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 27 May 2025 15:00:43 +0200 Subject: [PATCH 0296/1014] adapter:handle -ENOENT when enumerating buffers When the follower has no buffer suggestion, it can return -ENOENT, which should not generate an error but simply use the converter buffer suggestion instead. --- spa/plugins/audioconvert/audioadapter.c | 8 ++++++-- spa/plugins/videoconvert/videoadapter.c | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 971e0f765..927b0ddcc 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -468,9 +468,13 @@ static int negotiate_buffers(struct impl *this) this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->follower, this->direction, 0, + if (res == -ENOENT) + res = 0; + else { + debug_params(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, param, "follower buffers", res); - return -ENOTSUP; + return res < 0 ? res : -ENOTSUP; + } } if (param == NULL) return -ENOTSUP; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 1f1585e30..eefd7e3f9 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -470,9 +470,13 @@ static int negotiate_buffers(struct impl *this) this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->follower, this->direction, 0, + if (res == -ENOENT) + res = 0; + else { + debug_params(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, param, "follower buffers", res); - return -ENOTSUP; + return res < 0 ? res : -ENOTSUP; + } } if (param == NULL) return -ENOTSUP; From e126f9bcbf5ca27df3410343a6dea658463dd3e2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 27 May 2025 15:38:51 +0200 Subject: [PATCH 0297/1014] adapter: only clear the NEED_CONFIGURE flag when mode != none As long as we are in the 'none' PortConfig mode, we set the NEED_CONFIGURE flag. This fixes early start in audioadpter nodes because PortConfig is set to none at init time and this used to clear the NEED_CONFIGURE flag, which would start the node before the session manager could to a PortConfig and cause a -22 error. --- spa/plugins/audioconvert/audioadapter.c | 3 ++- spa/plugins/videoconvert/videoadapter.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 927b0ddcc..e9595aa1d 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -770,7 +770,8 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m link_io(this); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; - SPA_FLAG_CLEAR(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE); + SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE, + this->mode == SPA_PARAM_PORT_CONFIG_MODE_none); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); this->params[IDX_Props].user++; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index eefd7e3f9..9eae268e1 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -778,7 +778,8 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m link_io(this); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; - SPA_FLAG_CLEAR(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE); + SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE, + this->mode == SPA_PARAM_PORT_CONFIG_MODE_none); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); this->params[IDX_Props].user++; @@ -2045,8 +2046,7 @@ impl_init(const struct spa_handle_factory *factory, SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.flags = SPA_NODE_FLAG_RT | - 0; - //SPA_NODE_FLAG_NEED_CONFIGURE; + SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); From 51d9bdd9cb82d4ab1dedd5f5dfad03e0544a3c36 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Wed, 28 May 2025 09:50:43 +0200 Subject: [PATCH 0298/1014] pod: declare offset as unused in spa_pod_builder_bytes_end() Fixes compiler warning: /usr/include/spa-0.2/spa/pod/builder.h:357:69: error: unused parameter 'offset' [-Werror=unused-parameter] --- spa/include/spa/pod/builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index cac474540..8bd83b0ba 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -354,7 +354,7 @@ spa_pod_builder_bytes_append(struct spa_pod_builder *builder, uint32_t offset, } SPA_API_POD_BUILDER int -spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset) +spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset SPA_UNUSED) { return spa_pod_builder_pad(builder, builder->state.offset); } From 3f99c6f25968e41565811381a45f9140750610ac Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 28 May 2025 15:00:50 +0200 Subject: [PATCH 0299/1014] filter-graph: use new map/unmap URIs Don't use the legacy ones. --- spa/plugins/filter-graph/lv2_plugin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 0173ab4b4..418f0674b 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -177,11 +177,11 @@ static struct context *context_new(void) c->map.handle = &c->uri_table; c->map.map = uri_table_map; - c->map_feature.URI = LV2_URID_MAP_URI; + c->map_feature.URI = LV2_URID__map; c->map_feature.data = &c->map; c->unmap.handle = &c->uri_table; c->unmap.unmap = uri_table_unmap; - c->unmap_feature.URI = LV2_URID_UNMAP_URI; + c->unmap_feature.URI = LV2_URID__unmap; c->unmap_feature.data = &c->unmap; c->atom_Int = context_map(c, LV2_ATOM__Int); From a1be639894518c594e608afc3891e556e0d23f5c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 28 May 2025 15:02:31 +0200 Subject: [PATCH 0300/1014] filter-graph: add log feature to lv2 plugins --- spa/plugins/filter-graph/lv2_plugin.c | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 418f0674b..7628d7650 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -23,6 +23,7 @@ #include #include #include + #include # else @@ -32,6 +33,7 @@ #include #include #include + #include # endif @@ -236,6 +238,8 @@ struct instance { LilvInstance *instance; LV2_Worker_Schedule work_schedule; LV2_Feature work_schedule_feature; + LV2_Log_Log log; + LV2_Feature log_feature; LV2_Options_Option options[6]; LV2_Feature options_feature; @@ -335,6 +339,25 @@ static const void *state_retrieve_function(LV2_State_Handle handle, return NULL; } +SPA_PRINTF_FUNC(3, 0) +static int log_vprintf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, va_list ap) +{ + struct instance *i = (struct instance*)handle; + spa_log_logv(i->p->log, SPA_LOG_LEVEL_INFO, __FILE__,__LINE__,__func__, fmt, ap); + return 0; +} + +SPA_PRINTF_FUNC(3, 4) +static int log_printf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...) +{ + va_list args; + int ret; + va_start(args, fmt); + ret = log_vprintf(handle, type, fmt, args); + va_end(args); + return ret; +} + static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { @@ -355,6 +378,12 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s i->block_length = 1024; i->desc = d; i->p = p; + i->log.handle = i; + i->log.printf = log_printf; + i->log.vprintf = log_vprintf; + i->log_feature.URI = LV2_LOG__log; + i->log_feature.data = &i->log; + i->features[n_features++] = &i->log_feature; i->features[n_features++] = &c->map_feature; i->features[n_features++] = &c->unmap_feature; i->features[n_features++] = &buf_size_features[0]; From d9b742cfdad8cab7552fa12b7fff0c4e72016e49 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 28 May 2025 15:15:25 +0200 Subject: [PATCH 0301/1014] bluez: avoid some compiler warnings --- spa/plugins/bluez5/media-source.c | 2 +- spa/plugins/bluez5/sco-io.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index d6ac64bc0..9d09fd60e 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -506,7 +506,7 @@ static void media_on_ready_read(struct spa_source *source) int32_t size_read, decoded; uint32_t avail; uint64_t dt; - uint64_t now; + uint64_t now = 0; /* make sure the source is an input */ if ((source->rmask & SPA_IO_IN) == 0) { diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index bf41df645..2db7798dd 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -97,7 +97,7 @@ static void sco_io_on_ready(struct spa_source *source) if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { int res; - uint64_t rx_time; + uint64_t rx_time = 0; read_again: res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time); From 2fe77c47e174e24ffa388b438448f0589f4662ae Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Tue, 20 May 2025 11:55:30 +0200 Subject: [PATCH 0302/1014] spa: tools: spa-inspect: Output more detailed dlopen and dlsym errors When writing a custom SPA plugin, it is very useful to get more detailed errors related to libdl. One common error is that a symbol is not present because a related library was not properly linked into the plugin. With this change, the error will tell the user about the exact missing symbol. --- spa/tools/spa-inspect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/tools/spa-inspect.c b/spa/tools/spa-inspect.c index fd562b5f0..dd3ff752f 100644 --- a/spa/tools/spa-inspect.c +++ b/spa/tools/spa-inspect.c @@ -277,11 +277,11 @@ int main(int argc, char *argv[]) data.n_support = 3; if ((handle = dlopen(argv[1], RTLD_NOW)) == NULL) { - printf("can't load %s\n", argv[1]); + printf("can't load %s: %s\n", argv[1], dlerror()); return -1; } if ((enum_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { - printf("can't find function\n"); + printf("can't find \"%s\" function: %s\n", SPA_HANDLE_FACTORY_ENUM_FUNC_NAME, dlerror()); return -1; } From 023525eca2afdf0368222458e4ceb8a053d78b78 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Tue, 20 May 2025 12:03:14 +0200 Subject: [PATCH 0303/1014] rtp: aes67: Document sess.ts-direct property and set it to true for AES67 --- src/daemon/pipewire-aes67.conf.in | 5 +++++ src/modules/module-rtp-source.c | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/daemon/pipewire-aes67.conf.in b/src/daemon/pipewire-aes67.conf.in index 479c12c9b..167b89a6f 100644 --- a/src/daemon/pipewire-aes67.conf.in +++ b/src/daemon/pipewire-aes67.conf.in @@ -128,6 +128,11 @@ context.modules = [ # This property is used if you aren't using ptp4l 4 sess.ts-refclk = "ptp=traceable" sess.ts-offset = 0 + # Directly synchronize output against the PTP-synced driver using the RTP timestamps + # This can be set to true if the reference clocks are the same; it then makes the + # synchronization more robust against transport delay variations and can help lower + # latency + sess.ts-direct = false # You can adjust the latency buffering here. Use integer values only sess.latency.msec = 3 audio.format = "S24BE" diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 98a26065a..a3694197e 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -56,9 +56,19 @@ * - `sess.latency.msec = `: target network latency in milliseconds, default 100 * - `sess.ignore-ssrc = `: ignore SSRC, default false * - `sess.media = `: the media type audio|midi|opus, default audio + * - `sess.ts-direct = `: directly synchronize output against the current + * graph driver time, using the RTP timestamps, default false * - `stream.may-pause = `: pause the stream when no data is reveived, default false * - `stream.props = {}`: properties to be passed to the stream * + * Set `sess.ts-direct` to true if receivers shall play precisely in sync with the sender even + * if the transport delay differs. This can be important for use cases like AES67 sessions. + * The graph driver must then produce time that is in sync with the sender's graph driver. + * If it is set to false, the RTP timestamps will be used to reproduce the pace of the sender, + * but not directly for synchronizing when output starts. Note though that this requires that + * the receivers and senders have synchronized clocks. In PTP, the reference clocks must then + * be the same. Otherwise, senders and receives will be out of sync. + * * ## General options * * Options with well-known behavior: From 71c0c8e34c33ea752e4248faa98957b2ff7f3f35 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 19 May 2025 16:14:08 +0200 Subject: [PATCH 0304/1014] module-rtp: Rename timer to standby timer in rtp source This results in clearer code, and makes it easier to add more timers to the module in the future. --- src/modules/module-rtp-source.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index a3694197e..cb6690ff3 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -163,7 +163,7 @@ struct impl { bool always_process; uint32_t cleanup_interval; - struct spa_source *timer; + struct spa_source *standby_timer; struct pw_properties *stream_props; struct rtp_stream *stream; @@ -463,11 +463,12 @@ static const struct rtp_stream_events stream_events = { .param_changed = stream_param_changed, }; -static void on_timer_event(void *data, uint64_t expirations) +static void on_standby_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; - pw_log_debug("timer %d", impl->receiving); + pw_log_debug("standby timer event; receiving: %d standby: %d waiting: %d", + impl->receiving, impl->standby, impl->waiting); if (!impl->receiving) { if (!impl->standby) { @@ -512,8 +513,8 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->loop, impl->timer); + if (impl->standby_timer) + pw_loop_destroy_source(impl->loop, impl->standby_timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -701,8 +702,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); - if (impl->timer == NULL) { + impl->standby_timer = pw_loop_add_timer(impl->loop, on_standby_timer_event, impl); + if (impl->standby_timer == NULL) { res = -errno; pw_log_error("can't create timer source: %m"); goto out; @@ -711,7 +712,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) value.tv_nsec = 0; interval.tv_sec = impl->cleanup_interval; interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); + pw_loop_update_timer(impl->loop, impl->standby_timer, &value, &interval, false); impl->stream = rtp_stream_new(impl->core, PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props), From d258892392e9558188c6197741058705e5dcc433 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Tue, 20 May 2025 12:57:13 +0200 Subject: [PATCH 0305/1014] module-rtp: Retry starting stream if this failed with ENODEV errno ENODEV is not a fatal error, and trying again later to set up the socket usually succeeds. Do such retries with a timer. --- src/modules/module-rtp-source.c | 71 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index cb6690ff3..d2e44a9af 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -164,6 +164,9 @@ struct impl { uint32_t cleanup_interval; struct spa_source *standby_timer; + /* This timer is used when the first stream_start() call fails because + * of an ENODEV error (see the stream_start() code for details) */ + struct spa_source *stream_start_retry_timer; struct pw_properties *stream_props; struct rtp_stream *stream; @@ -334,6 +337,23 @@ error: return res; } +static int stream_start(struct impl *impl); + +static void on_stream_start_retry_timer_event(void *data, uint64_t expirations) +{ + struct impl *impl = data; + pw_log_debug("trying again to start RTP listener after previous attempt failed with ENODEV"); + stream_start(impl); +} + +static void destroy_stream_start_retry_timer(struct impl *impl) +{ + if (impl->stream_start_retry_timer != NULL) { + pw_loop_destroy_source(impl->loop, impl->stream_start_retry_timer); + impl->stream_start_retry_timer = NULL; + } +} + static int stream_start(struct impl *impl) { int fd; @@ -345,10 +365,53 @@ static int stream_start(struct impl *impl) if ((fd = make_socket((const struct sockaddr *)&impl->src_addr, impl->src_len, impl->ifname)) < 0) { - pw_log_error("failed to create socket: %m"); - return -errno; + /* If make_socket() tries to create a socket and join to a multicast + * group while the network interfaces are not ready yet to do so + * (usually because a network manager component is still setting up + * those network interfaces), ENODEV will be returned. This is essentially + * a race condition. There is no discernible way to be notified when the + * network interfaces are ready for that operation, so the next best + * approach is to essentially do a form of polling by retrying the + * stream_start() call after some time. The stream_start_retry_timer exists + * precisely for that purpose. This means that ENODEV is not treated as + * an error, but instead, it triggers the creation of that timer. */ + if (errno == ENODEV) { + pw_log_warn("failed to create socket because network device is not ready " + "and present yet; will try again"); + + if (impl->stream_start_retry_timer == NULL) { + struct timespec value, interval; + + impl->stream_start_retry_timer = pw_loop_add_timer(impl->loop, + on_stream_start_retry_timer_event, impl); + /* Use a 1-second retry interval. The network interfaces + * are likely to be up and running then. */ + value.tv_sec = 1; + value.tv_nsec = 0; + interval.tv_sec = 1; + interval.tv_nsec = 0; + pw_loop_update_timer(impl->loop, impl->stream_start_retry_timer, &value, + &interval, false); + } + /* Do nothing if the timer is already up. */ + + /* It is important to return 0 in this case. Otherwise, the nonzero return + * value will later be propagated through the core as an error. */ + return 0; + } else { + pw_log_error("failed to create socket: %m"); + /* If ENODEV was returned earlier, and the stream_start_retry_timer + * was consequently created, but then a non-ENODEV error occurred, + * the timer must be stopped and removed. */ + destroy_stream_start_retry_timer(impl); + return -errno; + } } + /* Cleanup the timer in case ENODEV occurred earlier, and this time, + * the socket creation succeeded. */ + destroy_stream_start_retry_timer(impl); + impl->source = pw_loop_add_io(impl->data_loop, fd, SPA_IO_IN, true, on_rtp_io, impl); if (impl->source == NULL) { @@ -366,6 +429,8 @@ static void stream_stop(struct impl *impl) pw_log_info("stopping RTP listener"); + destroy_stream_start_retry_timer(impl); + pw_loop_destroy_source(impl->data_loop, impl->source); impl->source = NULL; } @@ -516,6 +581,8 @@ static void impl_destroy(struct impl *impl) if (impl->standby_timer) pw_loop_destroy_source(impl->loop, impl->standby_timer); + destroy_stream_start_retry_timer(impl); + if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); From 65cbbf1a02f931598285ded23fe8171658795887 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 10 Mar 2025 09:13:52 +0100 Subject: [PATCH 0306/1014] spa: add locking to the loop We can add a PTHREAD_PRIO_INHERIT lock to the loop to protect the callbacks and then use this to update shared data in an RT-safe way. This can avoid some invoke calls that require a context switch but also due to the nature of epoll cause locking in the kernel with non-RT guarantees. Because we use PRIO_INHERIT, the code executed in the lock must not use any RT-unsafe functions. --- spa/include/spa/support/loop.h | 71 +++++++++++++++++++++++++- spa/plugins/support/loop.c | 93 ++++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 12 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 520a465d5..223964994 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -38,7 +38,7 @@ extern "C" { struct spa_loop { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopControl SPA_TYPE_INFO_INTERFACE_BASE "LoopControl" -#define SPA_VERSION_LOOP_CONTROL 1 +#define SPA_VERSION_LOOP_CONTROL 2 struct spa_loop_control { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopUtils SPA_TYPE_INFO_INTERFACE_BASE "LoopUtils" @@ -213,7 +213,7 @@ SPA_API_LOOP void spa_loop_control_hook_after(struct spa_hook_list *l) struct spa_loop_control_methods { /* the version of this structure. This can be used to expand this * structure in the future */ -#define SPA_VERSION_LOOP_CONTROL_METHODS 1 +#define SPA_VERSION_LOOP_CONTROL_METHODS 2 uint32_t version; /** get the loop fd @@ -275,6 +275,46 @@ struct spa_loop_control_methods { * returns 1 on success, 0 or negative errno value on error. */ int (*check) (void *object); + + /** Lock the loop. + * This will ensure the loop is not in the process of dispatching + * callbacks. Since version 2:2 + * + * \param[in] object the control + * \return 0 on success or a negative return value on error. + */ + int (*lock) (void *object); + + /** Unlock the loop. + * Unlocks the loop again so that callbacks can be dispatched + * again. Since version 2:2 + * + * \param[in] object the control + * \return 0 on success or a negative return value on error. + */ + int (*unlock) (void *object); + + /** get the absolute time + * Get the current time with \ref timeout that can be used in wait. + * Since version 2:2 + */ + int (*get_time) (void *object, struct timespec *abstime, int64_t timeout); + /** Wait for a signal + * Wait until a thread performs signal. Since version 2:2 + * + * \param[in] object the control + * \param[in] abstime the maximum time to wait for the signal or NULL + * \return 0 on success or a negative return value on error. + */ + int (*wait) (void *object, struct timespec *abstime); + + /** Signal waiters + * Wake up all thread blocked in wait. Since version 2:2 + * + * \param[in] object the control + * \return 0 on success or a negative return value on error. + */ + int (*signal) (void *object); }; SPA_API_LOOP int spa_loop_control_get_fd(struct spa_loop_control *object) @@ -314,6 +354,33 @@ SPA_API_LOOP int spa_loop_control_check(struct spa_loop_control *object) return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, check, 1); } +SPA_API_LOOP int spa_loop_control_lock(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, lock, 2); +} +SPA_API_LOOP int spa_loop_control_unlock(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, unlock, 2); +} +SPA_API_LOOP int spa_loop_control_get_time(struct spa_loop_control *object, + struct timespec *abstime, int64_t timeout) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, get_time, 2, abstime, timeout); +} +SPA_API_LOOP int spa_loop_control_wait(struct spa_loop_control *object, + struct timespec *abstime) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, wait, 2, abstime); +} +SPA_API_LOOP int spa_loop_control_signal(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, signal, 2); +} typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask); typedef void (*spa_source_idle_func_t) (void *data); diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 8af61e7ae..5543b9f11 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -90,7 +90,8 @@ struct impl { uint32_t n_queues; struct queue *queues[QUEUES_MAX]; - pthread_mutex_t queue_lock; + pthread_mutex_t lock; + pthread_cond_t cond; int poll_fd; pthread_t thread; @@ -458,12 +459,12 @@ again: * this invoking thread but we need to serialize the flushing here with * a mutex */ if (loop_thread == 0) - pthread_mutex_lock(&impl->queue_lock); + pthread_mutex_lock(&impl->lock); flush_all_queues(impl); if (loop_thread == 0) - pthread_mutex_unlock(&impl->queue_lock); + pthread_mutex_unlock(&impl->lock); res = item->res; } else { @@ -596,7 +597,9 @@ static void loop_leave(void *object) if (--impl->enter_count == 0) { impl->thread = 0; + pthread_mutex_lock(&impl->lock); flush_all_queues(impl); + pthread_mutex_unlock(&impl->lock); impl->polling = false; } } @@ -607,6 +610,42 @@ static int loop_check(void *object) pthread_t thread_id = pthread_self(); return (impl->thread == 0 || pthread_equal(impl->thread, thread_id)) ? 1 : 0; } +static int loop_lock(void *object) +{ + struct impl *impl = object; + return -pthread_mutex_lock(&impl->lock); +} +static int loop_unlock(void *object) +{ + struct impl *impl = object; + return -pthread_mutex_unlock(&impl->lock); +} +static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout) +{ + if (clock_gettime(CLOCK_REALTIME, abstime) < 0) + return -errno; + + abstime->tv_sec += timeout / SPA_NSEC_PER_SEC; + abstime->tv_nsec += timeout % SPA_NSEC_PER_SEC; + if (abstime->tv_nsec >= SPA_NSEC_PER_SEC) { + abstime->tv_sec++; + abstime->tv_nsec -= SPA_NSEC_PER_SEC; + } + return 0; +} +static int loop_wait(void *object, struct timespec *abstime) +{ + struct impl *impl = object; + if (abstime) + return pthread_cond_timedwait(&impl->cond, &impl->lock, abstime); + else + return pthread_cond_wait(&impl->cond, &impl->lock); +} +static int loop_signal(void *object) +{ + struct impl *impl = object; + return pthread_cond_signal(&impl->cond); +} static inline void free_source(struct source_impl *s) { @@ -678,11 +717,13 @@ static int loop_iterate_cancel(void *object, int timeout) if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) process_destroy(impl); + pthread_mutex_lock(&impl->lock); for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } + pthread_mutex_unlock(&impl->lock); pthread_cleanup_pop(true); @@ -720,11 +761,14 @@ static int loop_iterate(void *object, int timeout) if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) process_destroy(impl); + pthread_mutex_lock(&impl->lock); for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } + pthread_mutex_unlock(&impl->lock); + for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s)) { @@ -1126,6 +1170,11 @@ static const struct spa_loop_control_methods impl_loop_control_cancel = { .leave = loop_leave, .iterate = loop_iterate_cancel, .check = loop_check, + .lock = loop_lock, + .unlock = loop_unlock, + .get_time = loop_get_time, + .wait = loop_wait, + .signal = loop_signal, }; static const struct spa_loop_control_methods impl_loop_control = { @@ -1136,6 +1185,11 @@ static const struct spa_loop_control_methods impl_loop_control = { .leave = loop_leave, .iterate = loop_iterate, .check = loop_check, + .lock = loop_lock, + .unlock = loop_unlock, + .get_time = loop_get_time, + .wait = loop_wait, + .signal = loop_signal, }; static const struct spa_loop_utils_methods impl_loop_utils = { @@ -1196,7 +1250,8 @@ static int impl_clear(struct spa_handle *handle) spa_system_close(impl->system, impl->poll_fd); - pthread_mutex_destroy(&impl->queue_lock); + pthread_cond_destroy(&impl->cond); + pthread_mutex_destroy(&impl->lock); return 0; } @@ -1227,6 +1282,7 @@ impl_init(const struct spa_handle_factory *factory, struct impl *impl; const char *str; pthread_mutexattr_t attr; + pthread_condattr_t cattr; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); @@ -1249,6 +1305,9 @@ impl_init(const struct spa_handle_factory *factory, SPA_VERSION_LOOP_UTILS, &impl_loop_utils, impl); + CHECK(pthread_mutexattr_init(&attr), error_exit); + CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), error_exit_free_attr); + impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; impl->rate_limit.burst = 1; impl->retry_timeout = DEFAULT_RETRY; @@ -1258,11 +1317,21 @@ impl_init(const struct spa_handle_factory *factory, impl->control.iface.cb.funcs = &impl_loop_control_cancel; if ((str = spa_dict_lookup(info, "loop.retry-timeout")) != NULL) impl->retry_timeout = atoi(str); + if ((str = spa_dict_lookup(info, "loop.prio-inherit")) != NULL && + spa_atob(str)) { + CHECK(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT), + error_exit_free_attr) + } } - CHECK(pthread_mutexattr_init(&attr), error_exit); - CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), error_exit); - CHECK(pthread_mutex_init(&impl->queue_lock, &attr), error_exit); + CHECK(pthread_mutex_init(&impl->lock, &attr), error_exit_free_attr); + pthread_mutexattr_destroy(&attr); + + CHECK(pthread_condattr_init(&cattr), error_exit_free_mutex); + CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), error_exit_free_mutex); + + CHECK(pthread_cond_init(&impl->cond, &cattr), error_exit_free_mutex); + pthread_condattr_destroy(&cattr); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); @@ -1271,12 +1340,12 @@ impl_init(const struct spa_handle_factory *factory, if (impl->system == NULL) { spa_log_error(impl->log, "%p: a System is needed", impl); res = -EINVAL; - goto error_exit_free_mutex; + goto error_exit_free_cond; } if ((res = spa_system_pollfd_create(impl->system, SPA_FD_CLOEXEC)) < 0) { spa_log_error(impl->log, "%p: can't create pollfd: %s", impl, spa_strerror(res)); - goto error_exit_free_mutex; + goto error_exit_free_cond; } impl->poll_fd = res; @@ -1299,8 +1368,12 @@ impl_init(const struct spa_handle_factory *factory, error_exit_free_poll: spa_system_close(impl->system, impl->poll_fd); +error_exit_free_cond: + pthread_cond_destroy(&impl->cond); error_exit_free_mutex: - pthread_mutex_destroy(&impl->queue_lock); + pthread_mutex_destroy(&impl->lock); +error_exit_free_attr: + pthread_mutexattr_destroy(&attr); error_exit: return res; } From cd1d9ceff158c618c05b8f3baa2fe63886165bf2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 10 Mar 2025 09:21:46 +0100 Subject: [PATCH 0307/1014] context: make data loop prio-inherit --- src/pipewire/context.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipewire/context.c b/src/pipewire/context.c index 8f3b12f4f..29065ef83 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -229,6 +229,7 @@ static int setup_data_loops(struct impl *impl) pw_properties_clear(pr); pw_properties_update(pr, &this->properties->dict); pw_properties_set(pr, PW_KEY_LIBRARY_NAME_SYSTEM, lib_name); + pw_properties_set(pr, "loop.prio-inherit", "true"); while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_json_is_container(val, l)) @@ -261,6 +262,7 @@ static int setup_data_loops(struct impl *impl) } for (i = 0; i < impl->n_data_loops; i++) { pw_properties_setf(pr, SPA_KEY_THREAD_NAME, "data-loop.%d", i); + pw_properties_set(pr, "loop.prio-inherit", "true"); impl->data_loops[i].impl = pw_data_loop_new(&pr->dict); if (impl->data_loops[i].impl == NULL) { res = -errno; From fb49e0795c28c892116b03369785ba86c27c968d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 10 Mar 2025 13:31:41 +0100 Subject: [PATCH 0308/1014] loop: move thread-loop to support loop Add more synchronization primitives to spa loop so that we can replace the thread-loop with it. --- spa/include/spa/support/loop.h | 29 ++++-- spa/plugins/support/loop.c | 85 +++++++++++++++--- src/pipewire/loop.c | 12 --- src/pipewire/loop.h | 30 +++++++ src/pipewire/private.h | 12 --- src/pipewire/thread-loop.c | 159 ++------------------------------- 6 files changed, 133 insertions(+), 194 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 223964994..1c842c7bd 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -306,15 +306,27 @@ struct spa_loop_control_methods { * \param[in] abstime the maximum time to wait for the signal or NULL * \return 0 on success or a negative return value on error. */ - int (*wait) (void *object, struct timespec *abstime); + int (*wait) (void *object, const struct timespec *abstime); /** Signal waiters - * Wake up all thread blocked in wait. Since version 2:2 + * Wake up all threads blocked in wait. Since version 2:2 + * When wait_for_accept is set, this functions blocks until all + * threads performed accept. + * + * \param[in] object the control + * \param[in] wait_for_accept block for accept + * \return 0 on success or a negative return value on error. + */ + int (*signal) (void *object, bool wait_for_accept); + + + /** Accept signalers + * Resume the thread that signaled with wait_for accept. * * \param[in] object the control * \return 0 on success or a negative return value on error. */ - int (*signal) (void *object); + int (*accept) (void *object); }; SPA_API_LOOP int spa_loop_control_get_fd(struct spa_loop_control *object) @@ -371,15 +383,20 @@ SPA_API_LOOP int spa_loop_control_get_time(struct spa_loop_control *object, spa_loop_control, &object->iface, get_time, 2, abstime, timeout); } SPA_API_LOOP int spa_loop_control_wait(struct spa_loop_control *object, - struct timespec *abstime) + const struct timespec *abstime) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, wait, 2, abstime); } -SPA_API_LOOP int spa_loop_control_signal(struct spa_loop_control *object) +SPA_API_LOOP int spa_loop_control_signal(struct spa_loop_control *object, bool wait_for_accept) { return spa_api_method_r(int, -ENOTSUP, - spa_loop_control, &object->iface, signal, 2); + spa_loop_control, &object->iface, signal, 2, wait_for_accept); +} +SPA_API_LOOP int spa_loop_control_accept(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, accept, 2); } typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask); diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 5543b9f11..290fc7e68 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -92,10 +92,14 @@ struct impl { struct queue *queues[QUEUES_MAX]; pthread_mutex_t lock; pthread_cond_t cond; + pthread_cond_t accept_cond; + int n_waiting; + int n_waiting_for_accept; int poll_fd; pthread_t thread; int enter_count; + int recurse; struct spa_source *wakeup; @@ -573,6 +577,7 @@ static void loop_enter(void *object) struct impl *impl = object; pthread_t thread_id = pthread_self(); + pthread_mutex_lock(&impl->lock); if (impl->enter_count == 0) { spa_return_if_fail(impl->thread == 0); impl->thread = thread_id; @@ -597,28 +602,49 @@ static void loop_leave(void *object) if (--impl->enter_count == 0) { impl->thread = 0; - pthread_mutex_lock(&impl->lock); flush_all_queues(impl); - pthread_mutex_unlock(&impl->lock); impl->polling = false; } + pthread_mutex_unlock(&impl->lock); } static int loop_check(void *object) { struct impl *impl = object; pthread_t thread_id = pthread_self(); - return (impl->thread == 0 || pthread_equal(impl->thread, thread_id)) ? 1 : 0; + int res; + + /* we are in the thread running the loop */ + if (impl->thread == 0 || pthread_equal(impl->thread, thread_id)) + return 1; + + /* if lock taken by something else, error */ + if ((res = pthread_mutex_trylock(&impl->lock)) != 0) + return -res; + + /* we could take the lock, check if we actually locked it somewhere */ + res = impl->recurse > 0 ? 1 : -EPERM; + pthread_mutex_unlock(&impl->lock); + return res; } static int loop_lock(void *object) { struct impl *impl = object; - return -pthread_mutex_lock(&impl->lock); + int res; + + if ((res = pthread_mutex_lock(&impl->lock)) == 0) + impl->recurse++; + return -res; } static int loop_unlock(void *object) { struct impl *impl = object; - return -pthread_mutex_unlock(&impl->lock); + int res; + spa_return_val_if_fail(impl->recurse > 0, -EIO); + impl->recurse--; + if ((res = pthread_mutex_unlock(&impl->lock)) != 0) + impl->recurse++; + return -res; } static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout) { @@ -633,18 +659,46 @@ static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout } return 0; } -static int loop_wait(void *object, struct timespec *abstime) +static int loop_wait(void *object, const struct timespec *abstime) { struct impl *impl = object; + int res; + + impl->n_waiting++; + impl->recurse--; if (abstime) - return pthread_cond_timedwait(&impl->cond, &impl->lock, abstime); + res = pthread_cond_timedwait(&impl->cond, &impl->lock, abstime); else - return pthread_cond_wait(&impl->cond, &impl->lock); + res = pthread_cond_wait(&impl->cond, &impl->lock); + impl->recurse++; + impl->n_waiting--; + return -res; } -static int loop_signal(void *object) + +static int loop_signal(void *object, bool wait_for_accept) { struct impl *impl = object; - return pthread_cond_signal(&impl->cond); + int res; + if (impl->n_waiting > 0) + if ((res = pthread_cond_broadcast(&impl->cond)) != 0) + return -res; + + if (wait_for_accept) { + impl->n_waiting_for_accept++; + + while (impl->n_waiting_for_accept > 0) { + if ((res = pthread_cond_wait(&impl->accept_cond, &impl->lock)) != 0) + return -res; + } + } + return res; +} + +static int loop_accept(void *object) +{ + struct impl *impl = object; + impl->n_waiting_for_accept--; + return -pthread_cond_signal(&impl->accept_cond); } static inline void free_source(struct source_impl *s) @@ -689,9 +743,11 @@ static int loop_iterate_cancel(void *object, int timeout) impl->polling = true; spa_loop_control_hook_before(&impl->hooks_list); + pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); + pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); impl->polling = false; @@ -717,13 +773,11 @@ static int loop_iterate_cancel(void *object, int timeout) if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) process_destroy(impl); - pthread_mutex_lock(&impl->lock); for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } - pthread_mutex_unlock(&impl->lock); pthread_cleanup_pop(true); @@ -738,9 +792,11 @@ static int loop_iterate(void *object, int timeout) impl->polling = true; spa_loop_control_hook_before(&impl->hooks_list); + pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); + pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); impl->polling = false; @@ -761,13 +817,11 @@ static int loop_iterate(void *object, int timeout) if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) process_destroy(impl); - pthread_mutex_lock(&impl->lock); for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } - pthread_mutex_unlock(&impl->lock); for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; @@ -1175,6 +1229,7 @@ static const struct spa_loop_control_methods impl_loop_control_cancel = { .get_time = loop_get_time, .wait = loop_wait, .signal = loop_signal, + .accept = loop_accept, }; static const struct spa_loop_control_methods impl_loop_control = { @@ -1190,6 +1245,7 @@ static const struct spa_loop_control_methods impl_loop_control = { .get_time = loop_get_time, .wait = loop_wait, .signal = loop_signal, + .accept = loop_accept, }; static const struct spa_loop_utils_methods impl_loop_utils = { @@ -1331,6 +1387,7 @@ impl_init(const struct spa_handle_factory *factory, CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), error_exit_free_mutex); CHECK(pthread_cond_init(&impl->cond, &cattr), error_exit_free_mutex); + CHECK(pthread_cond_init(&impl->accept_cond, &cattr), error_exit_free_mutex); pthread_condattr_destroy(&cattr); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); diff --git a/src/pipewire/loop.c b/src/pipewire/loop.c index 2e7fb47d5..2d38d4bac 100644 --- a/src/pipewire/loop.c +++ b/src/pipewire/loop.c @@ -178,15 +178,3 @@ int pw_loop_set_name(struct pw_loop *loop, const char *name) snprintf(impl->name, sizeof(impl->name), "%s", name); return 0; } - -SPA_EXPORT -int pw_loop_check(struct pw_loop *loop) -{ - struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); - int res; - if (impl->cb && impl->cb->check) - res = impl->cb->check(impl->user_data, loop); - else - res = spa_loop_control_check(loop->control); - return res; -} diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index 8bb762170..2190344c1 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -88,6 +88,36 @@ PW_API_LOOP_IMPL int pw_loop_iterate(struct pw_loop *object, { return spa_loop_control_iterate_fast(object->control, timeout); } +PW_API_LOOP_IMPL int pw_loop_check(struct pw_loop *object) +{ + return spa_loop_control_check(object->control); +} +PW_API_LOOP_IMPL int pw_loop_lock(struct pw_loop *object) +{ + return spa_loop_control_lock(object->control); +} +PW_API_LOOP_IMPL int pw_loop_unlock(struct pw_loop *object) +{ + return spa_loop_control_unlock(object->control); +} +PW_API_LOOP_IMPL int pw_loop_get_time(struct pw_loop *object, struct timespec *abstime, int64_t timeout) +{ + return spa_loop_control_get_time(object->control, abstime, timeout); +} +PW_API_LOOP_IMPL int pw_loop_wait(struct pw_loop *object, const struct timespec *abstime) +{ + return spa_loop_control_wait(object->control, abstime); +} +PW_API_LOOP_IMPL int pw_loop_signal(struct pw_loop *object, bool wait_for_accept) +{ + return spa_loop_control_signal(object->control, wait_for_accept); +} +PW_API_LOOP_IMPL int pw_loop_accept(struct pw_loop *object) +{ + return spa_loop_control_accept(object->control); +} + + PW_API_LOOP_IMPL struct spa_source * pw_loop_add_io(struct pw_loop *object, int fd, uint32_t mask, diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 75db3fe43..3cdb258df 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -343,18 +343,6 @@ pw_core_resource_errorf(struct pw_resource *resource, uint32_t id, int seq, va_end(args); } -struct pw_loop_callbacks { -#define PW_VERSION_LOOP_CALLBACKS 0 - uint32_t version; - - int (*check) (void *data, struct pw_loop *loop); -}; - -void -pw_loop_set_callbacks(struct pw_loop *loop, const struct pw_loop_callbacks *cb, void *data); - -int pw_loop_check(struct pw_loop *loop); - #define ensure_loop(loop,...) ({ \ int res = pw_loop_check(loop); \ if (res != 1) { \ diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c index 5f74f9487..36fbd0183 100644 --- a/src/pipewire/thread-loop.c +++ b/src/pipewire/thread-loop.c @@ -27,92 +27,18 @@ struct pw_thread_loop { struct spa_hook_list listener_list; - pthread_mutex_t lock; - pthread_cond_t cond; - pthread_cond_t accept_cond; - pthread_t thread; - int recurse; struct spa_hook hook; struct spa_source *event; - int n_waiting; - int n_waiting_for_accept; unsigned int created:1; unsigned int running:1; unsigned int start_signal:1; }; /** \endcond */ -static int do_lock(struct pw_thread_loop *this) -{ - int res; - if ((res = pthread_mutex_lock(&this->lock)) != 0) - pw_log_error("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); - else - this->recurse++; - return -res; -} - -static int do_unlock(struct pw_thread_loop *this) -{ - int res; - spa_return_val_if_fail(this->recurse > 0, -EIO); - this->recurse--; - if ((res = pthread_mutex_unlock(&this->lock)) != 0) { - pw_log_error("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); - this->recurse++; - } - return -res; -} - -static void impl_before(void *data) -{ - struct pw_thread_loop *this = data; - do_unlock(this); -} - -static void impl_after(void *data) -{ - struct pw_thread_loop *this = data; - do_lock(this); -} - -static const struct spa_loop_control_hooks impl_hooks = { - SPA_VERSION_LOOP_CONTROL_HOOKS, - .before = impl_before, - .after = impl_after, -}; - -static int impl_check(void *data, struct pw_loop *loop) -{ - struct pw_thread_loop *this = data; - int res; - - /* we are in the thread running the loop */ - if (spa_loop_control_check(this->loop->control) == 1) - return 1; - - /* if lock taken by something else, error */ - if ((res = pthread_mutex_trylock(&this->lock)) != 0) { - pw_log_debug("%p: thread:%p: %s", this, (void *) pthread_self(), strerror(res)); - return -res; - } - /* we could take the lock, check if we actually locked it somewhere */ - res = this->recurse > 0 ? 1 : -EPERM; - if (res < 0) - pw_log_debug("%p: thread:%p: recurse:%d", this, (void *) pthread_self(), this->recurse); - pthread_mutex_unlock(&this->lock); - return res; -} - -static const struct pw_loop_callbacks impl_callbacks = { - PW_VERSION_LOOP_CALLBACKS, - .check = impl_check, -}; - static void do_stop(void *data, uint64_t count) { struct pw_thread_loop *this = data; @@ -134,8 +60,6 @@ static struct pw_thread_loop *loop_new(struct pw_loop *loop, const struct spa_dict *props) { struct pw_thread_loop *this; - pthread_mutexattr_t attr; - pthread_condattr_t cattr; int res; this = calloc(1, sizeof(struct pw_thread_loop)); @@ -162,32 +86,13 @@ static struct pw_thread_loop *loop_new(struct pw_loop *loop, spa_hook_list_init(&this->listener_list); - CHECK(pthread_mutexattr_init(&attr), clean_this); - CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), clean_this); - CHECK(pthread_mutex_init(&this->lock, &attr), clean_this); - - CHECK(pthread_condattr_init(&cattr), clean_lock); - CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), clean_lock); - - CHECK(pthread_cond_init(&this->cond, &cattr), clean_lock); - CHECK(pthread_cond_init(&this->accept_cond, &cattr), clean_cond); - if ((this->event = pw_loop_add_event(this->loop, do_stop, this)) == NULL) { res = -errno; - goto clean_acceptcond; + goto clean_this; } - pw_loop_set_callbacks(loop, &impl_callbacks, this); - pw_loop_add_hook(loop, &this->hook, &impl_hooks, this); - return this; -clean_acceptcond: - pthread_cond_destroy(&this->accept_cond); -clean_cond: - pthread_cond_destroy(&this->cond); -clean_lock: - pthread_mutex_destroy(&this->lock); clean_this: if (this->created && this->loop) pw_loop_destroy(this->loop); @@ -245,7 +150,6 @@ void pw_thread_loop_destroy(struct pw_thread_loop *loop) pw_thread_loop_stop(loop); - pw_loop_set_callbacks(loop->loop, NULL, NULL); spa_hook_remove(&loop->hook); spa_hook_list_clean(&loop->listener_list); @@ -255,10 +159,6 @@ void pw_thread_loop_destroy(struct pw_thread_loop *loop) if (loop->created) pw_loop_destroy(loop->loop); - pthread_cond_destroy(&loop->accept_cond); - pthread_cond_destroy(&loop->cond); - pthread_mutex_destroy(&loop->lock); - free(loop); } @@ -283,7 +183,6 @@ static void *do_loop(void *user_data) struct pw_thread_loop *this = user_data; int res; - do_lock(this); pw_log_debug("%p: enter thread", this); pw_loop_enter(this->loop); @@ -300,7 +199,6 @@ static void *do_loop(void *user_data) } pw_log_debug("%p: leave thread", this); pw_loop_leave(this->loop); - do_unlock(this); return NULL; } @@ -367,7 +265,7 @@ void pw_thread_loop_stop(struct pw_thread_loop *loop) SPA_EXPORT void pw_thread_loop_lock(struct pw_thread_loop *loop) { - do_lock(loop); + pw_loop_lock(loop->loop); pw_log_trace("%p", loop); } @@ -380,7 +278,7 @@ SPA_EXPORT void pw_thread_loop_unlock(struct pw_thread_loop *loop) { pw_log_trace("%p", loop); - do_unlock(loop); + pw_loop_unlock(loop->loop); } /** Signal the thread @@ -395,20 +293,8 @@ void pw_thread_loop_unlock(struct pw_thread_loop *loop) SPA_EXPORT void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept) { - pw_log_trace("%p, waiting:%d accept:%d", - loop, loop->n_waiting, wait_for_accept); - if (loop->n_waiting > 0) - pthread_cond_broadcast(&loop->cond); - - if (wait_for_accept) { - loop->n_waiting_for_accept++; - - while (loop->n_waiting_for_accept > 0) { - int res; - if ((res = pthread_cond_wait(&loop->accept_cond, &loop->lock)) != 0) - pw_log_error("%p: thread:%p: %s", loop, (void *) pthread_self(), strerror(res)); - } - } + pw_log_trace("%p,accept:%d", loop, wait_for_accept); + pw_loop_signal(loop->loop, wait_for_accept); } /** Wait for the loop thread to call \ref pw_thread_loop_signal() @@ -419,17 +305,7 @@ void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept) SPA_EXPORT void pw_thread_loop_wait(struct pw_thread_loop *loop) { - int res; - - pw_log_trace("%p, waiting:%d recurse:%d", loop, loop->n_waiting, loop->recurse); - spa_return_if_fail(loop->recurse > 0); - loop->n_waiting++; - loop->recurse--; - if ((res = pthread_cond_wait(&loop->cond, &loop->lock)) != 0) - pw_log_error("%p: thread:%p: %s", loop, (void *) pthread_self(), strerror(res)); - loop->recurse++; - loop->n_waiting--; - pw_log_trace("%p, waiting done %d", loop, loop->n_waiting); + pw_loop_wait(loop->loop, NULL); } /** Wait for the loop thread to call \ref pw_thread_loop_signal() @@ -464,16 +340,7 @@ int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec) SPA_EXPORT int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout) { - if (clock_gettime(CLOCK_REALTIME, abstime) < 0) - return -errno; - - abstime->tv_sec += timeout / SPA_NSEC_PER_SEC; - abstime->tv_nsec += timeout % SPA_NSEC_PER_SEC; - if (abstime->tv_nsec >= SPA_NSEC_PER_SEC) { - abstime->tv_sec++; - abstime->tv_nsec -= SPA_NSEC_PER_SEC; - } - return 0; + return pw_loop_get_time(loop->loop, abstime, timeout); } /** Wait for the loop thread to call \ref pw_thread_loop_signal() @@ -487,14 +354,7 @@ int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstim SPA_EXPORT int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, const struct timespec *abstime) { - int ret; - spa_return_val_if_fail(loop->recurse > 0, -EIO); - loop->n_waiting++; - loop->recurse--; - ret = pthread_cond_timedwait(&loop->cond, &loop->lock, abstime); - loop->recurse++; - loop->n_waiting--; - return -ret; + return pw_loop_wait(loop->loop, abstime); } /** Signal the loop thread waiting for accept with \ref pw_thread_loop_signal() @@ -505,8 +365,7 @@ int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, const struct tim SPA_EXPORT void pw_thread_loop_accept(struct pw_thread_loop *loop) { - loop->n_waiting_for_accept--; - pthread_cond_signal(&loop->accept_cond); + pw_loop_accept(loop->loop); } /** Check if we are inside the thread of the loop From f7fdafc203ed5a5d5f6d2e6c9efb34898dbc08d4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 10 Mar 2025 17:24:13 +0100 Subject: [PATCH 0309/1014] loop: add method to run a function with the lock Convert some _invoke to _locked --- pipewire-jack/src/pipewire-jack.c | 20 +++++++------- spa/include/spa/support/loop.h | 27 +++++++++++++++++++ spa/plugins/alsa/alsa-compress-offload-sink.c | 4 +-- spa/plugins/alsa/alsa-pcm.c | 6 ++--- spa/plugins/alsa/alsa-seq.c | 4 +-- spa/plugins/audioconvert/audioconvert.c | 4 +-- spa/plugins/audiomixer/audiomixer.c | 4 +-- spa/plugins/audiomixer/mixer-dsp.c | 4 +-- spa/plugins/audiotestsrc/audiotestsrc.c | 2 +- spa/plugins/avb/avb-pcm.c | 4 +-- spa/plugins/control/mixer.c | 4 +-- spa/plugins/support/loop.c | 12 ++++++++- src/modules/module-client-node/client-node.c | 3 +-- src/pipewire/filter.c | 6 ++--- src/pipewire/impl-node.c | 24 ++++++++--------- src/pipewire/impl-port.c | 16 +++++------ src/pipewire/loop.h | 6 +++++ src/pipewire/stream.c | 6 ++--- 18 files changed, 99 insertions(+), 57 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index fc5d28bc2..52668e544 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -626,8 +626,8 @@ do_mix_set_io(struct spa_loop *loop, bool async, uint32_t seq, static inline void mix_set_io(struct mix *mix, void *data, size_t size) { struct io_info info = { .mix = mix, .data = data, .size = size }; - pw_data_loop_invoke(mix->port->client->loop, - do_mix_set_io, SPA_ID_INVALID, &info, sizeof(info), false, NULL); + pw_loop_locked(mix->port->client->loop->loop, + do_mix_set_io, SPA_ID_INVALID, &info, sizeof(info), NULL); } static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32_t peer_id) @@ -2496,16 +2496,16 @@ static int client_node_command(void *data, const struct spa_command *command) case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (c->started) { - pw_data_loop_invoke(c->loop, - do_unprepare_client, SPA_ID_INVALID, NULL, 0, false, c); + pw_loop_locked(c->loop->loop, + do_unprepare_client, SPA_ID_INVALID, NULL, 0, c); c->started = false; } break; case SPA_NODE_COMMAND_Start: if (!c->started) { - pw_data_loop_invoke(c->loop, - do_prepare_client, SPA_ID_INVALID, NULL, 0, false, c); + pw_loop_locked(c->loop->loop, + do_prepare_client, SPA_ID_INVALID, NULL, 0, c); c->started = true; } break; @@ -3279,8 +3279,8 @@ static int client_node_set_activation(void *data, link->trigger = link->activation->server_version < 1 ? trigger_link_v0 : trigger_link_v1; spa_list_append(&c->links, &link->link); - pw_data_loop_invoke(c->loop, - do_add_link, SPA_ID_INVALID, NULL, 0, false, link); + pw_loop_locked(c->loop->loop, + do_add_link, SPA_ID_INVALID, NULL, 0, link); } else { link = find_activation(&c->links, node_id); @@ -3290,8 +3290,8 @@ static int client_node_set_activation(void *data, } spa_list_remove(&link->link); - pw_data_loop_invoke(c->loop, - do_remove_link, SPA_ID_INVALID, NULL, 0, false, link); + pw_loop_locked(c->loop->loop, + do_remove_link, SPA_ID_INVALID, NULL, 0, link); queue_free_link(c, link); } diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 1c842c7bd..97813bb14 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -133,6 +133,24 @@ struct spa_loop_methods { size_t size, bool block, void *user_data); + + /** Call a function with the loop lock acquired + * May be called from any thread and multiple threads at the same time. + * + * \param[in] object The callbacks data. + * \param func The function to be called. + * \param seq An opaque sequence number. This will be made + * available to func. + * \param[in] data Data that will be passed to func. + * \param size The size of data. + * \param user_data An opaque pointer passed to func. + * \return the return value of func. */ + int (*locked) (void *object, + spa_invoke_func_t func, + uint32_t seq, + const void *data, + size_t size, + void *user_data); }; SPA_API_LOOP int spa_loop_add_source(struct spa_loop *object, struct spa_source *source) @@ -158,6 +176,15 @@ SPA_API_LOOP int spa_loop_invoke(struct spa_loop *object, spa_loop, &object->iface, invoke, 0, func, seq, data, size, block, user_data); } +SPA_API_LOOP int spa_loop_locked(struct spa_loop *object, + spa_invoke_func_t func, uint32_t seq, const void *data, + size_t size, void *user_data) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop, &object->iface, locked, 0, func, seq, data, + size, user_data); +} + /** Control hooks. These hooks can't be removed from their * callbacks and must be removed from a safe place (when the loop diff --git a/spa/plugins/alsa/alsa-compress-offload-sink.c b/spa/plugins/alsa/alsa-compress-offload-sink.c index d10d9c071..1477b5e4d 100644 --- a/spa/plugins/alsa/alsa-compress-offload-sink.c +++ b/spa/plugins/alsa/alsa-compress-offload-sink.c @@ -684,7 +684,7 @@ static void stop_driver_timer(struct impl *this) /* Perform the actual stop within * the dataloop to avoid data races. */ - spa_loop_invoke(this->data_loop, do_remove_driver_timer_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_driver_timer_source, 0, NULL, 0, this); } static void on_driver_timeout(struct spa_source *source) @@ -795,7 +795,7 @@ static void reevaluate_following_state(struct impl *this) if (following != this->following) { spa_log_debug(this->log, "%p: following state changed: %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reevaluate_following_state, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_reevaluate_following_state, 0, NULL, 0, this); } } diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index bc5a0ae63..222a88d0b 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3739,7 +3739,7 @@ int spa_alsa_start(struct state *state) } state->started = true; - spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); return 0; } @@ -3783,7 +3783,7 @@ int spa_alsa_reassign_follower(struct state *state) } setup_matching(state); if (state->started) - spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); else if (state->want_started) spa_alsa_start(state); @@ -3812,7 +3812,7 @@ int spa_alsa_pause(struct state *state) spa_log_debug(state->log, "%p: pause", state); state->started = false; - spa_loop_invoke(state->data_loop, do_state_sync, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); spa_list_for_each(follower, &state->followers, driver_link) spa_alsa_pause(follower); diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 9cb707814..86621a1d2 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -1176,7 +1176,7 @@ int spa_alsa_seq_reassign_follower(struct seq_state *state) if (following != state->following) { spa_log_debug(state->log, "alsa %p: reassign follower %d->%d", state, state->following, following); state->following = following; - spa_loop_invoke(state->data_loop, do_reassign_follower, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_reassign_follower, 0, NULL, 0, state); } return 0; } @@ -1205,7 +1205,7 @@ int spa_alsa_seq_pause(struct seq_state *state) spa_log_debug(state->log, "alsa %p: pause", state); - spa_loop_invoke(state->data_loop, do_remove_source, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_remove_source, 0, NULL, 0, state); if ((res = snd_seq_stop_queue(state->event.hndl, state->event.queue_id, NULL)) < 0) { spa_log_warn(state->log, "failed to stop queue: %s", snd_strerror(res)); diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e5c0daf9f..04069a44a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1386,7 +1386,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) } res = setup_filter_graphs(impl); - spa_loop_invoke(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, true, impl); + spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl); if (impl->in_filter_props == 0) clean_filter_handles(impl, false); @@ -3283,7 +3283,7 @@ impl_node_port_set_io(void *object, case SPA_IO_Buffers: if (this->data_loop) { struct io_data d = { .port = port, .data = data, .size = size }; - spa_loop_invoke(this->data_loop, do_set_port_io, 0, NULL, 0, true, &d); + spa_loop_locked(this->data_loop, do_set_port_io, 0, NULL, 0, &d); } else port->io = data; diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index ad1e8fdba..80e514490 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -744,8 +744,8 @@ impl_node_port_set_io(void *object, switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: - spa_loop_invoke(this->data_loop, - do_port_set_io, SPA_ID_INVALID, NULL, 0, true, &info); + spa_loop_locked(this->data_loop, + do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index fb396c14e..b4ee7d18d 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -680,8 +680,8 @@ impl_node_port_set_io(void *object, switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: - spa_loop_invoke(this->data_loop, - do_port_set_io, SPA_ID_INVALID, NULL, 0, true, &info); + spa_loop_locked(this->data_loop, + do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 273e4a5a3..6dc0db07f 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -1013,7 +1013,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; if (this->data_loop) - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index 3f608d7f4..59b05a3a5 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -1131,7 +1131,7 @@ int spa_avb_reassign_follower(struct state *state) if (following != state->following) { spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following); state->following = following; - spa_loop_invoke(state->data_loop, do_reassign_follower, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_reassign_follower, 0, NULL, 0, state); } freewheel = state->position && @@ -1208,7 +1208,7 @@ int spa_avb_pause(struct state *state) spa_log_debug(state->log, "%p: pause", state); - spa_loop_invoke(state->data_loop, do_remove_source, 0, NULL, 0, true, state); + spa_loop_locked(state->data_loop, do_remove_source, 0, NULL, 0, state); state->started = false; diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index e97072d45..9085da5c5 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -605,8 +605,8 @@ impl_node_port_set_io(void *object, switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: - spa_loop_invoke(this->data_loop, - do_port_set_io, SPA_ID_INVALID, NULL, 0, true, &info); + spa_loop_locked(this->data_loop, + do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 290fc7e68..07e0bffff 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -195,7 +195,6 @@ static int remove_from_poll(struct impl *impl, struct spa_source *source) static int loop_remove_source(void *object, struct spa_source *source) { struct impl *impl = object; - spa_assert(!impl->polling); int res = remove_from_poll(impl, source); detach_source(source); @@ -553,6 +552,16 @@ static int loop_invoke(void *object, spa_invoke_func_t func, uint32_t seq, } return res; } +static int loop_locked(void *object, spa_invoke_func_t func, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct impl *impl = object; + int res; + pthread_mutex_lock(&impl->lock); + res = func(&impl->loop, false, seq, data, size, user_data); + pthread_mutex_unlock(&impl->lock); + return res; +} static int loop_get_fd(void *object) { @@ -1214,6 +1223,7 @@ static const struct spa_loop_methods impl_loop = { .update_source = loop_update_source, .remove_source = loop_remove_source, .invoke = loop_invoke, + .locked = loop_locked, }; static const struct spa_loop_control_methods impl_loop_control_cancel = { diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 134511b35..21f47538a 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -1239,12 +1239,11 @@ static void client_node_resource_destroy(void *data) spa_hook_remove(&impl->object_listener); if (impl->data_source.fd != -1) { - spa_loop_invoke(impl->data_loop, + spa_loop_locked(impl->data_loop, do_remove_source, SPA_ID_INVALID, NULL, 0, - true, &impl->data_source); } if (this->node) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 4c1bb6870..6c6bfc74a 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1449,7 +1449,7 @@ static void hook_removed(struct spa_hook *hook) { struct filter *impl = hook->priv; if (impl->data_loop) - pw_loop_invoke(impl->data_loop, do_remove_callbacks, 1, NULL, 0, true, impl); + pw_loop_locked(impl->data_loop, do_remove_callbacks, 1, NULL, 0, impl); else spa_zero(impl->rt_callbacks); hook->priv = NULL; @@ -2081,8 +2081,8 @@ SPA_EXPORT int pw_filter_flush(struct pw_filter *filter, bool drain) { struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); - pw_loop_invoke(impl->data_loop, - drain ? do_drain : do_flush, 1, NULL, 0, true, impl); + pw_loop_locked(impl->data_loop, + drain ? do_drain : do_flush, 1, NULL, 0, impl); return 0; } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index e5636d561..f37bfd411 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -209,7 +209,7 @@ do_node_prepare(struct spa_loop *loop, bool async, uint32_t seq, static void add_node_to_graph(struct pw_impl_node *node) { - pw_loop_invoke(node->data_loop, do_node_prepare, 1, NULL, 0, true, node); + pw_loop_locked(node->data_loop, do_node_prepare, 1, NULL, 0, node); } /* called from the node data loop and undoes the changes done in do_node_prepare. */ @@ -248,7 +248,7 @@ do_node_unprepare(struct spa_loop *loop, bool async, uint32_t seq, static void remove_node_from_graph(struct pw_impl_node *node) { - pw_loop_invoke(node->data_loop, do_node_unprepare, 1, NULL, 0, true, node); + pw_loop_locked(node->data_loop, do_node_unprepare, 1, NULL, 0, node); } static void node_deactivate(struct pw_impl_node *this) @@ -810,8 +810,8 @@ int pw_impl_node_set_io(struct pw_impl_node *this, uint32_t id, void *data, size if (data != NULL && size < sizeof(struct spa_io_position)) return -EINVAL; pw_log_debug("%p: set position %p", this, data); - pw_loop_invoke(this->data_loop, - do_update_position, SPA_ID_INVALID, &data, sizeof(void*), true, this); + pw_loop_locked(this->data_loop, + do_update_position, SPA_ID_INVALID, &data, sizeof(void*), this); break; case SPA_IO_Clock: if (data != NULL && size < sizeof(struct spa_io_clock)) @@ -869,8 +869,8 @@ do_add_target(struct spa_loop *loop, SPA_EXPORT int pw_impl_node_add_target(struct pw_impl_node *node, struct pw_node_target *t) { - pw_loop_invoke(node->data_loop, - do_add_target, SPA_ID_INVALID, &node, sizeof(void *), true, t); + pw_loop_locked(node->data_loop, + do_add_target, SPA_ID_INVALID, &node, sizeof(void *), t); if (t->node) pw_impl_node_emit_peer_added(node, t->node); @@ -905,8 +905,8 @@ int pw_impl_node_remove_target(struct pw_impl_node *node, struct pw_node_target { /* we also update the target list for remote nodes so that the profiler * can inspect the nodes as well */ - pw_loop_invoke(node->data_loop, - do_remove_target, SPA_ID_INVALID, &node, sizeof(void *), true, t); + pw_loop_locked(node->data_loop, + do_remove_target, SPA_ID_INVALID, &node, sizeof(void *), t); if (t->node) pw_impl_node_emit_peer_removed(node, t->node); @@ -2359,8 +2359,8 @@ void pw_impl_node_add_rt_listener(struct pw_impl_node *node, void *data) { struct listener_data d = { .listener = listener, .events = events, .data = data }; - pw_loop_invoke(node->data_loop, - do_add_rt_listener, SPA_ID_INVALID, &d, sizeof(d), false, node); + pw_loop_locked(node->data_loop, + do_add_rt_listener, SPA_ID_INVALID, &d, sizeof(d), node); } static int do_remove_listener(struct spa_loop *loop, @@ -2375,8 +2375,8 @@ SPA_EXPORT void pw_impl_node_remove_rt_listener(struct pw_impl_node *node, struct spa_hook *listener) { - pw_loop_invoke(node->data_loop, - do_remove_listener, SPA_ID_INVALID, NULL, 0, true, listener); + pw_loop_locked(node->data_loop, + do_remove_listener, SPA_ID_INVALID, NULL, 0, listener); } /** Destroy a node diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 4356604af..0bcff9f4c 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -241,8 +241,8 @@ static int port_set_io(void *object, case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: if (data == NULL || size == 0) { - pw_loop_invoke(this->node->data_loop, - do_remove_mix, SPA_ID_INVALID, NULL, 0, true, mix); + pw_loop_locked(this->node->data_loop, + do_remove_mix, SPA_ID_INVALID, NULL, 0, mix); mix->io_data = mix->io[0] = mix->io[1] = NULL; } else if (data != NULL && size >= sizeof(struct spa_io_buffers)) { if (size >= sizeof(struct spa_io_async_buffers)) { @@ -253,8 +253,8 @@ static int port_set_io(void *object, } else { mix->io_data = mix->io[0] = mix->io[1] = data; } - pw_loop_invoke(this->node->data_loop, - do_add_mix, SPA_ID_INVALID, NULL, 0, false, mix); + pw_loop_locked(this->node->data_loop, + do_add_mix, SPA_ID_INVALID, NULL, 0, mix); } } return 0; @@ -1388,7 +1388,7 @@ static void pw_impl_port_remove(struct pw_impl_port *port) pw_log_debug("%p: remove", port); - pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + pw_loop_locked(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, port); if (SPA_FLAG_IS_SET(port->flags, PW_IMPL_PORT_FLAG_TO_REMOVE)) { if ((res = spa_node_remove_port(node->node, port->direction, port->port_id)) < 0) @@ -1817,7 +1817,7 @@ int pw_impl_port_set_param(struct pw_impl_port *port, uint32_t id, uint32_t flag pw_log_debug("%p: %d set param %d %p", port, port->state, id, param); if (id == SPA_PARAM_Format) { - pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + pw_loop_locked(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, port); spa_node_port_set_io(node->node, port->direction, port->port_id, SPA_IO_Buffers, NULL, 0); @@ -1903,7 +1903,7 @@ static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, port->direction, port->port_id, SPA_IO_Buffers, NULL, 0); - pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port); + pw_loop_locked(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, port); pw_buffers_clear(&port->mix_buffers); @@ -1943,7 +1943,7 @@ static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, pw_direction_reverse(port->direction), 0, SPA_IO_Buffers, &port->rt.io, sizeof(port->rt.io)); - pw_loop_invoke(node->data_loop, do_add_port, SPA_ID_INVALID, NULL, 0, false, port); + pw_loop_locked(node->data_loop, do_add_port, SPA_ID_INVALID, NULL, 0, port); } return res; } diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index 2190344c1..d4f9be076 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -64,6 +64,12 @@ PW_API_LOOP_IMPL int pw_loop_invoke(struct pw_loop *object, { return spa_loop_invoke(object->loop, func, seq, data, size, block, user_data); } +PW_API_LOOP_IMPL int pw_loop_locked(struct pw_loop *object, + spa_invoke_func_t func, uint32_t seq, const void *data, + size_t size, void *user_data) +{ + return spa_loop_locked(object->loop, func, seq, data, size, user_data); +} PW_API_LOOP_IMPL int pw_loop_get_fd(struct pw_loop *object) { diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 2ee63fae1..0feeabe50 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1748,7 +1748,7 @@ static void hook_removed(struct spa_hook *hook) { struct stream *impl = hook->priv; if (impl->data_loop) - pw_loop_invoke(impl->data_loop, do_remove_callbacks, 1, NULL, 0, true, impl); + pw_loop_locked(impl->data_loop, do_remove_callbacks, 1, NULL, 0, impl); else spa_zero(impl->rt_callbacks); hook->priv = NULL; @@ -2590,8 +2590,8 @@ int pw_stream_flush(struct pw_stream *stream, bool drain) if (stream->node == NULL) return -EIO; - pw_loop_invoke(impl->data_loop, - drain ? do_drain : do_flush, 1, NULL, 0, true, impl); + pw_loop_locked(impl->data_loop, + drain ? do_drain : do_flush, 1, NULL, 0, impl); if (!drain) spa_node_send_command(stream->node->node, From f2452a6af71bed3cb50edbda849f621798712609 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 10 Mar 2025 17:46:28 +0100 Subject: [PATCH 0310/1014] spa: some more invoke -> locked calls --- spa/plugins/support/loop.c | 16 ++++++++-------- spa/plugins/support/node-driver.c | 8 ++++---- spa/plugins/support/null-audio-sink.c | 8 ++++---- spa/plugins/v4l2/v4l2-utils.c | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 07e0bffff..17abad226 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -85,6 +85,7 @@ struct impl { struct spa_ratelimit rate_limit; int retry_timeout; + bool prio_inherit; union tag head; @@ -1371,9 +1372,6 @@ impl_init(const struct spa_handle_factory *factory, SPA_VERSION_LOOP_UTILS, &impl_loop_utils, impl); - CHECK(pthread_mutexattr_init(&attr), error_exit); - CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), error_exit_free_attr); - impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; impl->rate_limit.burst = 1; impl->retry_timeout = DEFAULT_RETRY; @@ -1383,13 +1381,15 @@ impl_init(const struct spa_handle_factory *factory, impl->control.iface.cb.funcs = &impl_loop_control_cancel; if ((str = spa_dict_lookup(info, "loop.retry-timeout")) != NULL) impl->retry_timeout = atoi(str); - if ((str = spa_dict_lookup(info, "loop.prio-inherit")) != NULL && - spa_atob(str)) { - CHECK(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT), - error_exit_free_attr) - } + if ((str = spa_dict_lookup(info, "loop.prio-inherit")) != NULL) + impl->prio_inherit = spa_atob(str); } + CHECK(pthread_mutexattr_init(&attr), error_exit); + CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), error_exit_free_attr); + if (impl->prio_inherit) + CHECK(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT), + error_exit_free_attr) CHECK(pthread_mutex_init(&impl->lock, &attr), error_exit_free_attr); pthread_mutexattr_destroy(&attr); diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index c8777caa3..701a5b7bf 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -280,7 +280,7 @@ static int reassign_follower(struct impl *this) if (following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); } return 0; } @@ -431,7 +431,7 @@ static int do_start(struct impl *this) this->following = is_following(this); this->started = true; this->last_time = 0; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } @@ -440,7 +440,7 @@ static int do_stop(struct impl *this) if (!this->started) return 0; this->started = false; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } @@ -573,7 +573,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); if (this->clock_fd != -1) diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 997a66810..70edd4ff2 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -230,7 +230,7 @@ static int reassign_follower(struct impl *this) if (following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); } return 0; } @@ -314,7 +314,7 @@ static int do_start(struct impl *this) this->following = is_following(this); this->started = true; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } @@ -323,7 +323,7 @@ static int do_stop(struct impl *this) if (!this->started) return 0; this->started = false; - spa_loop_invoke(this->data_loop, do_set_timers, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } @@ -847,7 +847,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 887094eff..58758f758 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1928,7 +1928,7 @@ static int spa_v4l2_stream_off(struct impl *this) spa_log_debug(this->log, "stopping"); - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, port); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, port); type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(dev->fd, VIDIOC_STREAMOFF, &type) < 0) { From 34796d5bb849d5069b0ceb540ad9079cba11029e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 13 Mar 2025 09:35:05 +0100 Subject: [PATCH 0311/1014] loop: keep a free_list of sources When a source is destroyed, move it to a free_list and reuse the memory when a new source is made. This way we can avoid doing a free() from the epoll thread. --- spa/plugins/support/loop.c | 79 ++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 17abad226..119c67ec0 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -81,6 +81,7 @@ struct impl { struct spa_list source_list; struct spa_list destroy_list; + struct spa_list free_list; struct spa_hook_list hooks_list; struct spa_ratelimit rate_limit; @@ -711,20 +712,14 @@ static int loop_accept(void *object) return -pthread_cond_signal(&impl->accept_cond); } -static inline void free_source(struct source_impl *s) -{ - detach_source(&s->source); - free(s); -} - static inline void process_destroy(struct impl *impl) { - struct source_impl *source, *tmp; - - spa_list_for_each_safe(source, tmp, &impl->destroy_list, link) - free_source(source); - - spa_list_init(&impl->destroy_list); + struct source_impl *s; + spa_list_consume(s, &impl->destroy_list, link) { + spa_list_remove(&s->link); + detach_source(&s->source); + spa_list_append(&s->impl->free_list, &s->link); + } } struct cancellation_handler_data { @@ -843,6 +838,24 @@ static int loop_iterate(void *object, int timeout) return nfds; } +static struct source_impl *get_source(struct impl *impl) +{ + struct source_impl *source; + + if (!spa_list_is_empty(&impl->free_list)) { + source = spa_list_first(&impl->free_list, struct source_impl, link); + spa_list_remove(&source->link); + spa_zero(*source); + } else { + source = calloc(1, sizeof(struct source_impl)); + } + if (source != NULL) { + source->impl = impl; + spa_list_insert(&impl->source_list, &source->link); + } + return source; +} + static void source_io_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); @@ -859,7 +872,7 @@ static struct spa_source *loop_add_io(void *object, struct source_impl *source; int res; - source = calloc(1, sizeof(struct source_impl)); + source = get_source(impl); if (source == NULL) goto error_exit; @@ -867,7 +880,6 @@ static struct spa_source *loop_add_io(void *object, source->source.data = data; source->source.fd = fd; source->source.mask = mask; - source->impl = impl; source->close = close; source->func.io = func; @@ -885,9 +897,6 @@ static struct spa_source *loop_add_io(void *object, spa_log_trace(impl->log, "%p: adding fallback %p", impl, source->fallback); } - - spa_list_insert(&impl->source_list, &source->link); - return &source->source; error_exit_free: @@ -952,7 +961,7 @@ static struct spa_source *loop_add_idle(void *object, struct source_impl *source; int res; - source = calloc(1, sizeof(struct source_impl)); + source = get_source(impl); if (source == NULL) goto error_exit; @@ -962,7 +971,6 @@ static struct spa_source *loop_add_idle(void *object, source->source.func = source_idle_func; source->source.data = data; source->source.fd = res; - source->impl = impl; source->close = true; source->source.mask = SPA_IO_IN; source->func.idle = func; @@ -970,8 +978,6 @@ static struct spa_source *loop_add_idle(void *object, if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; - spa_list_insert(&impl->source_list, &source->link); - if (enabled) loop_enable_idle(impl, &source->source, true); @@ -1008,7 +1014,7 @@ static struct spa_source *loop_add_event(void *object, struct source_impl *source; int res; - source = calloc(1, sizeof(struct source_impl)); + source = get_source(impl); if (source == NULL) goto error_exit; @@ -1019,15 +1025,12 @@ static struct spa_source *loop_add_event(void *object, source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; - source->impl = impl; source->close = true; source->func.event = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; - spa_list_insert(&impl->source_list, &source->link); - return &source->source; error_exit_close: @@ -1076,7 +1079,7 @@ static struct spa_source *loop_add_timer(void *object, struct source_impl *source; int res; - source = calloc(1, sizeof(struct source_impl)); + source = get_source(impl); if (source == NULL) goto error_exit; @@ -1088,15 +1091,12 @@ static struct spa_source *loop_add_timer(void *object, source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; - source->impl = impl; source->close = true; source->func.timer = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; - spa_list_insert(&impl->source_list, &source->link); - return &source->source; error_exit_close: @@ -1160,7 +1160,7 @@ static struct spa_source *loop_add_signal(void *object, struct source_impl *source; int res; - source = calloc(1, sizeof(struct source_impl)); + source = get_source(impl); if (source == NULL) goto error_exit; @@ -1172,15 +1172,12 @@ static struct spa_source *loop_add_signal(void *object, source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; - source->impl = impl; source->close = true; source->func.signal = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; - spa_list_insert(&impl->source_list, &source->link); - return &source->source; error_exit_close: @@ -1200,8 +1197,6 @@ static void loop_destroy_source(void *object, struct spa_source *source) spa_log_trace(s->impl->log, "%p ", s); - spa_list_remove(&s->link); - if (s->fallback) loop_destroy_source(s->impl, s->fallback); else @@ -1212,9 +1207,11 @@ static void loop_destroy_source(void *object, struct spa_source *source) source->fd = -1; } - if (!s->impl->polling) - free_source(s); - else + spa_list_remove(&s->link); + if (!s->impl->polling) { + detach_source(source); + spa_list_insert(&s->impl->free_list, &s->link); + } else spa_list_insert(&s->impl->destroy_list, &s->link); } @@ -1312,6 +1309,11 @@ static int impl_clear(struct spa_handle *handle) spa_list_consume(source, &impl->source_list, link) loop_destroy_source(impl, &source->source); + + spa_list_consume(source, &impl->free_list, link) { + spa_list_remove(&source->link); + free(source); + } for (i = 0; i < impl->n_queues; i++) loop_queue_destroy(impl->queues[i]); @@ -1418,6 +1420,7 @@ impl_init(const struct spa_handle_factory *factory, spa_list_init(&impl->source_list); spa_list_init(&impl->destroy_list); + spa_list_init(&impl->free_list); spa_hook_list_init(&impl->hooks_list); impl->wakeup = loop_add_event(impl, wakeup_func, impl); From 2042a0483bed607c10244fe605b668239203b694 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 29 May 2025 12:21:28 +0530 Subject: [PATCH 0312/1014] ci: Add an x86 build Fairly minimal for now to save time, but we can add more deps and cover more code as needed. We don't test or install as this isn't a native build and we just want to make sure it builds for now. --- .gitlab-ci.yml | 63 ++++++++++++++++++++++++++++++++++++++++++++++++-- cross-x86.txt | 23 ++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 cross-x86.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8319a1626..e2a549469 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -97,6 +97,36 @@ include: # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson +# This is a pruned down container with enough dependencies for a basic i686 +# build to make sure we've not broken anything. This can be extended if we want +# to cover more of the code. +.fedora_x86: + variables: + # Update this tag when you want to trigger a rebuild + FDO_DISTRIBUTION_TAG: '2025-05-29.1' + FDO_DISTRIBUTION_VERSION: '42' + FDO_DISTRIBUTION_PACKAGES: >- + git + gcc + gcc-c++ + meson + glibc-devel.i686 + systemd-devel.i686 + dbus-devel.i686 + alsa-lib-devel.i686 + bluez-libs-devel.i686 + libffi-devel.i686 + pcre2-devel.i686 + sysprof-devel.i686 + zlib-ng-compat-devel.i686 + libblkid-devel.i686 + libmount-devel.i686 + libselinux-devel.i686 + glib2-devel.i686 + alsa-lib-devel + avahi-devel + bluez-libs-devel + .ubuntu: variables: # Update this tag when you want to trigger a rebuild @@ -208,8 +238,14 @@ include: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - - meson test -C "$BUILD_DIR" --no-rebuild - - meson install -C "$BUILD_DIR" --no-rebuild + - | + if [ -z "$MESON_SKIP_TEST" ]; then + meson test -C "$BUILD_DIR" --no-rebuild + fi + - | + if [ -z "$MESON_SKIP_INSTALL" ]; then + meson install -C "$BUILD_DIR" --no-rebuild + fi artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -232,6 +268,14 @@ container_fedora: variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image +container_fedora_x86: + extends: + - .fedora_x86 + - .fdo.container-build@fedora + stage: container + variables: + GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image + container_alpine: extends: - .alpine @@ -357,6 +401,21 @@ build_on_fedora_html_docs: rules: - !reference [pages, rules] +build_on_fedora_x86: + extends: + - .fedora_x86 + - .not_coverity + - .fdo.distribution-image@fedora + - .build + stage: build + needs: + - job: container_fedora_x86 + artifacts: false + variables: + MESON_OPTIONS: "--cross-file=cross-x86.txt" + MESON_SKIP_TEST: "true" + MESON_SKIP_INSTALL: "true" + build_on_alpine: extends: - .alpine diff --git a/cross-x86.txt b/cross-x86.txt new file mode 100644 index 000000000..8ddd5ecc5 --- /dev/null +++ b/cross-x86.txt @@ -0,0 +1,23 @@ +[binaries] +c = 'gcc' +cpp = 'g++' +ld = 'ld' +cmake = 'cmake' +strip = 'strip' +pkg-config = 'pkg-config' + +[properties] +pkg_config_libdir = '/usr/lib/pkgconfig' +ld_args = '-m elf_i386' + +[built-in options] +c_args = '-m32 -msse' +c_link_args = '-m32 -msse' +cpp_args = '-m32 -msse' +cpp_link_args = '-m32 -msse' + +[host_machine] +system = 'linux' +cpu_family = 'x86' +cpu = 'i686' +endian = 'little' From 592c7c404c813ee6d7ca35d2a97b4045bdc80b6d Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 20:46:46 +0200 Subject: [PATCH 0313/1014] spa: Add channel mode to mp3 audio info and add channel mode docs --- spa/include/spa/param/audio/mp3-utils.h | 8 ++++++-- spa/include/spa/param/audio/mp3.h | 15 +++++++++++++-- spa/include/spa/param/format-types.h | 4 ++++ spa/include/spa/param/format.h | 2 ++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/param/audio/mp3-utils.h b/spa/include/spa/param/audio/mp3-utils.h index 481000e25..3be1f13d0 100644 --- a/spa/include/spa/param/audio/mp3-utils.h +++ b/spa/include/spa/param/audio/mp3-utils.h @@ -33,8 +33,9 @@ spa_format_audio_mp3_parse(const struct spa_pod *format, struct spa_audio_info_m int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), + SPA_FORMAT_AUDIO_MP3_channelMode, SPA_POD_OPT_Id(&info->channel_mode)); return res; } @@ -55,6 +56,9 @@ spa_format_audio_mp3_build(struct spa_pod_builder *builder, uint32_t id, if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + if (info->channel_mode != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_MP3_channelMode, SPA_POD_Id(info->channel_mode), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } diff --git a/spa/include/spa/param/audio/mp3.h b/spa/include/spa/param/audio/mp3.h index 86be21a1e..a28bba661 100644 --- a/spa/include/spa/param/audio/mp3.h +++ b/spa/include/spa/param/audio/mp3.h @@ -18,15 +18,26 @@ extern "C" { enum spa_audio_mp3_channel_mode { SPA_AUDIO_MP3_CHANNEL_MODE_UNKNOWN, + /** Mono mode, only used if channel count is 1 */ SPA_AUDIO_MP3_CHANNEL_MODE_MONO, + /** Regular stereo mode with two independent channels */ SPA_AUDIO_MP3_CHANNEL_MODE_STEREO, + /** + * Joint stereo mode, exploiting the similarities between channels + * using techniques like mid-side coding + */ SPA_AUDIO_MP3_CHANNEL_MODE_JOINTSTEREO, + /** + * Two mono tracks, different from stereo in that each channel + * contains entirely different content (like two different mono songs) + */ SPA_AUDIO_MP3_CHANNEL_MODE_DUAL, }; struct spa_audio_info_mp3 { - uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels */ + uint32_t rate; /*< sample rate in Hz */ + uint32_t channels; /*< number of channels */ + enum spa_audio_mp3_channel_mode channel_mode; /*< MP3 channel mode */ }; #define SPA_AUDIO_INFO_MP3_INIT(...) ((struct spa_audio_info_mp3) { __VA_ARGS__ }) diff --git a/spa/include/spa/param/format-types.h b/spa/include/spa/param/format-types.h index 8daaa103e..46ff10624 100644 --- a/spa/include/spa/param/format-types.h +++ b/spa/include/spa/param/format-types.h @@ -97,6 +97,8 @@ static const struct spa_type_info spa_type_media_subtype[] = { #define SPA_TYPE_INFO_FORMAT_AUDIO_WMA_BASE SPA_TYPE_INFO_FORMAT_AUDIO_WMA ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_AMR SPA_TYPE_INFO_FORMAT_AUDIO_BASE "AMR" #define SPA_TYPE_INFO_FORMAT_AUDIO_AMR_BASE SPA_TYPE_INFO_FORMAT_AUDIO_AMR ":" +#define SPA_TYPE_INFO_FORMAT_AUDIO_MP3 SPA_TYPE_INFO_FORMAT_AUDIO_BASE "MP3" +#define SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE SPA_TYPE_INFO_FORMAT_AUDIO_MP3 ":" #define SPA_TYPE_INFO_FormatVideo SPA_TYPE_INFO_FORMAT_BASE "Video" #define SPA_TYPE_INFO_FORMAT_VIDEO_BASE SPA_TYPE_INFO_FormatVideo ":" @@ -139,6 +141,8 @@ static const struct spa_type_info spa_type_format[] = { spa_type_audio_wma_profile }, { SPA_FORMAT_AUDIO_AMR_bandMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_AMR_BASE "bandMode", spa_type_audio_amr_band_mode }, + { SPA_FORMAT_AUDIO_MP3_channelMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE "channelMode", + spa_type_audio_mp3_channel_mode }, { SPA_FORMAT_VIDEO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format", spa_type_video_format, }, diff --git a/spa/include/spa/param/format.h b/spa/include/spa/param/format.h index 486f5d8ef..ffb9959ce 100644 --- a/spa/include/spa/param/format.h +++ b/spa/include/spa/param/format.h @@ -110,6 +110,8 @@ enum spa_format { SPA_FORMAT_AUDIO_AMR_bandMode, /**< AMR band mode (Id enum spa_audio_amr_band_mode) */ + SPA_FORMAT_AUDIO_MP3_channelMode, /**< MP3 channel mode, (Id enum spa_audio_mp3_channel_mode) */ + /* Video Format keys */ SPA_FORMAT_START_Video = 0x20000, From 6f197484fc6185f25f2a030de6cc752cd1acfb1b Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 20:47:02 +0200 Subject: [PATCH 0314/1014] spa: Fix AAC stream format docs --- spa/include/spa/param/audio/aac.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/param/audio/aac.h b/spa/include/spa/param/audio/aac.h index 0bc1dea89..0a46d5835 100644 --- a/spa/include/spa/param/audio/aac.h +++ b/spa/include/spa/param/audio/aac.h @@ -18,19 +18,19 @@ extern "C" { enum spa_audio_aac_stream_format { SPA_AUDIO_AAC_STREAM_FORMAT_UNKNOWN, - /* Raw AAC frames */ + /** Raw AAC frames */ SPA_AUDIO_AAC_STREAM_FORMAT_RAW, - /* ISO/IEC 13818-7 MPEG-2 Audio Data Transport Stream (ADTS) */ + /** ISO/IEC 13818-7 MPEG-2 Audio Data Transport Stream (ADTS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP2ADTS, - /* ISO/IEC 14496-3 MPEG-4 Audio Data Transport Stream (ADTS) */ + /** ISO/IEC 14496-3 MPEG-4 Audio Data Transport Stream (ADTS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4ADTS, - /* ISO/IEC 14496-3 Low Overhead Audio Stream (LOAS) */ + /** ISO/IEC 14496-3 Low Overhead Audio Stream (LOAS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4LOAS, - /* ISO/IEC 14496-3 Low Overhead Audio Transport Multiplex (LATM) */ + /** ISO/IEC 14496-3 Low Overhead Audio Transport Multiplex (LATM) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM, - /* ISO/IEC 14496-3 Audio Data Interchange Format (ADIF) */ + /** ISO/IEC 14496-3 Audio Data Interchange Format (ADIF) */ SPA_AUDIO_AAC_STREAM_FORMAT_ADIF, - /* ISO/IEC 14496-12 MPEG-4 file format */ + /** ISO/IEC 14496-12 MPEG-4 file format */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4FF, SPA_AUDIO_AAC_STREAM_FORMAT_CUSTOM = 0x10000, From cef14695b648f61ca054897903996d65bcf86ac2 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 20:47:36 +0200 Subject: [PATCH 0315/1014] spa: Fix iec958 docs --- spa/include/spa/param/audio/iec958.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/iec958.h b/spa/include/spa/param/audio/iec958.h index 05475188e..57b081856 100644 --- a/spa/include/spa/param/audio/iec958.h +++ b/spa/include/spa/param/audio/iec958.h @@ -31,7 +31,7 @@ enum spa_audio_iec958_codec { }; struct spa_audio_info_iec958 { - enum spa_audio_iec958_codec codec; /*< format, one of the DSP formats in enum spa_audio_format_dsp */ + enum spa_audio_iec958_codec codec; /*< codec, one of the values in enum spa_audio_iec958_codec */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ }; From bd25614cb98fe14f7ab75fd74c514da0e62150fc Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 20:51:49 +0200 Subject: [PATCH 0316/1014] spa: Include Opus in compressed.h header Opus is a compressed format. --- spa/include/spa/param/audio/compressed.h | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/include/spa/param/audio/compressed.h b/spa/include/spa/param/audio/compressed.h index 1105415b9..da8b81456 100644 --- a/spa/include/spa/param/audio/compressed.h +++ b/spa/include/spa/param/audio/compressed.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include From 5db9bca75c0cb07c6b20416d1cb0f00a338bb8f8 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 21:41:51 +0200 Subject: [PATCH 0317/1014] spa: add AC3, EAC3, TrueHD, DTS, MPEG-H formats --- spa/include/spa/param/audio/ac3-utils.h | 69 ++++++++++++++++++++ spa/include/spa/param/audio/ac3.h | 35 +++++++++++ spa/include/spa/param/audio/compressed.h | 4 ++ spa/include/spa/param/audio/dts-types.h | 39 ++++++++++++ spa/include/spa/param/audio/dts-utils.h | 73 ++++++++++++++++++++++ spa/include/spa/param/audio/dts.h | 51 +++++++++++++++ spa/include/spa/param/audio/eac3-utils.h | 69 ++++++++++++++++++++ spa/include/spa/param/audio/eac3.h | 35 +++++++++++ spa/include/spa/param/audio/format-utils.h | 25 ++++++++ spa/include/spa/param/audio/format.h | 10 +++ spa/include/spa/param/audio/mpegh-utils.h | 69 ++++++++++++++++++++ spa/include/spa/param/audio/mpegh.h | 46 ++++++++++++++ spa/include/spa/param/audio/truehd-utils.h | 69 ++++++++++++++++++++ spa/include/spa/param/audio/truehd.h | 35 +++++++++++ spa/include/spa/param/audio/type-info.h | 1 + spa/include/spa/param/format-types.h | 9 +++ spa/include/spa/param/format.h | 7 +++ spa/lib/lib.c | 11 ++++ 18 files changed, 657 insertions(+) create mode 100644 spa/include/spa/param/audio/ac3-utils.h create mode 100644 spa/include/spa/param/audio/ac3.h create mode 100644 spa/include/spa/param/audio/dts-types.h create mode 100644 spa/include/spa/param/audio/dts-utils.h create mode 100644 spa/include/spa/param/audio/dts.h create mode 100644 spa/include/spa/param/audio/eac3-utils.h create mode 100644 spa/include/spa/param/audio/eac3.h create mode 100644 spa/include/spa/param/audio/mpegh-utils.h create mode 100644 spa/include/spa/param/audio/mpegh.h create mode 100644 spa/include/spa/param/audio/truehd-utils.h create mode 100644 spa/include/spa/param/audio/truehd.h diff --git a/spa/include/spa/param/audio/ac3-utils.h b/spa/include/spa/param/audio/ac3-utils.h new file mode 100644 index 000000000..bfea5b563 --- /dev/null +++ b/spa/include/spa/param/audio/ac3-utils.h @@ -0,0 +1,69 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_AC3_UTILS_H +#define SPA_AUDIO_AC3_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include +#include + +#ifndef SPA_API_AUDIO_AC3_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_AC3_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_AC3_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_AC3_UTILS int +spa_format_audio_ac3_parse(const struct spa_pod *format, struct spa_audio_info_ac3 *info) +{ + int res; + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + return res; +} + +SPA_API_AUDIO_AC3_UTILS struct spa_pod * +spa_format_audio_ac3_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_ac3 *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_ac3), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), + 0); + if (info->rate != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); + if (info->channels != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_AC3_UTILS_H */ diff --git a/spa/include/spa/param/audio/ac3.h b/spa/include/spa/param/audio/ac3.h new file mode 100644 index 000000000..cab27c3e3 --- /dev/null +++ b/spa/include/spa/param/audio/ac3.h @@ -0,0 +1,35 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_AC3_H +#define SPA_AUDIO_AC3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * \addtogroup spa_param + * \{ + */ + +/** Dolby AC-3 audio info. */ +struct spa_audio_info_ac3 { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ +}; + +#define SPA_AUDIO_INFO_AC3_INIT(...) ((struct spa_audio_info_ac3) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_AC3_H */ diff --git a/spa/include/spa/param/audio/compressed.h b/spa/include/spa/param/audio/compressed.h index da8b81456..d61ba321e 100644 --- a/spa/include/spa/param/audio/compressed.h +++ b/spa/include/spa/param/audio/compressed.h @@ -7,13 +7,17 @@ #define SPA_AUDIO_COMPRESSED_H #include +#include #include #include #include +#include #include #include +#include #include #include +#include #include #include diff --git a/spa/include/spa/param/audio/dts-types.h b/spa/include/spa/param/audio/dts-types.h new file mode 100644 index 000000000..6cf999a1a --- /dev/null +++ b/spa/include/spa/param/audio/dts-types.h @@ -0,0 +1,39 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_DTS_TYPES_H +#define SPA_AUDIO_DTS_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * \addtogroup spa_param + * \{ + */ + +#define SPA_TYPE_INFO_AudioDTSExtType SPA_TYPE_INFO_ENUM_BASE "AudioDTSExtType" +#define SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE SPA_TYPE_INFO_AudioDTSExtType ":" + +static const struct spa_type_info spa_type_audio_dts_ext_type[] = { + { SPA_AUDIO_DTS_EXT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "UNKNOWN", NULL }, + { SPA_AUDIO_DTS_EXT_NONE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "NONE", NULL }, + { SPA_AUDIO_DTS_EXT_HD_HRA, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "HRA", NULL }, + { SPA_AUDIO_DTS_EXT_HD_MA, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "MA", NULL }, + { 0, 0, NULL, NULL }, +}; + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_DTS_TYPES_H */ diff --git a/spa/include/spa/param/audio/dts-utils.h b/spa/include/spa/param/audio/dts-utils.h new file mode 100644 index 000000000..445c7e8fb --- /dev/null +++ b/spa/include/spa/param/audio/dts-utils.h @@ -0,0 +1,73 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_DTS_UTILS_H +#define SPA_AUDIO_DTS_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include +#include + +#ifndef SPA_API_AUDIO_DTS_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_DTS_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_DTS_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_DTS_UTILS int +spa_format_audio_dts_parse(const struct spa_pod *format, struct spa_audio_info_dts *info) +{ + int res; + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), + SPA_FORMAT_AUDIO_DTS_extType, SPA_POD_OPT_Id(&info->ext_type)); + return res; +} + +SPA_API_AUDIO_DTS_UTILS struct spa_pod * +spa_format_audio_dts_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_dts *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dts), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), + 0); + if (info->rate != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); + if (info->channels != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + if (info->ext_type != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_DTS_extType, SPA_POD_Id(info->ext_type), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_DTS_UTILS_H */ diff --git a/spa/include/spa/param/audio/dts.h b/spa/include/spa/param/audio/dts.h new file mode 100644 index 000000000..fd7a4133c --- /dev/null +++ b/spa/include/spa/param/audio/dts.h @@ -0,0 +1,51 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_DTS_H +#define SPA_AUDIO_DTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * \addtogroup spa_param + * \{ + */ + +/** + * Possible high-definition DTS extensions on top of core DTS. + */ +enum spa_audio_dts_ext_type { + SPA_AUDIO_DTS_EXT_UNKNOWN, + SPA_AUDIO_DTS_EXT_NONE, /**< No extension present; this is just regular DTS data */ + SPA_AUDIO_DTS_EXT_HD_HRA, /**< DTS-HD High Resolution Audio (lossy HD audio extension) */ + SPA_AUDIO_DTS_EXT_HD_MA, /**< DTS-HD Master Audio (lossless HD audio extension) */ +}; + +/** + * DTS Coherent Acoustics audio info. Optional extensions on top + * of the DTS content can be present, resulting in what is known + * as DTS-HD. \a ext_type specifies which extension is used in + * combination with the core DTS content (if any). + */ +struct spa_audio_info_dts { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ + enum spa_audio_dts_ext_type ext_type; /*< DTS-HD extension type */ +}; + +#define SPA_AUDIO_INFO_DTS_INIT(...) ((struct spa_audio_info_dts) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_DTS_H */ diff --git a/spa/include/spa/param/audio/eac3-utils.h b/spa/include/spa/param/audio/eac3-utils.h new file mode 100644 index 000000000..3cb7db25e --- /dev/null +++ b/spa/include/spa/param/audio/eac3-utils.h @@ -0,0 +1,69 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_EAC3_UTILS_H +#define SPA_AUDIO_EAC3_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include +#include + +#ifndef SPA_API_AUDIO_EAC3_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_EAC3_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_EAC3_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_EAC3_UTILS int +spa_format_audio_eac3_parse(const struct spa_pod *format, struct spa_audio_info_eac3 *info) +{ + int res; + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + return res; +} + +SPA_API_AUDIO_EAC3_UTILS struct spa_pod * +spa_format_audio_eac3_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_eac3 *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_eac3), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), + 0); + if (info->rate != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); + if (info->channels != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_EAC3_UTILS_H */ diff --git a/spa/include/spa/param/audio/eac3.h b/spa/include/spa/param/audio/eac3.h new file mode 100644 index 000000000..8d27b460e --- /dev/null +++ b/spa/include/spa/param/audio/eac3.h @@ -0,0 +1,35 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_EAC3_H +#define SPA_AUDIO_EAC3_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * \addtogroup spa_param + * \{ + */ + +/** Dolby E-AC-3 audio info. */ +struct spa_audio_info_eac3 { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ +}; + +#define SPA_AUDIO_INFO_EAC3_INIT(...) ((struct spa_audio_info_eac3) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_EAC3_H */ diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 4ff40faa1..da4d35fcb 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -27,6 +27,11 @@ extern "C" { #include #include #include +#include +#include +#include +#include +#include /** @@ -80,6 +85,16 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info return spa_format_audio_flac_parse(format, &info->info.flac); case SPA_MEDIA_SUBTYPE_ape: return spa_format_audio_ape_parse(format, &info->info.ape); + case SPA_MEDIA_SUBTYPE_ac3: + return spa_format_audio_ac3_parse(format, &info->info.ac3); + case SPA_MEDIA_SUBTYPE_eac3: + return spa_format_audio_eac3_parse(format, &info->info.eac3); + case SPA_MEDIA_SUBTYPE_truehd: + return spa_format_audio_truehd_parse(format, &info->info.truehd); + case SPA_MEDIA_SUBTYPE_dts: + return spa_format_audio_dts_parse(format, &info->info.dts); + case SPA_MEDIA_SUBTYPE_mpegh: + return spa_format_audio_mpegh_parse(format, &info->info.mpegh); } return -ENOTSUP; } @@ -115,6 +130,16 @@ spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, return spa_format_audio_flac_build(builder, id, &info->info.flac); case SPA_MEDIA_SUBTYPE_ape: return spa_format_audio_ape_build(builder, id, &info->info.ape); + case SPA_MEDIA_SUBTYPE_ac3: + return spa_format_audio_ac3_build(builder, id, &info->info.ac3); + case SPA_MEDIA_SUBTYPE_eac3: + return spa_format_audio_eac3_build(builder, id, &info->info.eac3); + case SPA_MEDIA_SUBTYPE_truehd: + return spa_format_audio_truehd_build(builder, id, &info->info.truehd); + case SPA_MEDIA_SUBTYPE_dts: + return spa_format_audio_dts_build(builder, id, &info->info.dts); + case SPA_MEDIA_SUBTYPE_mpegh: + return spa_format_audio_mpegh_build(builder, id, &info->info.mpegh); } errno = ENOTSUP; return NULL; diff --git a/spa/include/spa/param/audio/format.h b/spa/include/spa/param/audio/format.h index 0619de394..c38d505e3 100644 --- a/spa/include/spa/param/audio/format.h +++ b/spa/include/spa/param/audio/format.h @@ -29,6 +29,11 @@ extern "C" { #include #include #include +#include +#include +#include +#include +#include struct spa_audio_info { uint32_t media_type; @@ -48,6 +53,11 @@ struct spa_audio_info { struct spa_audio_info_flac flac; struct spa_audio_info_ape ape; struct spa_audio_info_ape opus; + struct spa_audio_info_ac3 ac3; + struct spa_audio_info_eac3 eac3; + struct spa_audio_info_truehd truehd; + struct spa_audio_info_dts dts; + struct spa_audio_info_mpegh mpegh; } info; }; diff --git a/spa/include/spa/param/audio/mpegh-utils.h b/spa/include/spa/param/audio/mpegh-utils.h new file mode 100644 index 000000000..6544c24c0 --- /dev/null +++ b/spa/include/spa/param/audio/mpegh-utils.h @@ -0,0 +1,69 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_MPEGH_UTILS_H +#define SPA_AUDIO_MPEGH_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include +#include + +#ifndef SPA_API_AUDIO_MPEGH_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_MPEGH_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_MPEGH_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_MPEGH_UTILS int +spa_format_audio_mpegh_parse(const struct spa_pod *format, struct spa_audio_info_mpegh *info) +{ + int res; + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + return res; +} + +SPA_API_AUDIO_MPEGH_UTILS struct spa_pod * +spa_format_audio_mpegh_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_mpegh *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mpegh), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), + 0); + if (info->rate != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); + if (info->channels != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_MPEGH_UTILS_H */ diff --git a/spa/include/spa/param/audio/mpegh.h b/spa/include/spa/param/audio/mpegh.h new file mode 100644 index 000000000..e654a500b --- /dev/null +++ b/spa/include/spa/param/audio/mpegh.h @@ -0,0 +1,46 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_MPEGH_H +#define SPA_AUDIO_MPEGH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * \addtogroup spa_param + * \{ + */ + +/** + * MPEG-H 3D audio info. + * + * MPEG-H content is assumed to be provided in the form of an MPEG-H + * 3D Audio Stream (MHAS). MHAS is a lightweight bitstream format that + * encapsulates MPEG-H 3D Audio frames along with associated metadata. + * It serves a similar role to the Annex B byte stream format used for + * H.264, providing framing and synchronization for MPEG-H frames. + * + * MPEG-H is documented in the ISO/IEC 23008-3 specification. + * MHAS is specified in ISO/IEC 23008-3, Clause 14. + */ +struct spa_audio_info_mpegh { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ +}; + +#define SPA_AUDIO_INFO_MPEGH_INIT(...) ((struct spa_audio_info_mpegh) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_MPEGH_H */ diff --git a/spa/include/spa/param/audio/truehd-utils.h b/spa/include/spa/param/audio/truehd-utils.h new file mode 100644 index 000000000..61c67c017 --- /dev/null +++ b/spa/include/spa/param/audio/truehd-utils.h @@ -0,0 +1,69 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_TRUEHD_UTILS_H +#define SPA_AUDIO_TRUEHD_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include +#include +#include +#include + +#ifndef SPA_API_AUDIO_TRUEHD_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_TRUEHD_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_TRUEHD_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_TRUEHD_UTILS int +spa_format_audio_truehd_parse(const struct spa_pod *format, struct spa_audio_info_truehd *info) +{ + int res; + res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), + SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + return res; +} + +SPA_API_AUDIO_TRUEHD_UTILS struct spa_pod * +spa_format_audio_truehd_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_truehd *info) +{ + struct spa_pod_frame f; + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_truehd), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), + 0); + if (info->rate != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); + if (info->channels != 0) + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_TRUEHD_UTILS_H */ diff --git a/spa/include/spa/param/audio/truehd.h b/spa/include/spa/param/audio/truehd.h new file mode 100644 index 000000000..44adbbb3e --- /dev/null +++ b/spa/include/spa/param/audio/truehd.h @@ -0,0 +1,35 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_TRUEHD_H +#define SPA_AUDIO_TRUEHD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * \addtogroup spa_param + * \{ + */ + +/** Dolby TrueHD audio info. */ +struct spa_audio_info_truehd { + uint32_t rate; /*< sample rate */ + uint32_t channels; /*< number of channels */ +}; + +#define SPA_AUDIO_INFO_TRUEHD_INIT(...) ((struct spa_audio_info_truehd) { __VA_ARGS__ }) + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_TRUEHD_H */ diff --git a/spa/include/spa/param/audio/type-info.h b/spa/include/spa/param/audio/type-info.h index 8a3aa49aa..1a15ad3dd 100644 --- a/spa/include/spa/param/audio/type-info.h +++ b/spa/include/spa/param/audio/type-info.h @@ -11,5 +11,6 @@ #include #include #include +#include #endif /* SPA_AUDIO_TYPES_H */ diff --git a/spa/include/spa/param/format-types.h b/spa/include/spa/param/format-types.h index 46ff10624..7af6f2784 100644 --- a/spa/include/spa/param/format-types.h +++ b/spa/include/spa/param/format-types.h @@ -65,6 +65,11 @@ static const struct spa_type_info spa_type_media_subtype[] = { { SPA_MEDIA_SUBTYPE_flac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "flac", NULL }, { SPA_MEDIA_SUBTYPE_ape, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ape", NULL }, { SPA_MEDIA_SUBTYPE_opus, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "opus", NULL }, + { SPA_MEDIA_SUBTYPE_ac3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ac3", NULL }, + { SPA_MEDIA_SUBTYPE_eac3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "eac3", NULL }, + { SPA_MEDIA_SUBTYPE_truehd, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "truehd", NULL }, + { SPA_MEDIA_SUBTYPE_dts, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dts", NULL }, + { SPA_MEDIA_SUBTYPE_mpegh, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegh", NULL }, /* video subtypes */ { SPA_MEDIA_SUBTYPE_h264, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", NULL }, { SPA_MEDIA_SUBTYPE_mjpg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", NULL }, @@ -99,6 +104,8 @@ static const struct spa_type_info spa_type_media_subtype[] = { #define SPA_TYPE_INFO_FORMAT_AUDIO_AMR_BASE SPA_TYPE_INFO_FORMAT_AUDIO_AMR ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_MP3 SPA_TYPE_INFO_FORMAT_AUDIO_BASE "MP3" #define SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE SPA_TYPE_INFO_FORMAT_AUDIO_MP3 ":" +#define SPA_TYPE_INFO_FORMAT_AUDIO_DTS SPA_TYPE_INFO_FORMAT_AUDIO_BASE "DTS" +#define SPA_TYPE_INFO_FORMAT_AUDIO_DTS_BASE SPA_TYPE_INFO_FORMAT_AUDIO_DTS ":" #define SPA_TYPE_INFO_FormatVideo SPA_TYPE_INFO_FORMAT_BASE "Video" #define SPA_TYPE_INFO_FORMAT_VIDEO_BASE SPA_TYPE_INFO_FormatVideo ":" @@ -143,6 +150,8 @@ static const struct spa_type_info spa_type_format[] = { spa_type_audio_amr_band_mode }, { SPA_FORMAT_AUDIO_MP3_channelMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE "channelMode", spa_type_audio_mp3_channel_mode }, + { SPA_FORMAT_AUDIO_DTS_extType, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_DTS_BASE "extType", + spa_type_audio_dts_ext_type }, { SPA_FORMAT_VIDEO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format", spa_type_video_format, }, diff --git a/spa/include/spa/param/format.h b/spa/include/spa/param/format.h index ffb9959ce..25052495a 100644 --- a/spa/include/spa/param/format.h +++ b/spa/include/spa/param/format.h @@ -52,6 +52,11 @@ enum spa_media_subtype { SPA_MEDIA_SUBTYPE_flac, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_ape, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_opus, /** since 0.3.68 */ + SPA_MEDIA_SUBTYPE_ac3, /** since 1.5.1 */ + SPA_MEDIA_SUBTYPE_eac3, /** since 1.5.1 */ + SPA_MEDIA_SUBTYPE_truehd, /** since 1.5.1 */ + SPA_MEDIA_SUBTYPE_dts, /** since 1.5.1 */ + SPA_MEDIA_SUBTYPE_mpegh, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_START_Video = 0x20000, SPA_MEDIA_SUBTYPE_h264, @@ -112,6 +117,8 @@ enum spa_format { SPA_FORMAT_AUDIO_MP3_channelMode, /**< MP3 channel mode, (Id enum spa_audio_mp3_channel_mode) */ + SPA_FORMAT_AUDIO_DTS_extType, /**< DTS extension type (Id enum spa_audio_dts_ext_type) */ + /* Video Format keys */ SPA_FORMAT_START_Video = 0x20000, diff --git a/spa/lib/lib.c b/spa/lib/lib.c index e2acb9cc2..648fb82a1 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include #include @@ -46,6 +48,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -57,6 +64,8 @@ #include #include #include +#include +#include #include #include #include @@ -65,6 +74,8 @@ #include #include #include +#include +#include #include #include #include From 7341cc401bd27a07022228e96c37742c1c11ab6b Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 18 Jan 2023 15:33:02 +0100 Subject: [PATCH 0318/1014] pw-cat: Add support for AC3, EAC3, TrueHD, DTS, MPEG-H --- src/tools/pw-cat.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 4e7e6954a..dab36b69b 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -412,6 +412,42 @@ static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *c info->info.amr.channels = data->channels; info->info.amr.band_mode = SPA_AUDIO_AMR_BAND_MODE_WB; break; + case AV_CODEC_ID_AC3: + info->media_subtype = SPA_MEDIA_SUBTYPE_ac3; + info->info.ac3.rate = data->rate; + info->info.ac3.channels = data->channels; + break; + case AV_CODEC_ID_EAC3: + info->media_subtype = SPA_MEDIA_SUBTYPE_eac3; + info->info.eac3.rate = data->rate; + info->info.eac3.channels = data->channels; + break; + case AV_CODEC_ID_TRUEHD: + info->media_subtype = SPA_MEDIA_SUBTYPE_truehd; + info->info.truehd.rate = data->rate; + info->info.truehd.channels = data->channels; + break; + case AV_CODEC_ID_DTS: + info->media_subtype = SPA_MEDIA_SUBTYPE_dts; + info->info.dts.rate = data->rate; + info->info.dts.channels = data->channels; + switch (codec_params->profile) { + case FF_PROFILE_DTS_HD_MA: + info->info.dts.ext_type = SPA_AUDIO_DTS_EXT_HD_MA; + break; + case FF_PROFILE_DTS_HD_HRA: + info->info.dts.ext_type = SPA_AUDIO_DTS_EXT_HD_HRA; + break; + default: + info->info.dts.ext_type = SPA_AUDIO_DTS_EXT_NONE; + break; + } + break; + case AV_CODEC_ID_MPEGH_3D_AUDIO: + info->media_subtype = SPA_MEDIA_SUBTYPE_mpegh; + info->info.mpegh.rate = data->rate; + info->info.mpegh.channels = data->channels; + break; default: fprintf(stderr, "Unsupported encoded media subtype\n"); return -EINVAL; From e2731914ad50cb8364517d28ce427fdfac2ae6c8 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 29 May 2025 22:24:31 +0200 Subject: [PATCH 0319/1014] pw-cat: Document numeric WMA constants --- src/tools/pw-cat.c | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index dab36b69b..2bcda784f 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -326,8 +326,6 @@ static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_fram static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *codec_params, struct spa_audio_info *info) { - int32_t profile; - switch (codec_params->codec_id) { case AV_CODEC_ID_VORBIS: info->media_subtype = SPA_MEDIA_SUBTYPE_vorbis; @@ -352,32 +350,50 @@ static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *c case AV_CODEC_ID_WMAVOICE: case AV_CODEC_ID_WMALOSSLESS: info->media_subtype = SPA_MEDIA_SUBTYPE_wma; + info->info.wma.rate = data->rate; + info->info.wma.channels = data->channels; + info->info.wma.bitrate = data->bitrate; + info->info.wma.block_align = codec_params->block_align; + /* The codec tag is not decided by FFmpeg; instead, it is + * directly taken from the container. FFmpeg currently has + * no named constants for the WMA versions, so numeric + * constants have to be used here instead. */ switch (codec_params->codec_tag) { - /* TODO see if these hex constants can be replaced by named constants from FFmpeg */ + /* This originates from Microsoft's mmreg.h , where + * 0x160 is specified as meaning WMA v1 (which is commonly + * referred to as WMA 7). */ + case 0x160: + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA7; + break; + /* This originates from Microsoft's mmreg.h , where + * 0x161 is specified as meaning WMA v2 (which is commonly + * referred to as WMA 8 or 9). */ case 0x161: - profile = SPA_AUDIO_WMA_PROFILE_WMA9; + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA9; break; + /* This originates from Microsoft's mmreg.h , where + * 0x162 is specified as meaning WMA v3 (which is commonly + * referred to as WMA 9 Pro). */ case 0x162: - profile = SPA_AUDIO_WMA_PROFILE_WMA9_PRO; + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA9_PRO; break; + /* This originates from Microsoft's mmreg.h , where + * 0x163 is specified as meaning WMA 9 lossless. */ case 0x163: - profile = SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS; + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS; break; + /* WMA 10 is not mentioned in mmreg.h - these were empirically + * determined instead. */ case 0x166: - profile = SPA_AUDIO_WMA_PROFILE_WMA10; + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA10; break; case 0x167: - profile = SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS; + info->info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS; break; default: fprintf(stderr, "error: invalid WMA profile\n"); return -EINVAL; } - info->info.wma.rate = data->rate; - info->info.wma.channels = data->channels; - info->info.wma.bitrate = data->bitrate; - info->info.wma.block_align = codec_params->block_align; - info->info.wma.profile = profile; break; case AV_CODEC_ID_FLAC: info->media_subtype = SPA_MEDIA_SUBTYPE_flac; From b943c31fd89f93ae79430a368e56954f9bf1c74f Mon Sep 17 00:00:00 2001 From: Sam James Date: Tue, 27 May 2025 09:06:08 +0100 Subject: [PATCH 0320/1014] *: don't include standard C headers inside of extern "C" Including C headers inside of `extern "C"` breaks use from C++. Hoist the includes of standard C headers above the block so we don't try to mangle the stdlib. I initially tried to scope this with a targeted change but it's too hard to do correctly that way. This way, we avoid whack-a-mole. Firefox is working around this in their e21461b7b8b39cc31ba53c47d4f6f310c673ff2f commit. Bug: https://bugzilla.mozilla.org/1953080 --- spa/include/spa/buffer/alloc.h | 4 ++-- spa/include/spa/buffer/buffer.h | 6 +++--- spa/include/spa/buffer/meta.h | 6 +++--- spa/include/spa/buffer/type-info.h | 8 ++++---- spa/include/spa/control/control.h | 6 +++--- spa/include/spa/control/type-info.h | 8 ++++---- spa/include/spa/control/ump-utils.h | 6 +++--- spa/include/spa/debug/buffer.h | 10 +++++----- spa/include/spa/debug/context.h | 9 +++++---- spa/include/spa/debug/dict.h | 6 +++--- spa/include/spa/debug/file.h | 8 ++++---- spa/include/spa/debug/format.h | 14 ++++++------- spa/include/spa/debug/log.h | 8 ++++---- spa/include/spa/debug/mem.h | 8 ++++---- spa/include/spa/debug/node.h | 8 ++++---- spa/include/spa/debug/pod.h | 12 +++++------ spa/include/spa/debug/types.h | 8 ++++---- spa/include/spa/filter-graph/filter-graph.h | 8 ++++---- spa/include/spa/graph/graph.h | 14 ++++++------- spa/include/spa/monitor/device.h | 8 ++++---- spa/include/spa/monitor/event.h | 4 ++-- spa/include/spa/monitor/type-info.h | 8 ++++---- spa/include/spa/monitor/utils.h | 6 +++--- spa/include/spa/node/command.h | 4 ++-- spa/include/spa/node/event.h | 4 ++-- spa/include/spa/node/io.h | 6 +++--- spa/include/spa/node/node.h | 16 +++++++-------- spa/include/spa/node/type-info.h | 12 +++++------ spa/include/spa/node/utils.h | 8 ++++---- spa/include/spa/param/audio/aac-types.h | 6 +++--- spa/include/spa/param/audio/aac-utils.h | 10 +++++----- spa/include/spa/param/audio/aac.h | 4 ++-- spa/include/spa/param/audio/ac3-utils.h | 10 +++++----- spa/include/spa/param/audio/ac3.h | 4 ++-- spa/include/spa/param/audio/alac-utils.h | 10 +++++----- spa/include/spa/param/audio/alac.h | 4 ++-- spa/include/spa/param/audio/amr-types.h | 6 +++--- spa/include/spa/param/audio/amr-utils.h | 10 +++++----- spa/include/spa/param/audio/amr.h | 4 ++-- spa/include/spa/param/audio/ape-utils.h | 10 +++++----- spa/include/spa/param/audio/ape.h | 4 ++-- spa/include/spa/param/audio/dsd-utils.h | 10 +++++----- spa/include/spa/param/audio/dsd.h | 6 +++--- spa/include/spa/param/audio/dsp-utils.h | 10 +++++----- spa/include/spa/param/audio/dsp.h | 4 ++-- spa/include/spa/param/audio/dts-types.h | 6 +++--- spa/include/spa/param/audio/dts-utils.h | 10 +++++----- spa/include/spa/param/audio/dts.h | 4 ++-- spa/include/spa/param/audio/eac3-utils.h | 10 +++++----- spa/include/spa/param/audio/eac3.h | 4 ++-- spa/include/spa/param/audio/flac-utils.h | 10 +++++----- spa/include/spa/param/audio/flac.h | 4 ++-- spa/include/spa/param/audio/format-utils.h | 7 +++---- spa/include/spa/param/audio/format.h | 18 ++++++++--------- spa/include/spa/param/audio/iec958-types.h | 6 +++--- spa/include/spa/param/audio/iec958-utils.h | 10 +++++----- spa/include/spa/param/audio/layout.h | 7 ++++--- spa/include/spa/param/audio/mp3-types.h | 6 +++--- spa/include/spa/param/audio/mp3-utils.h | 10 +++++----- spa/include/spa/param/audio/mp3.h | 4 ++-- spa/include/spa/param/audio/mpegh-utils.h | 10 +++++----- spa/include/spa/param/audio/mpegh.h | 4 ++-- spa/include/spa/param/audio/opus.h | 4 ++-- spa/include/spa/param/audio/ra-utils.h | 10 +++++----- spa/include/spa/param/audio/ra.h | 4 ++-- spa/include/spa/param/audio/raw-json.h | 10 +++++----- spa/include/spa/param/audio/raw-types.h | 8 ++++---- spa/include/spa/param/audio/raw-utils.h | 10 +++++----- spa/include/spa/param/audio/raw.h | 8 ++++---- spa/include/spa/param/audio/truehd-utils.h | 10 +++++----- spa/include/spa/param/audio/truehd.h | 4 ++-- spa/include/spa/param/audio/vorbis-utils.h | 10 +++++----- spa/include/spa/param/audio/vorbis.h | 4 ++-- spa/include/spa/param/audio/wma-types.h | 6 +++--- spa/include/spa/param/audio/wma-utils.h | 10 +++++----- spa/include/spa/param/audio/wma.h | 4 ++-- spa/include/spa/param/bluetooth/type-info.h | 4 ++-- spa/include/spa/param/buffers-types.h | 10 +++++----- spa/include/spa/param/buffers.h | 4 ++-- spa/include/spa/param/format-types.h | 14 ++++++------- spa/include/spa/param/format-utils.h | 6 +++--- spa/include/spa/param/format.h | 4 ++-- spa/include/spa/param/latency-types.h | 8 ++++---- spa/include/spa/param/latency-utils.h | 12 +++++------ spa/include/spa/param/latency.h | 4 ++-- spa/include/spa/param/param-types.h | 8 ++++---- spa/include/spa/param/param.h | 4 ++-- spa/include/spa/param/port-config-types.h | 8 ++++---- spa/include/spa/param/port-config.h | 4 ++-- spa/include/spa/param/profile-types.h | 8 ++++---- spa/include/spa/param/profile.h | 4 ++-- spa/include/spa/param/profiler-types.h | 6 +++--- spa/include/spa/param/profiler.h | 4 ++-- spa/include/spa/param/props-types.h | 8 ++++---- spa/include/spa/param/props.h | 4 ++-- spa/include/spa/param/route-types.h | 10 +++++----- spa/include/spa/param/route.h | 4 ++-- spa/include/spa/param/tag-types.h | 8 ++++---- spa/include/spa/param/tag-utils.h | 14 ++++++------- spa/include/spa/param/tag.h | 4 ++-- spa/include/spa/param/video/dsp-utils.h | 8 ++++---- spa/include/spa/param/video/format-utils.h | 8 ++++---- spa/include/spa/param/video/format.h | 10 +++++----- spa/include/spa/param/video/h264-utils.h | 8 ++++---- spa/include/spa/param/video/h264.h | 4 ++-- spa/include/spa/param/video/mjpg-utils.h | 8 ++++---- spa/include/spa/param/video/mjpg.h | 4 ++-- spa/include/spa/param/video/raw-types.h | 5 +++-- spa/include/spa/param/video/raw-utils.h | 8 ++++---- spa/include/spa/param/video/raw.h | 10 +++++----- spa/include/spa/pod/builder.h | 12 +++++------ spa/include/spa/pod/command.h | 6 +++--- spa/include/spa/pod/compare.h | 8 ++++---- spa/include/spa/pod/dynamic.h | 6 +++--- spa/include/spa/pod/event.h | 4 ++-- spa/include/spa/pod/filter.h | 8 ++++---- spa/include/spa/pod/iter.h | 8 ++++---- spa/include/spa/pod/parser.h | 8 ++++---- spa/include/spa/pod/pod.h | 6 +++--- spa/include/spa/pod/vararg.h | 8 ++++---- spa/include/spa/support/cpu.h | 8 ++++---- spa/include/spa/support/dbus.h | 4 ++-- spa/include/spa/support/i18n.h | 6 +++--- spa/include/spa/support/log-impl.h | 8 ++++---- spa/include/spa/support/log.h | 8 ++++---- spa/include/spa/support/loop.h | 8 ++++---- spa/include/spa/support/plugin-loader.h | 6 +++--- spa/include/spa/support/plugin.h | 8 ++++---- spa/include/spa/support/system.h | 12 +++++------ spa/include/spa/support/thread.h | 8 ++++---- spa/include/spa/utils/defs.h | 14 ++++++------- spa/include/spa/utils/dict.h | 8 ++++---- spa/include/spa/utils/dll.h | 8 ++++---- spa/include/spa/utils/enum-types.h | 6 +++--- spa/include/spa/utils/hook.h | 6 +++--- spa/include/spa/utils/json-core.h | 11 +++++----- spa/include/spa/utils/json-pod.h | 8 ++++---- spa/include/spa/utils/json.h | 11 +++++----- spa/include/spa/utils/list.h | 4 ++-- spa/include/spa/utils/ratelimit.h | 8 ++++---- spa/include/spa/utils/result.h | 8 ++++---- spa/include/spa/utils/ringbuffer.h | 4 ++-- spa/include/spa/utils/string.h | 8 ++++---- spa/include/spa/utils/type-info.h | 20 +++++++++---------- spa/include/spa/utils/type.h | 6 +++--- spa/plugins/alsa/acp/acp.h | 12 +++++------ spa/plugins/alsa/acp/array.h | 6 +++--- spa/plugins/alsa/acp/card.h | 4 ++-- spa/plugins/alsa/acp/channelmap.h | 4 ++-- spa/plugins/alsa/acp/compat.h | 12 +++++------ spa/plugins/alsa/acp/device-port.h | 4 ++-- spa/plugins/alsa/acp/dynarray.h | 4 ++-- spa/plugins/alsa/acp/hashmap.h | 4 ++-- spa/plugins/alsa/acp/idxset.h | 4 ++-- spa/plugins/alsa/acp/proplist.h | 8 ++++---- spa/plugins/alsa/acp/volume.h | 4 ++-- spa/plugins/alsa/alsa-pcm.h | 7 +++---- spa/plugins/alsa/alsa-seq.h | 7 +++---- spa/plugins/audioconvert/hilbert.h | 7 +++---- spa/plugins/avb/avb-pcm.h | 8 ++++---- spa/plugins/bluez5/defs.h | 8 ++++---- spa/plugins/jack/jack-client.h | 8 ++++---- src/modules/module-avb/internal.h | 4 ++-- .../module-client-node/v0/ext-client-node.h | 8 ++++---- src/modules/module-client-node/v0/transport.h | 8 ++++---- src/modules/module-jack-tunnel/weakjack.h | 8 ++++---- .../module-protocol-native/connection.h | 8 ++++---- .../module-protocol-native/v0/interfaces.h | 8 ++++---- src/modules/module-protocol-pulse/manager.h | 8 ++++---- .../module-protocol-pulse/pulse-server.h | 6 +++--- src/modules/module-raop/rtsp-client.h | 8 ++++---- src/pipewire/array.h | 8 ++++---- src/pipewire/client.h | 8 ++++---- src/pipewire/context.h | 6 +++--- src/pipewire/control.h | 4 ++-- src/pipewire/core.h | 8 ++++---- src/pipewire/data-loop.h | 6 +++--- src/pipewire/device.h | 8 ++++---- src/pipewire/extensions/client-node.h | 6 +++--- src/pipewire/extensions/metadata.h | 8 ++++---- src/pipewire/extensions/profiler.h | 4 ++-- src/pipewire/extensions/protocol-native.h | 8 ++++---- src/pipewire/extensions/security-context.h | 4 ++-- src/pipewire/factory.h | 8 ++++---- src/pipewire/i18n.h | 3 ++- src/pipewire/impl-client.h | 4 ++-- src/pipewire/impl-module.h | 8 ++++---- src/pipewire/impl-port.h | 4 ++-- src/pipewire/keys.h | 3 ++- src/pipewire/link.h | 8 ++++---- src/pipewire/loop.h | 6 +++--- src/pipewire/map.h | 8 ++++---- src/pipewire/module.h | 8 ++++---- src/pipewire/node.h | 8 ++++---- src/pipewire/permission.h | 4 ++-- src/pipewire/pipewire.h | 8 ++++---- src/pipewire/port.h | 8 ++++---- src/pipewire/private.h | 8 ++++---- src/pipewire/properties.h | 8 ++++---- src/pipewire/protocol.h | 4 ++-- src/pipewire/proxy.h | 4 ++-- src/pipewire/resource.h | 4 ++-- src/pipewire/thread-loop.h | 4 ++-- src/pipewire/thread.h | 8 ++++---- src/pipewire/type.h | 4 ++-- src/pipewire/utils.h | 16 +++++++-------- test/pwtest.h | 8 ++++---- 207 files changed, 753 insertions(+), 752 deletions(-) diff --git a/spa/include/spa/buffer/alloc.h b/spa/include/spa/buffer/alloc.h index dc8f4cc64..ca2c317ab 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -5,12 +5,12 @@ #ifndef SPA_BUFFER_ALLOC_H #define SPA_BUFFER_ALLOC_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - #ifndef SPA_API_BUFFER_ALLOC #ifdef SPA_API_IMPL #define SPA_API_BUFFER_ALLOC SPA_API_IMPL diff --git a/spa/include/spa/buffer/buffer.h b/spa/include/spa/buffer/buffer.h index 03fd13b2a..39ca6fb81 100644 --- a/spa/include/spa/buffer/buffer.h +++ b/spa/include/spa/buffer/buffer.h @@ -5,13 +5,13 @@ #ifndef SPA_BUFFER_H #define SPA_BUFFER_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_BUFFER #ifdef SPA_API_IMPL #define SPA_API_BUFFER SPA_API_IMPL diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index b484cfb01..85d9d655b 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -5,13 +5,13 @@ #ifndef SPA_META_H #define SPA_META_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_META #ifdef SPA_API_IMPL #define SPA_API_META SPA_API_IMPL diff --git a/spa/include/spa/buffer/type-info.h b/spa/include/spa/buffer/type-info.h index cdfe52770..47577dccb 100644 --- a/spa/include/spa/buffer/type-info.h +++ b/spa/include/spa/buffer/type-info.h @@ -5,6 +5,10 @@ #ifndef SPA_BUFFER_TYPES_H #define SPA_BUFFER_TYPES_H +#include +#include +#include + /** * \addtogroup spa_buffer * \{ @@ -14,10 +18,6 @@ extern "C" { #endif -#include -#include -#include - #define SPA_TYPE_INFO_Buffer SPA_TYPE_INFO_POINTER_BASE "Buffer" #define SPA_TYPE_INFO_BUFFER_BASE SPA_TYPE_INFO_Buffer ":" diff --git a/spa/include/spa/control/control.h b/spa/include/spa/control/control.h index 1c1ec81fb..69b0d106e 100644 --- a/spa/include/spa/control/control.h +++ b/spa/include/spa/control/control.h @@ -5,13 +5,13 @@ #ifndef SPA_CONTROL_H #define SPA_CONTROL_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** \defgroup spa_control Control * Control type declarations */ diff --git a/spa/include/spa/control/type-info.h b/spa/include/spa/control/type-info.h index 6c7bd6a96..eae237b01 100644 --- a/spa/include/spa/control/type-info.h +++ b/spa/include/spa/control/type-info.h @@ -5,6 +5,10 @@ #ifndef SPA_CONTROL_TYPES_H #define SPA_CONTROL_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - /* base for parameter object enumerations */ #define SPA_TYPE_INFO_Control SPA_TYPE_INFO_ENUM_BASE "Control" #define SPA_TYPE_INFO_CONTROL_BASE SPA_TYPE_INFO_Control ":" diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 463b12d14..0c0faa8ba 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -6,13 +6,13 @@ #ifndef SPA_CONTROL_UMP_UTILS_H #define SPA_CONTROL_UMP_UTILS_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_CONTROL_UMP_UTILS #ifdef SPA_API_IMPL #define SPA_API_CONTROL_UMP_UTILS SPA_API_IMPL diff --git a/spa/include/spa/debug/buffer.h b/spa/include/spa/debug/buffer.h index eea48ae4c..c0b533564 100644 --- a/spa/include/spa/debug/buffer.h +++ b/spa/include/spa/debug/buffer.h @@ -5,6 +5,11 @@ #ifndef SPA_DEBUG_BUFFER_H #define SPA_DEBUG_BUFFER_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -18,11 +23,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_DEBUG_BUFFER #ifdef SPA_API_IMPL #define SPA_API_DEBUG_BUFFER SPA_API_IMPL diff --git a/spa/include/spa/debug/context.h b/spa/include/spa/debug/context.h index 13002f666..73ae96535 100644 --- a/spa/include/spa/debug/context.h +++ b/spa/include/spa/debug/context.h @@ -5,15 +5,16 @@ #ifndef SPA_DEBUG_CONTEXT_H #define SPA_DEBUG_CONTEXT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include + +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_debug * \{ diff --git a/spa/include/spa/debug/dict.h b/spa/include/spa/debug/dict.h index 5657b2d98..5d9e22e6f 100644 --- a/spa/include/spa/debug/dict.h +++ b/spa/include/spa/debug/dict.h @@ -5,6 +5,9 @@ #ifndef SPA_DEBUG_DICT_H #define SPA_DEBUG_DICT_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,9 +17,6 @@ extern "C" { * \{ */ -#include -#include - #ifndef SPA_API_DEBUG_DICT #ifdef SPA_API_IMPL #define SPA_API_DEBUG_DICT SPA_API_IMPL diff --git a/spa/include/spa/debug/file.h b/spa/include/spa/debug/file.h index 17ce46b7e..9d1a2e2be 100644 --- a/spa/include/spa/debug/file.h +++ b/spa/include/spa/debug/file.h @@ -5,10 +5,6 @@ #ifndef SPA_DEBUG_FILE_H #define SPA_DEBUG_FILE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -21,6 +17,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_debug * \{ diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index ad7f20480..74c8b62bb 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -5,6 +5,13 @@ #ifndef SPA_DEBUG_FORMAT_H #define SPA_DEBUG_FORMAT_H +#include +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,13 +21,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include -#include -#include - #ifndef SPA_API_DEBUG_FORMAT #ifdef SPA_API_IMPL #define SPA_API_DEBUG_FORMAT SPA_API_IMPL diff --git a/spa/include/spa/debug/log.h b/spa/include/spa/debug/log.h index 05c3bd50f..4ede23905 100644 --- a/spa/include/spa/debug/log.h +++ b/spa/include/spa/debug/log.h @@ -5,10 +5,6 @@ #ifndef SPA_DEBUG_LOG_H #define SPA_DEBUG_LOG_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -20,6 +16,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_DEBUG_LOG #ifdef SPA_API_IMPL #define SPA_API_DEBUG_LOG SPA_API_IMPL diff --git a/spa/include/spa/debug/mem.h b/spa/include/spa/debug/mem.h index 966629482..98f1761a1 100644 --- a/spa/include/spa/debug/mem.h +++ b/spa/include/spa/debug/mem.h @@ -5,19 +5,19 @@ #ifndef SPA_DEBUG_MEM_H #define SPA_DEBUG_MEM_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_debug * \{ */ -#include - #ifndef SPA_API_DEBUG_MEM #ifdef SPA_API_IMPL #define SPA_API_DEBUG_MEM SPA_API_IMPL diff --git a/spa/include/spa/debug/node.h b/spa/include/spa/debug/node.h index baa273ffe..c25112f4d 100644 --- a/spa/include/spa/debug/node.h +++ b/spa/include/spa/debug/node.h @@ -5,6 +5,10 @@ #ifndef SPA_DEBUG_NODE_H #define SPA_DEBUG_NODE_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_DEBUG_NODE #ifdef SPA_API_IMPL #define SPA_API_DEBUG_NODE SPA_API_IMPL diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index 9db6f4b05..fe425676d 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -5,6 +5,12 @@ #ifndef SPA_DEBUG_POD_H #define SPA_DEBUG_POD_H +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,12 +20,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include -#include - #ifndef SPA_API_DEBUG_POD #ifdef SPA_API_IMPL #define SPA_API_DEBUG_POD SPA_API_IMPL diff --git a/spa/include/spa/debug/types.h b/spa/include/spa/debug/types.h index d7ca83666..d52dbc89f 100644 --- a/spa/include/spa/debug/types.h +++ b/spa/include/spa/debug/types.h @@ -5,6 +5,10 @@ #ifndef SPA_DEBUG_TYPES_H #define SPA_DEBUG_TYPES_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include - -#include - #ifndef SPA_API_DEBUG_TYPES #ifdef SPA_API_IMPL #define SPA_API_DEBUG_TYPES SPA_API_IMPL diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h index 05904c7f3..481085c5f 100644 --- a/spa/include/spa/filter-graph/filter-graph.h +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -5,16 +5,16 @@ #ifndef SPA_FILTER_GRAPH_H #define SPA_FILTER_GRAPH_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_FILTER_GRAPH #ifdef SPA_API_IMPL #define SPA_API_FILTER_GRAPH SPA_API_IMPL diff --git a/spa/include/spa/graph/graph.h b/spa/include/spa/graph/graph.h index 537e6e75f..0db28ad3f 100644 --- a/spa/include/spa/graph/graph.h +++ b/spa/include/spa/graph/graph.h @@ -5,6 +5,13 @@ #ifndef SPA_GRAPH_H #define SPA_GRAPH_H +#include +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -18,13 +25,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include -#include -#include - #ifndef SPA_API_GRAPH #ifdef SPA_API_IMPL #define SPA_API_GRAPH SPA_API_IMPL diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h index 73b4a94ff..cc51f9ef1 100644 --- a/spa/include/spa/monitor/device.h +++ b/spa/include/spa/monitor/device.h @@ -5,15 +5,15 @@ #ifndef SPA_DEVICE_H #define SPA_DEVICE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_DEVICE #ifdef SPA_API_IMPL #define SPA_API_DEVICE SPA_API_IMPL diff --git a/spa/include/spa/monitor/event.h b/spa/include/spa/monitor/event.h index 8955f81a5..accf8382f 100644 --- a/spa/include/spa/monitor/event.h +++ b/spa/include/spa/monitor/event.h @@ -5,12 +5,12 @@ #ifndef SPA_EVENT_DEVICE_H #define SPA_EVENT_DEVICE_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_device * \{ diff --git a/spa/include/spa/monitor/type-info.h b/spa/include/spa/monitor/type-info.h index e43a17b89..1658594b4 100644 --- a/spa/include/spa/monitor/type-info.h +++ b/spa/include/spa/monitor/type-info.h @@ -5,14 +5,14 @@ #ifndef SPA_DEVICE_TYPE_INFO_H #define SPA_DEVICE_TYPE_INFO_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_device * \{ diff --git a/spa/include/spa/monitor/utils.h b/spa/include/spa/monitor/utils.h index 4337ecebc..c4b74935b 100644 --- a/spa/include/spa/monitor/utils.h +++ b/spa/include/spa/monitor/utils.h @@ -5,13 +5,13 @@ #ifndef SPA_DEVICE_UTILS_H #define SPA_DEVICE_UTILS_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_DEVICE_UTILS #ifdef SPA_API_IMPL #define SPA_API_DEVICE_UTILS SPA_API_IMPL diff --git a/spa/include/spa/node/command.h b/spa/include/spa/node/command.h index e9e482d7a..49a1cc09d 100644 --- a/spa/include/spa/node/command.h +++ b/spa/include/spa/node/command.h @@ -5,6 +5,8 @@ #ifndef SPA_COMMAND_NODE_H #define SPA_COMMAND_NODE_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /* object id of SPA_TYPE_COMMAND_Node */ enum spa_node_command { SPA_NODE_COMMAND_Suspend, /**< suspend a node, this removes all configured diff --git a/spa/include/spa/node/event.h b/spa/include/spa/node/event.h index e70aad75f..d01f5f2a8 100644 --- a/spa/include/spa/node/event.h +++ b/spa/include/spa/node/event.h @@ -5,6 +5,8 @@ #ifndef SPA_EVENT_NODE_H #define SPA_EVENT_NODE_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /* object id of SPA_TYPE_EVENT_Node */ enum spa_node_event { SPA_NODE_EVENT_Error, diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index c1c725ebf..41f011720 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -5,6 +5,9 @@ #ifndef SPA_IO_H #define SPA_IO_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,9 +17,6 @@ extern "C" { * \{ */ -#include -#include - /** IO areas * * IO information for a port on a node. This is allocated diff --git a/spa/include/spa/node/node.h b/spa/include/spa/node/node.h index ae9f63549..85813a064 100644 --- a/spa/include/spa/node/node.h +++ b/spa/include/spa/node/node.h @@ -5,6 +5,14 @@ #ifndef SPA_NODE_H #define SPA_NODE_H +#include +#include +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -19,14 +27,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include -#include -#include -#include - #ifndef SPA_API_NODE #ifdef SPA_API_IMPL #define SPA_API_NODE SPA_API_IMPL diff --git a/spa/include/spa/node/type-info.h b/spa/include/spa/node/type-info.h index 4b92a4f9f..41cc5a73d 100644 --- a/spa/include/spa/node/type-info.h +++ b/spa/include/spa/node/type-info.h @@ -5,6 +5,12 @@ #ifndef SPA_NODE_TYPES_H #define SPA_NODE_TYPES_H +#include + +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,12 +20,6 @@ extern "C" { * \{ */ -#include - -#include -#include -#include - #define SPA_TYPE_INFO_IO SPA_TYPE_INFO_ENUM_BASE "IO" #define SPA_TYPE_INFO_IO_BASE SPA_TYPE_INFO_IO ":" diff --git a/spa/include/spa/node/utils.h b/spa/include/spa/node/utils.h index b7724e922..a46cbe44b 100644 --- a/spa/include/spa/node/utils.h +++ b/spa/include/spa/node/utils.h @@ -5,6 +5,10 @@ #ifndef SPA_NODE_UTILS_H #define SPA_NODE_UTILS_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include - -#include - #ifndef SPA_API_NODE_UTILS #ifdef SPA_API_IMPL #define SPA_API_NODE_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/aac-types.h b/spa/include/spa/param/audio/aac-types.h index 6e5047394..176185bc0 100644 --- a/spa/include/spa/param/audio/aac-types.h +++ b/spa/include/spa/param/audio/aac-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_AAC_TYPES_H #define SPA_AUDIO_AAC_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/aac-utils.h b/spa/include/spa/param/audio/aac-utils.h index 01f226bb8..3ec362d19 100644 --- a/spa/include/spa/param/audio/aac-utils.h +++ b/spa/include/spa/param/audio/aac-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_AAC_UTILS_H #define SPA_AUDIO_AAC_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_AAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AAC_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/aac.h b/spa/include/spa/param/audio/aac.h index 0a46d5835..1a7fb1612 100644 --- a/spa/include/spa/param/audio/aac.h +++ b/spa/include/spa/param/audio/aac.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_AAC_H #define SPA_AUDIO_AAC_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/ac3-utils.h b/spa/include/spa/param/audio/ac3-utils.h index bfea5b563..9ee48d4ea 100644 --- a/spa/include/spa/param/audio/ac3-utils.h +++ b/spa/include/spa/param/audio/ac3-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_AC3_UTILS_H #define SPA_AUDIO_AC3_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_AC3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AC3_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/ac3.h b/spa/include/spa/param/audio/ac3.h index cab27c3e3..1a02c665a 100644 --- a/spa/include/spa/param/audio/ac3.h +++ b/spa/include/spa/param/audio/ac3.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_AC3_H #define SPA_AUDIO_AC3_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/alac-utils.h b/spa/include/spa/param/audio/alac-utils.h index 898a84e5f..11a76c581 100644 --- a/spa/include/spa/param/audio/alac-utils.h +++ b/spa/include/spa/param/audio/alac-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_ALAC_UTILS_H #define SPA_AUDIO_ALAC_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_ALAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_ALAC_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/alac.h b/spa/include/spa/param/audio/alac.h index 6e6f19fb2..cf682da4c 100644 --- a/spa/include/spa/param/audio/alac.h +++ b/spa/include/spa/param/audio/alac.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_ALAC_H #define SPA_AUDIO_ALAC_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/amr-types.h b/spa/include/spa/param/audio/amr-types.h index 9ab4c9e41..73bfa051b 100644 --- a/spa/include/spa/param/audio/amr-types.h +++ b/spa/include/spa/param/audio/amr-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_AMR_TYPES_H #define SPA_AUDIO_AMR_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/amr-utils.h b/spa/include/spa/param/audio/amr-utils.h index cfe6aa5dc..6a782735d 100644 --- a/spa/include/spa/param/audio/amr-utils.h +++ b/spa/include/spa/param/audio/amr-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_AMR_UTILS_H #define SPA_AUDIO_AMR_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_AMR_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AMR_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/amr.h b/spa/include/spa/param/audio/amr.h index d00125a45..2d970e388 100644 --- a/spa/include/spa/param/audio/amr.h +++ b/spa/include/spa/param/audio/amr.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_AMR_H #define SPA_AUDIO_AMR_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/ape-utils.h b/spa/include/spa/param/audio/ape-utils.h index d05c596c0..3068e0d2e 100644 --- a/spa/include/spa/param/audio/ape-utils.h +++ b/spa/include/spa/param/audio/ape-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_APE_UTILS_H #define SPA_AUDIO_APE_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_APE_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_APE_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/ape.h b/spa/include/spa/param/audio/ape.h index 03f787f53..27c2aa5e2 100644 --- a/spa/include/spa/param/audio/ape.h +++ b/spa/include/spa/param/audio/ape.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_APE_H #define SPA_AUDIO_APE_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h index 3f7065b26..5b93b7a8b 100644 --- a/spa/include/spa/param/audio/dsd-utils.h +++ b/spa/include/spa/param/audio/dsd-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_DSD_UTILS_H #define SPA_AUDIO_DSD_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_DSD_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DSD_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/dsd.h b/spa/include/spa/param/audio/dsd.h index 53678d4c5..73f3d4e8b 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -7,13 +7,13 @@ #include +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/dsp-utils.h b/spa/include/spa/param/audio/dsp-utils.h index af107f1eb..ec183a914 100644 --- a/spa/include/spa/param/audio/dsp-utils.h +++ b/spa/include/spa/param/audio/dsp-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_DSP_UTILS_H #define SPA_AUDIO_DSP_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_DSP_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DSP_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/dsp.h b/spa/include/spa/param/audio/dsp.h index 592f25c18..a285fdf2d 100644 --- a/spa/include/spa/param/audio/dsp.h +++ b/spa/include/spa/param/audio/dsp.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_DSP_H #define SPA_AUDIO_DSP_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/dts-types.h b/spa/include/spa/param/audio/dts-types.h index 6cf999a1a..306859648 100644 --- a/spa/include/spa/param/audio/dts-types.h +++ b/spa/include/spa/param/audio/dts-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_DTS_TYPES_H #define SPA_AUDIO_DTS_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/dts-utils.h b/spa/include/spa/param/audio/dts-utils.h index 445c7e8fb..182f1ebbc 100644 --- a/spa/include/spa/param/audio/dts-utils.h +++ b/spa/include/spa/param/audio/dts-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_DTS_UTILS_H #define SPA_AUDIO_DTS_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_DTS_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DTS_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/dts.h b/spa/include/spa/param/audio/dts.h index fd7a4133c..285b8d96a 100644 --- a/spa/include/spa/param/audio/dts.h +++ b/spa/include/spa/param/audio/dts.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_DTS_H #define SPA_AUDIO_DTS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/eac3-utils.h b/spa/include/spa/param/audio/eac3-utils.h index 3cb7db25e..926d0fd80 100644 --- a/spa/include/spa/param/audio/eac3-utils.h +++ b/spa/include/spa/param/audio/eac3-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_EAC3_UTILS_H #define SPA_AUDIO_EAC3_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_EAC3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_EAC3_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/eac3.h b/spa/include/spa/param/audio/eac3.h index 8d27b460e..dc95bdd09 100644 --- a/spa/include/spa/param/audio/eac3.h +++ b/spa/include/spa/param/audio/eac3.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_EAC3_H #define SPA_AUDIO_EAC3_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/flac-utils.h b/spa/include/spa/param/audio/flac-utils.h index bc3d8afc2..2472abce0 100644 --- a/spa/include/spa/param/audio/flac-utils.h +++ b/spa/include/spa/param/audio/flac-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_FLAC_UTILS_H #define SPA_AUDIO_FLAC_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_FLAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_FLAC_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/flac.h b/spa/include/spa/param/audio/flac.h index f213e3f83..5b229ef16 100644 --- a/spa/include/spa/param/audio/flac.h +++ b/spa/include/spa/param/audio/flac.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_FLAC_H #define SPA_AUDIO_FLAC_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index da4d35fcb..d608a4bdf 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -5,10 +5,6 @@ #ifndef SPA_PARAM_AUDIO_FORMAT_UTILS_H #define SPA_PARAM_AUDIO_FORMAT_UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -33,6 +29,9 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif /** * \addtogroup spa_param diff --git a/spa/include/spa/param/audio/format.h b/spa/include/spa/param/audio/format.h index c38d505e3..ac9b10dda 100644 --- a/spa/include/spa/param/audio/format.h +++ b/spa/include/spa/param/audio/format.h @@ -5,15 +5,6 @@ #ifndef SPA_PARAM_AUDIO_FORMAT_H #define SPA_PARAM_AUDIO_FORMAT_H -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \addtogroup spa_param - * \{ - */ - #include #include #include @@ -35,6 +26,15 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + struct spa_audio_info { uint32_t media_type; uint32_t media_subtype; diff --git a/spa/include/spa/param/audio/iec958-types.h b/spa/include/spa/param/audio/iec958-types.h index adcffdc96..5fe876f1b 100644 --- a/spa/include/spa/param/audio/iec958-types.h +++ b/spa/include/spa/param/audio/iec958-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_IEC958_TYPES_H #define SPA_AUDIO_IEC958_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/iec958-utils.h b/spa/include/spa/param/audio/iec958-utils.h index 1c4ec105b..4c0342f4b 100644 --- a/spa/include/spa/param/audio/iec958-utils.h +++ b/spa/include/spa/param/audio/iec958-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_IEC958_UTILS_H #define SPA_AUDIO_IEC958_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_IEC958_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_IEC958_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 545ceae32..19e3a3c42 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -5,17 +5,18 @@ #ifndef SPA_AUDIO_LAYOUT_H #define SPA_AUDIO_LAYOUT_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ */ -#include struct spa_audio_layout_info { uint32_t n_channels; diff --git a/spa/include/spa/param/audio/mp3-types.h b/spa/include/spa/param/audio/mp3-types.h index a7ba22ace..826398d84 100644 --- a/spa/include/spa/param/audio/mp3-types.h +++ b/spa/include/spa/param/audio/mp3-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_MP3_TYPES_H #define SPA_AUDIO_MP3_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/mp3-utils.h b/spa/include/spa/param/audio/mp3-utils.h index 3be1f13d0..44b7310ec 100644 --- a/spa/include/spa/param/audio/mp3-utils.h +++ b/spa/include/spa/param/audio/mp3-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_MP3_UTILS_H #define SPA_AUDIO_MP3_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_MP3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_MP3_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/mp3.h b/spa/include/spa/param/audio/mp3.h index a28bba661..83a978f19 100644 --- a/spa/include/spa/param/audio/mp3.h +++ b/spa/include/spa/param/audio/mp3.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_MP3_H #define SPA_AUDIO_MP3_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/mpegh-utils.h b/spa/include/spa/param/audio/mpegh-utils.h index 6544c24c0..778dfa946 100644 --- a/spa/include/spa/param/audio/mpegh-utils.h +++ b/spa/include/spa/param/audio/mpegh-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_MPEGH_UTILS_H #define SPA_AUDIO_MPEGH_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_MPEGH_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_MPEGH_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/mpegh.h b/spa/include/spa/param/audio/mpegh.h index e654a500b..5c4c8afa1 100644 --- a/spa/include/spa/param/audio/mpegh.h +++ b/spa/include/spa/param/audio/mpegh.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_MPEGH_H #define SPA_AUDIO_MPEGH_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/opus.h b/spa/include/spa/param/audio/opus.h index cdd0e6c5b..ebdf678f7 100644 --- a/spa/include/spa/param/audio/opus.h +++ b/spa/include/spa/param/audio/opus.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_OPUS_H #define SPA_AUDIO_OPUS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/ra-utils.h b/spa/include/spa/param/audio/ra-utils.h index 79e96514a..4fd6a87e3 100644 --- a/spa/include/spa/param/audio/ra-utils.h +++ b/spa/include/spa/param/audio/ra-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_RA_UTILS_H #define SPA_AUDIO_RA_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_RA_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RA_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/ra.h b/spa/include/spa/param/audio/ra.h index b784ab588..5fb0dbc09 100644 --- a/spa/include/spa/param/audio/ra.h +++ b/spa/include/spa/param/audio/ra.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_RA_H #define SPA_AUDIO_RA_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 07f0e0c45..92d7a1aad 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_RAW_JSON_H #define SPA_AUDIO_RAW_JSON_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_RAW_JSON #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_JSON SPA_API_IMPL diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index 9aa9591c0..257789341 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -5,6 +5,10 @@ #ifndef SPA_AUDIO_RAW_TYPES_H #define SPA_AUDIO_RAW_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_AUDIO_RAW_TYPES #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_TYPES SPA_API_IMPL diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 178e3dd11..d0a174e85 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_RAW_UTILS_H #define SPA_AUDIO_RAW_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_RAW_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 8bed3f8a4..804e5df1d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -5,14 +5,14 @@ #ifndef SPA_AUDIO_RAW_H #define SPA_AUDIO_RAW_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/truehd-utils.h b/spa/include/spa/param/audio/truehd-utils.h index 61c67c017..7d386ee54 100644 --- a/spa/include/spa/param/audio/truehd-utils.h +++ b/spa/include/spa/param/audio/truehd-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_TRUEHD_UTILS_H #define SPA_AUDIO_TRUEHD_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_TRUEHD_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_TRUEHD_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/truehd.h b/spa/include/spa/param/audio/truehd.h index 44adbbb3e..fee222b87 100644 --- a/spa/include/spa/param/audio/truehd.h +++ b/spa/include/spa/param/audio/truehd.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_TRUEHD_H #define SPA_AUDIO_TRUEHD_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/vorbis-utils.h b/spa/include/spa/param/audio/vorbis-utils.h index bc901e616..13114e89b 100644 --- a/spa/include/spa/param/audio/vorbis-utils.h +++ b/spa/include/spa/param/audio/vorbis-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_VORBIS_UTILS_H #define SPA_AUDIO_VORBIS_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_VORBIS_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_VORBIS_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/vorbis.h b/spa/include/spa/param/audio/vorbis.h index e3ab490ef..e73895ba7 100644 --- a/spa/include/spa/param/audio/vorbis.h +++ b/spa/include/spa/param/audio/vorbis.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_VORBIS_H #define SPA_AUDIO_VORBIS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/wma-types.h b/spa/include/spa/param/audio/wma-types.h index 40f9d6682..663e54f24 100644 --- a/spa/include/spa/param/audio/wma-types.h +++ b/spa/include/spa/param/audio/wma-types.h @@ -5,13 +5,13 @@ #ifndef SPA_AUDIO_WMA_TYPES_H #define SPA_AUDIO_WMA_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/audio/wma-utils.h b/spa/include/spa/param/audio/wma-utils.h index ca15f7d0c..8dadb3992 100644 --- a/spa/include/spa/param/audio/wma-utils.h +++ b/spa/include/spa/param/audio/wma-utils.h @@ -5,6 +5,11 @@ #ifndef SPA_AUDIO_WMA_UTILS_H #define SPA_AUDIO_WMA_UTILS_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #ifndef SPA_API_AUDIO_WMA_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_WMA_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/audio/wma.h b/spa/include/spa/param/audio/wma.h index dd1e53a86..1cd3ff465 100644 --- a/spa/include/spa/param/audio/wma.h +++ b/spa/include/spa/param/audio/wma.h @@ -5,12 +5,12 @@ #ifndef SPA_AUDIO_WMA_H #define SPA_AUDIO_WMA_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_param * \{ diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 7d9cd3653..a936cca5f 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -5,6 +5,8 @@ #ifndef SPA_BLUETOOTH_TYPES_H #define SPA_BLUETOOTH_TYPES_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - #define SPA_TYPE_INFO_BluetoothAudioCodec SPA_TYPE_INFO_ENUM_BASE "BluetoothAudioCodec" #define SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE SPA_TYPE_INFO_BluetoothAudioCodec ":" diff --git a/spa/include/spa/param/buffers-types.h b/spa/include/spa/param/buffers-types.h index 4ca45cb02..723aa1b3a 100644 --- a/spa/include/spa/param/buffers-types.h +++ b/spa/include/spa/param/buffers-types.h @@ -5,6 +5,11 @@ #ifndef SPA_PARAM_BUFFERS_TYPES_H #define SPA_PARAM_BUFFERS_TYPES_H +#include +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include - -#include - #define SPA_TYPE_INFO_PARAM_Meta SPA_TYPE_INFO_PARAM_BASE "Meta" #define SPA_TYPE_INFO_PARAM_META_BASE SPA_TYPE_INFO_PARAM_Meta ":" diff --git a/spa/include/spa/param/buffers.h b/spa/include/spa/param/buffers.h index 9c157ae2a..cf0b4a698 100644 --- a/spa/include/spa/param/buffers.h +++ b/spa/include/spa/param/buffers.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_BUFFERS_H #define SPA_PARAM_BUFFERS_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties for SPA_TYPE_OBJECT_ParamBuffers */ enum spa_param_buffers { SPA_PARAM_BUFFERS_START, diff --git a/spa/include/spa/param/format-types.h b/spa/include/spa/param/format-types.h index 7af6f2784..13e475141 100644 --- a/spa/include/spa/param/format-types.h +++ b/spa/include/spa/param/format-types.h @@ -5,6 +5,13 @@ #ifndef SPA_PARAM_FORMAT_TYPES_H #define SPA_PARAM_FORMAT_TYPES_H +#include +#include + +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,13 +21,6 @@ extern "C" { * \{ */ -#include -#include - -#include -#include -#include - #define SPA_TYPE_INFO_Format SPA_TYPE_INFO_PARAM_BASE "Format" #define SPA_TYPE_INFO_FORMAT_BASE SPA_TYPE_INFO_Format ":" diff --git a/spa/include/spa/param/format-utils.h b/spa/include/spa/param/format-utils.h index 27fc5f58c..e4c96f391 100644 --- a/spa/include/spa/param/format-utils.h +++ b/spa/include/spa/param/format-utils.h @@ -5,6 +5,9 @@ #ifndef SPA_PARAM_FORMAT_UTILS_H #define SPA_PARAM_FORMAT_UTILS_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -15,9 +18,6 @@ extern "C" { * \{ */ -#include -#include - #ifndef SPA_API_FORMAT_UTILS #ifdef SPA_API_IMPL #define SPA_API_FORMAT_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/format.h b/spa/include/spa/param/format.h index 25052495a..6da11819f 100644 --- a/spa/include/spa/param/format.h +++ b/spa/include/spa/param/format.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_FORMAT_H #define SPA_PARAM_FORMAT_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** media type for SPA_TYPE_OBJECT_Format */ enum spa_media_type { SPA_MEDIA_TYPE_unknown, diff --git a/spa/include/spa/param/latency-types.h b/spa/include/spa/param/latency-types.h index 883375f3f..4ab1f3625 100644 --- a/spa/include/spa/param/latency-types.h +++ b/spa/include/spa/param/latency-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_LATENCY_TYPES_H #define SPA_PARAM_LATENCY_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #define SPA_TYPE_INFO_PARAM_Latency SPA_TYPE_INFO_PARAM_BASE "Latency" #define SPA_TYPE_INFO_PARAM_LATENCY_BASE SPA_TYPE_INFO_PARAM_Latency ":" diff --git a/spa/include/spa/param/latency-utils.h b/spa/include/spa/param/latency-utils.h index 45f817ebb..907c92816 100644 --- a/spa/include/spa/param/latency-utils.h +++ b/spa/include/spa/param/latency-utils.h @@ -5,6 +5,12 @@ #ifndef SPA_PARAM_LATENCY_UTILS_H #define SPA_PARAM_LATENCY_UTILS_H +#include + +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,12 +20,6 @@ extern "C" { * \{ */ -#include - -#include -#include -#include - #ifndef SPA_API_LATENCY_UTILS #ifdef SPA_API_IMPL #define SPA_API_LATENCY_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/latency.h b/spa/include/spa/param/latency.h index 4087941ca..0e9cfffa2 100644 --- a/spa/include/spa/param/latency.h +++ b/spa/include/spa/param/latency.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_LATENY_H #define SPA_PARAM_LATENY_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** * Properties for SPA_TYPE_OBJECT_ParamLatency * diff --git a/spa/include/spa/param/param-types.h b/spa/include/spa/param/param-types.h index 62ffe7da9..cb4bcd666 100644 --- a/spa/include/spa/param/param-types.h +++ b/spa/include/spa/param/param-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_TYPES_H #define SPA_PARAM_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - /* base for parameter object enumerations */ #define SPA_TYPE_INFO_ParamId SPA_TYPE_INFO_ENUM_BASE "ParamId" #define SPA_TYPE_INFO_PARAM_ID_BASE SPA_TYPE_INFO_ParamId ":" diff --git a/spa/include/spa/param/param.h b/spa/include/spa/param/param.h index be8deb54c..cf72ac43e 100644 --- a/spa/include/spa/param/param.h +++ b/spa/include/spa/param/param.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_H #define SPA_PARAM_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -18,8 +20,6 @@ extern "C" { * \{ */ -#include - /** different parameter types that can be queried */ enum spa_param_type { SPA_PARAM_Invalid, /**< invalid */ diff --git a/spa/include/spa/param/port-config-types.h b/spa/include/spa/param/port-config-types.h index 2eabb785c..9801b1117 100644 --- a/spa/include/spa/param/port-config-types.h +++ b/spa/include/spa/param/port-config-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_PORT_CONFIG_TYPES_H #define SPA_PARAM_PORT_CONFIG_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #define SPA_TYPE_INFO_ParamPortConfigMode SPA_TYPE_INFO_ENUM_BASE "ParamPortConfigMode" #define SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE SPA_TYPE_INFO_ParamPortConfigMode ":" diff --git a/spa/include/spa/param/port-config.h b/spa/include/spa/param/port-config.h index 7e05eda27..fcb39c162 100644 --- a/spa/include/spa/param/port-config.h +++ b/spa/include/spa/param/port-config.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_PORT_CONFIG_H #define SPA_PARAM_PORT_CONFIG_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - enum spa_param_port_config_mode { SPA_PARAM_PORT_CONFIG_MODE_none, /**< no configuration */ SPA_PARAM_PORT_CONFIG_MODE_passthrough, /**< passthrough configuration */ diff --git a/spa/include/spa/param/profile-types.h b/spa/include/spa/param/profile-types.h index 3373d64c5..9edc9fc30 100644 --- a/spa/include/spa/param/profile-types.h +++ b/spa/include/spa/param/profile-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_PROFILE_TYPES_H #define SPA_PARAM_PROFILE_TYPES_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include - -#include - #define SPA_TYPE_INFO_PARAM_Profile SPA_TYPE_INFO_PARAM_BASE "Profile" #define SPA_TYPE_INFO_PARAM_PROFILE_BASE SPA_TYPE_INFO_PARAM_Profile ":" diff --git a/spa/include/spa/param/profile.h b/spa/include/spa/param/profile.h index 00468f36d..11c195195 100644 --- a/spa/include/spa/param/profile.h +++ b/spa/include/spa/param/profile.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_PROFILE_H #define SPA_PARAM_PROFILE_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties for SPA_TYPE_OBJECT_ParamProfile */ enum spa_param_profile { SPA_PARAM_PROFILE_START, diff --git a/spa/include/spa/param/profiler-types.h b/spa/include/spa/param/profiler-types.h index 57f3f3692..d378bae94 100644 --- a/spa/include/spa/param/profiler-types.h +++ b/spa/include/spa/param/profiler-types.h @@ -5,6 +5,9 @@ #ifndef SPA_PARAM_PROFILER_TYPES_H #define SPA_PARAM_PROFILER_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,9 +17,6 @@ extern "C" { * \{ */ -#include -#include - #define SPA_TYPE_INFO_Profiler SPA_TYPE_INFO_OBJECT_BASE "Profiler" #define SPA_TYPE_INFO_PROFILER_BASE SPA_TYPE_INFO_Profiler ":" diff --git a/spa/include/spa/param/profiler.h b/spa/include/spa/param/profiler.h index 36af0fc24..eb3fbadc9 100644 --- a/spa/include/spa/param/profiler.h +++ b/spa/include/spa/param/profiler.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_PROFILER_H #define SPA_PARAM_PROFILER_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties for SPA_TYPE_OBJECT_Profiler */ enum spa_profiler { SPA_PROFILER_START, diff --git a/spa/include/spa/param/props-types.h b/spa/include/spa/param/props-types.h index e66125992..4da520283 100644 --- a/spa/include/spa/param/props-types.h +++ b/spa/include/spa/param/props-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_PROPS_TYPES_H #define SPA_PARAM_PROPS_TYPES_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include - -#include - /** Props Param */ #define SPA_TYPE_INFO_Props SPA_TYPE_INFO_PARAM_BASE "Props" #define SPA_TYPE_INFO_PROPS_BASE SPA_TYPE_INFO_Props ":" diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index a7a2e4c25..acc314ee1 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_PROPS_H #define SPA_PARAM_PROPS_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties of SPA_TYPE_OBJECT_PropInfo */ enum spa_prop_info { SPA_PROP_INFO_START, diff --git a/spa/include/spa/param/route-types.h b/spa/include/spa/param/route-types.h index 78ced495e..311491aaf 100644 --- a/spa/include/spa/param/route-types.h +++ b/spa/include/spa/param/route-types.h @@ -5,6 +5,11 @@ #ifndef SPA_PARAM_ROUTE_TYPES_H #define SPA_PARAM_ROUTE_TYPES_H +#include +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include - -#include - #define SPA_TYPE_INFO_PARAM_Route SPA_TYPE_INFO_PARAM_BASE "Route" #define SPA_TYPE_INFO_PARAM_ROUTE_BASE SPA_TYPE_INFO_PARAM_Route ":" diff --git a/spa/include/spa/param/route.h b/spa/include/spa/param/route.h index d73880c5a..b74267029 100644 --- a/spa/include/spa/param/route.h +++ b/spa/include/spa/param/route.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_ROUTE_H #define SPA_PARAM_ROUTE_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties for SPA_TYPE_OBJECT_ParamRoute */ enum spa_param_route { SPA_PARAM_ROUTE_START, diff --git a/spa/include/spa/param/tag-types.h b/spa/include/spa/param/tag-types.h index 573fb4aff..0284684f2 100644 --- a/spa/include/spa/param/tag-types.h +++ b/spa/include/spa/param/tag-types.h @@ -5,6 +5,10 @@ #ifndef SPA_PARAM_TAG_TYPES_H #define SPA_PARAM_TAG_TYPES_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #define SPA_TYPE_INFO_PARAM_Tag SPA_TYPE_INFO_PARAM_BASE "Tag" #define SPA_TYPE_INFO_PARAM_TAG_BASE SPA_TYPE_INFO_PARAM_Tag ":" diff --git a/spa/include/spa/param/tag-utils.h b/spa/include/spa/param/tag-utils.h index ba8a952c1..590448ae0 100644 --- a/spa/include/spa/param/tag-utils.h +++ b/spa/include/spa/param/tag-utils.h @@ -5,6 +5,13 @@ #ifndef SPA_PARAM_TAG_UTILS_H #define SPA_PARAM_TAG_UTILS_H +#include + +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,13 +21,6 @@ extern "C" { * \{ */ -#include - -#include -#include -#include -#include - #ifndef SPA_API_TAG_UTILS #ifdef SPA_API_IMPL #define SPA_API_TAG_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/tag.h b/spa/include/spa/param/tag.h index 8e36ce5c3..5bfff552f 100644 --- a/spa/include/spa/param/tag.h +++ b/spa/include/spa/param/tag.h @@ -5,6 +5,8 @@ #ifndef SPA_PARAM_TAG_H #define SPA_PARAM_TAG_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - /** properties for SPA_TYPE_OBJECT_ParamTag */ enum spa_param_tag { SPA_PARAM_TAG_START, diff --git a/spa/include/spa/param/video/dsp-utils.h b/spa/include/spa/param/video/dsp-utils.h index 6e76309b3..338b21608 100644 --- a/spa/include/spa/param/video/dsp-utils.h +++ b/spa/include/spa/param/video/dsp-utils.h @@ -5,6 +5,10 @@ #ifndef SPA_VIDEO_DSP_UTILS_H #define SPA_VIDEO_DSP_UTILS_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_VIDEO_DSP_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_DSP_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/video/format-utils.h b/spa/include/spa/param/video/format-utils.h index 43bbb22c8..f7d3864e5 100644 --- a/spa/include/spa/param/video/format-utils.h +++ b/spa/include/spa/param/video/format-utils.h @@ -5,10 +5,6 @@ #ifndef SPA_PARAM_VIDEO_FORMAT_UTILS_H #define SPA_PARAM_VIDEO_FORMAT_UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -17,6 +13,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_VIDEO_FORMAT_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_FORMAT_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/video/format.h b/spa/include/spa/param/video/format.h index 7fa992c96..d49196b31 100644 --- a/spa/include/spa/param/video/format.h +++ b/spa/include/spa/param/video/format.h @@ -5,6 +5,11 @@ #ifndef SPA_PARAM_VIDEO_FORMAT_H #define SPA_PARAM_VIDEO_FORMAT_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - struct spa_video_info { uint32_t media_type; uint32_t media_subtype; diff --git a/spa/include/spa/param/video/h264-utils.h b/spa/include/spa/param/video/h264-utils.h index fa6933291..966315d1a 100644 --- a/spa/include/spa/param/video/h264-utils.h +++ b/spa/include/spa/param/video/h264-utils.h @@ -5,6 +5,10 @@ #ifndef SPA_VIDEO_H264_UTILS_H #define SPA_VIDEO_H264_UTILS_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_VIDEO_H264_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_H264_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/video/h264.h b/spa/include/spa/param/video/h264.h index 33ddffc4e..3a1d62306 100644 --- a/spa/include/spa/param/video/h264.h +++ b/spa/include/spa/param/video/h264.h @@ -5,6 +5,8 @@ #ifndef SPA_VIDEO_H264_H #define SPA_VIDEO_H264_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - enum spa_h264_stream_format { SPA_H264_STREAM_FORMAT_UNKNOWN = 0, SPA_H264_STREAM_FORMAT_AVC, diff --git a/spa/include/spa/param/video/mjpg-utils.h b/spa/include/spa/param/video/mjpg-utils.h index f1aa27af5..3b15d3d68 100644 --- a/spa/include/spa/param/video/mjpg-utils.h +++ b/spa/include/spa/param/video/mjpg-utils.h @@ -5,6 +5,10 @@ #ifndef SPA_VIDEO_MJPG_UTILS_H #define SPA_VIDEO_MJPG_UTILS_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_VIDEO_MJPG_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_MJPG_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/video/mjpg.h b/spa/include/spa/param/video/mjpg.h index fb85daade..eedd93f5e 100644 --- a/spa/include/spa/param/video/mjpg.h +++ b/spa/include/spa/param/video/mjpg.h @@ -5,6 +5,8 @@ #ifndef SPA_VIDEO_MJPG_H #define SPA_VIDEO_MJPG_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,8 +16,6 @@ extern "C" { * \{ */ -#include - struct spa_video_info_mjpg { struct spa_rectangle size; struct spa_fraction framerate; diff --git a/spa/include/spa/param/video/raw-types.h b/spa/include/spa/param/video/raw-types.h index bca0c8d4e..2b19d73a8 100644 --- a/spa/include/spa/param/video/raw-types.h +++ b/spa/include/spa/param/video/raw-types.h @@ -5,6 +5,9 @@ #ifndef SPA_VIDEO_RAW_TYPES_H #define SPA_VIDEO_RAW_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -13,8 +16,6 @@ extern "C" { * \addtogroup spa_param * \{ */ -#include -#include #ifndef SPA_API_VIDEO_RAW_TYPES #ifdef SPA_API_IMPL diff --git a/spa/include/spa/param/video/raw-utils.h b/spa/include/spa/param/video/raw-utils.h index 8a5a27784..1e462036a 100644 --- a/spa/include/spa/param/video/raw-utils.h +++ b/spa/include/spa/param/video/raw-utils.h @@ -5,6 +5,10 @@ #ifndef SPA_VIDEO_RAW_UTILS_H #define SPA_VIDEO_RAW_UTILS_H +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,10 +18,6 @@ extern "C" { * \{ */ -#include -#include -#include - #ifndef SPA_API_VIDEO_RAW_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_RAW_UTILS SPA_API_IMPL diff --git a/spa/include/spa/param/video/raw.h b/spa/include/spa/param/video/raw.h index 84f78ad6a..971aac5ba 100644 --- a/spa/include/spa/param/video/raw.h +++ b/spa/include/spa/param/video/raw.h @@ -5,6 +5,11 @@ #ifndef SPA_VIDEO_RAW_H #define SPA_VIDEO_RAW_H +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -14,11 +19,6 @@ extern "C" { * \{ */ -#include -#include -#include -#include - #define SPA_VIDEO_MAX_PLANES 4 #define SPA_VIDEO_MAX_COMPONENTS 4 diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 8bd83b0ba..f4b68fd63 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -5,6 +5,12 @@ #ifndef SPA_POD_BUILDER_H #define SPA_POD_BUILDER_H +#include + +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -18,12 +24,6 @@ extern "C" { * \{ */ -#include - -#include -#include -#include - #ifndef SPA_API_POD_BUILDER #ifdef SPA_API_IMPL #define SPA_API_POD_BUILDER SPA_API_IMPL diff --git a/spa/include/spa/pod/command.h b/spa/include/spa/pod/command.h index 330f56edc..5afcca8b8 100644 --- a/spa/include/spa/pod/command.h +++ b/spa/include/spa/pod/command.h @@ -5,13 +5,13 @@ #ifndef SPA_COMMAND_H #define SPA_COMMAND_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_pod * \{ diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 05c061363..a9d0f2696 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -5,10 +5,6 @@ #ifndef SPA_POD_COMPARE_H #define SPA_POD_COMPARE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -20,6 +16,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_POD_COMPARE #ifdef SPA_API_IMPL #define SPA_API_POD_COMPARE SPA_API_IMPL diff --git a/spa/include/spa/pod/dynamic.h b/spa/include/spa/pod/dynamic.h index dad9c3d71..ac69381a9 100644 --- a/spa/include/spa/pod/dynamic.h +++ b/spa/include/spa/pod/dynamic.h @@ -5,13 +5,13 @@ #ifndef SPA_POD_DYNAMIC_H #define SPA_POD_DYNAMIC_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_POD_DYNAMIC #ifdef SPA_API_IMPL #define SPA_API_POD_DYNAMIC SPA_API_IMPL diff --git a/spa/include/spa/pod/event.h b/spa/include/spa/pod/event.h index c631bd392..00e66f194 100644 --- a/spa/include/spa/pod/event.h +++ b/spa/include/spa/pod/event.h @@ -5,12 +5,12 @@ #ifndef SPA_EVENT_H #define SPA_EVENT_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_pod * \{ diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 3a2e4868f..5f94683fd 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -5,10 +5,6 @@ #ifndef SPA_POD_FILTER_H #define SPA_POD_FILTER_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -21,6 +17,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_POD_FILTER #ifdef SPA_API_IMPL #define SPA_API_POD_FILTER SPA_API_IMPL diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 3c6b9e8af..19ed9823a 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -5,15 +5,15 @@ #ifndef SPA_POD_ITER_H #define SPA_POD_ITER_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_POD_ITER #ifdef SPA_API_IMPL #define SPA_API_POD_ITER SPA_API_IMPL diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index d2aa206ba..922992e92 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -5,16 +5,16 @@ #ifndef SPA_POD_PARSER_H #define SPA_POD_PARSER_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_POD_PARSER #ifdef SPA_API_IMPL #define SPA_API_POD_PARSER SPA_API_IMPL diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 3703b4b06..398236e79 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -5,13 +5,13 @@ #ifndef SPA_POD_H #define SPA_POD_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_pod * \{ diff --git a/spa/include/spa/pod/vararg.h b/spa/include/spa/pod/vararg.h index a30e11479..df2efb51f 100644 --- a/spa/include/spa/pod/vararg.h +++ b/spa/include/spa/pod/vararg.h @@ -5,14 +5,14 @@ #ifndef SPA_POD_VARARG_H #define SPA_POD_VARARG_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_pod * \{ diff --git a/spa/include/spa/support/cpu.h b/spa/include/spa/support/cpu.h index ce8551e74..c69338855 100644 --- a/spa/include/spa/support/cpu.h +++ b/spa/include/spa/support/cpu.h @@ -5,16 +5,16 @@ #ifndef SPA_CPU_H #define SPA_CPU_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_CPU #ifdef SPA_API_IMPL #define SPA_API_CPU SPA_API_IMPL diff --git a/spa/include/spa/support/dbus.h b/spa/include/spa/support/dbus.h index 3908bfe53..d781bc1ba 100644 --- a/spa/include/spa/support/dbus.h +++ b/spa/include/spa/support/dbus.h @@ -5,12 +5,12 @@ #ifndef SPA_DBUS_H #define SPA_DBUS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - #ifndef SPA_API_DBUS #ifdef SPA_API_IMPL #define SPA_API_DBUS SPA_API_IMPL diff --git a/spa/include/spa/support/i18n.h b/spa/include/spa/support/i18n.h index 3b258873f..859838e14 100644 --- a/spa/include/spa/support/i18n.h +++ b/spa/include/spa/support/i18n.h @@ -5,13 +5,13 @@ #ifndef SPA_I18N_H #define SPA_I18N_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_I18N #ifdef SPA_API_IMPL #define SPA_API_I18N SPA_API_IMPL diff --git a/spa/include/spa/support/log-impl.h b/spa/include/spa/support/log-impl.h index 8132d05bf..c1ce51b82 100644 --- a/spa/include/spa/support/log-impl.h +++ b/spa/include/spa/support/log-impl.h @@ -5,15 +5,15 @@ #ifndef SPA_LOG_IMPL_H #define SPA_LOG_IMPL_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * \addtogroup spa_log * \{ diff --git a/spa/include/spa/support/log.h b/spa/include/spa/support/log.h index e14414403..a1917886e 100644 --- a/spa/include/spa/support/log.h +++ b/spa/include/spa/support/log.h @@ -5,16 +5,16 @@ #ifndef SPA_LOG_H #define SPA_LOG_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_LOG #ifdef SPA_API_IMPL #define SPA_API_LOG SPA_API_IMPL diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 97813bb14..c6c6df9f7 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -5,16 +5,16 @@ #ifndef SPA_LOOP_H #define SPA_LOOP_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_LOOP #ifdef SPA_API_IMPL #define SPA_API_LOOP SPA_API_IMPL diff --git a/spa/include/spa/support/plugin-loader.h b/spa/include/spa/support/plugin-loader.h index 9540853cd..0b059ec51 100644 --- a/spa/include/spa/support/plugin-loader.h +++ b/spa/include/spa/support/plugin-loader.h @@ -5,13 +5,13 @@ #ifndef SPA_PLUGIN_LOADER_H #define SPA_PLUGIN_LOADER_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_PLUGIN_LOADER #ifdef SPA_API_IMPL #define SPA_API_PLUGIN_LOADER SPA_API_IMPL diff --git a/spa/include/spa/support/plugin.h b/spa/include/spa/support/plugin.h index 576c19509..68299fe5b 100644 --- a/spa/include/spa/support/plugin.h +++ b/spa/include/spa/support/plugin.h @@ -5,16 +5,16 @@ #ifndef SPA_PLUGIN_H #define SPA_PLUGIN_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_PLUGIN #ifdef SPA_API_IMPL #define SPA_API_PLUGIN SPA_API_IMPL diff --git a/spa/include/spa/support/system.h b/spa/include/spa/support/system.h index aa140c958..07a31a55f 100644 --- a/spa/include/spa/support/system.h +++ b/spa/include/spa/support/system.h @@ -5,12 +5,6 @@ #ifndef SPA_SYSTEM_H #define SPA_SYSTEM_H -#ifdef __cplusplus -extern "C" { -#endif - -struct itimerspec; - #include #include #include @@ -18,6 +12,12 @@ struct itimerspec; #include #include +#ifdef __cplusplus +extern "C" { +#endif + +struct itimerspec; + #ifndef SPA_API_SYSTEM #ifdef SPA_API_IMPL #define SPA_API_SYSTEM SPA_API_IMPL diff --git a/spa/include/spa/support/thread.h b/spa/include/spa/support/thread.h index b69cb688c..bb523ce5c 100644 --- a/spa/include/spa/support/thread.h +++ b/spa/include/spa/support/thread.h @@ -5,10 +5,6 @@ #ifndef SPA_THREAD_H #define SPA_THREAD_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -16,6 +12,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_THREAD #ifdef SPA_API_IMPL #define SPA_API_THREAD SPA_API_IMPL diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index 1c1a73abf..55bf9ad97 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -5,6 +5,13 @@ #ifndef SPA_UTILS_DEFS_H #define SPA_UTILS_DEFS_H +#include +#include +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { # if __cplusplus >= 201103L @@ -34,13 +41,6 @@ extern "C" { #define SPA_CONCAT_NOEXPAND(a, b) a ## b #define SPA_CONCAT(a, b) SPA_CONCAT_NOEXPAND(a, b) -#include -#include -#include -#include -#include -#include - /** * \defgroup spa_utils_defs Miscellaneous * Helper macros and functions diff --git a/spa/include/spa/utils/dict.h b/spa/include/spa/utils/dict.h index c88a833f4..979605d54 100644 --- a/spa/include/spa/utils/dict.h +++ b/spa/include/spa/utils/dict.h @@ -5,14 +5,14 @@ #ifndef SPA_DICT_H #define SPA_DICT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_DICT #ifdef SPA_API_IMPL #define SPA_API_DICT SPA_API_IMPL diff --git a/spa/include/spa/utils/dll.h b/spa/include/spa/utils/dll.h index 7b8fd207e..824750f5d 100644 --- a/spa/include/spa/utils/dll.h +++ b/spa/include/spa/utils/dll.h @@ -5,15 +5,15 @@ #ifndef SPA_DLL_H #define SPA_DLL_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_DLL #ifdef SPA_API_IMPL #define SPA_API_DLL SPA_API_IMPL diff --git a/spa/include/spa/utils/enum-types.h b/spa/include/spa/utils/enum-types.h index 881ffd8b3..aebde1845 100644 --- a/spa/include/spa/utils/enum-types.h +++ b/spa/include/spa/utils/enum-types.h @@ -5,13 +5,13 @@ #ifndef SPA_ENUM_TYPES_H #define SPA_ENUM_TYPES_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** * \addtogroup spa_types * \{ diff --git a/spa/include/spa/utils/hook.h b/spa/include/spa/utils/hook.h index dbbb01976..d0ae744c8 100644 --- a/spa/include/spa/utils/hook.h +++ b/spa/include/spa/utils/hook.h @@ -5,13 +5,13 @@ #ifndef SPA_HOOK_H #define SPA_HOOK_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_HOOK #ifdef SPA_API_IMPL #define SPA_API_HOOK SPA_API_IMPL diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 10c87e68c..800763571 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -5,11 +5,6 @@ #ifndef SPA_UTILS_JSON_H #define SPA_UTILS_JSON_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif #include #include #include @@ -20,6 +15,12 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + #ifndef SPA_API_JSON #ifdef SPA_API_IMPL #define SPA_API_JSON SPA_API_IMPL diff --git a/spa/include/spa/utils/json-pod.h b/spa/include/spa/utils/json-pod.h index 22f2b2817..6dfc6241f 100644 --- a/spa/include/spa/utils/json-pod.h +++ b/spa/include/spa/utils/json-pod.h @@ -5,16 +5,16 @@ #ifndef SPA_UTILS_JSON_POD_H #define SPA_UTILS_JSON_POD_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_JSON_POD #ifdef SPA_API_IMPL #define SPA_API_JSON_POD SPA_API_IMPL diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index a36554d1e..c8030345e 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -5,11 +5,6 @@ #ifndef SPA_UTILS_JSON_UTILS_H #define SPA_UTILS_JSON_UTILS_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif #include #include #include @@ -19,6 +14,12 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + #ifndef SPA_API_JSON_UTILS #ifdef SPA_API_IMPL #define SPA_API_JSON_UTILS SPA_API_IMPL diff --git a/spa/include/spa/utils/list.h b/spa/include/spa/utils/list.h index 60c89f23b..05abc9518 100644 --- a/spa/include/spa/utils/list.h +++ b/spa/include/spa/utils/list.h @@ -5,12 +5,12 @@ #ifndef SPA_LIST_H #define SPA_LIST_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - #ifndef SPA_API_LIST #ifdef SPA_API_IMPL #define SPA_API_LIST SPA_API_IMPL diff --git a/spa/include/spa/utils/ratelimit.h b/spa/include/spa/utils/ratelimit.h index 2af2c26be..9dcd40405 100644 --- a/spa/include/spa/utils/ratelimit.h +++ b/spa/include/spa/utils/ratelimit.h @@ -5,15 +5,15 @@ #ifndef SPA_RATELIMIT_H #define SPA_RATELIMIT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_RATELIMIT #ifdef SPA_API_IMPL #define SPA_API_RATELIMIT SPA_API_IMPL diff --git a/spa/include/spa/utils/result.h b/spa/include/spa/utils/result.h index 312a6bb0c..49f777e82 100644 --- a/spa/include/spa/utils/result.h +++ b/spa/include/spa/utils/result.h @@ -5,6 +5,10 @@ #ifndef SPA_UTILS_RESULT_H #define SPA_UTILS_RESULT_H +#include + +#include + #ifdef __cplusplus extern "C" { #endif @@ -19,10 +23,6 @@ extern "C" { * \{ */ -#include - -#include - #ifndef SPA_API_RESULT #ifdef SPA_API_IMPL #define SPA_API_RESULT SPA_API_IMPL diff --git a/spa/include/spa/utils/ringbuffer.h b/spa/include/spa/utils/ringbuffer.h index e8c5d6250..59bdd175f 100644 --- a/spa/include/spa/utils/ringbuffer.h +++ b/spa/include/spa/utils/ringbuffer.h @@ -5,6 +5,8 @@ #ifndef SPA_RINGBUFFER_H #define SPA_RINGBUFFER_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -21,8 +23,6 @@ extern "C" { struct spa_ringbuffer; -#include - #include #ifndef SPA_API_RINGBUFFER diff --git a/spa/include/spa/utils/string.h b/spa/include/spa/utils/string.h index 060ef7d62..4b6ad8bfd 100644 --- a/spa/include/spa/utils/string.h +++ b/spa/include/spa/utils/string.h @@ -5,10 +5,6 @@ #ifndef SPA_UTILS_STRING_H #define SPA_UTILS_STRING_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -17,6 +13,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef SPA_API_STRING #ifdef SPA_API_IMPL #define SPA_API_STRING SPA_API_IMPL diff --git a/spa/include/spa/utils/type-info.h b/spa/include/spa/utils/type-info.h index 9ee2f3abc..a099b9f0d 100644 --- a/spa/include/spa/utils/type-info.h +++ b/spa/include/spa/utils/type-info.h @@ -5,12 +5,19 @@ #ifndef SPA_TYPE_INFO_H #define SPA_TYPE_INFO_H +#include +#include +#include + +#include +#include +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** * \addtogroup spa_types * \{ @@ -20,15 +27,6 @@ extern "C" { #define SPA_TYPE_ROOT spa_types #endif - -#include -#include - -#include -#include -#include -#include - static const struct spa_type_info spa_types[] = { /* Basic types */ { SPA_TYPE_START, SPA_TYPE_START, SPA_TYPE_INFO_BASE, NULL }, diff --git a/spa/include/spa/utils/type.h b/spa/include/spa/utils/type.h index 88de2c624..758a9bd58 100644 --- a/spa/include/spa/utils/type.h +++ b/spa/include/spa/utils/type.h @@ -5,13 +5,13 @@ #ifndef SPA_TYPE_H #define SPA_TYPE_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #ifndef SPA_API_TYPE #ifdef SPA_API_IMPL #define SPA_API_TYPE SPA_API_IMPL diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index 01ad11806..ff5f360d7 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -5,18 +5,18 @@ #ifndef ACP_H #define ACP_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - #include #include #include #include #include +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + #ifdef __GNUC__ #define ACP_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) #else diff --git a/spa/plugins/alsa/acp/array.h b/spa/plugins/alsa/acp/array.h index 84e9f65eb..dfb344025 100644 --- a/spa/plugins/alsa/acp/array.h +++ b/spa/plugins/alsa/acp/array.h @@ -5,13 +5,13 @@ #ifndef PA_ARRAY_H #define PA_ARRAY_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - typedef struct pa_array { void *data; /**< pointer to array data */ size_t size; /**< length of array in bytes */ diff --git a/spa/plugins/alsa/acp/card.h b/spa/plugins/alsa/acp/card.h index c1126fe23..814e3c6ef 100644 --- a/spa/plugins/alsa/acp/card.h +++ b/spa/plugins/alsa/acp/card.h @@ -22,14 +22,14 @@ #ifndef PULSE_CARD_H #define PULSE_CARD_H +#include "compat.h" + #ifdef __cplusplus extern "C" { #else #include #endif -#include "compat.h" - typedef struct pa_card pa_card; struct pa_card { diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index adb486809..2d4b54444 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -21,12 +21,12 @@ #ifndef PULSE_CHANNELMAP_H #define PULSE_CHANNELMAP_H +#include "spa/utils/defs.h" + #ifdef __cplusplus extern "C" { #endif -#include "spa/utils/defs.h" - #define PA_CHANNELS_MAX 64 #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index 7660e1c27..c7779759f 100644 --- a/spa/plugins/alsa/acp/compat.h +++ b/spa/plugins/alsa/acp/compat.h @@ -22,12 +22,6 @@ #ifndef PULSE_COMPAT_H #define PULSE_COMPAT_H -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - #include #include #include @@ -39,6 +33,12 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + typedef struct pa_core pa_core; typedef void *(*pa_copy_func_t)(const void *p); diff --git a/spa/plugins/alsa/acp/device-port.h b/spa/plugins/alsa/acp/device-port.h index d6bdc2297..d0224b4e0 100644 --- a/spa/plugins/alsa/acp/device-port.h +++ b/spa/plugins/alsa/acp/device-port.h @@ -22,14 +22,14 @@ #ifndef PULSE_DEVICE_PORT_H #define PULSE_DEVICE_PORT_H +#include "compat.h" + #ifdef __cplusplus extern "C" { #else #include #endif -#include "compat.h" - typedef struct pa_card pa_card; typedef struct pa_device_port pa_device_port; diff --git a/spa/plugins/alsa/acp/dynarray.h b/spa/plugins/alsa/acp/dynarray.h index eb7961847..87904d165 100644 --- a/spa/plugins/alsa/acp/dynarray.h +++ b/spa/plugins/alsa/acp/dynarray.h @@ -5,12 +5,12 @@ #ifndef PA_DYNARRAY_H #define PA_DYNARRAY_H +#include "compat.h" + #ifdef __cplusplus extern "C" { #endif -#include "array.h" - typedef struct pa_dynarray_item { void *ptr; } pa_dynarray_item; diff --git a/spa/plugins/alsa/acp/hashmap.h b/spa/plugins/alsa/acp/hashmap.h index 02a4950e0..e2c3ddd3b 100644 --- a/spa/plugins/alsa/acp/hashmap.h +++ b/spa/plugins/alsa/acp/hashmap.h @@ -5,12 +5,12 @@ #ifndef PA_HASHMAP_H #define PA_HASHMAP_H +#include "array.h" + #ifdef __cplusplus extern "C" { #endif -#include "array.h" - typedef unsigned (*pa_hash_func_t)(const void *p); typedef int (*pa_compare_func_t)(const void *a, const void *b); diff --git a/spa/plugins/alsa/acp/idxset.h b/spa/plugins/alsa/acp/idxset.h index 2638133da..598017eb6 100644 --- a/spa/plugins/alsa/acp/idxset.h +++ b/spa/plugins/alsa/acp/idxset.h @@ -5,12 +5,12 @@ #ifndef PA_IDXSET_H #define PA_IDXSET_H +#include "array.h" + #ifdef __cplusplus extern "C" { #endif -#include "array.h" - #define PA_IDXSET_INVALID ((uint32_t) -1) typedef unsigned (*pa_hash_func_t)(const void *p); diff --git a/spa/plugins/alsa/acp/proplist.h b/spa/plugins/alsa/acp/proplist.h index a647f520a..23d03d0b1 100644 --- a/spa/plugins/alsa/acp/proplist.h +++ b/spa/plugins/alsa/acp/proplist.h @@ -5,15 +5,15 @@ #ifndef PA_PROPLIST_H #define PA_PROPLIST_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include "array.h" #include "acp.h" +#ifdef __cplusplus +extern "C" { +#endif + #define PA_PROP_DEVICE_DESCRIPTION "device.description" #define PA_PROP_DEVICE_CLASS "device.class" diff --git a/spa/plugins/alsa/acp/volume.h b/spa/plugins/alsa/acp/volume.h index de786b7c2..5b5585953 100644 --- a/spa/plugins/alsa/acp/volume.h +++ b/spa/plugins/alsa/acp/volume.h @@ -21,12 +21,12 @@ #ifndef PA_VOLUME_H #define PA_VOLUME_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - typedef uint32_t pa_volume_t; #define PA_VOLUME_MUTED ((pa_volume_t) 0U) diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index bd77abc2e..cc0b107e4 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -5,10 +5,6 @@ #ifndef SPA_ALSA_UTILS_H #define SPA_ALSA_UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -35,6 +31,9 @@ extern "C" { #include "alsa.h" +#ifdef __cplusplus +extern "C" { +#endif #define MAX_RATES 16 diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 274311c70..3475de834 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -5,10 +5,6 @@ #ifndef SPA_ALSA_SEQ_H #define SPA_ALSA_SEQ_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -33,6 +29,9 @@ extern "C" { #include "alsa.h" +#ifdef __cplusplus +extern "C" { +#endif struct props { char device[64]; diff --git a/spa/plugins/audioconvert/hilbert.h b/spa/plugins/audioconvert/hilbert.h index aa00940ba..2a794592c 100644 --- a/spa/plugins/audioconvert/hilbert.h +++ b/spa/plugins/audioconvert/hilbert.h @@ -5,14 +5,13 @@ #ifndef HILBERT_H #define HILBERT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif static inline void blackman_window(float *taps, int n_taps) { diff --git a/spa/plugins/avb/avb-pcm.h b/spa/plugins/avb/avb-pcm.h index d4dfa03fb..7e026741d 100644 --- a/spa/plugins/avb/avb-pcm.h +++ b/spa/plugins/avb/avb-pcm.h @@ -5,10 +5,6 @@ #ifndef SPA_AVB_PCM_H #define SPA_AVB_PCM_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -37,6 +33,10 @@ extern "C" { #include "avb.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MAX_RATES 16 #define DEFAULT_IFNAME "eth0" diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 148b00bbf..7b7922b15 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -5,10 +5,6 @@ #ifndef SPA_BLUEZ5_DEFS_H #define SPA_BLUEZ5_DEFS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -23,6 +19,10 @@ extern "C" { #include "config.h" +#ifdef __cplusplus +extern "C" { +#endif + #define BLUEZ_SERVICE "org.bluez" #define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" diff --git a/spa/plugins/jack/jack-client.h b/spa/plugins/jack/jack-client.h index decd9aea4..8ae37f93c 100644 --- a/spa/plugins/jack/jack-client.h +++ b/spa/plugins/jack/jack-client.h @@ -5,16 +5,16 @@ #ifndef SPA_JACK_CLIENT_H #define SPA_JACK_CLIENT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct spa_jack_client_events { #define SPA_VERSION_JACK_CLIENT_EVENTS 0 uint32_t version; diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h index d1e72c8cb..4a108a486 100644 --- a/src/modules/module-avb/internal.h +++ b/src/modules/module-avb/internal.h @@ -5,12 +5,12 @@ #ifndef AVB_INTERNAL_H #define AVB_INTERNAL_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - struct server; struct avb_mrp; diff --git a/src/modules/module-client-node/v0/ext-client-node.h b/src/modules/module-client-node/v0/ext-client-node.h index bd2f69bd6..ccc57e204 100644 --- a/src/modules/module-client-node/v0/ext-client-node.h +++ b/src/modules/module-client-node/v0/ext-client-node.h @@ -5,16 +5,16 @@ #ifndef __PIPEWIRE_EXT_CLIENT_NODE0_H__ #define __PIPEWIRE_EXT_CLIENT_NODE0_H__ -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode" #define PW_VERSION_CLIENT_NODE0 0 diff --git a/src/modules/module-client-node/v0/transport.h b/src/modules/module-client-node/v0/transport.h index 46072a2e8..22b9a3036 100644 --- a/src/modules/module-client-node/v0/transport.h +++ b/src/modules/module-client-node/v0/transport.h @@ -5,16 +5,16 @@ #ifndef __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ #define __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** information about the transport region \memberof pw_client_node */ struct pw_client_node0_transport_info { int memfd; /**< the memfd of the transport area */ diff --git a/src/modules/module-jack-tunnel/weakjack.h b/src/modules/module-jack-tunnel/weakjack.h index 1b057b0ba..6d5b5503e 100644 --- a/src/modules/module-jack-tunnel/weakjack.h +++ b/src/modules/module-jack-tunnel/weakjack.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_WEAK_JACK_H #define PIPEWIRE_WEAK_JACK_H -#ifdef __cplusplus -extern "C" { -#endif - #include "config.h" #include @@ -17,6 +13,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct weakjack { jack_nframes_t (*cycle_wait) (jack_client_t* client); void (*cycle_signal) (jack_client_t* client, int status); diff --git a/src/modules/module-protocol-native/connection.h b/src/modules/module-protocol-native/connection.h index 56276fbac..0d3b4fba2 100644 --- a/src/modules/module-protocol-native/connection.h +++ b/src/modules/module-protocol-native/connection.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_PROTOCOL_NATIVE_CONNECTION_H #define PIPEWIRE_PROTOCOL_NATIVE_CONNECTION_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct pw_protocol_native_connection_events { #define PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS 0 uint32_t version; diff --git a/src/modules/module-protocol-native/v0/interfaces.h b/src/modules/module-protocol-native/v0/interfaces.h index 1951e69a9..09d0acfc7 100644 --- a/src/modules/module-protocol-native/v0/interfaces.h +++ b/src/modules/module-protocol-native/v0/interfaces.h @@ -5,16 +5,16 @@ #ifndef PIPEWIRE_INTERFACES_V0_H #define PIPEWIRE_INTERFACES_V0_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** Core */ #define PW_VERSION_CORE_V0 0 diff --git a/src/modules/module-protocol-pulse/manager.h b/src/modules/module-protocol-pulse/manager.h index 5e831b39b..6dc15b305 100644 --- a/src/modules/module-protocol-pulse/manager.h +++ b/src/modules/module-protocol-pulse/manager.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_MANAGER_H #define PIPEWIRE_MANAGER_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -16,6 +12,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + struct client; struct pw_manager_object; diff --git a/src/modules/module-protocol-pulse/pulse-server.h b/src/modules/module-protocol-pulse/pulse-server.h index df3e40848..dfcfd15f1 100644 --- a/src/modules/module-protocol-pulse/pulse-server.h +++ b/src/modules/module-protocol-pulse/pulse-server.h @@ -5,13 +5,13 @@ #ifndef PIPEWIRE_PROTOCOL_PULSE_H #define PIPEWIRE_PROTOCOL_PULSE_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - #define PW_PROTOCOL_PULSE_DEFAULT_PORT 4713 #define PW_PROTOCOL_PULSE_DEFAULT_SOCKET "native" diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h index 1b832cbcc..bfb4a4fe2 100644 --- a/src/modules/module-raop/rtsp-client.h +++ b/src/modules/module-raop/rtsp-client.h @@ -5,14 +5,14 @@ #ifndef PIPEWIRE_RTSP_CLIENT_H #define PIPEWIRE_RTSP_CLIENT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct pw_rtsp_client; struct pw_rtsp_client_events { diff --git a/src/pipewire/array.h b/src/pipewire/array.h index ca00e7b9b..4af60a961 100644 --- a/src/pipewire/array.h +++ b/src/pipewire/array.h @@ -5,14 +5,14 @@ #ifndef PIPEWIRE_ARRAY_H #define PIPEWIRE_ARRAY_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef PW_API_ARRAY #define PW_API_ARRAY static inline #endif diff --git a/src/pipewire/client.h b/src/pipewire/client.h index 7798e212d..7f9749bf0 100644 --- a/src/pipewire/client.h +++ b/src/pipewire/client.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_CLIENT_H #define PIPEWIRE_CLIENT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -16,6 +12,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_client Client * Client interface */ diff --git a/src/pipewire/context.h b/src/pipewire/context.h index 810127323..eef297b4f 100644 --- a/src/pipewire/context.h +++ b/src/pipewire/context.h @@ -5,13 +5,13 @@ #ifndef PIPEWIRE_CONTEXT_H #define PIPEWIRE_CONTEXT_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** \defgroup pw_context Context * * \brief The PipeWire context object manages all locally available diff --git a/src/pipewire/control.h b/src/pipewire/control.h index d5e73629b..daedcb8cf 100644 --- a/src/pipewire/control.h +++ b/src/pipewire/control.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_CONTROL_H #define PIPEWIRE_CONTROL_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_control Control * * \brief A control can be used to control a port property. diff --git a/src/pipewire/core.h b/src/pipewire/core.h index be7beb416..cd4a5d551 100644 --- a/src/pipewire/core.h +++ b/src/pipewire/core.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_CORE_H #define PIPEWIRE_CORE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -16,6 +12,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_core Core * * \brief The core global object. diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h index 166d3e08d..5abf0e7f2 100644 --- a/src/pipewire/data-loop.h +++ b/src/pipewire/data-loop.h @@ -5,13 +5,13 @@ #ifndef PIPEWIRE_DATA_LOOP_H #define PIPEWIRE_DATA_LOOP_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** \defgroup pw_data_loop Data Loop * * \brief PipeWire rt-loop object diff --git a/src/pipewire/device.h b/src/pipewire/device.h index 9154daee0..bff40530f 100644 --- a/src/pipewire/device.h +++ b/src/pipewire/device.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_DEVICE_H #define PIPEWIRE_DEVICE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_device Device * Device interface */ diff --git a/src/pipewire/extensions/client-node.h b/src/pipewire/extensions/client-node.h index 69f278e04..274441319 100644 --- a/src/pipewire/extensions/client-node.h +++ b/src/pipewire/extensions/client-node.h @@ -5,13 +5,13 @@ #ifndef PIPEWIRE_EXT_CLIENT_NODE_H #define PIPEWIRE_EXT_CLIENT_NODE_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** \defgroup pw_client_node Client Node * Client node interface */ diff --git a/src/pipewire/extensions/metadata.h b/src/pipewire/extensions/metadata.h index 58057c608..5a5f8d3c8 100644 --- a/src/pipewire/extensions/metadata.h +++ b/src/pipewire/extensions/metadata.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_EXT_METADATA_H #define PIPEWIRE_EXT_METADATA_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_metadata Metadata * Metadata interface */ diff --git a/src/pipewire/extensions/profiler.h b/src/pipewire/extensions/profiler.h index d0e8908ed..c7a155d95 100644 --- a/src/pipewire/extensions/profiler.h +++ b/src/pipewire/extensions/profiler.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_EXT_PROFILER_H #define PIPEWIRE_EXT_PROFILER_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_profiler Profiler * Profiler interface */ diff --git a/src/pipewire/extensions/protocol-native.h b/src/pipewire/extensions/protocol-native.h index ca4369200..7b88d8210 100644 --- a/src/pipewire/extensions/protocol-native.h +++ b/src/pipewire/extensions/protocol-native.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_EXT_PROTOCOL_NATIVE_H #define PIPEWIRE_EXT_PROTOCOL_NATIVE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_protocol_native Native Protocol * PipeWire native protocol interface */ diff --git a/src/pipewire/extensions/security-context.h b/src/pipewire/extensions/security-context.h index 5a3cd2c13..3da96ff45 100644 --- a/src/pipewire/extensions/security-context.h +++ b/src/pipewire/extensions/security-context.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_EXT_SECURITY_CONTEXT_H #define PIPEWIRE_EXT_SECURITY_CONTEXT_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_security_context Security Context * Security Context interface */ diff --git a/src/pipewire/factory.h b/src/pipewire/factory.h index 9c6ab74fc..e337b1d2f 100644 --- a/src/pipewire/factory.h +++ b/src/pipewire/factory.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_FACTORY_H #define PIPEWIRE_FACTORY_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -17,6 +13,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_factory Factory * Factory interface */ diff --git a/src/pipewire/i18n.h b/src/pipewire/i18n.h index cfe981180..8ed238720 100644 --- a/src/pipewire/i18n.h +++ b/src/pipewire/i18n.h @@ -5,6 +5,8 @@ #ifndef PIPEWIRE_I18N_H #define PIPEWIRE_I18N_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -17,7 +19,6 @@ extern "C" { * \addtogroup pw_gettext * \{ */ -#include SPA_FORMAT_ARG_FUNC(1) const char *pw_gettext(const char *msgid); SPA_FORMAT_ARG_FUNC(1) const char *pw_ngettext(const char *msgid, const char *msgid_plural, unsigned long int n); diff --git a/src/pipewire/impl-client.h b/src/pipewire/impl-client.h index de9325cbd..98475c33f 100644 --- a/src/pipewire/impl-client.h +++ b/src/pipewire/impl-client.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_IMPL_CLIENT_H #define PIPEWIRE_IMPL_CLIENT_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \page page_client_impl Client Implementation * * \see \ref pw_impl_client diff --git a/src/pipewire/impl-module.h b/src/pipewire/impl-module.h index 6e0930333..37c6d51c9 100644 --- a/src/pipewire/impl-module.h +++ b/src/pipewire/impl-module.h @@ -7,14 +7,14 @@ #ifndef PIPEWIRE_IMPL_MODULE_H #define PIPEWIRE_IMPL_MODULE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include +#ifdef __cplusplus +extern "C" { +#endif + #define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init" #define PIPEWIRE_MODULE_PREFIX "libpipewire-" diff --git a/src/pipewire/impl-port.h b/src/pipewire/impl-port.h index 34b8b7b9c..f68e850b8 100644 --- a/src/pipewire/impl-port.h +++ b/src/pipewire/impl-port.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_IMPL_PORT_H #define PIPEWIRE_IMPL_PORT_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_impl_port Port Impl * * \brief A port can be used to link two nodes. diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index de4a9d408..ae7df2372 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -5,11 +5,12 @@ #ifndef PIPEWIRE_KEYS_H #define PIPEWIRE_KEYS_H +#include + #ifdef __cplusplus extern "C" { #endif -#include /** * \defgroup pw_keys Key Names * diff --git a/src/pipewire/link.h b/src/pipewire/link.h index 316fb0442..ac3133bc9 100644 --- a/src/pipewire/link.h +++ b/src/pipewire/link.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_LINK_H #define PIPEWIRE_LINK_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_link Link * * A link is the connection between 2 nodes (\ref pw_node). Nodes are diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index d4f9be076..3fdf2818e 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -5,13 +5,13 @@ #ifndef PIPEWIRE_LOOP_H #define PIPEWIRE_LOOP_H +#include +#include + #ifdef __cplusplus extern "C" { #endif -#include -#include - /** \defgroup pw_loop Loop * * PipeWire loop object provides an implementation of diff --git a/src/pipewire/map.h b/src/pipewire/map.h index fb00ddf6c..3600fd0d7 100644 --- a/src/pipewire/map.h +++ b/src/pipewire/map.h @@ -5,16 +5,16 @@ #ifndef PIPEWIRE_MAP_H #define PIPEWIRE_MAP_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef PW_API_MAP #define PW_API_MAP static inline #endif diff --git a/src/pipewire/module.h b/src/pipewire/module.h index f5531af89..cd5fa2b7b 100644 --- a/src/pipewire/module.h +++ b/src/pipewire/module.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_MODULE_H #define PIPEWIRE_MODULE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_module Module * Module interface */ diff --git a/src/pipewire/node.h b/src/pipewire/node.h index 1ee9d2123..6fcbeb312 100644 --- a/src/pipewire/node.h +++ b/src/pipewire/node.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_NODE_H #define PIPEWIRE_NODE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -19,6 +15,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_node Node * Node interface */ diff --git a/src/pipewire/permission.h b/src/pipewire/permission.h index 22eebdb26..55eeff2a3 100644 --- a/src/pipewire/permission.h +++ b/src/pipewire/permission.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_PERMISSION_H #define PIPEWIRE_PERMISSION_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_permission Permission * * Permissions are kept for a client and describe what the client is diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h index 0c495ed39..f09d65faa 100644 --- a/src/pipewire/pipewire.h +++ b/src/pipewire/pipewire.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_H #define PIPEWIRE_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -41,6 +37,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_pipewire Initialization * Initializing PipeWire and loading SPA modules. */ diff --git a/src/pipewire/port.h b/src/pipewire/port.h index ea4cb33b1..b711cda72 100644 --- a/src/pipewire/port.h +++ b/src/pipewire/port.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_PORT_H #define PIPEWIRE_PORT_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -18,6 +14,10 @@ extern "C" { #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_port Port * Port interface */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 3cdb258df..320e49912 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -7,10 +7,6 @@ /** \privatesection */ -#ifdef __cplusplus -extern "C" { -#endif - #include #include /* for pthread_t */ @@ -24,6 +20,10 @@ extern "C" { #include #include +#ifdef __cplusplus +extern "C" { +#endif + #if defined(__FreeBSD__) || defined(__MidnightBSD__) || defined(__GNU__) struct ucred { }; diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h index 769198dc5..5dbcc283f 100644 --- a/src/pipewire/properties.h +++ b/src/pipewire/properties.h @@ -5,16 +5,16 @@ #ifndef PIPEWIRE_PROPERTIES_H #define PIPEWIRE_PROPERTIES_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + #ifndef PW_API_PROPERTIES #define PW_API_PROPERTIES static inline #endif diff --git a/src/pipewire/protocol.h b/src/pipewire/protocol.h index 60a029033..bec5c4eb2 100644 --- a/src/pipewire/protocol.h +++ b/src/pipewire/protocol.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_PROTOCOL_H #define PIPEWIRE_PROTOCOL_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_protocol Protocol * * \brief Manages protocols and their implementation diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h index b46c41725..34bdfee06 100644 --- a/src/pipewire/proxy.h +++ b/src/pipewire/proxy.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_PROXY_H #define PIPEWIRE_PROXY_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \page page_proxy Proxy * * \see \ref pw_proxy diff --git a/src/pipewire/resource.h b/src/pipewire/resource.h index 86be9ee3d..41adab468 100644 --- a/src/pipewire/resource.h +++ b/src/pipewire/resource.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_RESOURCE_H #define PIPEWIRE_RESOURCE_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_resource Resource * * \brief Client owned objects diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h index 2734799df..0d8dbdcea 100644 --- a/src/pipewire/thread-loop.h +++ b/src/pipewire/thread-loop.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_THREAD_LOOP_H #define PIPEWIRE_THREAD_LOOP_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \page page_thread_loop Thread Loop * * \see \ref pw_thread_loop diff --git a/src/pipewire/thread.h b/src/pipewire/thread.h index f53e62950..2f214988c 100644 --- a/src/pipewire/thread.h +++ b/src/pipewire/thread.h @@ -5,15 +5,15 @@ #ifndef PIPEWIRE_THREAD_H #define PIPEWIRE_THREAD_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** \defgroup pw_thread Thread * * \brief functions to manipulate threads diff --git a/src/pipewire/type.h b/src/pipewire/type.h index a8f982c59..e55cbaf33 100644 --- a/src/pipewire/type.h +++ b/src/pipewire/type.h @@ -5,12 +5,12 @@ #ifndef PIPEWIRE_TYPE_H #define PIPEWIRE_TYPE_H +#include + #ifdef __cplusplus extern "C" { #endif -#include - /** \defgroup pw_type Type info * Type information */ diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h index 528f6764b..29e210a5a 100644 --- a/src/pipewire/utils.h +++ b/src/pipewire/utils.h @@ -5,10 +5,6 @@ #ifndef PIPEWIRE_UTILS_H #define PIPEWIRE_UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -17,14 +13,18 @@ extern "C" { #endif #include -#ifndef ENODATA -#define ENODATA 9919 -#endif - #include #include #include +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ENODATA +#define ENODATA 9919 +#endif + /** \defgroup pw_utils Utilities * * Various utility functions diff --git a/test/pwtest.h b/test/pwtest.h index 8234624ce..ae768f08e 100644 --- a/test/pwtest.h +++ b/test/pwtest.h @@ -7,10 +7,6 @@ #ifndef PWTEST_H #define PWTEST_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include @@ -20,6 +16,10 @@ extern "C" { #include #include "spa/support/plugin.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * \defgroup pwtest Test Suite * \brief `pwtest` is a test runner framework for PipeWire. From c45d66793472c1549820312a58692b66823a80d3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 30 May 2025 11:59:35 +0200 Subject: [PATCH 0321/1014] loop: spa_loop_invoke -> spa_loop_locked where possible When we simply need to change some state for the code executed in the loop, we can use locked() instead of invoke(). This is more efficient and avoids some context switches in the normal case. --- spa/plugins/bluez5/iso-io.c | 6 +++--- spa/plugins/bluez5/media-sink.c | 10 +++++----- spa/plugins/bluez5/media-source.c | 10 +++++----- spa/plugins/bluez5/midi-node.c | 6 +++--- spa/plugins/bluez5/sco-io.c | 2 +- spa/plugins/bluez5/sco-sink.c | 8 ++++---- spa/plugins/bluez5/sco-source.c | 10 +++++----- spa/plugins/filter-graph/sofa_plugin.c | 2 +- spa/plugins/libcamera/libcamera-utils.cpp | 2 +- spa/plugins/test/fakesink.c | 2 +- spa/plugins/test/fakesrc.c | 2 +- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 2 +- spa/plugins/videotestsrc/videotestsrc.c | 2 +- spa/plugins/vulkan/vulkan-compute-source.c | 2 +- .../module-client-node/v0/client-node.c | 3 +-- src/modules/module-combine-stream.c | 8 ++++---- src/modules/module-protocol-pulse/client.c | 8 ++++---- src/modules/module-protocol-pulse/internal.h | 2 +- .../modules/module-combine-sink.c | 6 +++--- .../modules/module-gsettings.c | 2 +- .../module-protocol-pulse/pulse-server.c | 4 ++-- src/modules/module-protocol-pulse/server.c | 8 ++++---- src/modules/module-protocol-pulse/stream.c | 2 +- src/modules/module-rtp-session.c | 2 +- src/modules/module-rtp-source.c | 18 +++++++++--------- src/modules/module-vban-recv.c | 12 ++++++------ src/pipewire/data-loop.c | 2 +- src/pipewire/data-loop.h | 5 +++-- src/pipewire/main-loop.c | 2 +- 29 files changed, 75 insertions(+), 75 deletions(-) diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index bb52575bd..8a0c30ac7 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -86,7 +86,7 @@ static void stream_link(struct group *group, struct stream *stream) struct modify_info info = { .stream = stream, .streams = &group->streams }; int res; - res = spa_loop_invoke(group->data_loop, do_modify, 0, NULL, 0, true, &info); + res = spa_loop_locked(group->data_loop, do_modify, 0, NULL, 0, &info); spa_assert_se(res == 0); } @@ -95,7 +95,7 @@ static void stream_unlink(struct stream *stream) struct modify_info info = { .stream = stream, .streams = NULL }; int res; - res = spa_loop_invoke(stream->group->data_loop, do_modify, 0, NULL, 0, true, &info); + res = spa_loop_locked(stream->group->data_loop, do_modify, 0, NULL, 0, &info); spa_assert_se(res == 0); } @@ -393,7 +393,7 @@ static void group_destroy(struct group *group) spa_assert(spa_list_is_empty(&group->streams)); - res = spa_loop_invoke(group->data_loop, do_remove_source, 0, NULL, 0, true, group); + res = spa_loop_locked(group->data_loop, do_remove_source, 0, NULL, 0, group); spa_assert_se(res == 0); close(group->timerfd); diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index f7748630b..374db005a 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -452,7 +452,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) } if (this->started) { - spa_loop_invoke(this->data_loop, do_reassign_io, 0, NULL, 0, true, &info); + spa_loop_locked(this->data_loop, do_reassign_io, 0, NULL, 0, &info); } else { this->clock = info.clock; this->position = info.position; @@ -1548,7 +1548,7 @@ static int transport_start(struct impl *this) this->transport_started = true; if (this->transport->iso_io) - spa_loop_invoke(this->data_loop, do_start_iso_io, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_start_iso_io, 0, NULL, 0, this); if (is_asha) { struct spa_bt_asha *asha = this->asha; @@ -1678,7 +1678,7 @@ static void transport_stop(struct impl *this) spa_log_trace(this->log, "%p: stop transport", this); - spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); if (this->codec_data && this->own_codec_data) this->codec->deinit(this->codec_data); @@ -1696,7 +1696,7 @@ static int do_stop(struct impl *this) this->start_ready = false; - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); @@ -2310,7 +2310,7 @@ static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_invoke(this->data_loop, do_transport_destroy, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static void transport_state_changed(void *data, diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 9d09fd60e..e8d77c1d3 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -315,7 +315,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; } @@ -799,7 +799,7 @@ static int transport_start(struct impl *this) spa_strerror(res)); if (this->transport->iso_io) - spa_loop_invoke(this->data_loop, do_start_iso_io, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_start_iso_io, 0, NULL, 0, this); this->transport_started = true; @@ -899,7 +899,7 @@ static void transport_stop(struct impl *this) spa_log_debug(this->log, "%p: transport stop", this); - spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); if (this->fd >= 0) { close(this->fd); @@ -924,7 +924,7 @@ static int do_stop(struct impl *this) this->start_ready = false; - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); @@ -1736,7 +1736,7 @@ static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_invoke(this->data_loop, do_transport_destroy, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static const struct spa_bt_transport_events transport_events = { diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index cfba2bfcf..0436f12bc 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -1135,7 +1135,7 @@ static int do_stop(struct impl *this) spa_log_debug(this->log, "%p: stop", this); - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); this->started = false; @@ -1149,7 +1149,7 @@ static int do_release(struct impl *this) spa_log_debug(this->log, "%p: release", this); - spa_loop_invoke(this->data_loop, do_remove_port_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_port_source, 0, NULL, 0, this); for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; @@ -1260,7 +1260,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 2db7798dd..b67de8bbf 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -271,7 +271,7 @@ static int do_remove_source(struct spa_loop *loop, void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) { if (io->started) - spa_loop_invoke(io->data_loop, do_remove_source, 0, NULL, 0, true, io); + spa_loop_locked(io->data_loop, do_remove_source, 0, NULL, 0, io); io->started = false; free(io); diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index fff5bbbd2..298b9dcbd 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -308,7 +308,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; } @@ -954,7 +954,7 @@ static void transport_stop(struct impl *this) spa_log_trace(this->log, "sco-sink %p: transport stop", this); - spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); if (this->buffer) { free(this->buffer); @@ -978,7 +978,7 @@ static int do_stop(struct impl *this) this->start_ready = false; - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); @@ -1571,7 +1571,7 @@ static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_invoke(this->data_loop, do_transport_destroy, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static const struct spa_bt_transport_events transport_events = { diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 9a98f44f8..3e0e6bf56 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -285,7 +285,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; - spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; @@ -764,7 +764,7 @@ static int transport_start(struct impl *this) /* Start socket i/o */ if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) goto fail; - spa_loop_invoke(this->data_loop, do_add_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_add_source, 0, NULL, 0, this); /* Set the started flag */ this->transport_started = true; @@ -862,7 +862,7 @@ static void transport_stop(struct impl *this) spa_log_debug(this->log, "sco-source %p: transport stop", this); - spa_loop_invoke(this->data_loop, do_remove_transport_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); spa_bt_decode_buffer_clear(&port->buffer); @@ -882,7 +882,7 @@ static int do_stop(struct impl *this) this->start_ready = false; - spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); @@ -1589,7 +1589,7 @@ static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_invoke(this->data_loop, do_transport_destroy, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static const struct spa_bt_transport_events transport_events = { diff --git a/spa/plugins/filter-graph/sofa_plugin.c b/spa/plugins/filter-graph/sofa_plugin.c index aea14278f..cd1d3e49f 100644 --- a/spa/plugins/filter-graph/sofa_plugin.c +++ b/spa/plugins/filter-graph/sofa_plugin.c @@ -262,7 +262,7 @@ static void spatializer_reload(void * Instance) spa_log_error(impl->log, "reloading left or right convolver failed"); return; } - spa_loop_invoke(impl->plugin->data_loop, do_switch, 1, NULL, 0, true, impl); + spa_loop_locked(impl->plugin->data_loop, do_switch, 1, NULL, 0, impl); } struct free_data { diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index bbd351afe..d116a4094 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -1059,7 +1059,7 @@ static int spa_libcamera_stream_off(struct impl *impl) impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - spa_loop_invoke(impl->data_loop, do_remove_source, 0, NULL, 0, true, impl); + spa_loop_locked(impl->data_loop, do_remove_source, 0, NULL, 0, impl); if (impl->source.fd >= 0) { spa_system_close(impl->system, impl->source.fd); impl->source.fd = -1; diff --git a/spa/plugins/test/fakesink.c b/spa/plugins/test/fakesink.c index f71dc3d5e..71550300c 100644 --- a/spa/plugins/test/fakesink.c +++ b/spa/plugins/test/fakesink.c @@ -708,7 +708,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; if (this->data_loop) - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; diff --git a/spa/plugins/test/fakesrc.c b/spa/plugins/test/fakesrc.c index d9658c9fc..f411359a1 100644 --- a/spa/plugins/test/fakesrc.c +++ b/spa/plugins/test/fakesrc.c @@ -739,7 +739,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; if (this->data_loop) - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index a53591010..e8253e883 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -2220,7 +2220,7 @@ impl_node_port_set_io(void *object, case SPA_IO_Buffers: if (this->data_loop) { struct io_data d = { .port = port, .data = data, .size = size }; - spa_loop_invoke(this->data_loop, do_set_port_io, 0, NULL, 0, true, &d); + spa_loop_locked(this->data_loop, do_set_port_io, 0, NULL, 0, &d); } else port->io = data; diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index aff3f5364..db5c90113 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -852,7 +852,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; if (this->data_loop) - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_loop_utils_destroy_source(this->loop_utils, this->timer_source); return 0; diff --git a/spa/plugins/vulkan/vulkan-compute-source.c b/spa/plugins/vulkan/vulkan-compute-source.c index 8512c155e..1daf3b47c 100644 --- a/spa/plugins/vulkan/vulkan-compute-source.c +++ b/spa/plugins/vulkan/vulkan-compute-source.c @@ -933,7 +933,7 @@ static int impl_clear(struct spa_handle *handle) spa_vulkan_compute_deinit(&this->state); if (this->data_loop) - spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; diff --git a/src/modules/module-client-node/v0/client-node.c b/src/modules/module-client-node/v0/client-node.c index 771e16684..c5321afa5 100644 --- a/src/modules/module-client-node/v0/client-node.c +++ b/src/modules/module-client-node/v0/client-node.c @@ -1229,12 +1229,11 @@ static void client_node0_resource_destroy(void *data) spa_hook_remove(&impl->object_listener); if (node->data_source.fd != -1) { - spa_loop_invoke(node->data_loop, + spa_loop_locked(node->data_loop, do_remove_source, SPA_ID_INVALID, NULL, 0, - true, &node->data_source); } if (this->node) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 4afcb8f0b..c897e6e14 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -553,7 +553,7 @@ static void resize_delay(struct stream *stream, uint32_t size) for (i = 0; i < channels; ++i) ringbuffer_init(&info.delay[i], SPA_PTROFF(info.buf, i*size, void), size); - pw_loop_invoke(stream->impl->data_loop, do_replace_delay, 0, NULL, 0, true, &info); + pw_loop_locked(stream->impl->data_loop, do_replace_delay, 0, NULL, 0, &info); free(info.buf); } @@ -616,7 +616,7 @@ static int do_clear_delaybuf(struct spa_loop *loop, bool async, uint32_t seq, static void clear_delaybuf(struct impl *impl) { - pw_loop_invoke(impl->data_loop, do_clear_delaybuf, 0, NULL, 0, true, impl); + pw_loop_locked(impl->data_loop, do_clear_delaybuf, 0, NULL, 0, impl); } static int do_add_stream(struct spa_loop *loop, bool async, uint32_t seq, @@ -669,7 +669,7 @@ static void remove_stream(struct stream *s, bool destroy) { pw_log_debug("destroy stream %d", s->id); - pw_loop_invoke(s->impl->data_loop, do_remove_stream, 0, NULL, 0, true, s); + pw_loop_locked(s->impl->data_loop, do_remove_stream, 0, NULL, 0, s); if (destroy && s->stream) { spa_hook_remove(&s->stream_listener); @@ -896,7 +896,7 @@ static int create_stream(struct stream_info *info) direction, PW_ID_ANY, flags, params, n_params)) < 0) goto error; - pw_loop_invoke(impl->data_loop, do_add_stream, 0, NULL, 0, true, s); + pw_loop_locked(impl->data_loop, do_add_stream, 0, NULL, 0, s); update_delay(impl); return 0; diff --git a/src/modules/module-protocol-pulse/client.c b/src/modules/module-protocol-pulse/client.c index 454ffe7b2..b189fdbe7 100644 --- a/src/modules/module-protocol-pulse/client.c +++ b/src/modules/module-protocol-pulse/client.c @@ -87,7 +87,7 @@ bool client_detach(struct client *client) if (server->wait_clients > 0 && --server->wait_clients == 0) { int mask = server->source->mask; SPA_FLAG_SET(mask, SPA_IO_IN); - pw_loop_update_io(impl->loop, server->source, mask); + pw_loop_update_io(impl->main_loop, server->source, mask); } client->server = NULL; @@ -112,7 +112,7 @@ void client_disconnect(struct client *client) pw_map_for_each(&client->streams, client_free_stream, client); if (client->source) { - pw_loop_destroy_source(impl->loop, client->source); + pw_loop_destroy_source(impl->main_loop, client->source); client->source = NULL; } @@ -207,7 +207,7 @@ int client_queue_message(struct client *client, struct message *msg) uint32_t mask = client->source->mask; if (!SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) { SPA_FLAG_SET(mask, SPA_IO_OUT); - pw_loop_update_io(impl->loop, client->source, mask); + pw_loop_update_io(impl->main_loop, client->source, mask); } client->new_msg_since_last_flush = true; @@ -278,7 +278,7 @@ int client_flush_messages(struct client *client) if (SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) { SPA_FLAG_CLEAR(mask, SPA_IO_OUT); - pw_loop_update_io(client->impl->loop, client->source, mask); + pw_loop_update_io(client->impl->main_loop, client->source, mask); } } else { if (res != -EAGAIN && res != -EWOULDBLOCK) diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h index 6e7eba7ba..cd031692b 100644 --- a/src/modules/module-protocol-pulse/internal.h +++ b/src/modules/module-protocol-pulse/internal.h @@ -47,7 +47,7 @@ struct stats { }; struct impl { - struct pw_loop *loop; + struct pw_loop *main_loop; struct pw_context *context; struct spa_hook context_listener; diff --git a/src/modules/module-protocol-pulse/modules/module-combine-sink.c b/src/modules/module-protocol-pulse/modules/module-combine-sink.c index 4951b4e6b..aa5881b73 100644 --- a/src/modules/module-protocol-pulse/modules/module-combine-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-combine-sink.c @@ -214,12 +214,12 @@ static int module_combine_sink_load(struct module *module) pw_manager_add_listener(data->manager, &data->manager_listener, &manager_events, data); - data->sinks_timeout = pw_loop_add_timer(module->impl->loop, on_sinks_timeout, data); + data->sinks_timeout = pw_loop_add_timer(module->impl->main_loop, on_sinks_timeout, data); if (data->sinks_timeout) { struct timespec timeout = {0}; timeout.tv_sec = TIMEOUT_SINKS_MSEC / 1000; timeout.tv_nsec = (TIMEOUT_SINKS_MSEC % 1000) * SPA_NSEC_PER_MSEC; - pw_loop_update_timer(module->impl->loop, data->sinks_timeout, &timeout, NULL, false); + pw_loop_update_timer(module->impl->main_loop, data->sinks_timeout, &timeout, NULL, false); } return data->load_emitted ? 0 : SPA_RESULT_RETURN_ASYNC(0); } @@ -229,7 +229,7 @@ static int module_combine_sink_unload(struct module *module) struct module_combine_sink_data *d = module->user_data; if (d->sinks_timeout != NULL) - pw_loop_destroy_source(module->impl->loop, d->sinks_timeout); + pw_loop_destroy_source(module->impl->main_loop, d->sinks_timeout); if (d->mod != NULL) { spa_hook_remove(&d->mod_listener); diff --git a/src/modules/module-protocol-pulse/modules/module-gsettings.c b/src/modules/module-protocol-pulse/modules/module-gsettings.c index 1968cc101..2b7648528 100644 --- a/src/modules/module-protocol-pulse/modules/module-gsettings.c +++ b/src/modules/module-protocol-pulse/modules/module-gsettings.c @@ -197,7 +197,7 @@ static void handle_module_group(struct module_gsettings_data *d, gchar *name) snprintf(p, sizeof(p), "args%i", i); info.args[i] = g_settings_get_string(settings, p); } - pw_loop_invoke(impl->loop, do_handle_info, 0, + pw_loop_invoke(impl->main_loop, do_handle_info, 0, &info, sizeof(info), false, d); g_object_unref(G_OBJECT(settings)); diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index cb66f75e2..45a346e2f 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1484,7 +1484,7 @@ static void stream_process(void *data) pw_stream_get_time_n(stream->stream, &pd.pwt, sizeof(pd.pwt)); - pw_loop_invoke(impl->loop, + pw_loop_invoke(impl->main_loop, do_process_done, 1, &pd, sizeof(pd), false, stream); } @@ -5492,7 +5492,7 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, spa_list_init(&impl->cleanup_clients); spa_list_init(&impl->free_messages); - impl->loop = pw_context_get_main_loop(context); + impl->main_loop = pw_context_get_main_loop(context); impl->work_queue = pw_context_get_work_queue(context); if (props == NULL) diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index e1ffafffa..13425debc 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -384,7 +384,7 @@ on_connect(void *data, int fd, uint32_t mask) if (server->n_clients > 0) { int m = server->source->mask; SPA_FLAG_CLEAR(m, SPA_IO_IN); - pw_loop_update_io(impl->loop, server->source, m); + pw_loop_update_io(impl->main_loop, server->source, m); server->wait_clients++; } } @@ -404,7 +404,7 @@ on_connect(void *data, int fd, uint32_t mask) pw_log_debug("server %p: new client %p fd:%d", server, client, client_fd); - client->source = pw_loop_add_io(impl->loop, + client->source = pw_loop_add_io(impl->main_loop, client_fd, SPA_IO_ERR | SPA_IO_HUP | SPA_IO_IN, true, on_client_data, client); @@ -949,7 +949,7 @@ static int server_start(struct server *server, const struct sockaddr_storage *ad if (fd < 0) return fd; - server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_connect, server); + server->source = pw_loop_add_io(impl->main_loop, fd, SPA_IO_IN, true, on_connect, server); if (server->source == NULL) { res = -errno; pw_log_error("server %p: can't create server source: %m", impl); @@ -1100,7 +1100,7 @@ void server_free(struct server *server) spa_hook_list_call(&impl->hooks, struct impl_events, server_stopped, 0, server); if (server->source) - pw_loop_destroy_source(impl->loop, server->source); + pw_loop_destroy_source(impl->main_loop, server->source); if (server->addr.ss_family == AF_UNIX && !server->activated) unlink(((const struct sockaddr_un *) &server->addr)->sun_path); diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index e3b8b7b0f..9426428d2 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -118,7 +118,7 @@ void stream_free(struct stream *stream) /* force processing of all pending messages before we destroy * the stream */ - pw_loop_invoke(impl->loop, NULL, 0, NULL, 0, false, client); + pw_loop_invoke(impl->main_loop, NULL, 0, NULL, 0, false, client); pw_stream_destroy(stream->stream); } diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index ab5e1d8a7..91809e6ae 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -584,7 +584,7 @@ static void free_session(struct session *sess) { struct impl *impl = sess->impl; - pw_loop_invoke(impl->data_loop, do_unlink_session, 1, NULL, 0, true, sess); + pw_loop_locked(impl->data_loop, do_unlink_session, 1, NULL, 0, sess); sess->impl->n_sessions--; diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index d2e44a9af..c942e32fe 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -151,7 +151,7 @@ struct impl { struct pw_properties *props; struct pw_context *context; - struct pw_loop *loop; + struct pw_loop *main_loop; struct pw_loop *data_loop; struct pw_core *core; @@ -226,7 +226,7 @@ on_rtp_io(void *data, int fd, uint32_t mask) if (!impl->receiving) { impl->receiving = true; - pw_loop_invoke(impl->loop, do_start, 1, NULL, 0, false, impl); + pw_loop_invoke(impl->main_loop, do_start, 1, NULL, 0, false, impl); } } return; @@ -349,7 +349,7 @@ static void on_stream_start_retry_timer_event(void *data, uint64_t expirations) static void destroy_stream_start_retry_timer(struct impl *impl) { if (impl->stream_start_retry_timer != NULL) { - pw_loop_destroy_source(impl->loop, impl->stream_start_retry_timer); + pw_loop_destroy_source(impl->main_loop, impl->stream_start_retry_timer); impl->stream_start_retry_timer = NULL; } } @@ -382,7 +382,7 @@ static int stream_start(struct impl *impl) if (impl->stream_start_retry_timer == NULL) { struct timespec value, interval; - impl->stream_start_retry_timer = pw_loop_add_timer(impl->loop, + impl->stream_start_retry_timer = pw_loop_add_timer(impl->main_loop, on_stream_start_retry_timer_event, impl); /* Use a 1-second retry interval. The network interfaces * are likely to be up and running then. */ @@ -390,7 +390,7 @@ static int stream_start(struct impl *impl) value.tv_nsec = 0; interval.tv_sec = 1; interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->stream_start_retry_timer, &value, + pw_loop_update_timer(impl->main_loop, impl->stream_start_retry_timer, &value, &interval, false); } /* Do nothing if the timer is already up. */ @@ -579,7 +579,7 @@ static void impl_destroy(struct impl *impl) pw_core_disconnect(impl->core); if (impl->standby_timer) - pw_loop_destroy_source(impl->loop, impl->standby_timer); + pw_loop_destroy_source(impl->main_loop, impl->standby_timer); destroy_stream_start_retry_timer(impl); @@ -663,7 +663,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; - impl->loop = pw_context_get_main_loop(context); + impl->main_loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) @@ -769,7 +769,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->standby_timer = pw_loop_add_timer(impl->loop, on_standby_timer_event, impl); + impl->standby_timer = pw_loop_add_timer(impl->main_loop, on_standby_timer_event, impl); if (impl->standby_timer == NULL) { res = -errno; pw_log_error("can't create timer source: %m"); @@ -779,7 +779,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) value.tv_nsec = 0; interval.tv_sec = impl->cleanup_interval; interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->standby_timer, &value, &interval, false); + pw_loop_update_timer(impl->main_loop, impl->standby_timer, &value, &interval, false); impl->stream = rtp_stream_new(impl->core, PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props), diff --git a/src/modules/module-vban-recv.c b/src/modules/module-vban-recv.c index 902057965..53bad5252 100644 --- a/src/modules/module-vban-recv.c +++ b/src/modules/module-vban-recv.c @@ -166,7 +166,7 @@ struct impl { struct pw_properties *props; struct pw_context *context; - struct pw_loop *loop; + struct pw_loop *main_loop; struct pw_loop *data_loop; struct pw_core *core; @@ -457,7 +457,7 @@ static struct stream *make_stream(struct impl *impl, const struct vban_header *h stream->salen = salen; spa_list_append(&impl->streams, &stream->link); - pw_loop_invoke(impl->loop, do_setup_stream, 1, NULL, 0, false, stream); + pw_loop_invoke(impl->main_loop, do_setup_stream, 1, NULL, 0, false, stream); return stream; } @@ -603,7 +603,7 @@ static void impl_destroy(struct impl *impl) pw_core_disconnect(impl->core); if (impl->timer) - pw_loop_destroy_source(impl->loop, impl->timer); + pw_loop_destroy_source(impl->main_loop, impl->timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -681,7 +681,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; - impl->loop = pw_context_get_main_loop(context); + impl->main_loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); spa_list_init(&impl->streams); @@ -747,7 +747,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); + impl->timer = pw_loop_add_timer(impl->main_loop, on_timer_event, impl); if (impl->timer == NULL) { res = -errno; pw_log_error("can't create timer source: %m"); @@ -757,7 +757,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) value.tv_nsec = 0; interval.tv_sec = impl->cleanup_interval; interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); + pw_loop_update_timer(impl->main_loop, impl->timer, &value, &interval, false); if ((res = listen_start(impl)) < 0) { pw_log_error("failed to start VBAN stream: %s", spa_strerror(res)); diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c index 3cee8a2e7..92c1bdf55 100644 --- a/src/pipewire/data-loop.c +++ b/src/pipewire/data-loop.c @@ -280,7 +280,7 @@ int pw_data_loop_stop(struct pw_data_loop *loop) pthread_cancel(loop->thread); } else { pw_log_debug("%p signal", loop); - pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); + pw_loop_locked(loop->loop, do_stop, 1, NULL, 0, loop); } pw_log_debug("%p join", loop); if ((utils = loop->thread_utils) == NULL) diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h index 5abf0e7f2..373bd9807 100644 --- a/src/pipewire/data-loop.h +++ b/src/pipewire/data-loop.h @@ -79,8 +79,9 @@ bool pw_data_loop_in_thread(struct pw_data_loop *loop); struct spa_thread *pw_data_loop_get_thread(struct pw_data_loop *loop); /** invoke func in the context of the thread or in the caller thread when - * the loop is not running. May be called from the loop's thread, but otherwise - * can only be called by a single thread at a time. + * the loop is not running. May be called from the loop's thread, and + * can be called by multiple threads at the same time. + * * If called from the loop's thread, all callbacks previously queued with * pw_data_loop_invoke() will be run synchronously, which might cause * unexpected reentrancy problems. diff --git a/src/pipewire/main-loop.c b/src/pipewire/main-loop.c index 6e73a13cb..41ac5bf28 100644 --- a/src/pipewire/main-loop.c +++ b/src/pipewire/main-loop.c @@ -107,7 +107,7 @@ SPA_EXPORT int pw_main_loop_quit(struct pw_main_loop *loop) { pw_log_debug("%p: quit", loop); - return pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); + return pw_loop_locked(loop->loop, do_stop, 1, NULL, 0, loop); } /** Start a main loop From 820e0fccb1d2b3d0001371451196e8e3370dbb2e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 30 May 2025 12:07:47 +0200 Subject: [PATCH 0322/1014] loop: we can't actually use locked to stop the loop We need to signal the event and wake up the loop to actually make it stop and joinable. --- src/pipewire/data-loop.c | 2 +- src/pipewire/main-loop.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c index 92c1bdf55..3cee8a2e7 100644 --- a/src/pipewire/data-loop.c +++ b/src/pipewire/data-loop.c @@ -280,7 +280,7 @@ int pw_data_loop_stop(struct pw_data_loop *loop) pthread_cancel(loop->thread); } else { pw_log_debug("%p signal", loop); - pw_loop_locked(loop->loop, do_stop, 1, NULL, 0, loop); + pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); } pw_log_debug("%p join", loop); if ((utils = loop->thread_utils) == NULL) diff --git a/src/pipewire/main-loop.c b/src/pipewire/main-loop.c index 41ac5bf28..6e73a13cb 100644 --- a/src/pipewire/main-loop.c +++ b/src/pipewire/main-loop.c @@ -107,7 +107,7 @@ SPA_EXPORT int pw_main_loop_quit(struct pw_main_loop *loop) { pw_log_debug("%p: quit", loop); - return pw_loop_locked(loop->loop, do_stop, 1, NULL, 0, loop); + return pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); } /** Start a main loop From fecf1bcba4058372a2961153ae19607164c4fbac Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 30 May 2025 12:12:03 +0200 Subject: [PATCH 0323/1014] thread-loop: remove the event We don't need the event to wake up and stop the thread loop, we can simply use _invoke() of the loop implementation, like we do for the data loop. --- src/pipewire/thread-loop.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c index 36fbd0183..d36482821 100644 --- a/src/pipewire/thread-loop.c +++ b/src/pipewire/thread-loop.c @@ -31,21 +31,12 @@ struct pw_thread_loop { struct spa_hook hook; - struct spa_source *event; - unsigned int created:1; unsigned int running:1; unsigned int start_signal:1; }; /** \endcond */ -static void do_stop(void *data, uint64_t count) -{ - struct pw_thread_loop *this = data; - pw_log_debug("stopping"); - this->running = false; -} - #define CHECK(expression,label) \ do { \ if ((errno = (expression)) != 0) { \ @@ -86,11 +77,6 @@ static struct pw_thread_loop *loop_new(struct pw_loop *loop, spa_hook_list_init(&this->listener_list); - if ((this->event = pw_loop_add_event(this->loop, do_stop, this)) == NULL) { - res = -errno; - goto clean_this; - } - return this; clean_this: @@ -154,8 +140,6 @@ void pw_thread_loop_destroy(struct pw_thread_loop *loop) spa_hook_list_clean(&loop->listener_list); - pw_loop_destroy_source(loop->loop, loop->event); - if (loop->created) pw_loop_destroy(loop->loop); @@ -237,6 +221,15 @@ error: return -err; } +static int do_stop(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct pw_thread_loop *this = user_data; + pw_log_debug("%p: stopping", this); + this->running = false; + return 0; +} + /** Quit the loop and stop its thread * * \param loop a \ref pw_thread_loop @@ -248,7 +241,7 @@ void pw_thread_loop_stop(struct pw_thread_loop *loop) pw_log_debug("%p stopping %d", loop, loop->running); if (loop->running) { pw_log_debug("%p signal", loop); - pw_loop_signal_event(loop->loop, loop->event); + pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop); pw_log_debug("%p join", loop); pthread_join(loop->thread, NULL); pw_log_debug("%p joined", loop); From 50fe63ea7636effc6259ca5182d77d273b78c607 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 30 May 2025 12:17:41 +0200 Subject: [PATCH 0324/1014] examples: exit early when no longer running When we are no longer running after the eventfd was signaled, stop producing samples. --- src/examples/audio-src-ring2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/examples/audio-src-ring2.c b/src/examples/audio-src-ring2.c index 19f8eb4d3..b3943cd11 100644 --- a/src/examples/audio-src-ring2.c +++ b/src/examples/audio-src-ring2.c @@ -89,6 +89,8 @@ static void push_samples(void *userdata, float *samples, uint32_t n_samples) /* no space.. block and wait for free space */ spa_system_eventfd_read(data->loop->system, data->eventfd, &count); + if (!data->running) + return; } if (avail > n_samples) avail = n_samples; From 2cec77e7df1a4a6a98b87981d818e0eea727e0fa Mon Sep 17 00:00:00 2001 From: Sam James Date: Fri, 30 May 2025 10:15:49 +0100 Subject: [PATCH 0325/1014] *: unify config.h handling config.h needs to be consistently included before any standard headers if we ever want to set feature test macros (like _GNU_SOURCE or whatever) inside. It can lead to hard-to-debug issues without that. It can also be problematic just for our own HAVE_* that it may define if it's not consistently made available before our own headers. Just always include it first, before everything. We already did this in many files, just not consistently. --- spa/plugins/alsa/acp/compat.c | 3 ++- spa/plugins/alsa/alsa-seq.h | 4 ++-- spa/plugins/bluez5/bluez5-dbus.c | 3 ++- spa/plugins/bluez5/defs.h | 4 ++-- spa/plugins/bluez5/iso-io.c | 3 ++- spa/plugins/bluez5/midi-enum.c | 3 ++- spa/plugins/filter-graph/audio-dsp-avx.c | 3 ++- spa/plugins/filter-graph/audio-dsp-c.c | 3 ++- spa/plugins/filter-graph/audio-dsp-sse.c | 3 ++- spa/plugins/filter-graph/filter-graph.c | 4 ++-- spa/plugins/v4l2/v4l2-udev.c | 3 ++- spa/plugins/v4l2/v4l2.c | 3 ++- src/daemon/pipewire.c | 4 ++-- src/examples/bluez-session.c | 4 ++-- src/modules/module-access.c | 4 ++-- src/modules/module-adapter.c | 4 ++-- src/modules/module-adapter/adapter.c | 4 ++-- src/modules/module-avb.c | 4 ++-- src/modules/module-client-device.c | 4 ++-- src/modules/module-client-node.c | 4 ++-- src/modules/module-combine-stream.c | 4 ++-- src/modules/module-example-filter.c | 4 ++-- src/modules/module-example-sink.c | 4 ++-- src/modules/module-example-source.c | 4 ++-- src/modules/module-fallback-sink.c | 4 ++-- src/modules/module-ffado-driver.c | 4 ++-- src/modules/module-filter-chain.c | 4 ++-- src/modules/module-jack-tunnel.c | 4 ++-- src/modules/module-jackdbus-detect.c | 4 ++-- src/modules/module-link-factory.c | 4 ++-- src/modules/module-loopback.c | 4 ++-- src/modules/module-metadata.c | 4 ++-- src/modules/module-netjack2-driver.c | 4 ++-- src/modules/module-netjack2-manager.c | 4 ++-- src/modules/module-parametric-equalizer.c | 4 ++-- src/modules/module-pipe-tunnel.c | 4 ++-- src/modules/module-portal.c | 4 ++-- src/modules/module-profiler.c | 4 ++-- src/modules/module-protocol-pulse.c | 4 ++-- src/modules/module-protocol-pulse/snap-policy.c | 2 -- src/modules/module-pulse-tunnel.c | 4 ++-- src/modules/module-raop-discover.c | 4 ++-- src/modules/module-raop-sink.c | 4 ++-- src/modules/module-roc-sink.c | 4 ++-- src/modules/module-roc-source.c | 4 ++-- src/modules/module-rt.c | 4 ++-- src/modules/module-rtp/stream.c | 4 ++-- src/modules/module-snapcast-discover.c | 4 ++-- src/modules/module-spa-device-factory.c | 4 ++-- src/modules/module-spa-node-factory.c | 4 ++-- src/modules/module-vban/stream.c | 4 ++-- src/modules/module-x11-bell.c | 4 ++-- src/modules/module-zeroconf-discover.c | 4 ++-- src/pipewire/impl-node.c | 4 ++-- src/tools/pw-cat.c | 4 ++-- test/pwtest.h | 4 ++-- test/test-functional.c | 4 ++-- 57 files changed, 112 insertions(+), 105 deletions(-) diff --git a/spa/plugins/alsa/acp/compat.c b/spa/plugins/alsa/acp/compat.c index e2f317b09..050f2ec54 100644 --- a/spa/plugins/alsa/acp/compat.c +++ b/spa/plugins/alsa/acp/compat.c @@ -18,13 +18,14 @@ along with PulseAudio; if not, see . ***/ +#include "config.h" + #include #include #include "compat.h" #include "device-port.h" #include "alsa-mixer.h" -#include "config.h" static const char *port_types[] = { [PA_DEVICE_PORT_TYPE_UNKNOWN] = "unknown", diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 3475de834..75fb1b217 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -5,11 +5,11 @@ #ifndef SPA_ALSA_SEQ_H #define SPA_ALSA_SEQ_H +#include "config.h" + #include #include -#include "config.h" - #include #ifdef HAVE_ALSA_UMP #include diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 04f029ca4..61491056f 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -35,7 +37,6 @@ #include #include -#include "config.h" #include "codec-loader.h" #include "player.h" #include "iso-io.h" diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 7b7922b15..75ddd109c 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -5,6 +5,8 @@ #ifndef SPA_BLUEZ5_DEFS_H #define SPA_BLUEZ5_DEFS_H +#include "config.h" + #include #include @@ -17,8 +19,6 @@ #include -#include "config.h" - #ifdef __cplusplus extern "C" { #endif diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 8a0c30ac7..cdfc6ec4f 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Pauli Virtanen. */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -15,7 +17,6 @@ #include #include -#include "config.h" #include "iso-io.h" #include "media-codecs.h" diff --git a/spa/plugins/bluez5/midi-enum.c b/spa/plugins/bluez5/midi-enum.c index 9ef7d2bf5..662228032 100644 --- a/spa/plugins/bluez5/midi-enum.c +++ b/spa/plugins/bluez5/midi-enum.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include @@ -21,7 +23,6 @@ #include #include "midi.h" -#include "config.h" #include "bluez5-interface-gen.h" #include "dbus-monitor.h" diff --git a/spa/plugins/filter-graph/audio-dsp-avx.c b/spa/plugins/filter-graph/audio-dsp-avx.c index 1509284c7..9b49fb556 100644 --- a/spa/plugins/filter-graph/audio-dsp-avx.c +++ b/spa/plugins/filter-graph/audio-dsp-avx.c @@ -2,13 +2,14 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" #ifndef HAVE_FFTW #include "pffft.h" #endif diff --git a/spa/plugins/filter-graph/audio-dsp-c.c b/spa/plugins/filter-graph/audio-dsp-c.c index f2a509a76..ee22b481e 100644 --- a/spa/plugins/filter-graph/audio-dsp-c.c +++ b/spa/plugins/filter-graph/audio-dsp-c.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,7 +12,6 @@ #include -#include "config.h" #ifdef HAVE_FFTW #include #else diff --git a/spa/plugins/filter-graph/audio-dsp-sse.c b/spa/plugins/filter-graph/audio-dsp-sse.c index 8c2ffa8e6..59aee8271 100644 --- a/spa/plugins/filter-graph/audio-dsp-sse.c +++ b/spa/plugins/filter-graph/audio-dsp-sse.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,7 +12,6 @@ #include -#include "config.h" #ifndef HAVE_FFTW #include "pffft.h" #endif diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 480452b1e..233f5ea1a 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -13,8 +15,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/spa/plugins/v4l2/v4l2-udev.c b/spa/plugins/v4l2/v4l2-udev.c index c45ef06ff..df99f7b4a 100644 --- a/spa/plugins/v4l2/v4l2-udev.c +++ b/spa/plugins/v4l2/v4l2-udev.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -24,7 +26,6 @@ #include #include -#include "config.h" #include "v4l2.h" #ifdef HAVE_LOGIND diff --git a/spa/plugins/v4l2/v4l2.c b/spa/plugins/v4l2/v4l2.c index 71489320d..e080bf546 100644 --- a/spa/plugins/v4l2/v4l2.c +++ b/spa/plugins/v4l2/v4l2.c @@ -2,12 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include -#include "config.h" #include "v4l2.h" extern const struct spa_handle_factory spa_v4l2_source_factory; diff --git a/src/daemon/pipewire.c b/src/daemon/pipewire.c index 9193d817b..7d8f185c7 100644 --- a/src/daemon/pipewire.c +++ b/src/daemon/pipewire.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -16,8 +18,6 @@ #include -#include "config.h" - static void do_quit(void *data, int signal_number) { struct pw_main_loop *loop = data; diff --git a/src/examples/bluez-session.c b/src/examples/bluez-session.c index 81f9926a6..2183071ce 100644 --- a/src/examples/bluez-session.c +++ b/src/examples/bluez-session.c @@ -8,14 +8,14 @@ [title] */ +#include "config.h" + #include #include #include #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-access.c b/src/modules/module-access.c index 05bc14483..26e64f749 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #ifdef HAVE_SYS_VFS_H #include #endif diff --git a/src/modules/module-adapter.c b/src/modules/module-adapter.c index ea913ebad..9f5474a08 100644 --- a/src/modules/module-adapter.c +++ b/src/modules/module-adapter.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-adapter/adapter.c b/src/modules/module-adapter/adapter.c index 93ba0d305..6d2614f08 100644 --- a/src/modules/module-adapter/adapter.c +++ b/src/modules/module-adapter/adapter.c @@ -2,14 +2,14 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-avb.c b/src/modules/module-avb.c index b730b17c1..47afb3759 100644 --- a/src/modules/module-avb.c +++ b/src/modules/module-avb.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-client-device.c b/src/modules/module-client-device.c index 84b8456b2..c02a4058f 100644 --- a/src/modules/module-client-device.c +++ b/src/modules/module-client-device.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c index dc978084d..ea082b33d 100644 --- a/src/modules/module-client-node.c +++ b/src/modules/module-client-node.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index c897e6e14..4591e3e1f 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 5f6268dd8..ccd8c0e01 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index 8014f61f9..b44470f68 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index 47a061891..f04ae1afc 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-fallback-sink.c b/src/modules/module-fallback-sink.c index 86982e624..2e949cdb2 100644 --- a/src/modules/module-fallback-sink.c +++ b/src/modules/module-fallback-sink.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 1c9c7c590..761e62471 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index e04bb1590..8f1b11d16 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index b69ec7704..6c7416941 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-jackdbus-detect.c b/src/modules/module-jackdbus-detect.c index 23bce1b45..622ca6efe 100644 --- a/src/modules/module-jackdbus-detect.c +++ b/src/modules/module-jackdbus-detect.c @@ -3,6 +3,8 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Red Hat Inc. */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index ca2b8a75c..039ed16ff 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index f4b068be7..a9fa8c4d9 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-metadata.c b/src/modules/module-metadata.c index a1255da30..79d80d1b4 100644 --- a/src/modules/module-metadata.c +++ b/src/modules/module-metadata.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 011052e7b..5cd93b253 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -18,8 +20,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index fe482fd94..c5d8d308b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -19,8 +21,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-parametric-equalizer.c b/src/modules/module-parametric-equalizer.c index 315662f82..a11398166 100644 --- a/src/modules/module-parametric-equalizer.c +++ b/src/modules/module-parametric-equalizer.c @@ -3,11 +3,11 @@ /* SPDX-FileCopyrightText: Copyright © 2024 Asymptotic Inc. */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index afc8e0f14..9798336df 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -3,6 +3,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -16,8 +18,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-portal.c b/src/modules/module-portal.c index 566e67d4b..4491b0317 100644 --- a/src/modules/module-portal.c +++ b/src/modules/module-portal.c @@ -3,6 +3,8 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Red Hat Inc. */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index ec8c5c7f0..3031caec3 100644 --- a/src/modules/module-profiler.c +++ b/src/modules/module-profiler.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index ea5eb6cbe..4c82d97c0 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-protocol-pulse/snap-policy.c b/src/modules/module-protocol-pulse/snap-policy.c index 2921f06de..774763f9a 100644 --- a/src/modules/module-protocol-pulse/snap-policy.c +++ b/src/modules/module-protocol-pulse/snap-policy.c @@ -2,9 +2,7 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Canonical Ltd. */ /* SPDX-License-Identifier: MIT */ -#ifdef HAVE_CONFIG_H #include -#endif #include #include diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 4999a199d..6e6a7f2e1 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -14,8 +16,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 9782eb458..1d5a9a82a 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 076c388d7..dbc4da39a 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -27,8 +29,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c index dcd736848..7b834e3eb 100644 --- a/src/modules/module-roc-sink.c +++ b/src/modules/module-roc-sink.c @@ -3,12 +3,12 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Sanchayan Maity */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c index b5a5ea99b..0da4560e7 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -3,12 +3,12 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Sanchayan Maity */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index b2d0739cf..108627a13 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -26,6 +26,8 @@ SOFTWARE. ***/ +#include "config.h" + #include #include #include @@ -44,8 +46,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index cbd3ee994..9d3713dd5 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include @@ -17,8 +19,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index a11409169..17069c9fc 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -17,8 +19,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/modules/module-spa-device-factory.c b/src/modules/module-spa-device-factory.c index eb8d2436a..a3b08a285 100644 --- a/src/modules/module-spa-device-factory.c +++ b/src/modules/module-spa-device-factory.c @@ -2,13 +2,13 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include #include -#include "config.h" - #include #include "pipewire/impl.h" diff --git a/src/modules/module-spa-node-factory.c b/src/modules/module-spa-node-factory.c index 0f9a97020..c7fedd47e 100644 --- a/src/modules/module-spa-node-factory.c +++ b/src/modules/module-spa-node-factory.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -9,8 +11,6 @@ #include -#include "config.h" - #include "pipewire/impl.h" #include "spa/spa-node.h" diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 10eb34a82..3630441b3 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include @@ -18,8 +20,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index 1617c20c8..3e3660775 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 68edd605c..57462342d 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -10,8 +12,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index f37bfd411..b30b99f8b 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -11,8 +13,6 @@ #include #include -#include "config.h" - #include #include #include diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 2bcda784f..406675c7a 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -3,6 +3,8 @@ /* @author Pantelis Antoniou */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -34,8 +36,6 @@ #include #include -#include "config.h" - #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #include #include diff --git a/test/pwtest.h b/test/pwtest.h index ae768f08e..dd9f3d7db 100644 --- a/test/pwtest.h +++ b/test/pwtest.h @@ -2,11 +2,11 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Red Hat, Inc. */ /* SPDX-License-Identifier: MIT */ -#include "config.h" - #ifndef PWTEST_H #define PWTEST_H +#include "config.h" + #include #include #include diff --git a/test/test-functional.c b/test/test-functional.c index 3cdcb6161..8c3438688 100644 --- a/test/test-functional.c +++ b/test/test-functional.c @@ -2,10 +2,10 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include - #include "config.h" +#include + #include "pwtest.h" PWTEST(openal_info_test) From 60669920f0344c7d0b6df8c78ce631e26ba2d6db Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 2 Jun 2025 14:36:38 +0530 Subject: [PATCH 0326/1014] pulse-server: Implement PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND We do this by setting the node as passive. Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4255 Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4726 --- src/modules/module-protocol-pulse/pulse-server.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 45a346e2f..671e35abd 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1756,6 +1756,9 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui PW_KEY_TARGET_OBJECT, "%u", sink_index); } + if (dont_inhibit_auto_suspend) + pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true"); + stream->stream = pw_stream_new(client->core, name, props); props = NULL; if (stream->stream == NULL) From 13105a8e64e38fee1d30d0d74bc3599d4408a9a3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 2 Jun 2025 11:48:01 +0200 Subject: [PATCH 0327/1014] pulse-server: Implement record PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND Also implement the flag for record streams, where it is more usually used, like in pavucontrol to disable starting a network source. --- src/modules/module-protocol-pulse/pulse-server.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 671e35abd..2f775fd8d 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -2051,6 +2051,8 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); } + if (dont_inhibit_auto_suspend) + pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true"); stream->stream = pw_stream_new(client->core, name, props); props = NULL; From 5dd65dccf32d3440d1176d47ec4de851d7485376 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 2 Jun 2025 11:55:31 +0200 Subject: [PATCH 0328/1014] spa: Improve spa_io_clock flags documentation --- spa/include/spa/node/io.h | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 41f011720..a1c365655 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -123,14 +123,30 @@ struct spa_io_range { * * A node is a driver when \ref spa_io_clock.id in \ref SPA_IO_Clock and * \ref spa_io_position.clock.id in \ref SPA_IO_Position are the same. + * + * The flags are set by the graph driver at the start of each cycle. */ struct spa_io_clock { -#define SPA_IO_CLOCK_FLAG_FREEWHEEL (1u<<0) /* graph is freewheeling */ -#define SPA_IO_CLOCK_FLAG_XRUN_RECOVER (1u<<1) /* recovering from xrun */ -#define SPA_IO_CLOCK_FLAG_LAZY (1u<<2) /* lazy scheduling */ -#define SPA_IO_CLOCK_FLAG_NO_RATE (1u<<3) /* the rate of the clock is only approximately. - * it is recommended to use the nsec as a clock source. - * The rate_diff contains the measured inaccuracy. */ +#define SPA_IO_CLOCK_FLAG_FREEWHEEL (1u<<0) /**< Graph is freewheeling. It runs at the maximum + * possible rate, only constrained by the processing + * power of the machine it runs on. This can be useful + * for offline processing, where processing in real + * time is not desired. */ +#define SPA_IO_CLOCK_FLAG_XRUN_RECOVER (1u<<1) /**< A node's process callback did not complete within + * the last cycle's deadline, resulting in an xrun. + * This flag is not set for the entire graph. Instead, + * it is set at the start of the current cycle before + * a node that experienced an xrun has its process + * callback invoked. After said callback finished, the + * flag is cleared again. That way, the node knows that + * during the last cycle it experienced an xrun. They + * can use this information for example to resynchronize + * or clear custom stale states. */ +#define SPA_IO_CLOCK_FLAG_LAZY (1u<<2) /**< The driver uses lazy scheduling. For details, see + * \ref PW_KEY_NODE_SUPPORTS_LAZY . */ +#define SPA_IO_CLOCK_FLAG_NO_RATE (1u<<3) /**< The rate of the clock is only approximately. + * It is recommended to use the nsec as a clock source. + * The rate_diff contains the measured inaccuracy. */ uint32_t flags; /**< Clock flags */ uint32_t id; /**< Unique clock id, set by host application */ char name[64]; /**< Clock name prefixed with API, set by node when it receives From abb55697c18af73fa5825ec7f87c48552486f6ba Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 2 Jun 2025 11:55:47 +0200 Subject: [PATCH 0329/1014] spa: Add SPA_IO_CLOCK_FLAG_DISCONT --- spa/include/spa/node/io.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index a1c365655..01983a26d 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -147,6 +147,20 @@ struct spa_io_clock { #define SPA_IO_CLOCK_FLAG_NO_RATE (1u<<3) /**< The rate of the clock is only approximately. * It is recommended to use the nsec as a clock source. * The rate_diff contains the measured inaccuracy. */ +#define SPA_IO_CLOCK_FLAG_DISCONT (1u<<4) /**< The clock experienced a discontinuity in its + * timestamps since the last cycle. If this is set, + * nodes know that timestamps between the last and + * the current cycle cannot be assumed to be + * continuous. Nodes that synchronize playback against + * clock timestamps should resynchronize (for example + * by flushing buffers to avoid incorrect delays). + * This differs from an xrun in that it is not necessariy + * an error and that it is not caused by missed process + * deadlines. If for example a custom network time + * based driver starts to follow a different time + * server, and the offset between that server and its + * local clock consequently suddenly changes, then that + * driver should set this flag. */ uint32_t flags; /**< Clock flags */ uint32_t id; /**< Unique clock id, set by host application */ char name[64]; /**< Clock name prefixed with API, set by node when it receives From f65f5cf8665f73cd01fab30769989a5f617c142c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 2 Jun 2025 12:22:59 +0200 Subject: [PATCH 0330/1014] stream: make ticks continuous on DISCONT A discont of the clock does not mean a discont in the stream, so keep the ticks continuous. --- src/pipewire/filter.c | 3 ++- src/pipewire/stream.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 6c6bfc74a..101cdd603 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1970,7 +1970,8 @@ int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time) if (SPA_LIKELY(p != NULL)) { impl->time.now = p->clock.nsec; impl->time.rate = p->clock.rate; - if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { + if (SPA_UNLIKELY(impl->clock_id != p->clock.id || + SPA_FLAG_IS_SET(p->clock.flags, SPA_IO_CLOCK_FLAG_DISCONT))) { impl->base_pos = p->clock.position - impl->time.ticks; impl->clock_id = p->clock.id; } diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 0feeabe50..8e88dbb2b 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -643,7 +643,8 @@ static inline void copy_position(struct stream *impl, int64_t queued) if (SPA_LIKELY(p != NULL)) { impl->time.now = p->clock.nsec; impl->time.rate = p->clock.rate; - if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) { + if (SPA_UNLIKELY(impl->clock_id != p->clock.id || + SPA_FLAG_IS_SET(p->clock.flags, SPA_IO_CLOCK_FLAG_DISCONT))) { impl->base_pos = p->clock.position - impl->time.ticks; impl->clock_id = p->clock.id; } From a5e63102d9d43b677197230b0184df778f08534e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 2 Jun 2025 19:14:45 +0200 Subject: [PATCH 0331/1014] alsa: unlock pending drain in drop When we set activated=false, signal the thread because it might be waiting in drain. See #4728 --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 1 + 1 file changed, 1 insertion(+) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index fdfce3bd4..4b92bbb13 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -616,6 +616,7 @@ static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) if (pw->activated && pw->stream != NULL) { pw_stream_set_active(pw->stream, false); pw->activated = false; + pw_thread_loop_signal(pw->main_loop, false); } pw_thread_loop_unlock(pw->main_loop); return 0; From 38a3ebdca14b1e79f9e7053cb43ea464e0a92015 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 3 Jun 2025 11:31:07 +0200 Subject: [PATCH 0332/1014] adapter: use the right default when filtering default We should prefer the value of the follower when fixating to the PortConfig format. To make this actually work we need to be able to check if the value is within the configured ranges. Implement the check for all types by simply comparing the memory. This should then work also for checking arrays, such as channel positions. --- spa/include/spa/pod/compare.h | 4 +--- spa/plugins/audioconvert/audioadapter.c | 2 +- spa/plugins/videoconvert/videoadapter.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index a9d0f2696..12c65fd44 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -52,8 +52,6 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con return SPA_CMP(*(double *)r1, *(double *)r2); case SPA_TYPE_String: return strcmp((char *)r1, (char *)r2); - case SPA_TYPE_Bytes: - return memcmp((char *)r1, (char *)r2, size); case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, @@ -75,7 +73,7 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con return SPA_CMP(n1, n2); } default: - break; + return memcmp(r1, r2, size); } return 0; } diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index e9595aa1d..e9ff63009 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -927,7 +927,7 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * p2 = spa_pod_object_find_prop(o2, p2, p1->key); if (p2 != NULL) { spa_pod_builder_get_state(b, &state); - res = spa_pod_filter_prop(b, p1, p2); + res = spa_pod_filter_prop(b, p2, p1); if (res < 0) spa_pod_builder_reset(b, &state); } diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 9eae268e1..84a24cd2d 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -978,7 +978,7 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * p2 = spa_pod_object_find_prop(o2, p2, p1->key); if (p2 != NULL) { spa_pod_builder_get_state(b, &state); - res = spa_pod_filter_prop(b, p1, p2); + res = spa_pod_filter_prop(b, p2, p1); if (res < 0) spa_pod_builder_reset(b, &state); } From 187df01b5eb1cfba054b2e2a6da6d4323a5178d9 Mon Sep 17 00:00:00 2001 From: ValdikSS Date: Tue, 3 Jun 2025 07:28:42 +0300 Subject: [PATCH 0333/1014] bluez5: aac: disable Perceptual Noise Substitution for MPEG-2 profile When the Bluetooth earphones claim to support AAC `MPEG-2 AAC LC` type only, PipeWire encodes audio using MPEG-4 FDK-AAC profile which enables unsupported Perceptual Noise Substitution (PNS) encoder feature. Use AOT_MP2_AAC_LC pseudo-profile which is AOT_AAC_LC with PNS disabled. --- spa/plugins/bluez5/a2dp-codec-aac.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index bf0b735f7..7522f3bb7 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -370,11 +370,14 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, /* If object type has multiple bits set (invalid per spec, see above), * assume the device usually means AAC-LC. */ - if (conf->object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | - AAC_OBJECT_TYPE_MPEG4_AAC_LC)) { + if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); if (res != AACENC_OK) goto error; + } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) { + res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC); + if (res != AACENC_OK) + goto error; } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_ER_AAC_ELD); if (res != AACENC_OK) From c515f9bf8effdda78f4bfa091a8b35b6e7552c1a Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 3 Jun 2025 14:47:23 +0530 Subject: [PATCH 0334/1014] spa: loop: Change get_time() timeout to unsigned A signed value doesn't really make sense in this context, so let's keep it unsigned so the semantics are clear. This does break the interface, but should be okay since it's not in a release yet. --- spa/include/spa/support/loop.h | 4 ++-- spa/plugins/support/loop.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index c6c6df9f7..110f4faa1 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -325,7 +325,7 @@ struct spa_loop_control_methods { * Get the current time with \ref timeout that can be used in wait. * Since version 2:2 */ - int (*get_time) (void *object, struct timespec *abstime, int64_t timeout); + int (*get_time) (void *object, struct timespec *abstime, uint64_t timeout); /** Wait for a signal * Wait until a thread performs signal. Since version 2:2 * @@ -404,7 +404,7 @@ SPA_API_LOOP int spa_loop_control_unlock(struct spa_loop_control *object) spa_loop_control, &object->iface, unlock, 2); } SPA_API_LOOP int spa_loop_control_get_time(struct spa_loop_control *object, - struct timespec *abstime, int64_t timeout) + struct timespec *abstime, uint64_t timeout) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, get_time, 2, abstime, timeout); diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 119c67ec0..478e5522c 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -657,7 +657,7 @@ static int loop_unlock(void *object) impl->recurse++; return -res; } -static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout) +static int loop_get_time(void *object, struct timespec *abstime, uint64_t timeout) { if (clock_gettime(CLOCK_REALTIME, abstime) < 0) return -errno; From 56c6e19f99044c84ccdc41cc3016996578838c17 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Tue, 3 Jun 2025 15:20:15 +0530 Subject: [PATCH 0335/1014] Revert "spa: loop: Change get_time() timeout to unsigned" This reverts commit c515f9bf8effdda78f4bfa091a8b35b6e7552c1a. The PW APIs use int64_t (partly because SPA_NSEC_PER_SEC is an LL), and we don't want to change already public API. --- spa/include/spa/support/loop.h | 4 ++-- spa/plugins/support/loop.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 110f4faa1..c6c6df9f7 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -325,7 +325,7 @@ struct spa_loop_control_methods { * Get the current time with \ref timeout that can be used in wait. * Since version 2:2 */ - int (*get_time) (void *object, struct timespec *abstime, uint64_t timeout); + int (*get_time) (void *object, struct timespec *abstime, int64_t timeout); /** Wait for a signal * Wait until a thread performs signal. Since version 2:2 * @@ -404,7 +404,7 @@ SPA_API_LOOP int spa_loop_control_unlock(struct spa_loop_control *object) spa_loop_control, &object->iface, unlock, 2); } SPA_API_LOOP int spa_loop_control_get_time(struct spa_loop_control *object, - struct timespec *abstime, uint64_t timeout) + struct timespec *abstime, int64_t timeout) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, get_time, 2, abstime, timeout); diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 478e5522c..119c67ec0 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -657,7 +657,7 @@ static int loop_unlock(void *object) impl->recurse++; return -res; } -static int loop_get_time(void *object, struct timespec *abstime, uint64_t timeout) +static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout) { if (clock_gettime(CLOCK_REALTIME, abstime) < 0) return -errno; From 457b1c2e1ecd018aa5eb0b1733f290246f48ce1e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 3 Jun 2025 17:54:06 +0200 Subject: [PATCH 0336/1014] filter-graph: run when both in and out != NULL The check got reverted in 8ee51cd88f47c2598756e1b82aa03b9b384ae81f --- spa/plugins/filter-graph/builtin_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 260f36c53..010e54e9e 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -1269,7 +1269,7 @@ static void delay_run(void * Instance, unsigned long SampleCount) impl->delay_samples = SPA_CLAMP((uint32_t)(delay * impl->rate), 0u, impl->buffer_samples-1); impl->delay = delay; } - if (in != NULL && out == NULL) { + if (in != NULL && out != NULL) { spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, impl->delay_samples, out, in, SampleCount); } From 6e64d5da47a35da6ea0898ed1e364b9733f50f4b Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Tue, 3 Jun 2025 17:48:51 +0200 Subject: [PATCH 0337/1014] raop: set mtu to 1448 by default --- src/modules/module-raop-sink.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index dbc4da39a..dc731b951 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -911,7 +911,6 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head rtp_stream_set_first(impl->stream); impl->sync = 0; - impl->sync_period = impl->rate / (impl->mtu / impl->stride); impl->recording = true; rtsp_send_volume(impl); @@ -1916,8 +1915,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->rate = RAOP_RATE; impl->latency = msec_to_samples(impl, RAOP_LATENCY_MS); impl->stride = RAOP_STRIDE; - impl->mtu = impl->stride * impl->psamples; - impl->sync_period = impl->rate / impl->psamples; if ((str = pw_properties_get(props, "raop.latency.ms")) == NULL) str = SPA_STRINGIFY(DEFAULT_LATENCY_MS); @@ -1946,7 +1943,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_MEDIA_FORMAT) == NULL) pw_properties_setf(props, PW_KEY_MEDIA_FORMAT, "%d", SPA_AUDIO_FORMAT_S16_LE); if (pw_properties_get(props, "net.mtu") == NULL) - pw_properties_setf(props, "net.mtu", "%d", impl->mtu); + pw_properties_set(props, "net.mtu", "1448"); if (pw_properties_get(props, "rtp.sender-ts-offset") == NULL) pw_properties_setf(props, "rtp.sender-ts-offset", "%d", 0); if (pw_properties_get(props, "sess.ts-direct") == NULL) @@ -1982,6 +1979,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "sess.ts-refclk"); copy_props(impl, props, "sess.ts-direct"); + impl->mtu = pw_properties_get_uint32(impl->props, "net.mtu", 1448); + impl->sync_period = impl->rate / (impl->mtu / impl->stride); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); From 113e22cb72e54d3d0cab04bb7e121ebf63f6846e Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Tue, 3 Jun 2025 18:12:42 +0200 Subject: [PATCH 0338/1014] raop: don't set improper media.format --- src/modules/module-raop-sink.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index dc731b951..8cfcf06db 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1940,8 +1940,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); - if (pw_properties_get(props, PW_KEY_MEDIA_FORMAT) == NULL) - pw_properties_setf(props, PW_KEY_MEDIA_FORMAT, "%d", SPA_AUDIO_FORMAT_S16_LE); if (pw_properties_get(props, "net.mtu") == NULL) pw_properties_set(props, "net.mtu", "1448"); if (pw_properties_get(props, "rtp.sender-ts-offset") == NULL) From efb1208b9783a861eddacf8666366a5235ab525c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 5 Jun 2025 11:21:28 +0200 Subject: [PATCH 0339/1014] filter-graph: use spa_memcpy to make it instrumentable --- spa/plugins/filter-graph/builtin_plugin.c | 6 +++--- spa/plugins/filter-graph/ebur128_plugin.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 010e54e9e..fea43d307 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -908,7 +908,7 @@ error: #else spa_log_error(impl->log, "compiled without spa-plugins support, can't resample"); float *out_samples = calloc(*n_samples, sizeof(float)); - memcpy(out_samples, samples, *n_samples * sizeof(float)); + spa_memcpy(out_samples, samples, *n_samples * sizeof(float)); return out_samples; #endif } @@ -2033,7 +2033,7 @@ static void *param_eq_instantiate(const struct spa_fga_plugin *plugin, const str } if (idx == 0) { for (i = 1; i < 8; i++) - memcpy(&impl->bq[i*PARAM_EQ_MAX], impl->bq, + spa_memcpy(&impl->bq[i*PARAM_EQ_MAX], impl->bq, sizeof(struct biquad) * PARAM_EQ_MAX); } } @@ -2513,7 +2513,7 @@ static void debug_run(void * Instance, unsigned long SampleCount) if (in != NULL) { spa_debug_log_mem(impl->log, SPA_LOG_LEVEL_INFO, 0, in, SampleCount * sizeof(float)); if (out != NULL) - memcpy(out, in, SampleCount * sizeof(float)); + spa_memcpy(out, in, SampleCount * sizeof(float)); } if (control != NULL) { spa_log_info(impl->log, "control: %f", control[0]); diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c index 0c94449ff..dfd2b3add 100644 --- a/spa/plugins/filter-graph/ebur128_plugin.c +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -153,7 +153,7 @@ static void ebur128_run(void * Instance, unsigned long SampleCount) ebur128_add_frames_float(st[i], in, SampleCount); if (out != NULL) - memcpy(out, in, SampleCount * sizeof(float)); + spa_memcpy(out, in, SampleCount * sizeof(float)); } if (impl->port[PORT_OUT_MOMENTARY] != NULL) { double sum = 0.0; From 7ef8dc4dee732f46ccd037a827f4a6f0814d5848 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 5 Jun 2025 11:21:56 +0200 Subject: [PATCH 0340/1014] filter-graph: a 64 tap hilbert function is a better default 1024 taps for the default hilbert functions seems excessive, use 64 instead. --- spa/plugins/filter-graph/builtin_plugin.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index fea43d307..6f2f0e8fd 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -794,7 +794,7 @@ static float *create_hilbert(struct plugin *pl, const char *filename, float gain int delay = (int) (delay_sec * rate); if (length <= 0) - length = 1024; + length = 64; length -= SPA_MIN(offset, length); @@ -814,6 +814,7 @@ static float *create_hilbert(struct plugin *pl, const char *filename, float gain samples[delay + h - i] = v; } *n_samples = n; + spa_log_info(pl->log, "created hilbert function"); return samples; } @@ -832,6 +833,7 @@ static float *create_dirac(struct plugin *pl, const char *filename, float gain, samples[delay] = gain; + spa_log_info(pl->log, "created dirac function"); *n_samples = n; return samples; } From e43324a1863d70342f6e827452107f9e2f957c94 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 5 Jun 2025 11:35:32 +0200 Subject: [PATCH 0341/1014] filter-graph: remove a memcpy and some cleanups We don't need to memcpy if we swap between 2 buffers for overlap. Remove a duplicate variable. --- spa/plugins/filter-graph/convolver.c | 69 +++++++++++++--------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/spa/plugins/filter-graph/convolver.c b/spa/plugins/filter-graph/convolver.c index e5bd47b30..a077c6ec1 100644 --- a/spa/plugins/filter-graph/convolver.c +++ b/spa/plugins/filter-graph/convolver.c @@ -19,14 +19,13 @@ struct convolver1 { float **segments; float **segmentsIr; - float *fft_buffer; + float *fft_buffer[2]; void *fft; void *ifft; float *pre_mult; float *conv; - float *overlap; float *inputBuffer; int inputBufferFill; @@ -48,7 +47,8 @@ static void convolver1_reset(struct spa_fga_dsp *dsp, struct convolver1 *conv) int i; for (i = 0; i < conv->segCount; i++) spa_fga_dsp_fft_memclear(dsp, conv->segments[i], conv->fftComplexSize, false); - spa_fga_dsp_fft_memclear(dsp, conv->overlap, conv->blockSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0], conv->segSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[1], conv->segSize, true); spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer, conv->segSize, true); spa_fga_dsp_fft_memclear(dsp, conv->pre_mult, conv->fftComplexSize, false); spa_fga_dsp_fft_memclear(dsp, conv->conv, conv->fftComplexSize, false); @@ -69,13 +69,14 @@ static void convolver1_free(struct spa_fga_dsp *dsp, struct convolver1 *conv) spa_fga_dsp_fft_free(dsp, conv->fft); if (conv->ifft) spa_fga_dsp_fft_free(dsp, conv->ifft); - if (conv->fft_buffer) - spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer); + if (conv->fft_buffer[0]) + spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[0]); + if (conv->fft_buffer[1]) + spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[1]); free(conv->segments); free(conv->segmentsIr); spa_fga_dsp_fft_memfree(dsp, conv->pre_mult); spa_fga_dsp_fft_memfree(dsp, conv->conv); - spa_fga_dsp_fft_memfree(dsp, conv->overlap); spa_fga_dsp_fft_memfree(dsp, conv->inputBuffer); free(conv); } @@ -110,8 +111,9 @@ static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, con if (conv->ifft == NULL) goto error; - conv->fft_buffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); - if (conv->fft_buffer == NULL) + conv->fft_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + conv->fft_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + if (conv->fft_buffer[0] == NULL || conv->fft_buffer[1] == NULL) goto error; conv->segments = calloc(conv->segCount, sizeof(float*)); @@ -128,18 +130,16 @@ static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, con if (conv->segments[i] == NULL || conv->segmentsIr[i] == NULL) goto error; - spa_fga_dsp_copy(dsp, conv->fft_buffer, &ir[i * conv->blockSize], copy); + spa_fga_dsp_copy(dsp, conv->fft_buffer[0], &ir[i * conv->blockSize], copy); if (copy < conv->segSize) - spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer + copy, conv->segSize - copy, true); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0] + copy, conv->segSize - copy, true); - spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->fft_buffer, conv->segmentsIr[i]); + spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->fft_buffer[0], conv->segmentsIr[i]); } conv->pre_mult = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); conv->conv = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); - conv->overlap = spa_fga_dsp_fft_memalloc(dsp, conv->blockSize, true); conv->inputBuffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); - if (conv->pre_mult == NULL || conv->conv == NULL || conv->overlap == NULL || - conv->inputBuffer == NULL) + if (conv->pre_mult == NULL || conv->conv == NULL || conv->inputBuffer == NULL) goto error; conv->scale = 1.0f / conv->segSize; convolver1_reset(dsp, conv); @@ -159,18 +159,18 @@ static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, cons return len; } + int inputBufferFill = conv->inputBufferFill; while (processed < len) { - const int processing = SPA_MIN(len - processed, conv->blockSize - conv->inputBufferFill); - const int inputBufferPos = conv->inputBufferFill; - - spa_fga_dsp_copy(dsp, conv->inputBuffer + inputBufferPos, input + processed, processing); - if (inputBufferPos == 0 && processing < conv->blockSize) - spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer + processing, conv->blockSize - processing, true); + const int processing = SPA_MIN(len - processed, conv->blockSize - inputBufferFill); + spa_fga_dsp_copy(dsp, conv->inputBuffer + inputBufferFill, input + processed, processing); + if (inputBufferFill == 0 && processing < conv->blockSize) + spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer + processing, + conv->blockSize - processing, true); spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]); if (conv->segCount > 1) { - if (conv->inputBufferFill == 0) { + if (inputBufferFill == 0) { int indexAudio = (conv->current + 1) % conv->segCount; spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->pre_mult, @@ -203,22 +203,23 @@ static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, cons conv->fftComplexSize, conv->scale); } - spa_fga_dsp_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer); + spa_fga_dsp_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer[0]); - spa_fga_dsp_sum(dsp, output + processed, conv->fft_buffer + inputBufferPos, - conv->overlap + inputBufferPos, processing); + spa_fga_dsp_sum(dsp, output + processed, conv->fft_buffer[0] + inputBufferFill, + conv->fft_buffer[1] + conv->blockSize + inputBufferFill, processing); - conv->inputBufferFill += processing; - if (conv->inputBufferFill == conv->blockSize) { - conv->inputBufferFill = 0; + inputBufferFill += processing; + if (inputBufferFill == conv->blockSize) { + inputBufferFill = 0; - spa_fga_dsp_copy(dsp, conv->overlap, conv->fft_buffer + conv->blockSize, conv->blockSize); + SPA_SWAP(conv->fft_buffer[0], conv->fft_buffer[1]); conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1); } processed += processing; } + conv->inputBufferFill = inputBufferFill; return len; } @@ -236,7 +237,6 @@ struct convolver float *tailPrecalculated; float *tailInput; int tailInputFill; - int precalculatedPos; }; void convolver_reset(struct convolver *conv) @@ -256,7 +256,6 @@ void convolver_reset(struct convolver *conv) spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated, conv->tailBlockSize, true); } conv->tailInputFill = 0; - conv->precalculatedPos = 0; } struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen) @@ -358,13 +357,12 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int if (conv->tailPrecalculated0) spa_fga_dsp_sum(dsp, &output[processed], &output[processed], - &conv->tailPrecalculated0[conv->precalculatedPos], + &conv->tailPrecalculated0[conv->tailInputFill], processing); if (conv->tailPrecalculated) spa_fga_dsp_sum(dsp, &output[processed], &output[processed], - &conv->tailPrecalculated[conv->precalculatedPos], + &conv->tailPrecalculated[conv->tailInputFill], processing); - conv->precalculatedPos += processing; spa_fga_dsp_copy(dsp, conv->tailInput + conv->tailInputFill, input + processed, processing); conv->tailInputFill += processing; @@ -385,10 +383,9 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int convolver1_run(dsp, conv->tailConvolver, conv->tailInput, conv->tailOutput, conv->tailBlockSize); } - if (conv->tailInputFill == conv->tailBlockSize) { + if (conv->tailInputFill == conv->tailBlockSize) conv->tailInputFill = 0; - conv->precalculatedPos = 0; - } + processed += processing; } } From 420c510d4719a85b36c1b40611d76f4ba4c80aea Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 6 Jun 2025 12:47:09 +0530 Subject: [PATCH 0342/1014] spa: loop: Fix potential uninitialised result --- spa/plugins/support/loop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 119c67ec0..cf0005522 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -689,7 +689,7 @@ static int loop_wait(void *object, const struct timespec *abstime) static int loop_signal(void *object, bool wait_for_accept) { struct impl *impl = object; - int res; + int res = 0; if (impl->n_waiting > 0) if ((res = pthread_cond_broadcast(&impl->cond)) != 0) return -res; From 4f3a2d723b617e278efc82835baa93f65d756fa4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 6 Jun 2025 13:00:29 +0200 Subject: [PATCH 0343/1014] filter-graph: add a pipe filter --- spa/plugins/filter-graph/builtin_plugin.c | 221 ++++++++++++++++++++++ src/modules/module-filter-chain.c | 32 ++++ 2 files changed, 253 insertions(+) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 6f2f0e8fd..6c60113cf 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -11,9 +11,13 @@ #endif #include #include +#include +#include +#include #include #include +#include #include #include #include @@ -2557,6 +2561,221 @@ static const struct spa_fga_descriptor debug_desc = { .cleanup = builtin_cleanup, }; +/* pipe */ +struct pipe_impl { + struct plugin *plugin; + + struct spa_log *log; + struct spa_fga_dsp *dsp; + unsigned long rate; + float *port[3]; + float latency; + + int write_fd; + int read_fd; + size_t written; + size_t read; +}; + +static int do_exec(struct pipe_impl *impl, const char *command) +{ + int pid, res, len, argc = 0; + char *argv[512]; + struct spa_json it[2]; + const char *value; + int stdin_pipe[2]; + int stdout_pipe[2]; + + if (spa_json_begin_array_relax(&it[0], command, strlen(command)) <= 0) + return -EINVAL; + + while ((len = spa_json_next(&it[0], &value)) > 0) { + char *s; + + if ((s = malloc(len+1)) == NULL) + return -errno; + + spa_json_parse_stringn(value, len, s, len+1); + + argv[argc++] = s; + } + argv[argc++] = NULL; + + pipe2(stdin_pipe, 0); + pipe2(stdout_pipe, 0); + + impl->write_fd = stdin_pipe[1]; + impl->read_fd = stdout_pipe[0]; + + pid = fork(); + + if (pid == 0) { + char buf[1024]; + char *const *p; + struct spa_strbuf s; + + /* Double fork to avoid zombies; we don't want to set SIGCHLD handler */ + pid = fork(); + + if (pid < 0) { + spa_log_error(impl->log, "fork error: %m"); + goto done; + } else if (pid != 0) { + exit(0); + } + + dup2(stdin_pipe[0], 0); + dup2(stdout_pipe[1], 1); + + spa_strbuf_init(&s, buf, sizeof(buf)); + for (p = argv; *p; ++p) + spa_strbuf_append(&s, " '%s'", *p); + + spa_log_info(impl->log, "exec%s", s.buffer); + res = execvp(argv[0], argv); + + if (res == -1) { + res = -errno; + spa_log_error(impl->log, "execvp error '%s': %m", argv[0]); + } +done: + exit(1); + } else if (pid < 0) { + spa_log_error(impl->log, "fork error: %m"); + } else { + int status = 0; + do { + errno = 0; + res = waitpid(pid, &status, 0); + } while (res < 0 && errno == EINTR); + spa_log_debug(impl->log, "exec got pid %d res:%d status:%d", (int)pid, res, status); + } + return 0; +} + +static void pipe_transfer(struct pipe_impl *impl, float *in, float *out, int count) +{ + ssize_t sz; + + sz = read(impl->read_fd, out, count * sizeof(float)); + if (sz > 0) { + impl->read += sz; + if (impl->read == (size_t)sz) { + while ((sz = read(impl->read_fd, out, count * sizeof(float))) != -1) + impl->read += sz; + } + } else { + memset(out, 0, count * sizeof(float)); + } + if ((sz = write(impl->write_fd, in, count * sizeof(float))) != -1) + impl->written += sz; +} + +static void *pipe_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct pipe_impl *impl; + struct spa_json it[2]; + const char *val; + char key[256]; + spa_autofree char*command = NULL; + int len; + + errno = EINVAL; + if (config == NULL) { + spa_log_error(pl->log, "pipe: requires a config section"); + return NULL; + } + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "pipe: config must be an object"); + return NULL; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "command")) { + if ((command = malloc(len+1)) == NULL) + return NULL; + + if (spa_json_parse_stringn(val, len, command, len+1) <= 0) { + spa_log_error(pl->log, "pipe: command requires a string"); + return NULL; + } + } + else { + spa_log_warn(pl->log, "pipe: ignoring config key: '%s'", key); + } + } + if (command == NULL || command[0] == '\0') { + spa_log_error(pl->log, "pipe: command must be given and can not be empty"); + return NULL; + } + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->log = pl->log; + impl->dsp = pl->dsp; + impl->rate = SampleRate; + + do_exec(impl, command); + + fcntl(impl->write_fd, F_SETFL, fcntl(impl->write_fd, F_GETFL) | O_NONBLOCK); + fcntl(impl->read_fd, F_SETFL, fcntl(impl->read_fd, F_GETFL) | O_NONBLOCK); + + return impl; +} + +static void pipe_connect_port(void *Instance, unsigned long Port, float * DataLocation) +{ + struct pipe_impl *impl = Instance; + impl->port[Port] = DataLocation; +} + +static void pipe_run(void * Instance, unsigned long SampleCount) +{ + struct pipe_impl *impl = Instance; + float *in = impl->port[0], *out = impl->port[1]; + + if (in != NULL && out != NULL) + pipe_transfer(impl, in, out, SampleCount); +} + +static void pipe_cleanup(void * Instance) +{ + struct pipe_impl *impl = Instance; + close(impl->write_fd); + close(impl->read_fd); + free(impl); +} + +static struct spa_fga_port pipe_ports[] = { + { .index = 0, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor pipe_desc = { + .name = "pipe", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(pipe_ports), + .ports = pipe_ports, + + .instantiate = pipe_instantiate, + .connect_port = pipe_connect_port, + .run = pipe_run, + .cleanup = pipe_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -2616,6 +2835,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &sqrt_desc; case 27: return &debug_desc; + case 28: + return &pipe_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 8f1b11d16..19488d914 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -704,6 +704,38 @@ extern struct spa_handle_factory spa_filter_graph_factory; * control from "Control" will be copied to "Notify" and the control value will be * dumped into the INFO log. * + * ### pipe + * + * The pipe plugin can be used to filter the audio with another application using pipes + * for sending and receiving the raw audio. + * + * The application needs to consume raw float32 samples from stdin and produce filtered + * float32 samples on stdout. + * + * It has an "In" input port and an "Out" output data ports. + * + * The node requires a `config` section with extra configuration: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = builtin + * name = ... + * label = pipe + * config = { + * command = "ffmpeg -f f32le -ac 1 -ar 48000 -blocksize 1024 -fflags nobuffer -i \"pipe:\" \"-filter:a\" \"loudnorm=I=-18:TP=-3:LRA=4\" -f f32le -ac 1 -ar 48000 \"pipe:\"" + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * - `command` the command to execute. It should consume samples from stdin and produce + * samples on stdout. + * * ## General options * * Options with well-known behavior. Most options can be added to the global From b51d3d7e8bc6ed0f2fc1a8d78cbe7e8249f9b1b7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Jun 2025 11:44:06 +0200 Subject: [PATCH 0344/1014] filter-graph: ignore ports without descriptor Ports without a descriptor are to be ignored. They also don't have a node associated with them so don't try to get its latency. Fixes #4700 --- spa/plugins/filter-graph/filter-graph.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 233f5ea1a..fa3cc980e 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1689,6 +1689,9 @@ static int impl_activate(void *object, const struct spa_dict *props) max_latency = 0.0f; for (i = 0; i < graph->n_outputs; i++) { struct graph_port *port = &graph->output[i]; + /* ports with no descriptor are ignored */ + if (port->desc == NULL) + continue; max_latency = fmaxf(max_latency, port->node->max_latency); min_latency = fminf(min_latency, port->node->min_latency); } @@ -1701,6 +1704,9 @@ static int impl_activate(void *object, const struct spa_dict *props) "align the signals", min_latency, max_latency); for (i = 0; i < graph->n_outputs; i++) { struct graph_port *port = &graph->output[i]; + /* port with no descriptor are ignored */ + if (port->desc == NULL) + continue; if (min_latency != port->node->min_latency || max_latency != port->node->max_latency) spa_log_warn(impl->log, "output port %d from %s min:%f max:%f", From 930f2f3e2d6cd6f012e16a3adfb1c2b2693b8c9a Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Mon, 9 Jun 2025 16:36:05 +0200 Subject: [PATCH 0345/1014] gst: deviceprovider: take a ref to devices When _probe() is called, take a ref to the newly created devices instead if sinking the floating ref, since gst_clear_object() is called when core is disconnected. Otherwise the devices will be freed before the caller gets them. Fixes the following assert in the caller: g_object_is_floating: assertion 'G_IS_OBJECT (object)' failed Or sometimes a segfault with the backtrace: 0 g_type_check_instance_is_fundamentally_a (type_instance=type_instance@entry=0x116c1b0, fundamental_type=fundamental_type@entry=80) at /usr/src/debug/glib-2.0/2.84.0/gobject/gtype.c:3918 1 0xb6d40cc6 in g_object_is_floating (_object=0x116c1b0) at /usr/src/debug/glib-2.0/2.84.0/gobject/gobject.c:3843 2 0xb6bc4c74 in gst_device_provider_get_devices (provider=0x109ba00) at /usr/src/debug/gstreamer1.0/1.24.12/gst/gstdeviceprovider.c:426 --- src/gst/gstpipewiredeviceprovider.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c index 5bdd09b7d..744cf2752 100644 --- a/src/gst/gstpipewiredeviceprovider.c +++ b/src/gst/gstpipewiredeviceprovider.c @@ -324,7 +324,7 @@ static void do_add_nodes(GstPipeWireDeviceProvider *self) if(self->list_only) { self->devices = g_list_insert_sorted (self->devices, - gst_object_ref_sink (device), + gst_object_ref (device), compare_device_session_priority); } else { gst_object_ref (device); From 9b36e576cb67c138dd48292219cff6d75e24a413 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 10:51:23 +0200 Subject: [PATCH 0346/1014] alsa: handle NULL io It is possible that the port io is set to NULL when the node is negotiating or destroying. Fixes #4734 --- spa/plugins/alsa/alsa-pcm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 222a88d0b..667c788db 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3404,11 +3404,13 @@ static int playback_ready(struct state *state) { struct spa_io_buffers *io = state->io; - spa_log_trace_fp(state->log, "%p: %d", state, io->status); + spa_log_trace_fp(state->log, "%p: %d", state, io ? io->status : 0); update_sources(state, false); - io->status = SPA_STATUS_NEED_DATA; + if (io != NULL) + io->status = SPA_STATUS_NEED_DATA; + return spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); } From 94116901cef8c32507d84d0409d8cc7212942112 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Jun 2025 11:50:58 +0200 Subject: [PATCH 0347/1014] filter-graph: add an ffmpeg plugin Allows for using an ffmpeg filter-graph as a filter-graph node. --- meson.build | 2 + spa/meson.build | 2 + spa/plugins/filter-graph/ffmpeg_plugin.c | 375 +++++++++++++++++++++++ spa/plugins/filter-graph/meson.build | 10 + 4 files changed, 389 insertions(+) create mode 100644 spa/plugins/filter-graph/ffmpeg_plugin.c diff --git a/meson.build b/meson.build index 1a97d9d98..5b4ff095a 100644 --- a/meson.build +++ b/meson.build @@ -342,10 +342,12 @@ ffmpeg = get_option('ffmpeg') if pw_cat_ffmpeg.allowed() or ffmpeg.allowed() avcodec_dep = dependency('libavcodec', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) avformat_dep = dependency('libavformat', required: pw_cat_ffmpeg.enabled()) + avfilter_dep = dependency('libavfilter', required: ffmpeg.enabled()) avutil_dep = dependency('libavutil', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) swscale_dep = dependency('libswscale', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) else avcodec_dep = dependency('', required: false) + avfilter = dependency('', required: false) endif cdata.set('HAVE_PW_CAT_FFMPEG_INTEGRATION', pw_cat_ffmpeg.allowed()) diff --git a/spa/meson.build b/spa/meson.build index 27e8ff049..fd65269bb 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -144,6 +144,8 @@ if get_option('spa-plugins').allowed() ebur128_lib = dependency('libebur128', required: get_option('ebur128').enabled()) summary({'EBUR128': ebur128_lib.found()}, bool_yn: true, section: 'filter-graph') + summary({'ffmpeg': avfilter_dep.found()}, bool_yn: true, section: 'filter-graph') + cdata.set('HAVE_SPA_PLUGINS', true) subdir('plugins') endif diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/ffmpeg_plugin.c new file mode 100644 index 000000000..830cd20aa --- /dev/null +++ b/spa/plugins/filter-graph/ffmpeg_plugin.c @@ -0,0 +1,375 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "audio-plugin.h" + +struct plugin { + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_log *log; +}; + +struct descriptor { + struct spa_fga_descriptor desc; + struct plugin *p; + AVFilterGraph *filter_graph; + const AVFilter *format; + const AVFilter *buffersrc; + const AVFilter *buffersink; +}; + +struct instance { + struct descriptor *desc; + + AVFilterGraph *filter_graph; + AVFilterInOut *in; + AVFilterInOut *out; + + uint32_t rate; + + AVFilterContext *ctx[128]; + uint32_t n_ctx; + + float *data[128]; + AVFrame *frame; +}; + +static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, + unsigned long SampleRate, int index, const char *config) +{ + struct descriptor *d = (struct descriptor *)desc; + struct plugin *p = d->p; + struct instance *i; + AVFilterInOut *fp; + AVFilterContext *cnv, *ctx; + int res; + char options_str[1024]; + + i = calloc(1, sizeof(*i)); + if (i == NULL) + return NULL; + + i->desc = d; + i->rate = SampleRate; + + i->filter_graph = avfilter_graph_alloc(); + if (i->filter_graph == NULL) { + errno = ENOMEM; + return NULL; + } + + res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &i->in, &i->out); + if (res < 0) { + spa_log_error(p->log, "can parse filter graph %s", d->desc.name); + errno = EINVAL; + return NULL; + } + + for (fp = i->in; fp != NULL; fp = fp->next) { + ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersrc, "src"); + if (ctx == NULL) { + spa_log_error(p->log, "can't alloc buffersrc"); + return NULL; + } + snprintf(options_str, sizeof(options_str), + "sample_fmt=%s:sample_rate=%ld:channel_layout=mono", + av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate); + avfilter_init_str(ctx, options_str); + avfilter_link(ctx, 0, fp->filter_ctx, fp->pad_idx); + i->ctx[i->n_ctx++] = ctx; + } + for (fp = i->out; fp != NULL; fp = fp->next) { + + cnv = avfilter_graph_alloc_filter(i->filter_graph, d->format, "format"); + if (cnv == NULL) { + spa_log_error(p->log, "can't alloc format"); + return NULL; + } + + snprintf(options_str, sizeof(options_str), + "sample_fmts=%s:sample_rates=%ld:channel_layouts=mono", + av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate); + + avfilter_init_str(cnv, options_str); + avfilter_link(fp->filter_ctx, fp->pad_idx, cnv, 0); + + ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersink, "sink"); + if (ctx == NULL) { + spa_log_error(p->log, "can't alloc buffersink"); + return NULL; + } + avfilter_init_str(ctx, NULL); + avfilter_link(cnv, 0, ctx, 0); + i->ctx[i->n_ctx++] = ctx; + } + avfilter_graph_config(i->filter_graph, NULL); + + i->frame = av_frame_alloc(); + +#if 1 + char *dump = avfilter_graph_dump(i->filter_graph, NULL); + fprintf(stderr, "%s\n", dump); + free(dump); +#endif + return i; +} + +static void ffmpeg_cleanup(void *instance) +{ + struct instance *i = instance; + free(i); +} + +static void ffmpeg_free(const struct spa_fga_descriptor *desc) +{ + struct descriptor *d = (struct descriptor*)desc; + avfilter_graph_free(&d->filter_graph); + free(d); +} + +static void ffmpeg_connect_port(void *instance, unsigned long port, float *data) +{ + struct instance *i = instance; + i->data[port] = data; +} + +static void ffmpeg_run(void *instance, unsigned long SampleCount) +{ + struct instance *i = instance; + char buf[1024]; + int err; + + spa_log_debug(i->desc->p->log, "run %ld", SampleCount); + i->frame->nb_samples = SampleCount; + i->frame->sample_rate = i->rate; + i->frame->format = AV_SAMPLE_FMT_FLTP; + av_channel_layout_copy(&i->frame->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO); + + i->frame->data[0] = (uint8_t*)i->data[0]; + + if ((err = av_buffersrc_add_frame_flags(i->ctx[0], i->frame, + AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) { + av_strerror(err, buf, sizeof(buf)); + spa_log_error(i->desc->p->log, "can't add frame %s", buf); + av_frame_unref(i->frame); + return; + } + if ((err = av_buffersink_get_samples(i->ctx[1], i->frame, SampleCount)) >= 0) { + spa_log_trace(i->desc->p->log, "got frame %d %d %d %s", + i->frame->nb_samples, + i->frame->ch_layout.nb_channels, + i->frame->sample_rate, + av_get_sample_fmt_name(i->frame->format)); + + memcpy(i->data[1], i->frame->data[0], SampleCount * sizeof(float)); + + av_frame_unref(i->frame); + } +} + +static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, const char *name) +{ + struct plugin *p = (struct plugin *)plugin; + struct descriptor *desc; + uint32_t i; + AVFilterInOut *in = NULL, *out = NULL, *fp; + int res; + + spa_log_info(p->log, "%s", name); + + desc = calloc(1, sizeof(*desc)); + if (desc == NULL) + return NULL; + + desc->p = p; + desc->filter_graph = avfilter_graph_alloc(); + if (desc->filter_graph == NULL) { + errno = ENOMEM; + return NULL; + } + + res = avfilter_graph_parse2(desc->filter_graph, name, &in, &out); + if (res < 0) { + spa_log_error(p->log, "can parse filter graph %s", name); + errno = EINVAL; + return NULL; + } + + desc->desc.n_ports = 0; + for (fp = in; fp != NULL; fp = fp->next) { + spa_log_info(p->log, "%p: in %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); + desc->desc.n_ports++; + } + for (fp = out; fp != NULL; fp = fp->next) { + spa_log_info(p->log, "%p: out %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); + desc->desc.n_ports++; + } + + + desc->desc.instantiate = ffmpeg_instantiate; + desc->desc.cleanup = ffmpeg_cleanup; + desc->desc.free = ffmpeg_free; + desc->desc.connect_port = ffmpeg_connect_port; + desc->desc.run = ffmpeg_run; + + desc->desc.name = strdup(name); + desc->desc.flags = 0; + + desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); + + for (i = 0, fp = in; fp != NULL; i++, fp = fp->next) { + desc->desc.ports[i].index = i; + desc->desc.ports[i].name = fp->name; + desc->desc.ports[i].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; + } + for (fp = out; fp != NULL; i++, fp = fp->next) { + desc->desc.ports[i].index = i; + desc->desc.ports[i].name = fp->name; + desc->desc.ports[i].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; + } + desc->buffersrc = avfilter_get_by_name("abuffer"); + desc->buffersink = avfilter_get_by_name("abuffersink"); + desc->format = avfilter_get_by_name("aformat"); + + return &desc->desc; +} + +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = ffmpeg_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct plugin *impl = (struct plugin *)handle; + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct plugin); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct plugin *impl; + uint32_t i; + int res; + const char *path = NULL; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "filter.graph.path")) + path = s; + } + if (path == NULL) + return -EINVAL; + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static struct spa_handle_factory spa_fga_plugin_ffmpeg_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.ffmpeg", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_fga_plugin_ffmpeg_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build index c346a9649..1968d8a48 100644 --- a/spa/plugins/filter-graph/meson.build +++ b/spa/plugins/filter-graph/meson.build @@ -115,3 +115,13 @@ spa_filter_graph_plugin_ebur128 = shared_library('spa-filter-graph-plugin-ebur12 ) endif +if avfilter_dep.found() +spa_filter_graph_plugin_ffmpeg = shared_library('spa-filter-graph-plugin-ffmpeg', + [ 'ffmpeg_plugin.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, avfilter_dep, avutil_dep] +) +endif + From 2f51b9a5d93c8b1532199a2d3a8d932fb7c8a3ec Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Jun 2025 17:03:20 +0200 Subject: [PATCH 0348/1014] filter-graph: add multiple in/out support for avfilter Make one buffersrc for each input and configure it to mono. Try to guess the channel position from the port name or use the config option and fall back to FC (MONO) if unspecified. Make one buffersink for each output and place a format converter in front of it. Configure the converter to produce 1 channel with a layout guessed from the port name or from the config. With this we can use channelsplit and amerge to create multichannel streams for avfilter plugins. --- spa/plugins/filter-graph/ffmpeg_plugin.c | 163 ++++++++++++++++------- 1 file changed, 114 insertions(+), 49 deletions(-) diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/ffmpeg_plugin.c index 830cd20aa..bdf6033cb 100644 --- a/spa/plugins/filter-graph/ffmpeg_plugin.c +++ b/spa/plugins/filter-graph/ffmpeg_plugin.c @@ -7,11 +7,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -21,6 +23,9 @@ #include "audio-plugin.h" +#define MAX_PORTS 256 +#define MAX_CTX 64 + struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; @@ -31,6 +36,7 @@ struct plugin { struct descriptor { struct spa_fga_descriptor desc; struct plugin *p; + AVFilterGraph *filter_graph; const AVFilter *format; const AVFilter *buffersrc; @@ -41,16 +47,17 @@ struct instance { struct descriptor *desc; AVFilterGraph *filter_graph; - AVFilterInOut *in; - AVFilterInOut *out; uint32_t rate; - AVFilterContext *ctx[128]; - uint32_t n_ctx; - - float *data[128]; AVFrame *frame; + AVFilterContext *ctx[MAX_CTX]; + AVChannelLayout layout[MAX_CTX]; + uint32_t n_ctx; + uint32_t n_src; + uint32_t n_sink; + + float *data[MAX_PORTS]; }; static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, @@ -59,10 +66,13 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc struct descriptor *d = (struct descriptor *)desc; struct plugin *p = d->p; struct instance *i; - AVFilterInOut *fp; + AVFilterInOut *fp, *in, *out; AVFilterContext *cnv, *ctx; int res; + char channel[512]; char options_str[1024]; + uint32_t n_fp; + const char *chan; i = calloc(1, sizeof(*i)); if (i == NULL) @@ -77,38 +87,74 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc return NULL; } - res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &i->in, &i->out); + res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &in, &out); if (res < 0) { spa_log_error(p->log, "can parse filter graph %s", d->desc.name); errno = EINVAL; return NULL; } - for (fp = i->in; fp != NULL; fp = fp->next) { + for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersrc, "src"); if (ctx == NULL) { spa_log_error(p->log, "can't alloc buffersrc"); return NULL; } + if ((chan = strrchr(fp->name, '_')) != NULL) + chan++; + + if (config == NULL || + spa_json_str_object_find(config, strlen(config), + fp->name, channel, sizeof(channel)) < 0) + snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC"); + + if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE || + i->layout[n_fp].nb_channels != 1) { + spa_log_warn(p->log, "invalid channels %s, using MONO", channel); + av_channel_layout_from_string(&i->layout[n_fp], "FC"); + } + + av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel)); + snprintf(options_str, sizeof(options_str), - "sample_fmt=%s:sample_rate=%ld:channel_layout=mono", - av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate); + "sample_fmt=%s:sample_rate=%ld:channel_layout=%s", + av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel); + + spa_log_info(p->log, "%d buffersrc %s", n_fp, options_str); avfilter_init_str(ctx, options_str); avfilter_link(ctx, 0, fp->filter_ctx, fp->pad_idx); - i->ctx[i->n_ctx++] = ctx; - } - for (fp = i->out; fp != NULL; fp = fp->next) { + i->ctx[n_fp] = ctx; + } + i->n_src = n_fp; + for (fp = out; fp != NULL; fp = fp->next, n_fp++) { cnv = avfilter_graph_alloc_filter(i->filter_graph, d->format, "format"); if (cnv == NULL) { spa_log_error(p->log, "can't alloc format"); return NULL; } - snprintf(options_str, sizeof(options_str), - "sample_fmts=%s:sample_rates=%ld:channel_layouts=mono", - av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate); + if ((chan = strrchr(fp->name, '_')) != NULL) + chan++; + if (config == NULL || + spa_json_str_object_find(config, strlen(config), + fp->name, channel, sizeof(channel)) < 0) + snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC"); + + if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE || + i->layout[n_fp].nb_channels != 1) { + spa_log_warn(p->log, "invalid channels %s, using MONO", channel); + av_channel_layout_from_string(&i->layout[n_fp], "FC"); + } + + av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel)); + + snprintf(options_str, sizeof(options_str), + "sample_fmts=%s:sample_rates=%ld:channel_layouts=%s", + av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel); + + spa_log_info(p->log, "%d format %s", n_fp, options_str); avfilter_init_str(cnv, options_str); avfilter_link(fp->filter_ctx, fp->pad_idx, cnv, 0); @@ -119,15 +165,17 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc } avfilter_init_str(ctx, NULL); avfilter_link(cnv, 0, ctx, 0); - i->ctx[i->n_ctx++] = ctx; + i->ctx[n_fp] = ctx; } + i->n_sink = n_fp; + avfilter_graph_config(i->filter_graph, NULL); i->frame = av_frame_alloc(); -#if 1 +#if 0 char *dump = avfilter_graph_dump(i->filter_graph, NULL); - fprintf(stderr, "%s\n", dump); + spa_log_debug(p->log, "%s", dump); free(dump); #endif return i; @@ -136,6 +184,8 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc static void ffmpeg_cleanup(void *instance) { struct instance *i = instance; + avfilter_graph_free(&i->filter_graph); + av_frame_free(&i->frame); free(i); } @@ -156,31 +206,43 @@ static void ffmpeg_run(void *instance, unsigned long SampleCount) { struct instance *i = instance; char buf[1024]; - int err; + int err, j; + uint32_t c, d = 0; - spa_log_debug(i->desc->p->log, "run %ld", SampleCount); - i->frame->nb_samples = SampleCount; - i->frame->sample_rate = i->rate; - i->frame->format = AV_SAMPLE_FMT_FLTP; - av_channel_layout_copy(&i->frame->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO); + spa_log_trace(i->desc->p->log, "run %ld", SampleCount); - i->frame->data[0] = (uint8_t*)i->data[0]; + for (c = 0; c < i->n_src; c++) { + i->frame->nb_samples = SampleCount; + i->frame->sample_rate = i->rate; + i->frame->format = AV_SAMPLE_FMT_FLTP; + av_channel_layout_copy(&i->frame->ch_layout, &i->layout[c]); - if ((err = av_buffersrc_add_frame_flags(i->ctx[0], i->frame, - AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) { - av_strerror(err, buf, sizeof(buf)); - spa_log_error(i->desc->p->log, "can't add frame %s", buf); - av_frame_unref(i->frame); - return; + for (j = 0; j < i->layout[c].nb_channels; j++) + i->frame->data[j] = (uint8_t*)i->data[d++]; + + if ((err = av_buffersrc_add_frame_flags(i->ctx[c], i->frame, + AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) { + av_strerror(err, buf, sizeof(buf)); + spa_log_error(i->desc->p->log, "can't add frame: %s", buf); + av_frame_unref(i->frame); + continue; + } } - if ((err = av_buffersink_get_samples(i->ctx[1], i->frame, SampleCount)) >= 0) { + for (; c < i->n_sink; c++) { + if ((err = av_buffersink_get_samples(i->ctx[c], i->frame, SampleCount)) < 0) { + av_strerror(err, buf, sizeof(buf)); + spa_log_error(i->desc->p->log, "can't get frame: %s", buf); + continue; + } + spa_log_trace(i->desc->p->log, "got frame %d %d %d %s", i->frame->nb_samples, i->frame->ch_layout.nb_channels, i->frame->sample_rate, av_get_sample_fmt_name(i->frame->format)); - memcpy(i->data[1], i->frame->data[0], SampleCount * sizeof(float)); + for (j = 0; j < i->layout[c].nb_channels; j++) + memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float)); av_frame_unref(i->frame); } @@ -190,7 +252,7 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co { struct plugin *p = (struct plugin *)plugin; struct descriptor *desc; - uint32_t i; + uint32_t n_fp; AVFilterInOut *in = NULL, *out = NULL, *fp; int res; @@ -215,15 +277,20 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co } desc->desc.n_ports = 0; - for (fp = in; fp != NULL; fp = fp->next) { + for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { spa_log_info(p->log, "%p: in %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); desc->desc.n_ports++; } - for (fp = out; fp != NULL; fp = fp->next) { + for (fp = out; fp != NULL; fp = fp->next, n_fp++) { spa_log_info(p->log, "%p: out %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); desc->desc.n_ports++; } - + if (desc->desc.n_ports > MAX_CTX) { + spa_log_error(p->log, "%p: too many ports %d > %d", desc, + desc->desc.n_ports, MAX_CTX); + errno = ENOSPC; + return NULL; + } desc->desc.instantiate = ffmpeg_instantiate; desc->desc.cleanup = ffmpeg_cleanup; @@ -236,15 +303,15 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); - for (i = 0, fp = in; fp != NULL; i++, fp = fp->next) { - desc->desc.ports[i].index = i; - desc->desc.ports[i].name = fp->name; - desc->desc.ports[i].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; + for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { + desc->desc.ports[n_fp].index = n_fp; + desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name); + desc->desc.ports[n_fp].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; } - for (fp = out; fp != NULL; i++, fp = fp->next) { - desc->desc.ports[i].index = i; - desc->desc.ports[i].name = fp->name; - desc->desc.ports[i].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; + for (fp = out; fp != NULL; fp = fp->next, n_fp++) { + desc->desc.ports[n_fp].index = n_fp; + desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name); + desc->desc.ports[n_fp].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; } desc->buffersrc = avfilter_get_by_name("abuffer"); desc->buffersink = avfilter_get_by_name("abuffersink"); @@ -277,7 +344,6 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { - struct plugin *impl = (struct plugin *)handle; return 0; } @@ -297,7 +363,6 @@ impl_init(const struct spa_handle_factory *factory, { struct plugin *impl; uint32_t i; - int res; const char *path = NULL; handle->get_interface = impl_get_interface; From 41cafb4d2f59b9b9d4b1d92fe1701fe491b66647 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 9 Jun 2025 18:22:06 +0200 Subject: [PATCH 0349/1014] filter-graph: add avfilter multichannel support Use the port name extension to set the channel layout on the ports. Add latency measurement and reporting. --- spa/plugins/filter-graph/ffmpeg_plugin.c | 140 ++++++++++++++--------- 1 file changed, 84 insertions(+), 56 deletions(-) diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/ffmpeg_plugin.c index bdf6033cb..fb02e00c9 100644 --- a/spa/plugins/filter-graph/ffmpeg_plugin.c +++ b/spa/plugins/filter-graph/ffmpeg_plugin.c @@ -41,6 +41,9 @@ struct descriptor { const AVFilter *format; const AVFilter *buffersrc; const AVFilter *buffersink; + + AVChannelLayout layout[MAX_CTX]; + uint32_t latency_idx; }; struct instance { @@ -52,14 +55,27 @@ struct instance { AVFrame *frame; AVFilterContext *ctx[MAX_CTX]; - AVChannelLayout layout[MAX_CTX]; uint32_t n_ctx; uint32_t n_src; uint32_t n_sink; + uint64_t frame_num; float *data[MAX_PORTS]; }; +static void layout_from_name(AVChannelLayout *layout, const char *name) +{ + const char *chan; + + if ((chan = strrchr(name, '_')) != NULL) + chan++; + else + chan = "FC"; + + if (av_channel_layout_from_string(layout, chan) < 0) + av_channel_layout_from_string(layout, "FC"); +} + static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { @@ -72,7 +88,6 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc char channel[512]; char options_str[1024]; uint32_t n_fp; - const char *chan; i = calloc(1, sizeof(*i)); if (i == NULL) @@ -100,21 +115,7 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc spa_log_error(p->log, "can't alloc buffersrc"); return NULL; } - if ((chan = strrchr(fp->name, '_')) != NULL) - chan++; - - if (config == NULL || - spa_json_str_object_find(config, strlen(config), - fp->name, channel, sizeof(channel)) < 0) - snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC"); - - if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE || - i->layout[n_fp].nb_channels != 1) { - spa_log_warn(p->log, "invalid channels %s, using MONO", channel); - av_channel_layout_from_string(&i->layout[n_fp], "FC"); - } - - av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel)); + av_channel_layout_describe(&d->layout[n_fp], channel, sizeof(channel)); snprintf(options_str, sizeof(options_str), "sample_fmt=%s:sample_rate=%ld:channel_layout=%s", @@ -134,21 +135,7 @@ static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struc return NULL; } - if ((chan = strrchr(fp->name, '_')) != NULL) - chan++; - - if (config == NULL || - spa_json_str_object_find(config, strlen(config), - fp->name, channel, sizeof(channel)) < 0) - snprintf(channel, sizeof(channel), "%s", chan ? chan : "FC"); - - if (av_channel_layout_from_string(&i->layout[n_fp], channel) == AV_CHAN_NONE || - i->layout[n_fp].nb_channels != 1) { - spa_log_warn(p->log, "invalid channels %s, using MONO", channel); - av_channel_layout_from_string(&i->layout[n_fp], "FC"); - } - - av_channel_layout_describe(&i->layout[n_fp], channel, sizeof(channel)); + av_channel_layout_describe(&d->layout[n_fp], channel, sizeof(channel)); snprintf(options_str, sizeof(options_str), "sample_fmts=%s:sample_rates=%ld:channel_layouts=%s", @@ -205,9 +192,11 @@ static void ffmpeg_connect_port(void *instance, unsigned long port, float *data) static void ffmpeg_run(void *instance, unsigned long SampleCount) { struct instance *i = instance; + struct descriptor *desc = i->desc; char buf[1024]; int err, j; uint32_t c, d = 0; + float delay; spa_log_trace(i->desc->p->log, "run %ld", SampleCount); @@ -215,46 +204,56 @@ static void ffmpeg_run(void *instance, unsigned long SampleCount) i->frame->nb_samples = SampleCount; i->frame->sample_rate = i->rate; i->frame->format = AV_SAMPLE_FMT_FLTP; - av_channel_layout_copy(&i->frame->ch_layout, &i->layout[c]); + i->frame->pts = i->frame_num; - for (j = 0; j < i->layout[c].nb_channels; j++) + av_channel_layout_copy(&i->frame->ch_layout, &desc->layout[c]); + + for (j = 0; j < desc->layout[c].nb_channels; j++) i->frame->data[j] = (uint8_t*)i->data[d++]; if ((err = av_buffersrc_add_frame_flags(i->ctx[c], i->frame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) { av_strerror(err, buf, sizeof(buf)); - spa_log_error(i->desc->p->log, "can't add frame: %s", buf); + spa_log_warn(i->desc->p->log, "can't add frame: %s", buf); av_frame_unref(i->frame); continue; } } + delay = 0.0f; for (; c < i->n_sink; c++) { if ((err = av_buffersink_get_samples(i->ctx[c], i->frame, SampleCount)) < 0) { av_strerror(err, buf, sizeof(buf)); - spa_log_error(i->desc->p->log, "can't get frame: %s", buf); + spa_log_debug(i->desc->p->log, "can't get frame: %s", buf); + for (j = 0; j < desc->layout[c].nb_channels; j++) + memset(i->data[d++], 0, SampleCount * sizeof(float)); continue; } + delay = fmaxf(delay, i->frame_num - i->frame->pts); - spa_log_trace(i->desc->p->log, "got frame %d %d %d %s", + spa_log_trace(i->desc->p->log, "got frame %d %d %d %s %f", i->frame->nb_samples, i->frame->ch_layout.nb_channels, i->frame->sample_rate, - av_get_sample_fmt_name(i->frame->format)); + av_get_sample_fmt_name(i->frame->format), + delay); - for (j = 0; j < i->layout[c].nb_channels; j++) + for (j = 0; j < desc->layout[c].nb_channels; j++) memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float)); av_frame_unref(i->frame); } + i->frame_num += SampleCount; + if (i->data[desc->latency_idx] != NULL) + i->data[desc->latency_idx][0] = delay; } static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, const char *name) { struct plugin *p = (struct plugin *)plugin; struct descriptor *desc; - uint32_t n_fp; + uint32_t n_fp, n_p; AVFilterInOut *in = NULL, *out = NULL, *fp; - int res; + int res, j; spa_log_info(p->log, "%s", name); @@ -277,17 +276,30 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co } desc->desc.n_ports = 0; - for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { - spa_log_info(p->log, "%p: in %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); - desc->desc.n_ports++; + for (n_fp = 0, fp = in; fp != NULL && n_fp < MAX_CTX; fp = fp->next, n_fp++) { + layout_from_name(&desc->layout[n_fp], fp->name); + spa_log_info(p->log, "%p: in %s %p:%d channels:%d", fp, fp->name, + fp->filter_ctx, fp->pad_idx, desc->layout[n_fp].nb_channels); + desc->desc.n_ports += desc->layout[n_fp].nb_channels; } - for (fp = out; fp != NULL; fp = fp->next, n_fp++) { - spa_log_info(p->log, "%p: out %s %p:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx); - desc->desc.n_ports++; + for (fp = out; fp != NULL && n_fp < MAX_CTX; fp = fp->next, n_fp++) { + layout_from_name(&desc->layout[n_fp], fp->name); + spa_log_info(p->log, "%p: out %s %p:%d channels:%d", fp, fp->name, + fp->filter_ctx, fp->pad_idx, desc->layout[n_fp].nb_channels); + desc->desc.n_ports += desc->layout[n_fp].nb_channels; } - if (desc->desc.n_ports > MAX_CTX) { + /* one for the latency */ + desc->desc.n_ports++; + + if (n_fp >= MAX_CTX) { + spa_log_error(p->log, "%p: too many in/out ports %d > %d", desc, + n_fp, MAX_CTX); + errno = ENOSPC; + return NULL; + } + if (desc->desc.n_ports >= MAX_PORTS) { spa_log_error(p->log, "%p: too many ports %d > %d", desc, - desc->desc.n_ports, MAX_CTX); + desc->desc.n_ports, MAX_PORTS); errno = ENOSPC; return NULL; } @@ -303,16 +315,32 @@ static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, co desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); - for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { - desc->desc.ports[n_fp].index = n_fp; - desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name); - desc->desc.ports[n_fp].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; + for (n_fp = 0, n_p = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { + for (j = 0; j < desc->layout[n_fp].nb_channels; j++, n_p++) { + desc->desc.ports[n_p].index = n_p; + if (desc->layout[n_fp].nb_channels == 1) + desc->desc.ports[n_p].name = spa_aprintf("%s", fp->name); + else + desc->desc.ports[n_p].name = spa_aprintf("%s_%d", fp->name, j); + desc->desc.ports[n_p].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; + } } for (fp = out; fp != NULL; fp = fp->next, n_fp++) { - desc->desc.ports[n_fp].index = n_fp; - desc->desc.ports[n_fp].name = spa_aprintf("%s", fp->name); - desc->desc.ports[n_fp].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; + for (j = 0; j < desc->layout[n_fp].nb_channels; j++, n_p++) { + desc->desc.ports[n_p].index = n_p; + if (desc->layout[n_fp].nb_channels == 1) + desc->desc.ports[n_p].name = spa_aprintf("%s", fp->name); + else + desc->desc.ports[n_p].name = spa_aprintf("%s_%d", fp->name, j); + desc->desc.ports[n_p].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; + } } + desc->desc.ports[n_p].index = n_p; + desc->desc.ports[n_p].name = strdup("latency"); + desc->desc.ports[n_p].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL; + desc->desc.ports[n_p].hint = SPA_FGA_HINT_LATENCY; + desc->latency_idx = n_p++; + desc->buffersrc = avfilter_get_by_name("abuffer"); desc->buffersink = avfilter_get_by_name("abuffersink"); desc->format = avfilter_get_by_name("aformat"); From 8c685374460954701edc876cfb31329a7904dceb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 10:34:01 +0200 Subject: [PATCH 0350/1014] filter-graph: clean up descriptor memory --- spa/plugins/filter-graph/ffmpeg_plugin.c | 5 +++++ spa/plugins/filter-graph/lv2_plugin.c | 3 +++ 2 files changed, 8 insertions(+) diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/ffmpeg_plugin.c index fb02e00c9..ee2b81f4b 100644 --- a/spa/plugins/filter-graph/ffmpeg_plugin.c +++ b/spa/plugins/filter-graph/ffmpeg_plugin.c @@ -179,7 +179,12 @@ static void ffmpeg_cleanup(void *instance) static void ffmpeg_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; + uint32_t i; avfilter_graph_free(&d->filter_graph); + for (i = 0; i < d->desc.n_ports; i++) + free((void*)d->desc.ports[i].name); + free((char*)d->desc.name); + free(d->desc.ports); free(d); } diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 7628d7650..3928b236c 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -486,6 +486,9 @@ static void lv2_run(void *instance, unsigned long SampleCount) static void lv2_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; + uint32_t i; + for (i = 0; i < d->desc.n_ports; i++) + free((void*)d->desc.ports[i].name); free((char*)d->desc.name); free(d->desc.ports); free(d); From a78e97a53b5555aa53fb10c00de92a5261b9e4b9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 10:34:23 +0200 Subject: [PATCH 0351/1014] filter-chain: document ffmpeg plugin --- spa/plugins/filter-graph/ffmpeg_plugin.c | 2 +- src/modules/module-filter-chain.c | 170 ++++++++++++++++++----- 2 files changed, 136 insertions(+), 36 deletions(-) diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/ffmpeg_plugin.c index ee2b81f4b..ba9c525d5 100644 --- a/spa/plugins/filter-graph/ffmpeg_plugin.c +++ b/spa/plugins/filter-graph/ffmpeg_plugin.c @@ -411,7 +411,7 @@ impl_init(const struct spa_handle_factory *factory, if (spa_streq(k, "filter.graph.path")) path = s; } - if (path == NULL) + if (!spa_streq(path, "filtergraph")) return -EINVAL; impl->plugin.iface = SPA_INTERFACE_INIT( diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 19488d914..df30bd228 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -32,8 +32,8 @@ extern struct spa_handle_factory spa_filter_graph_factory; * \page page_module_filter_chain Filter-Chain * * The filter-chain allows you to create an arbitrary processing graph - * from LADSPA, LV2 and builtin filters. This filter can be made into a - * virtual sink/source or between any 2 nodes in the graph. + * from LADSPA, LV2, sofa, ffmpeg and builtin filters. This filter can be + * made into a virtual sink/source or between any 2 nodes in the graph. * * The filter chain is built with 2 streams, a capture stream providing * the input to the filter chain and a playback stream sending out the @@ -95,7 +95,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * Nodes describe the processing filters in the graph. Use a tool like lv2ls * or listplugins to get a list of available plugins, labels and the port names. * - * - `type` is one of `ladspa`, `lv2`, `builtin`, `sofa` or `ebur128`. + * - `type` is one of `ladspa`, `lv2`, `builtin`, `sofa`, `ebur128` of `ffmpeg`. * - `name` is the name for this node, you might need this later to refer to this node * and its ports when setting controls or making links. * - `plugin` is the type specific plugin name. @@ -103,10 +103,12 @@ extern struct spa_handle_factory spa_filter_graph_factory; * name in the LADSPA plugin path. * - For LV2, this is the plugin URI obtained with lv2ls. * - For builtin, sofa and ebur128 this is ignored + * - For ffmpeg this should be filtergraph * - `label` is the type specific filter inside the plugin. * - For LADSPA this is the label * - For LV2 this is unused * - For builtin, sofa and ebur128 this is the name of the filter to use + * - For ffmpeg this is an FFMpeg filtergraph description * * - `config` contains a filter specific configuration section. Some plugins need * this. (convolver, sofa, delay, ...) @@ -209,8 +211,8 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * ## Builtin filters * - * There are some useful builtin filters available. You select them with the label - * of the filter node. + * There are some useful builtin filters available. The type should be `builtin` and + * you select the specific builtin filter with the `label` of the filter node. * * ### Mixer * @@ -561,9 +563,52 @@ extern struct spa_handle_factory spa_filter_graph_factory; * a volume ramp up or down. For more a more coarse volume ramp, the "Current" value * can be used in the `linear` plugin. * - * ## SOFA filter + * ### Debug * - * There is an optional builtin SOFA filter available. + * The `debug` plugin can be used to debug the audio and control data of other plugins. + * + * It has an "In" input port and an "Out" output data ports. The data from "In" will + * be copied to "Out" and the data will be dumped into the INFO log. + * + * There is also a "Control" input port and an "Notify" output control ports. The + * control from "Control" will be copied to "Notify" and the control value will be + * dumped into the INFO log. + * + * ### Pipe + * + * The `pipe` plugin can be used to filter the audio with another application using pipes + * for sending and receiving the raw audio. + * + * The application needs to consume raw float32 samples from stdin and produce filtered + * float32 samples on stdout. + * + * It has an "In" input port and an "Out" output data ports. + * + * The node requires a `config` section with extra configuration: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = builtin + * name = ... + * label = pipe + * config = { + * command = "ffmpeg -f f32le -ac 1 -ar 48000 -blocksize 1024 -fflags nobuffer -i \"pipe:\" \"-filter:a\" \"loudnorm=I=-18:TP=-3:LRA=4\" -f f32le -ac 1 -ar 48000 \"pipe:\"" + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * - `command` the command to execute. It should consume samples from stdin and produce + * samples on stdout. + * + * ## SOFA filters + * + * There is an optional `sofa` type available (when compiled with `libmysofa`). * * ### Spatializer * @@ -616,13 +661,15 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `Radius` controls how far away the signal is as a value between 0 and 100. * default is 1.0. * - * ## EBUR128 filter + * ## EBUR128 filters * - * There is an optional EBU R128 filter available. + * There is an optional EBU R128 plugin available (when compiled with + * `libebur128`) selected with the `ebur128` type. Filters in the plugin + * can be selected with the `label` field. * * ### ebur128 * - * The ebur128 plugin can be used to measure the loudness of a signal. + * The ebur128 filter can be used to measure the loudness of a signal. * * It has 7 input ports "In FL", "In FR", "In FC", "In UNUSED", "In SL", "In SR" * and "In DUAL MONO", corresponding to the different input channels for EBUR128. @@ -686,55 +733,108 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * ### lufs2gain * - * The lufs2gain plugin can be used to convert LUFS control values to gain. It needs + * The lufs2gain filter can be used to convert LUFS control values to gain. It needs * a target LUFS control input to drive the conversion. * * It has 2 input control ports "LUFS" and "Target LUFS" and will produce 1 output * control value "Gain". This gain can be used as input for the builtin `linear` - * node, for example, to adust the gain. + * filter, for example, to adust the gain. * - * ### debug * - * The debug plugin can be used to debug the audio and control data of other plugins. + * ## FFmpeg * - * It has an "In" input port and an "Out" output data ports. The data from "In" will - * be copied to "Out" and the data will be dumped into the INFO log. + * There is an optional FFmpeg filter available (when compiled with `libavfilter`) + * that can be selected with the `ffmpeg` type. Use the `plugin` field to select + * the plugin to use. * - * There is also a "Control" input port and an "Notify" output control ports. The - * control from "Control" will be copied to "Notify" and the control value will be - * dumped into the INFO log. + * ### Filtergraph * - * ### pipe + * The filtergraph FFmpeg plugin is selected with the `filtergraph` plugin + * field in the node. * - * The pipe plugin can be used to filter the audio with another application using pipes - * for sending and receiving the raw audio. + * The filtergraph filter allows you to specify an set of audio filters using + * the FFmpeg filtergraph syntax (https://ffmpeg.org/ffmpeg-filters.html). * - * The application needs to consume raw float32 samples from stdin and produce filtered - * float32 samples on stdout. + * The `label` field should be used to describe the filtergraph in use. * - * It has an "In" input port and an "Out" output data ports. + * FFmpeg filtergraph input and output ports can have multiple channels. The + * filter-chain can split those into individual ports to use as input and output + * ports. For this, the ports in the filtergraph need to have a specific name + * convention, either `_` or `_`. * - * The node requires a `config` section with extra configuration: + * When a single channel is specified, the port can be referenced in inputs and + * outputs sections with `:_`. When a channel-layout + * is specified, each port name gets a `_` appended, starting from 0 and + * counting up for each channel in the layout. + * + * The `filtergraph` plugin will automatically add format converters when the input + * port channel-layout, format or graph sample-rates don't match. + * + * Note that the FFmpeg filtergraph is not Real-time safe because it might does + * allocations from the processing thread. It is advised to run the filter-chain + * streams in async mode (`node.async = true`) to avoid interrupting the other + * RT threads. + * + * Some examples: + * + * The stereo ports are split into their channels with the `_0` and `_1` suffixes. * *\code{.unparsed} * filter.graph = { * nodes = [ * { - * type = builtin - * name = ... - * label = pipe - * config = { - * command = "ffmpeg -f f32le -ac 1 -ar 48000 -blocksize 1024 -fflags nobuffer -i \"pipe:\" \"-filter:a\" \"loudnorm=I=-18:TP=-3:LRA=4\" -f f32le -ac 1 -ar 48000 \"pipe:\"" - * } - * ... + * type = ffmpeg + * plugin = filtergraph + * name = filter + * label = "[in_stereo]loudnorm=I=-18:TP=-3:LRA=4[out_stereo]" * } * } + * inputs = [ "filter:in_stereo_0" "filter:in_stereo_1" ] + * outputs = [ "filter:out_stereo_0" "filter:out_stereo_1" ] * ... * } *\endcode * - * - `command` the command to execute. It should consume samples from stdin and produce - * samples on stdout. + * It is possible to have multiple input and output ports for the filtergraphs. + * In the next example, the ports have a single channel name and so don't have + * the `_0` suffix to identify them. This can be simplified by removing the `amerge` + * and `channelsplit` filters and using the `_stereo` suffix on port names to let + * PipeWire do the splitting and merging more efficiently. + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = ffmpeg + * plugin = filtergraph + * name = filter + * label = "[in_FL][in_FR]amerge,extrastereo,channelsplit[out_FL][out_FR]" + * } + * } + * inputs = [ "filter:in_FL" "filter:in_FR" ] + * outputs = [ "filter:out_FL" "filter:out_FR" ] + * ... + * } + *\endcode + * + * Here is a last example of a surround sound upmixer: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = ffmpeg + * plugin = filtergraph + * name = filter + * label = "[in_stereo]surround[out_5.1]" + * } + * } + * inputs = [ "filter:in_FL" "filter:in_FR" ] + * outputs = [ "filter:out_5.1_0" "filter:out_5.1_1" "filter:out_5.1_2" + * "filter:out_5.1_3" "filter:out_5.1_4" "filter:out_5.1_5" ] + * ... + * } + *\endcode * * ## General options * From feba1fd260152697ab182cc5feada5ccacd33ac5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 11:23:15 +0200 Subject: [PATCH 0352/1014] meson: fix avfilter dependency --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b4ff095a..2331ac826 100644 --- a/meson.build +++ b/meson.build @@ -347,7 +347,7 @@ if pw_cat_ffmpeg.allowed() or ffmpeg.allowed() swscale_dep = dependency('libswscale', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) else avcodec_dep = dependency('', required: false) - avfilter = dependency('', required: false) + avfilter_dep = dependency('', required: false) endif cdata.set('HAVE_PW_CAT_FFMPEG_INTEGRATION', pw_cat_ffmpeg.allowed()) From 8e32afb86363b3bd8f85ff7df665bc06a5a98e09 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 11:57:38 +0200 Subject: [PATCH 0353/1014] loop: don't call the hooks around blocking wait The hooks were previously used to unlock the loop but now that the lock is handled inside the loop itself and we don't unlock before the blocking read anymore, we should also not call the hooks. The blocking invoke function is not meant to be called with any of the loop context locks acquired in order to avoid a deadlock. Make this (and other blocking risks) clear in the documentation. See #4472 --- spa/include/spa/support/loop.h | 7 +++++-- spa/plugins/support/loop.c | 4 ---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index c6c6df9f7..18f04bc5d 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -119,8 +119,11 @@ struct spa_loop_methods { * an object that has identity. * \param size The size of data to copy. * \param block If \true, do not return until func has been called. Otherwise, - * returns immediately. Passing \true does not risk a deadlock because - * the data thread is never allowed to wait on any other thread. + * returns immediately. Passing \true can cause a deadlock when + * the calling thread is holding the loop context lock. A blocking + * invoke should never be done from a realtime thread. Also beware + * of blocking invokes between 2 threads as you can easily end up + * in a deadly embrace. * \param user_data An opaque pointer passed to func. * \return `-EPIPE` if the internal ring buffer filled up, * if block is \false, 0 if seq was SPA_ID_INVALID or diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index cf0005522..2f4559651 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -478,14 +478,10 @@ again: if (block && queue->ack_fd != -1) { uint64_t count = 1; - spa_loop_control_hook_before(&impl->hooks_list); - if ((res = spa_system_eventfd_read(impl->system, queue->ack_fd, &count)) < 0) spa_log_warn(impl->log, "%p: failed to read event fd:%d: %s", queue, queue->ack_fd, spa_strerror(res)); - spa_loop_control_hook_after(&impl->hooks_list); - res = item->res; } else { From 550ec8c2a4dba4ae1494e3ff584db7fa639d70b6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 12:01:20 +0200 Subject: [PATCH 0354/1014] loop: deprecate the hooks They were mostly useful for locking and unlocking but since that is builtin right now, we should remove those eventually. --- spa/include/spa/support/loop.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 18f04bc5d..561d86e93 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -191,15 +191,17 @@ SPA_API_LOOP int spa_loop_locked(struct spa_loop *object, /** Control hooks. These hooks can't be removed from their * callbacks and must be removed from a safe place (when the loop - * is not running or when it is locked). */ + * is not running or when it is locked). + * + * \deprecated This was used to lock and unlock the loop but because + * this is now standard behaviour, these extra hooks are not very + * useful anymore. */ struct spa_loop_control_hooks { #define SPA_VERSION_LOOP_CONTROL_HOOKS 0 uint32_t version; - /** Executed right before waiting for events. It is typically used to - * release locks. */ + /** Executed right before waiting for events. \deprecated */ void (*before) (void *data); - /** Executed right after waiting for events. It is typically used to - * reacquire locks. */ + /** Executed right after waiting for events. \deprecated */ void (*after) (void *data); }; From 88963e9a9c0c5b065bd9f4696eec4c389624f262 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 13:04:25 +0200 Subject: [PATCH 0355/1014] Revert "loop: deprecate the hooks" This reverts commit 550ec8c2a4dba4ae1494e3ff584db7fa639d70b6. Before hooks are still useful when integrating other fds in the pipewire main loop. --- spa/include/spa/support/loop.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 561d86e93..a1e6a9a3b 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -191,17 +191,15 @@ SPA_API_LOOP int spa_loop_locked(struct spa_loop *object, /** Control hooks. These hooks can't be removed from their * callbacks and must be removed from a safe place (when the loop - * is not running or when it is locked). - * - * \deprecated This was used to lock and unlock the loop but because - * this is now standard behaviour, these extra hooks are not very - * useful anymore. */ + * is not running or when it is locked). */ struct spa_loop_control_hooks { #define SPA_VERSION_LOOP_CONTROL_HOOKS 0 uint32_t version; - /** Executed right before waiting for events. \deprecated */ + /** Executed right before waiting for events. It is typically used to + * release locks or integrate other fds into the loop. */ void (*before) (void *data); - /** Executed right after waiting for events. \deprecated */ + /** Executed right after waiting for events. It is typically used to + * reacquire locks or integrate other fds into the loop. */ void (*after) (void *data); }; From ff3bc8818f2498bc0b27816846ccd21913b5ba3c Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Mon, 25 Mar 2024 12:32:29 +0100 Subject: [PATCH 0356/1014] module-combine-stream: update latency right before playback starts Combine stream selects the biggest latency from all output streams and sent the latency upstream. To select the biggest latency, each stream needs to have the sample rate and the quantum size set. The combine stream recalculates the latency in the latency changed callback or during data processing. Stream sets the sample rate and the quantum size in a copy_position call which is normally called during processing the output data or when state changes to streaming. Before this change, it wasn't guarantee the copy_position was called for each stream already and latency in the combine stream was selected from random stream. Signed-off-by: Martin Geier --- src/modules/module-combine-stream.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 4591e3e1f..137aafc2b 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -730,6 +730,9 @@ static void stream_state_changed(void *d, enum pw_stream_state old, case PW_STREAM_STATE_UNCONNECTED: stream_destroy(s); break; + case PW_STREAM_STATE_STREAMING: + update_latency(s->impl); + break; default: break; } From c2e539b780c8cc7518cc5b933e322f7f0f8e369b Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Thu, 17 Apr 2025 11:58:28 +0200 Subject: [PATCH 0357/1014] module-combine-stream: flush data in paused state When stream is paused, internal delay buffers were cleared, but some data could stay in stream output queue. Without a flush, these data where played in front of a new data. Patch was inspired by 64d6ff4184339fe4222b11ceaf9777a88523a7eb fixing the same issue in a filter-chain module. Signed-off-by: Martin Geier --- src/modules/module-combine-stream.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 137aafc2b..de63c4a6f 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -1073,6 +1073,7 @@ static void combine_state_changed(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; + struct stream *s; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: @@ -1080,6 +1081,10 @@ static void combine_state_changed(void *d, enum pw_stream_state old, break; case PW_STREAM_STATE_PAUSED: clear_delaybuf(impl); + spa_list_for_each(s, &impl->streams, link) { + pw_stream_flush(s->stream, false); + } + pw_stream_flush(impl->combine, false); impl->combine_id = pw_stream_get_node_id(impl->combine); pw_log_info("got combine id %d", impl->combine_id); break; From 9afa0cd2707a82490ca45d61d0a7cd54cf840b4c Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Tue, 16 Apr 2024 09:28:49 +0200 Subject: [PATCH 0358/1014] alsa-pcm: enable interrupts after alsa recovery Interrupts are disabled in alsa_irq_wakeup_event -> playback_ready method to not produce another wakeups when waiting for a new data. Interrupts are enabled again when a new data arrives in a method spa_alsa_write. In rare cases, when there is multiple streams providing data and one of them is disconnected, a new data fails to be delivered and the spa_alsa_write is not called. Not providing data produces underrun and alsa-pcm invokes recovery process. Recovery process starts a new playback, but without interrupts enabled is graph not triggered and new data are not delivered (to enable interrupts). Recovery process keeps running in loop. Now the interrupts are enabled again after the recovery and the starvation should not occur. Signed-off-by: Martin Geier --- spa/plugins/alsa/alsa-pcm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 667c788db..9bcf56053 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2601,6 +2601,7 @@ static inline int do_start(struct state *state) } static inline int check_position_config(struct state *state, bool starting); +static void update_sources(struct state *state, bool active); static int alsa_recover(struct state *state) { @@ -2680,6 +2681,8 @@ recover: if (follower != driver && follower->linked) do_start(follower); } + + update_sources(state, true); return 0; } From 4976ac7ef9189cf0cb33b81767426f33484051d4 Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Wed, 27 Nov 2024 16:23:24 +0100 Subject: [PATCH 0359/1014] alsa-pcm: add dsd bit order parameter Signed-off-by: Martin Geier --- spa/plugins/alsa/alsa-pcm.c | 4 +++- spa/plugins/alsa/alsa-pcm.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 9bcf56053..15ced4466 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -208,6 +208,8 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) state->htimestamp_max_errors = atoi(s); } else if (spa_streq(k, "api.alsa.auto-link")) { state->auto_link = spa_atob(s); + } else if (spa_streq(k, "api.alsa.dsd-lsb")) { + state->dsd_lsb = spa_atob(s); } else if (spa_streq(k, "latency.internal.rate")) { state->process_latency.rate = atoi(s); } else if (spa_streq(k, "latency.internal.ns")) { @@ -1918,7 +1920,7 @@ static int enum_dsd_formats(struct state *state, uint32_t index, uint32_t *next, 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_bitorder, 0); - spa_pod_builder_id(b, SPA_PARAM_BITORDER_msb); + spa_pod_builder_id(b, state->dsd_lsb ? SPA_PARAM_BITORDER_lsb : SPA_PARAM_BITORDER_msb); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_interleave, 0); spa_pod_builder_int(b, interleave); diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index cc0b107e4..6fb4a3403 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -228,6 +228,7 @@ struct state { unsigned int is_pro:1; unsigned int sources_added:1; unsigned int auto_link:1; + unsigned int dsd_lsb:1; unsigned int linked:1; unsigned int is_batch:1; unsigned int force_rate:1; From b6b61a956fed2963fb6836d2d760f282fbb20b9e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 15:45:04 +0200 Subject: [PATCH 0360/1014] modules: implement Latency according to docs Make one function to update the Latencies on all streams. We need to do this because if one of the process or latency params change, it influences the latency params on all streams. --- src/modules/module-filter-chain.c | 75 ++++++++--------- src/modules/module-loopback.c | 129 ++++++++++++++---------------- 2 files changed, 97 insertions(+), 107 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index df30bd228..102eaa827 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1138,52 +1138,43 @@ done: pw_stream_queue_buffer(impl->playback, out); } +static void update_latencies(struct impl *impl) +{ + struct spa_latency_info latency; + uint8_t buffer[1024]; + struct spa_pod_builder b; + const struct spa_pod *play_params[2], *capt_params[2]; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + latency = impl->latency[SPA_DIRECTION_INPUT]; + play_params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + capt_params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + latency = impl->latency[SPA_DIRECTION_OUTPUT]; + capt_params[1] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + play_params[1] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + if (impl->playback) + pw_stream_update_params(impl->playback, play_params, 2); + if (impl->capture) + pw_stream_update_params(impl->capture, capt_params, 2); +} + static void param_latency_changed(struct impl *impl, const struct spa_pod *param, enum spa_direction direction) { struct spa_latency_info latency; - uint8_t buffer[1024]; - struct spa_pod_builder b; - const struct spa_pod *params[1]; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; + if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) + return; impl->latency[latency.direction] = latency; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (latency.direction != direction) - spa_process_latency_info_add(&impl->process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - if (latency.direction == SPA_DIRECTION_INPUT) - pw_stream_update_params(impl->capture, params, 1); - else - pw_stream_update_params(impl->playback, params, 1); -} - -static void update_process_latency(struct impl *impl) -{ - uint8_t buffer[1024]; - struct spa_pod_builder b; - struct spa_latency_info latency; - const struct spa_pod *params[2]; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - latency = impl->latency[SPA_DIRECTION_INPUT]; - spa_process_latency_info_add(&impl->process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - if (impl->playback) - pw_stream_update_params(impl->playback, params, 1); - - latency = impl->latency[SPA_DIRECTION_OUTPUT]; - spa_process_latency_info_add(&impl->process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - params[1] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); - - if (impl->capture) - pw_stream_update_params(impl->capture, params, 2); + update_latencies(impl); } static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, @@ -1195,9 +1186,11 @@ static void param_process_latency_changed(struct impl *impl, const struct spa_po spa_zero(process_latency); else if (spa_process_latency_parse(param, &process_latency) < 0) return; + if (spa_process_latency_info_compare(&impl->process_latency, &process_latency) == 0) + return; impl->process_latency = process_latency; - update_process_latency(impl); + update_latencies(impl); } static void param_tag_changed(struct impl *impl, const struct spa_pod *param, @@ -1500,8 +1493,10 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) if (spa_streq(k, "latency")) { double latency; if (spa_atod(s, &latency)) { - impl->process_latency.rate = (int32_t)latency; - update_process_latency(impl); + if (impl->process_latency.rate != (int32_t)latency) { + impl->process_latency.rate = (int32_t)latency; + update_latencies(impl); + } } } } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index a9fa8c4d9..d65768f0a 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -290,7 +290,7 @@ struct impl { struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; - struct spa_process_latency_info process_latency[2]; + struct spa_process_latency_info process_latency; struct spa_latency_info latency[2]; unsigned int do_disconnect:1; @@ -441,70 +441,63 @@ static void playback_process(void *d) pw_stream_queue_buffer(impl->playback, out); } -static void build_latency_params(struct impl *impl, struct spa_pod_builder *b, - const struct spa_pod *params[], uint32_t max_params) -{ - struct spa_latency_info latency; - latency = impl->latency[0]; - spa_process_latency_info_add(&impl->process_latency[0], &latency); - params[0] = spa_latency_build(b, SPA_PARAM_Latency, &latency); - latency = impl->latency[1]; - spa_process_latency_info_add(&impl->process_latency[1], &latency); - params[1] = spa_latency_build(b, SPA_PARAM_Latency, &latency); -} - -static struct spa_pod *build_props(struct impl *impl, struct spa_pod_builder *b, - enum spa_direction direction) -{ - int64_t nsec = impl->process_latency[direction].ns; - - return spa_pod_builder_add_object(b, - SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, - SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); -} - -static void param_latency_changed(struct impl *impl, const struct spa_pod *param, - struct pw_stream *stream, struct pw_stream *other) +static void update_latencies(struct impl *impl, bool props, bool process) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *params[2]; + const struct spa_pod *play_params[4], *capt_params[4]; + uint32_t n_params = 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + latency = impl->latency[SPA_DIRECTION_INPUT]; + play_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + capt_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + latency = impl->latency[SPA_DIRECTION_OUTPUT]; + capt_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + play_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + if (props) { + int64_t nsec = impl->process_latency.ns; + play_params[n_params] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); + capt_params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); + } + if (process) { + play_params[n_params] = spa_process_latency_build(&b, + SPA_PARAM_ProcessLatency, &impl->process_latency); + capt_params[n_params++] = spa_process_latency_build(&b, + SPA_PARAM_ProcessLatency, &impl->process_latency); + } + + if (impl->playback) + pw_stream_update_params(impl->playback, play_params, n_params); + if (impl->capture) + pw_stream_update_params(impl->capture, capt_params, n_params); +} + + +static void param_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + struct spa_latency_info latency; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; + if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) + return; impl->latency[latency.direction] = latency; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - build_latency_params(impl, &b, params, 2); - - pw_stream_update_params(stream, params, 2); - pw_stream_update_params(other, params, 2); - - impl->recalc_delay = true; + update_latencies(impl, false, false); } -static void emit_process_latency_changed(struct impl *impl, - enum spa_direction direction, struct pw_stream *stream) -{ - uint8_t buffer[4096]; - struct spa_pod_builder b; - const struct spa_pod *params[4]; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[0] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, - &impl->process_latency[direction]); - if (stream == impl->capture) - params[1] = build_props(impl, &b, SPA_DIRECTION_INPUT); - else - params[1] = build_props(impl, &b, SPA_DIRECTION_OUTPUT); - build_latency_params(impl, &b, ¶ms[2], 2); - pw_stream_update_params(stream, params, 4); -} - -static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, - enum spa_direction direction, struct pw_stream *stream) +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_process_latency_info info; @@ -512,14 +505,14 @@ static void param_process_latency_changed(struct impl *impl, const struct spa_po spa_zero(info); else if (spa_process_latency_parse(param, &info) < 0) return; + if (spa_process_latency_info_compare(&impl->process_latency, &info) == 0) + return; - impl->process_latency[direction] = info; - - emit_process_latency_changed(impl, direction, stream); + impl->process_latency = info; + update_latencies(impl, true, false); } -static void param_props_changed(struct impl *impl, const struct spa_pod *param, - enum spa_direction direction, struct pw_stream *stream) +static void param_props_changed(struct impl *impl, const struct spa_pod *param) { int64_t nsec; @@ -530,8 +523,10 @@ static void param_props_changed(struct impl *impl, const struct spa_pod *param, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(&nsec)) < 0) return; - impl->process_latency[direction].ns = nsec; - emit_process_latency_changed(impl, direction, stream); + if (impl->process_latency.ns == nsec) + return; + impl->process_latency.ns = nsec; + update_latencies(impl, false, true); } static void param_tag_changed(struct impl *impl, const struct spa_pod *param, @@ -652,13 +647,13 @@ static void capture_param_changed(void *data, uint32_t id, const struct spa_pod param_format_changed(impl, param, impl->capture, true); break; case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->capture, impl->playback); + param_latency_changed(impl, param); break; case SPA_PARAM_Props: - param_props_changed(impl, param, SPA_DIRECTION_INPUT, impl->capture); + param_props_changed(impl, param); break; case SPA_PARAM_ProcessLatency: - param_process_latency_changed(impl, param, SPA_DIRECTION_INPUT, impl->capture); + param_process_latency_changed(impl, param); break; case SPA_PARAM_Tag: param_tag_changed(impl, param, impl->playback); @@ -703,13 +698,13 @@ static void playback_param_changed(void *data, uint32_t id, const struct spa_pod param_format_changed(impl, param, impl->playback, false); break; case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->playback, impl->capture); + param_latency_changed(impl, param); break; case SPA_PARAM_Props: - param_props_changed(impl, param, SPA_DIRECTION_OUTPUT, impl->playback); + param_props_changed(impl, param); break; case SPA_PARAM_ProcessLatency: - param_process_latency_changed(impl, param, SPA_DIRECTION_OUTPUT, impl->playback); + param_process_latency_changed(impl, param); break; case SPA_PARAM_Tag: param_tag_changed(impl, param, impl->capture); From a2f2bded0bfde196f103cc514d5cd22a4f577c9f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 16:07:34 +0200 Subject: [PATCH 0361/1014] modules: update latency handling in example filter --- src/modules/module-example-filter.c | 46 +++++++++++++++++++++-------- src/modules/module-loopback.c | 1 - 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index ccd8c0e01..271c7c9fe 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -140,13 +140,14 @@ struct impl { struct pw_stream *capture; struct spa_hook capture_listener; struct spa_audio_info_raw capture_info; - struct spa_latency_info capture_latency; struct pw_properties *playback_props; struct pw_stream *playback; struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; - struct spa_latency_info playback_latency; + + struct spa_latency_info latency[2]; + struct spa_process_latency_info process_latency; unsigned int do_disconnect:1; }; @@ -236,22 +237,43 @@ static void playback_process(void *d) pw_stream_queue_buffer(impl->playback, out); } -static void param_latency_changed(struct impl *impl, const struct spa_pod *param, - struct spa_latency_info *info, struct pw_stream *other) +static void update_latencies(struct impl *impl) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *params[1]; + const struct spa_pod *play_params[2], *capt_params[2]; + uint32_t n_params = 0; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + latency = impl->latency[SPA_DIRECTION_INPUT]; + play_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + capt_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + latency = impl->latency[SPA_DIRECTION_OUTPUT]; + capt_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + spa_process_latency_info_add(&impl->process_latency, &latency); + play_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + if (impl->playback) + pw_stream_update_params(impl->playback, play_params, n_params); + if (impl->capture) + pw_stream_update_params(impl->capture, capt_params, n_params); +} + +static void param_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + struct spa_latency_info latency; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; + if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) + return; - *info = latency; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - pw_stream_update_params(other, params, 1); + impl->latency[latency.direction] = latency; + update_latencies(impl); } static void stream_state_changed(void *data, enum pw_stream_state old, @@ -294,7 +316,7 @@ static void capture_param_changed(void *data, uint32_t id, const struct spa_pod break; } case SPA_PARAM_Latency: - param_latency_changed(impl, param, &impl->capture_latency, impl->playback); + param_latency_changed(impl, param); break; } } @@ -320,7 +342,7 @@ static void playback_param_changed(void *data, uint32_t id, const struct spa_pod switch (id) { case SPA_PARAM_Latency: - param_latency_changed(impl, param, &impl->playback_latency, impl->capture); + param_latency_changed(impl, param); break; } } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index d65768f0a..7cb7d5932 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -483,7 +483,6 @@ static void update_latencies(struct impl *impl, bool props, bool process) pw_stream_update_params(impl->capture, capt_params, n_params); } - static void param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; From 190d49d874f2986888970fdca570316729a057b7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Jun 2025 18:00:01 +0200 Subject: [PATCH 0362/1014] loop: add docs about the locking --- spa/include/spa/support/loop.h | 25 +++++++++++++++++++++---- src/pipewire/thread-loop.h | 16 +++++++++++----- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index a1e6a9a3b..1aec922de 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -105,6 +105,7 @@ struct spa_loop_methods { /** Invoke a function in the context of this loop. * May be called from any thread and multiple threads at the same time. + * * If called from the loop's thread, all callbacks previously queued with * invoke() will be run synchronously, which might cause unexpected * reentrancy problems. @@ -274,7 +275,7 @@ struct spa_loop_control_methods { * This function should be called before calling iterate and is * typically used to capture the thread that this loop will run in. * It should ideally be called once from the thread that will run - * the loop. + * the loop. This function will lock the loop. */ void (*enter) (void *object); /** Leave a loop @@ -282,6 +283,8 @@ struct spa_loop_control_methods { * * It should ideally be called once after calling iterate when the loop * will no longer be iterated from the thread that called enter(). + * + * This function will unlock the loop. */ void (*leave) (void *object); @@ -290,8 +293,10 @@ struct spa_loop_control_methods { * \param timeout an optional timeout in milliseconds. * 0 for no timeout, -1 for infinite timeout. * - * This function will block - * up to \a timeout milliseconds and then dispatch the fds with activity. + * This function will first unlock the loop and then block + * up to \a timeout milliseconds, lock the loop again and then + * dispatch the fds with activity. + * * The number of dispatched fds is returned. */ int (*iterate) (void *object, int timeout); @@ -327,11 +332,17 @@ struct spa_loop_control_methods { /** get the absolute time * Get the current time with \ref timeout that can be used in wait. * Since version 2:2 + * + * This function can be called from any thread. */ int (*get_time) (void *object, struct timespec *abstime, int64_t timeout); + /** Wait for a signal * Wait until a thread performs signal. Since version 2:2 * + * This function must be called with the loop lock. Because this is a + * blocking call, it should not be performed from a realtime thread. + * * \param[in] object the control * \param[in] abstime the maximum time to wait for the signal or NULL * \return 0 on success or a negative return value on error. @@ -343,16 +354,22 @@ struct spa_loop_control_methods { * When wait_for_accept is set, this functions blocks until all * threads performed accept. * + * This function must be called with the loop lock and is safe to + * call from a realtime thread source dispatch functions when + * wait_for_accept is false. + * * \param[in] object the control * \param[in] wait_for_accept block for accept * \return 0 on success or a negative return value on error. */ int (*signal) (void *object, bool wait_for_accept); - /** Accept signalers * Resume the thread that signaled with wait_for accept. * + * This function must be called with the loop lock and is safe to + * call from a realtime thread source dispatch functions. + * * \param[in] object the control * \return 0 on success or a negative return value on error. */ diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h index 0d8dbdcea..d40e47b7a 100644 --- a/src/pipewire/thread-loop.h +++ b/src/pipewire/thread-loop.h @@ -126,29 +126,35 @@ void pw_thread_loop_lock(struct pw_thread_loop *loop); /** Unlock the loop */ void pw_thread_loop_unlock(struct pw_thread_loop *loop); -/** Release the lock and wait until some thread calls \ref pw_thread_loop_signal */ +/** Release the lock and wait until some thread calls \ref pw_thread_loop_signal. + * This must be called with the loop locked. */ void pw_thread_loop_wait(struct pw_thread_loop *loop); /** Release the lock and wait a maximum of 'wait_max_sec' seconds - * until some thread calls \ref pw_thread_loop_signal or time out */ + * until some thread calls \ref pw_thread_loop_signal or time out. + * This must be called with the loop locked. */ int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec); /** Get a struct timespec suitable for \ref pw_thread_loop_timed_wait_full. + * This function may be called from any thread. * Since: 0.3.7 */ int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout); /** Release the lock and wait up to \a abstime until some thread calls * \ref pw_thread_loop_signal. Use \ref pw_thread_loop_get_time to make a timeout. + * This must be called with the loop locked. * Since: 0.3.7 */ int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, const struct timespec *abstime); -/** Signal all threads waiting with \ref pw_thread_loop_wait */ +/** Signal all threads waiting with \ref pw_thread_loop_wait + * This must be called with the loop locked. */ void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept); -/** Signal all threads executing \ref pw_thread_loop_signal with wait_for_accept */ +/** Signal all threads executing \ref pw_thread_loop_signal with wait_for_accept. + * This must be called with the loop locked. */ void pw_thread_loop_accept(struct pw_thread_loop *loop); -/** Check if inside the thread */ +/** Check if inside the thread. This can be called from any thread. */ bool pw_thread_loop_in_thread(struct pw_thread_loop *loop); /** From c08ef8474544a1fb56b038276e65ed6de6a2b29e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 10:17:32 +0200 Subject: [PATCH 0363/1014] modules: initialize the latency params Otherwise we get nonsense values for the direction. See #4731 --- src/modules/module-example-filter.c | 2 ++ src/modules/module-filter-chain.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 271c7c9fe..99ada25d3 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -542,6 +542,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; + impl->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + impl->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "filter-%u-%u", pid, id); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 102eaa827..bc00dcec3 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1667,6 +1667,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; + impl->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + impl->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "filter-chain-%u-%u", pid, id); From b0462cb5de8aced18762feca48de0335170855e6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 11:43:13 +0200 Subject: [PATCH 0364/1014] filter-chain: do graph reconfigure in playback state change Because we do the processing of the graph in the playback process function, only do graph reset and reconfigure from the playback state change so that we don't have process and state change at the same time and crash. --- src/modules/module-filter-chain.c | 81 ++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index bc00dcec3..23e7dc4ac 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1063,6 +1063,8 @@ static void capture_process(void *d) struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) break; + /* playback part is not ready, consume, discard and recycle + * the capture buffers */ pw_stream_queue_buffer(impl->capture, t); } } @@ -1213,14 +1215,10 @@ static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; - struct spa_filter_graph *graph = impl->graph; - int res; switch (state) { case PW_STREAM_STATE_PAUSED: - pw_stream_flush(impl->playback, false); pw_stream_flush(impl->capture, false); - spa_filter_graph_reset(graph); break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("module %p: unconnected", impl); @@ -1230,34 +1228,10 @@ static void capture_state_changed(void *data, enum pw_stream_state old, pw_log_info("module %p: error: %s", impl, error); break; case PW_STREAM_STATE_STREAMING: - { - uint32_t target = impl->info.rate; - if (target == 0) - target = impl->position ? - impl->position->clock.target_rate.denom : DEFAULT_RATE; - if (target == 0) { - res = -EINVAL; - goto error; - } - if (impl->rate != target) { - char rate[64]; - impl->rate = target; - snprintf(rate, sizeof(rate), "%lu", impl->rate); - spa_filter_graph_deactivate(graph); - if ((res = spa_filter_graph_activate(graph, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate)))) < 0) - goto error; - } - break; - } default: break; } return; -error: - pw_stream_set_error(impl->capture, res, "can't start graph: %s", - spa_strerror(res)); } static void io_changed(void *data, uint32_t id, void *area, uint32_t size) @@ -1331,6 +1305,56 @@ static const struct pw_stream_events in_stream_events = { .param_changed = capture_param_changed }; +static void playback_state_changed(void *data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct impl *impl = data; + struct spa_filter_graph *graph = impl->graph; + int res; + + switch (state) { + case PW_STREAM_STATE_PAUSED: + pw_stream_flush(impl->playback, false); + spa_filter_graph_reset(graph); + break; + case PW_STREAM_STATE_UNCONNECTED: + pw_log_info("module %p: unconnected", impl); + pw_impl_module_schedule_destroy(impl->module); + break; + case PW_STREAM_STATE_ERROR: + pw_log_info("module %p: error: %s", impl, error); + break; + case PW_STREAM_STATE_STREAMING: + { + uint32_t target = impl->info.rate; + if (target == 0) + target = impl->position ? + impl->position->clock.target_rate.denom : DEFAULT_RATE; + if (target == 0) { + res = -EINVAL; + goto error; + } + if (impl->rate != target) { + char rate[64]; + impl->rate = target; + snprintf(rate, sizeof(rate), "%lu", impl->rate); + spa_filter_graph_deactivate(graph); + if ((res = spa_filter_graph_activate(graph, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate)))) < 0) + goto error; + } + break; + } + default: + break; + } + return; +error: + pw_stream_set_error(impl->capture, res, "can't start graph: %s", + spa_strerror(res)); +} + static void playback_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; @@ -1349,6 +1373,7 @@ static const struct pw_stream_events out_stream_events = { .destroy = playback_destroy, .process = playback_process, .io_changed = io_changed, + .state_changed = playback_state_changed, .param_changed = playback_param_changed, }; From 524da5962d62e2378584ff1d72aeb74da9ace65c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 13:03:18 +0200 Subject: [PATCH 0365/1014] alsa: reset dll in prepare When we do_prepare, always reset the dll. We already set the alsa_sync field but that is only used by followers to resync in some cases. When reseting the dll, we also reset the next_time and base_time values, we however need to do this before calculating the error in update_time when we are the driver in IRQ mode or else we get some crazy error that distorts the rate estimation. --- spa/plugins/alsa/alsa-pcm.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 15ced4466..14fa967df 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2571,6 +2571,7 @@ static int do_prepare(struct state *state) state->alsa_sync = true; state->alsa_sync_warning = false; state->alsa_started = false; + spa_dll_init(&state->dll); return 0; } @@ -2797,6 +2798,12 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram double err, corr, avg; int32_t diff; + if (SPA_UNLIKELY(state->dll.bw == 0.0)) { + spa_dll_set_bw(&state->dll, SPA_DLL_BW_MAX, state->threshold, state->rate); + state->next_time = current_time; + state->base_time = current_time; + } + if (state->disable_tsched && !follower) { err = (int64_t)(current_time - state->next_time); err = err / 1e9 * state->rate; @@ -2807,11 +2814,6 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram err = target - delay; } - if (SPA_UNLIKELY(state->dll.bw == 0.0)) { - spa_dll_set_bw(&state->dll, SPA_DLL_BW_MAX, state->threshold, state->rate); - state->next_time = current_time; - state->base_time = current_time; - } diff = (int32_t) (state->last_threshold - state->threshold); if (SPA_UNLIKELY(diff != 0)) { @@ -2888,9 +2890,9 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram state->clock->next_nsec = state->next_time; } - spa_log_trace_fp(state->log, "%p: follower:%d %"PRIu64" %f %ld %ld %f %f %u", - state, follower, current_time, corr, delay, target, err, state->threshold * corr, - state->threshold); + spa_log_trace_fp(state->log, "%p: follower:%d %"PRIu64" %"PRIu64" %f %ld %ld %f %f %u", + state, follower, current_time, state->next_time, corr, delay, target, + err, state->threshold * corr, state->threshold); return 0; } From 3fdcf0d34c03babedc1f5bb1365096af92fe8153 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 13:06:54 +0200 Subject: [PATCH 0366/1014] alsa: reset alsa_sync when linked When a linked node needs to be resynced we actually never clear the flag or reset the dll. Move the code around so that it still does the reset of the flag and dll without actually doing the resync in the ringbuffer when it is a linked node. --- spa/plugins/alsa/alsa-pcm.c | 69 +++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 14fa967df..9d823d9d2 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3002,28 +3002,30 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, following)) < 0)) return res; - if (following && state->alsa_started && !state->linked) { + if (following && state->alsa_started) { if (SPA_UNLIKELY(state->alsa_sync)) { enum spa_log_level lev; - if (SPA_UNLIKELY(state->alsa_sync_warning)) - lev = SPA_LOG_LEVEL_WARN; - else - lev = SPA_LOG_LEVEL_INFO; + if (!state->linked) { + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; - if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) - lev = SPA_LOG_LEVEL_DEBUG; + if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) + lev = SPA_LOG_LEVEL_DEBUG; - spa_log_lev(state->log, lev, "%s: follower avail:%lu delay:%ld " - "target:%ld thr:%u, resync (%d suppressed)", - state->name, avail, delay, - target, state->threshold, suppressed); + spa_log_lev(state->log, lev, "%s: follower avail:%lu delay:%ld " + "target:%ld thr:%u, resync (%d suppressed)", + state->name, avail, delay, + target, state->threshold, suppressed); - if (avail > target) - snd_pcm_rewind(state->hndl, avail - target); - else if (avail < target) - spa_alsa_silence(state, target - avail); - avail = target; + if (avail > target) + snd_pcm_rewind(state->hndl, avail - target); + else if (avail < target) + spa_alsa_silence(state, target - avail); + avail = target; + } spa_dll_init(&state->dll); state->alsa_sync = false; } else @@ -3268,27 +3270,28 @@ static int alsa_read_sync(struct state *state, uint64_t current_time) return res; max_read = state->buffer_frames; - if (following && !state->linked) { + if (following) { if (state->alsa_sync) { - enum spa_log_level lev; + if (!state->linked) { + enum spa_log_level lev; + if (SPA_UNLIKELY(state->alsa_sync_warning)) + lev = SPA_LOG_LEVEL_WARN; + else + lev = SPA_LOG_LEVEL_INFO; - if (SPA_UNLIKELY(state->alsa_sync_warning)) - lev = SPA_LOG_LEVEL_WARN; - else - lev = SPA_LOG_LEVEL_INFO; + if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) + lev = SPA_LOG_LEVEL_DEBUG; - if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) - lev = SPA_LOG_LEVEL_DEBUG; + spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u " + "resample:%d, resync (%d suppressed)", state->name, delay, + target, state->threshold, state->resample, suppressed); - spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u " - "resample:%d, resync (%d suppressed)", state->name, delay, - target, state->threshold, state->resample, suppressed); - - if (avail < target) - max_read = target - avail; - else if (avail > target) { - snd_pcm_forward(state->hndl, avail - target); - avail = target; + if (avail < target) + max_read = target - avail; + else if (avail > target) { + snd_pcm_forward(state->hndl, avail - target); + avail = target; + } } state->alsa_sync = false; spa_dll_init(&state->dll); From 82229b3d87c3a05da9d4bf4213955c501da4b875 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 15:21:26 +0200 Subject: [PATCH 0367/1014] modules: make sure we deactivate the graph safely Use the loop lock to ensure the loop is not using the graph anymore when the deactivate or reset it. --- src/modules/module-filter-chain.c | 76 ++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 23e7dc4ac..3bc1ef7de 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1041,6 +1041,7 @@ struct impl { struct spa_hook graph_listener; uint32_t n_inputs; uint32_t n_outputs; + bool graph_active; struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; @@ -1131,7 +1132,8 @@ static void playback_process(void *d) pw_log_trace_fp("%p: stride:%d size:%d requested:%"PRIu64" (%"PRIu64")", impl, stride, data_size, out->requested, out->requested * stride); - spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float)); + if (impl->graph_active) + spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float)); done: if (in != NULL) @@ -1140,6 +1142,62 @@ done: pw_stream_queue_buffer(impl->playback, out); } +static int activate_graph(struct impl *impl) +{ + char rate[64]; + int res; + + if (impl->graph_active) + return 0; + + snprintf(rate, sizeof(rate), "%lu", impl->rate); + res = spa_filter_graph_activate(impl->graph, &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate))); + + if (res >= 0) { + struct pw_loop *data_loop = pw_stream_get_data_loop(impl->playback); + pw_loop_lock(data_loop); + impl->graph_active = true; + pw_loop_unlock(data_loop); + } + return res; +} + +static int deactivate_graph(struct impl *impl) +{ + struct pw_loop *data_loop; + + if (!impl->graph_active) + return 0; + + data_loop = pw_stream_get_data_loop(impl->playback); + + pw_loop_lock(data_loop); + impl->graph_active = false; + pw_loop_unlock(data_loop); + + return spa_filter_graph_deactivate(impl->graph); +} + +static int reset_graph(struct impl *impl) +{ + struct pw_loop *data_loop = pw_stream_get_data_loop(impl->playback); + int res; + bool old_active = impl->graph_active; + + pw_loop_lock(data_loop); + impl->graph_active = false; + pw_loop_unlock(data_loop); + + res = spa_filter_graph_reset(impl->graph); + + pw_loop_lock(data_loop); + impl->graph_active = old_active; + pw_loop_unlock(data_loop); + + return res; +} + static void update_latencies(struct impl *impl) { struct spa_latency_info latency; @@ -1249,7 +1307,6 @@ static void io_changed(void *data, uint32_t id, void *area, uint32_t size) static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod *param, enum spa_direction direction, struct pw_stream *stream, struct pw_stream *other) { - struct spa_filter_graph *graph = impl->graph; int res; switch (id) { @@ -1260,7 +1317,7 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod * if (param == NULL) { pw_log_info("module %p: filter deactivate", impl); if (direction == SPA_DIRECTION_INPUT) - spa_filter_graph_deactivate(graph); + deactivate_graph(impl); impl->rate = 0; } else { if ((res = spa_format_audio_raw_parse(param, &info)) < 0) @@ -1309,13 +1366,12 @@ static void playback_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; - struct spa_filter_graph *graph = impl->graph; int res; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->playback, false); - spa_filter_graph_reset(graph); + reset_graph(impl); break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("module %p: unconnected", impl); @@ -1335,15 +1391,11 @@ static void playback_state_changed(void *data, enum pw_stream_state old, goto error; } if (impl->rate != target) { - char rate[64]; impl->rate = target; - snprintf(rate, sizeof(rate), "%lu", impl->rate); - spa_filter_graph_deactivate(graph); - if ((res = spa_filter_graph_activate(graph, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate)))) < 0) - goto error; + deactivate_graph(impl); } + if ((res = activate_graph(impl)) < 0) + goto error; break; } default: From 3a0ffe21e68996cee31c42201831e4895b66ca45 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 9 Jun 2025 13:22:35 +0100 Subject: [PATCH 0368/1014] libcamera: Default to auto-focus & auto-exposure libcamera says that cameras should default to manual focus mode. This means that unless pipewire clients specifically change this control, users with an autofocus-capable camera are left with an out-of-focus image. This patch sets the autofocus mode to continuous and enables auto-exposure (as the default for this is unspecified). Testing with an imx708 on Raspberry Pi OS on a Raspberry Pi 4, before this patch the image was generally out of focus in Firefox/webrtc, after this patch autofocus works correctly. --- spa/plugins/libcamera/libcamera-source.cpp | 1 + spa/plugins/libcamera/libcamera-utils.cpp | 29 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 0dffbc92b..5523f7c8b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -163,6 +163,7 @@ struct impl { struct spa_source source = {}; ControlList ctrls; + ControlList initial_controls; bool active = false; bool acquired = false; diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index d116a4094..97634a5d8 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -16,6 +16,30 @@ #include #include +static void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) +{ + /* Libcamera recommends cameras default to manual focus mode, but we don't + * expose any focus controls. So, specifically enable autofocus on + * cameras which support it. */ + auto af_it = ctrl_infos.find(libcamera::controls::AF_MODE); + if (af_it != ctrl_infos.end()) { + const ControlInfo &ctrl_info = af_it->second; + auto is_af_continuous = [](const ControlValue &value) { + return value.get() == libcamera::controls::AfModeContinuous; + }; + if (std::any_of(ctrl_info.values().begin(), + ctrl_info.values().end(), is_af_continuous)) { + ctrls.set(libcamera::controls::AF_MODE, + libcamera::controls::AfModeContinuous); + } + } + + auto ae_it = ctrl_infos.find(libcamera::controls::AE_ENABLE); + if (ae_it != ctrl_infos.end()) { + ctrls.set(libcamera::controls::AE_ENABLE, true); + } +} + int spa_libcamera_open(struct impl *impl) { if (impl->acquired) @@ -26,6 +50,9 @@ int spa_libcamera_open(struct impl *impl) impl->allocator = new FrameBufferAllocator(impl->camera); + const ControlInfoMap &controls = impl->camera->controls(); + setup_initial_controls(controls, impl->initial_controls); + impl->acquired = true; return 0; } @@ -990,7 +1017,7 @@ static int spa_libcamera_stream_on(struct impl *impl) impl->camera->requestCompleted.connect(impl, &impl::requestComplete); spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); - if ((res = impl->camera->start()) < 0) + if ((res = impl->camera->start(&impl->initial_controls)) < 0) goto error; for (Request *req : impl->pendingRequests) { From 5480a1382fcd3ea8722cde8fea7e0c8e66bd1bea Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 11 Jun 2025 16:26:03 +0200 Subject: [PATCH 0369/1014] modules: always update latency params Even if the latency didn't change, the current pw-stream implementation will have wiped all Latency params away and we want to put them back in all cases. See #4731 --- src/modules/module-example-filter.c | 2 -- src/modules/module-filter-chain.c | 2 -- src/modules/module-loopback.c | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 99ada25d3..ae2cb729b 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -269,8 +269,6 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param if (param == NULL || spa_latency_parse(param, &latency) < 0) return; - if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) - return; impl->latency[latency.direction] = latency; update_latencies(impl); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 3bc1ef7de..0c2558719 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1230,8 +1230,6 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param if (param == NULL || spa_latency_parse(param, &latency) < 0) return; - if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) - return; impl->latency[latency.direction] = latency; update_latencies(impl); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 7cb7d5932..a13ee91e0 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -489,8 +489,6 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param if (param == NULL || spa_latency_parse(param, &latency) < 0) return; - if (spa_latency_info_compare(&impl->latency[latency.direction], &latency) == 0) - return; impl->latency[latency.direction] = latency; update_latencies(impl, false, false); From 2393be4543a14000f3b0186af888637ed7c9202e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 12:12:28 +0200 Subject: [PATCH 0370/1014] spa: add SPA_LATENCY_INFO_UNSET Add a initializer for an unset latency info. Use this for combining latency info. --- spa/include/spa/param/latency-utils.h | 22 +++++++++------------- spa/include/spa/param/latency.h | 4 ++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spa/include/spa/param/latency-utils.h b/spa/include/spa/param/latency-utils.h index 907c92816..bd3c274dd 100644 --- a/spa/include/spa/param/latency-utils.h +++ b/spa/include/spa/param/latency-utils.h @@ -44,28 +44,24 @@ spa_latency_info_compare(const struct spa_latency_info *a, const struct spa_late SPA_API_LATENCY_UTILS void spa_latency_info_combine_start(struct spa_latency_info *info, enum spa_direction direction) { - *info = SPA_LATENCY_INFO(direction, - .min_quantum = FLT_MAX, - .max_quantum = FLT_MIN, - .min_rate = INT32_MAX, - .max_rate = INT32_MIN, - .min_ns = INT64_MAX, - .max_ns = INT64_MIN); + *info = SPA_LATENCY_INFO_UNSET(direction); } + SPA_API_LATENCY_UTILS void spa_latency_info_combine_finish(struct spa_latency_info *info) { - if (info->min_quantum == FLT_MAX) + struct spa_latency_info unset = SPA_LATENCY_INFO_UNSET(info->direction); + if (info->min_quantum == unset.min_quantum) info->min_quantum = 0; - if (info->max_quantum == FLT_MIN) + if (info->max_quantum == unset.max_quantum) info->max_quantum = 0; - if (info->min_rate == INT32_MAX) + if (info->min_rate == unset.min_rate) info->min_rate = 0; - if (info->max_rate == INT32_MIN) + if (info->max_rate == unset.max_rate) info->max_rate = 0; - if (info->min_ns == INT64_MAX) + if (info->min_ns == unset.min_ns) info->min_ns = 0; - if (info->max_ns == INT64_MIN) + if (info->max_ns == unset.max_ns) info->max_ns = 0; } diff --git a/spa/include/spa/param/latency.h b/spa/include/spa/param/latency.h index 0e9cfffa2..fdc479858 100644 --- a/spa/include/spa/param/latency.h +++ b/spa/include/spa/param/latency.h @@ -57,6 +57,10 @@ struct spa_latency_info { }; #define SPA_LATENCY_INFO(dir,...) ((struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ }) +#define SPA_LATENCY_INFO_UNSET(dir) SPA_LATENCY_INFO(dir, \ + .min_quantum = FLT_MAX, .max_quantum = FLT_MIN, \ + .min_rate = INT32_MAX, .max_rate = INT32_MIN, \ + .min_ns = INT64_MAX, .max_ns = INT64_MIN) /** * Properties for SPA_TYPE_OBJECT_ParamProcessLatency From 92243038c1fb3fe46eb480608adeba8ebc2f1c17 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 13:32:54 +0200 Subject: [PATCH 0371/1014] stream: handle Latency params better LOCK the Latency param we get from the peer so that we don't remove it when we update our own port latency. Also don't remove our port latency when we get an update from the peer. This essentially keeps the update/clear of the upstream and downstrem latencies separate and makes it easier to implement the latency logic in the pw-stream. When a filter receives a Latency event on a port, it can simply update the other port latency and none of the peer latencies are removed. --- src/pipewire/stream.c | 74 ++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 8e88dbb2b..6b367ec75 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -228,7 +228,7 @@ static int add_param(struct stream *impl, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct param *p; - int idx; + int idx, res; if (param != NULL && !spa_pod_is_object(param)) return -EINVAL; @@ -240,6 +240,21 @@ static int add_param(struct stream *impl, if (id == SPA_ID_INVALID) id = SPA_POD_OBJECT_ID(param); + switch (id) { + case SPA_PARAM_Latency: + struct spa_latency_info info; + if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (impl->this.node_id != SPA_ID_INVALID && + info.direction != impl->direction && + !SPA_FLAG_IS_SET(flags, PARAM_FLAG_LOCKED)) { + pw_log_warn("not adding locked Latency param %s %s", + pw_direction_as_string(info.direction), + pw_direction_as_string(impl->direction)); + return 0; + } + } + p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); if (p == NULL) return -errno; @@ -273,7 +288,7 @@ static int add_param(struct stream *impl, return 0; } -static void clear_params(struct stream *impl, uint32_t id) +static void clear_params(struct stream *impl, uint32_t id, uint32_t flags) { struct param *p, *t; bool found = false; @@ -281,7 +296,7 @@ static void clear_params(struct stream *impl, uint32_t id) spa_list_for_each_safe(p, t, &impl->param_list, link) { if (id == SPA_ID_INVALID || - (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) { + (p->id == id && (p->flags & PARAM_FLAG_LOCKED) == flags)) { found = true; spa_list_remove(&p->link); free(p); @@ -321,12 +336,12 @@ static int update_params(struct stream *impl, uint32_t id, uint32_t flags, int res = 0; if (id != SPA_ID_INVALID) { - clear_params(impl, id); + clear_params(impl, id, flags); } else { for (i = 0; i < n_params; i++) { if (params[i] == NULL || !spa_pod_is_object(params[i])) continue; - clear_params(impl, SPA_POD_OBJECT_ID(params[i])); + clear_params(impl, SPA_POD_OBJECT_ID(params[i]), flags); } } for (i = 0; i < n_params; i++) { @@ -336,7 +351,6 @@ static int update_params(struct stream *impl, uint32_t id, uint32_t flags, return res; } - static inline int queue_push(struct stream *stream, struct queue *queue, struct buffer *buffer) { uint32_t index; @@ -863,20 +877,20 @@ static void clear_buffers(struct pw_stream *stream) clear_queue(impl, &impl->queued); } -static int parse_latency(struct pw_stream *stream, const struct spa_pod *param) +static int parse_latency(struct pw_stream *stream, const struct spa_pod *param, uint32_t *flags) { - struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); - struct spa_latency_info info; - int res; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct spa_latency_info info; + int res; - if (param == NULL) - return 0; - - if ((res = spa_latency_parse(param, &info)) < 0) + if (param == NULL) + info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(impl->direction)); + else if ((res = spa_latency_parse(param, &info)) < 0) return res; - pw_log_info("stream %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, stream, + pw_log_info("stream %p: set %s/%s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, stream, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + impl->direction == SPA_DIRECTION_INPUT ? "input" : "output", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); @@ -885,6 +899,7 @@ static int parse_latency(struct pw_stream *stream, const struct spa_pod *param) return 0; impl->latency = info; + *flags = PARAM_FLAG_LOCKED; return 0; } @@ -895,8 +910,10 @@ static int impl_port_set_param(void *object, { struct stream *impl = object; struct pw_stream *stream = &impl->this; - uint32_t user; + uint32_t user, fl = 0; int res; + const struct spa_pod *params[1]; + uint32_t n_params = 0; pw_log_debug("%p: port:%d.%d id:%d (%s) param:%p disconnecting:%d", impl, direction, port_id, id, @@ -909,7 +926,16 @@ static int impl_port_set_param(void *object, if (param) pw_log_pod(SPA_LOG_LEVEL_DEBUG, param); - if ((res = update_params(impl, id, 0, ¶m, param ? 1 : 0)) < 0) + params[0] = param; + n_params = param ? 1 : 0; + + switch (id) { + case SPA_PARAM_Latency: + parse_latency(stream, param, &fl); + break; + } + + if ((res = update_params(impl, id, fl, params, n_params)) < 0) return res; switch (id) { @@ -917,9 +943,6 @@ static int impl_port_set_param(void *object, clear_buffers(stream); user = impl->params[NODE_Format].user; break; - case SPA_PARAM_Latency: - parse_latency(stream, param); - break; default: break; } @@ -1712,7 +1735,7 @@ void pw_stream_destroy(struct pw_stream *stream) stream->core = NULL; } - clear_params(impl, SPA_ID_INVALID); + clear_params(impl, SPA_ID_INVALID, 0); pw_log_debug("%p: free", stream); free(stream->error); @@ -1990,7 +2013,7 @@ pw_stream_connect(struct pw_stream *stream, impl->port_info.params = impl->port_params; impl->port_info.n_params = N_PORT_PARAMS; - clear_params(impl, SPA_ID_INVALID); + clear_params(impl, SPA_ID_INVALID, 0); for (i = 0; i < n_params; i++) add_param(impl, SPA_ID_INVALID, 0, params[i]); @@ -2390,6 +2413,7 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t uintptr_t seq1, seq2; uint32_t buffered, quantum, index, rate_size; int32_t avail_buffers; + struct spa_latency_info *latency = &impl->latency; do { seq1 = SPA_SEQ_READ(impl->seq); @@ -2405,9 +2429,9 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t else time->queued = (int64_t)(impl->queued.incount - time->queued); - time->delay += (int64_t)(((impl->latency.min_quantum + impl->latency.max_quantum) / 2.0f) * quantum); - time->delay += (impl->latency.min_rate + impl->latency.max_rate) / 2; - time->delay += ((impl->latency.min_ns + impl->latency.max_ns) / 2) * + time->delay += (int64_t)(((latency->min_quantum + latency->max_quantum) / 2.0f) * quantum); + time->delay += (latency->min_rate + latency->max_rate) / 2; + time->delay += ((latency->min_ns + latency->max_ns) / 2) * (int64_t)time->rate.denom / (int64_t)SPA_NSEC_PER_SEC; avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index); From 046fdad4eea9015711ea44d3da2dcbc4c55a90c1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 15:20:18 +0200 Subject: [PATCH 0372/1014] modules: improve latency handling Now that the stream remembers the latency for us, we can only care about the other latency. So, if we get (output) latency on an input port/stream, we add our own latency and then set it on the output port/stream. We do the same for input ports. --- src/modules/module-example-filter.c | 26 ++++++------------ src/modules/module-filter-chain.c | 29 +++++++++----------- src/modules/module-loopback.c | 41 +++++++++++++---------------- 3 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index ae2cb729b..ec9cdbd19 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -237,30 +237,20 @@ static void playback_process(void *d) pw_stream_queue_buffer(impl->playback, out); } -static void update_latencies(struct impl *impl) +static void update_latency(struct impl *impl, enum spa_direction direction) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *play_params[2], *capt_params[2]; - uint32_t n_params = 0; + const struct spa_pod *params[1]; + struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? + impl->playback : impl->capture; spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - latency = impl->latency[SPA_DIRECTION_INPUT]; - play_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); - capt_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - latency = impl->latency[SPA_DIRECTION_OUTPUT]; - capt_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - spa_process_latency_info_add(&impl->process_latency, &latency); - play_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - if (impl->playback) - pw_stream_update_params(impl->playback, play_params, n_params); - if (impl->capture) - pw_stream_update_params(impl->capture, capt_params, n_params); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + pw_stream_update_params(s, params, 1); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param) @@ -271,7 +261,7 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param return; impl->latency[latency.direction] = latency; - update_latencies(impl); + update_latency(impl, latency.direction); } static void stream_state_changed(void *data, enum pw_stream_state old, diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 0c2558719..e2a8f0683 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1198,29 +1198,26 @@ static int reset_graph(struct impl *impl) return res; } -static void update_latencies(struct impl *impl) +static void update_latency(struct impl *impl, enum spa_direction direction) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *play_params[2], *capt_params[2]; + const struct spa_pod *params[1]; + struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? + impl->playback : impl->capture; spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - latency = impl->latency[SPA_DIRECTION_INPUT]; - play_params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); - capt_params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + pw_stream_update_params(s, params, 1); +} - latency = impl->latency[SPA_DIRECTION_OUTPUT]; - capt_params[1] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - spa_process_latency_info_add(&impl->process_latency, &latency); - play_params[1] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - if (impl->playback) - pw_stream_update_params(impl->playback, play_params, 2); - if (impl->capture) - pw_stream_update_params(impl->capture, capt_params, 2); +static void update_latencies(struct impl *impl) +{ + update_latency(impl, SPA_DIRECTION_INPUT); + update_latency(impl, SPA_DIRECTION_OUTPUT); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param, @@ -1232,7 +1229,7 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param return; impl->latency[latency.direction] = latency; - update_latencies(impl); + update_latency(impl, latency.direction); } static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index a13ee91e0..0b552222f 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -441,46 +441,41 @@ static void playback_process(void *d) pw_stream_queue_buffer(impl->playback, out); } -static void update_latencies(struct impl *impl, bool props, bool process) +static void update_latency(struct impl *impl, enum spa_direction direction, bool props, bool process) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *play_params[4], *capt_params[4]; + const struct spa_pod *params[3]; uint32_t n_params = 0; + struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? + impl->playback : impl->capture; + + if (s == NULL) + return; spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - latency = impl->latency[SPA_DIRECTION_INPUT]; - play_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); - capt_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - latency = impl->latency[SPA_DIRECTION_OUTPUT]; - capt_params[n_params] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - spa_process_latency_info_add(&impl->process_latency, &latency); - play_params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); if (props) { int64_t nsec = impl->process_latency.ns; - play_params[n_params] = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, - SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); - capt_params[n_params++] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); } if (process) { - play_params[n_params] = spa_process_latency_build(&b, - SPA_PARAM_ProcessLatency, &impl->process_latency); - capt_params[n_params++] = spa_process_latency_build(&b, + params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); } + pw_stream_update_params(s, params, n_params); +} - if (impl->playback) - pw_stream_update_params(impl->playback, play_params, n_params); - if (impl->capture) - pw_stream_update_params(impl->capture, capt_params, n_params); +static void update_latencies(struct impl *impl, bool props, bool process) +{ + update_latency(impl, SPA_DIRECTION_INPUT, props, process); + update_latency(impl, SPA_DIRECTION_OUTPUT, props, process); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param) @@ -491,7 +486,7 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param return; impl->latency[latency.direction] = latency; - update_latencies(impl, false, false); + update_latency(impl, latency.direction, false, false); } static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) From 549f3fc46f77aa73eaa4d6d28fb600c5991e258a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 16:35:49 +0200 Subject: [PATCH 0373/1014] module-combine: use a better empty property string The empty string fails to parse as a properties, use "{}" instead. --- src/modules/module-combine-stream.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index de63c4a6f..76f01dc3a 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -1497,7 +1497,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) spa_list_init(&impl->streams); if (args == NULL) - args = ""; + args = "{}"; props = pw_properties_new_string_checked(args, strlen(args), &loc); if (props == NULL) { From 4c1be71773ab985ef660ba494b2c33c0430023fd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 16:36:51 +0200 Subject: [PATCH 0374/1014] modules: Propagate the combine latency When we get latency on the combine stream, forward it to all streams after setting the per stream compensate latency (if any). See #4731 --- src/modules/module-combine-stream.c | 44 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 76f01dc3a..842138688 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -319,6 +319,7 @@ struct stream { int64_t delay_samples; /* for main loop */ int64_t data_delay_samples; /* for data loop */ + int64_t compensate_samples; /* for main loop */ unsigned int ready:1; unsigned int added:1; @@ -574,6 +575,7 @@ static void update_delay(struct impl *impl) max_delay = SPA_MAX(max_delay, delay); s->delay_samples = delay; + s->compensate_samples = 0; } spa_list_for_each(s, &impl->streams, link) { @@ -581,9 +583,9 @@ static void update_delay(struct impl *impl) if (s->delay_samples != INT64_MIN) { int64_t delay = max_delay - s->delay_samples; + s->compensate_samples = delay; size = delay * sizeof(float); } - resize_delay(s, size); } @@ -653,6 +655,40 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param) } } +static void param_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + if (param == NULL) + return; + + pw_log_debug("latency update"); + struct stream *s; + struct spa_latency_info info; + const struct spa_pod *params[1]; + + if (spa_latency_parse(param, &info) < 0) + return; + + spa_list_for_each(s, &impl->streams, link) { + uint8_t buffer[1024]; + struct spa_pod_builder b; + + if (s->stream == NULL) + continue; + + pw_log_debug("updating stream %d", s->id); + if (impl->latency_compensate) { + struct spa_latency_info other = info; + other.min_rate += s->compensate_samples; + other.max_rate += s->compensate_samples; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &other); + } else { + params[0] = param; + } + pw_stream_update_params(s->stream, params, 1); + } +} + static int do_remove_stream(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { @@ -1299,10 +1335,12 @@ static void combine_param_changed(void *d, uint32_t id, const struct spa_pod *pa update_latency(impl); break; } - case SPA_PARAM_Tag: { + case SPA_PARAM_Tag: param_tag_changed(impl, param); break; - } + case SPA_PARAM_Latency: + param_latency_changed(impl, param); + break; default: break; } From fa8208eeef1554667058176d2b84edc49a4907ad Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 12 Jun 2025 17:16:27 +0200 Subject: [PATCH 0375/1014] tools: make some things configurable Things like indentation and simplified spa layout are behind an if 0 but wil some option parsing this could be improved. --- spa/tools/spa-json-dump.c | 69 +++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c index a1f2e619a..afd9569f6 100644 --- a/spa/tools/spa-json-dump.c +++ b/spa/tools/spa-json-dump.c @@ -16,9 +16,34 @@ #include #include -static void encode_string(FILE *f, const char *val, int len) +#define REJECT "\"\\'=:,{}[]()#" + +struct data { + FILE *file; + int indent; + bool simple_string; + const char *comma; + const char *key_sep; +}; + +static bool is_simple_string(const char *val, int len) { int i; + for (i = 0; i < len; i++) { + if (val[i] < 0x20 || strchr(REJECT, val[i]) != NULL) + return false; + } + return true; +} + +static void encode_string(struct data *d, const char *val, int len) +{ + FILE *f = d->file; + int i; + if (d->simple_string && is_simple_string(val, len)) { + fprintf(f, "%.*s", len, val); + return; + } fprintf(f, "\""); for (i = 0; i < len; i++) { char v = val[i]; @@ -52,8 +77,9 @@ static void encode_string(FILE *f, const char *val, int len) fprintf(f, "\""); } -static int dump(FILE *file, int indent, struct spa_json *it, const char *value, int len) +static int dump(struct data *d, int indent, struct spa_json *it, const char *value, int len) { + FILE *file = d->file; struct spa_json sub; bool toplevel = false; int count = 0, res; @@ -69,9 +95,9 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value, fprintf(file, "["); spa_json_enter(it, &sub); while ((len = spa_json_next(&sub, &value)) > 0) { - fprintf(file, "%s\n%*s", count++ > 0 ? "," : "", - indent+2, ""); - if ((res = dump(file, indent+2, &sub, value, len)) < 0) + fprintf(file, "%s\n%*s", count++ > 0 ? d->comma : "", + indent+d->indent, ""); + if ((res = dump(d, indent+d->indent, &sub, value, len)) < 0) return res; } fprintf(file, "%s%*s]", count > 0 ? "\n" : "", @@ -84,11 +110,11 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value, sub = *it; while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { fprintf(file, "%s\n%*s", - count++ > 0 ? "," : "", - indent+2, ""); - encode_string(file, key, strlen(key)); - fprintf(file, ": "); - res = dump(file, indent+2, &sub, value, len); + count++ > 0 ? d->comma : "", + indent+d->indent, ""); + encode_string(d, key, strlen(key)); + fprintf(file, "%s ", d->key_sep); + res = dump(d, indent+d->indent, &sub, value, len); if (res < 0) { if (toplevel) *it = sub; @@ -106,7 +132,7 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value, spa_json_is_float(value, len)) { fprintf(file, "%.*s", len, value); } else { - encode_string(file, value, len); + encode_string(d, value, len); } if (spa_json_get_error(it, NULL, NULL)) @@ -117,10 +143,12 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value, static int process_json(const char *filename, void *buf, size_t size) { + struct data d; int len, res; struct spa_json it; const char *value; + spa_zero(d); if ((len = spa_json_begin(&it, buf, size, &value)) <= 0) { fprintf(stderr, "not a valid file '%s': %s\n", filename, spa_strerror(len)); return -EINVAL; @@ -130,12 +158,25 @@ static int process_json(const char *filename, void *buf, size_t size) value = NULL; len = 0; } - res = dump(stdout, 0, &it, value, len); + d.file = stdout; +#if 1 + d.simple_string = false; + d.comma = ","; + d.key_sep = ":"; + d.indent = 2; +#else + d.simple_string = true; + d.comma = ""; + d.key_sep = " ="; + d.indent = 4; +#endif + + res = dump(&d, 0, &it, value, len); if (spa_json_next(&it, &value) < 0) res = -EINVAL; - fprintf(stdout, "\n"); - fflush(stdout); + fprintf(d.file, "\n"); + fflush(d.file); if (res < 0) { struct spa_error_location loc; From d07a383d6eee60cf3a2bd26e26e8688bfac08c5a Mon Sep 17 00:00:00 2001 From: ZZyVSmOzMz OZaAEBlUIZ <1080187-ZZyVSmOzMz@users.noreply.gitlab.freedesktop.org> Date: Thu, 12 Jun 2025 06:15:39 +0000 Subject: [PATCH 0376/1014] filter-chain: use only builtin filter for dolby surround sink --- .../filter-chain/sink-dolby-surround.conf | 149 +++++++++++++----- 1 file changed, 111 insertions(+), 38 deletions(-) diff --git a/src/daemon/filter-chain/sink-dolby-surround.conf b/src/daemon/filter-chain/sink-dolby-surround.conf index f51395806..7eb66e6da 100644 --- a/src/daemon/filter-chain/sink-dolby-surround.conf +++ b/src/daemon/filter-chain/sink-dolby-surround.conf @@ -3,45 +3,118 @@ # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # -context.modules = [ - { name = libpipewire-module-filter-chain - flags = [ nofail ] - args = { - node.description = "Dolby Surround Sink" - media.name = "Dolby Surround Sink" - filter.graph = { - nodes = [ - { - type = builtin - name = mixer - label = mixer - control = { "Gain 1" = 0.5 "Gain 2" = 0.5 } - } - { - type = ladspa - name = enc - plugin = surround_encoder_1401 - label = surroundEncoder - } - ] - links = [ - { output = "mixer:Out" input = "enc:S" } - ] - inputs = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ] - outputs = [ "enc:Lt" "enc:Rt" ] +{ + "context.modules": [ + { + "name": "libpipewire-module-filter-chain", + "flags": [ + "nofail" + ], + "args": { + "node.description": "Dolby Surround Sink", + "media.name": "Dolby Surround Sink", + "filter.graph": { + "nodes": [ + { + "type": "builtin", + "name": "mixer_fc", + "label": "mixer" + }, + { + "type": "builtin", + "name": "mixer_s", + "label": "mixer" + }, + { + "type": "builtin", + "name": "s_phased", + "label": "convolver", + "config": { + "filename": "/hilbert", + "length": 90 + } + }, + { + "type": "builtin", + "name": "mixer_lt", + "label": "mixer", + "control": { + "Gain 1": 1, + "Gain 2": 0, + "Gain 3": 0.7071067811865475, + "Gain 4": -0.7071067811865475 + } + }, + { + "type": "builtin", + "name": "mixer_rt", + "label": "mixer", + "control": { + "Gain 1": 0, + "Gain 2": 1, + "Gain 3": 0.7071067811865475, + "Gain 4": 0.7071067811865475 + } } - capture.props = { - node.name = "effect_input.dolby_surround" - media.class = Audio/Sink - audio.channels = 6 - audio.position = [ FL FR FC LFE SL SR ] - } - playback.props = { - node.name = "effect_output.dolby_surround" - node.passive = true - audio.channels = 2 - audio.position = [ FL FR ] + ], + "links": [ + { + "output": "mixer_fc:Out", + "input": "mixer_lt:In 3" + }, + { + "output": "mixer_fc:Out", + "input": "mixer_rt:In 3" + }, + { + "output": "mixer_s:Out", + "input": "s_phased:In" + }, + { + "output": "s_phased:Out", + "input": "mixer_lt:In 4" + }, + { + "output": "s_phased:Out", + "input": "mixer_rt:In 4" } + ], + "inputs": [ + "mixer_lt:In 1", + "mixer_rt:In 2", + "mixer_fc:In 1", + "mixer_fc:In 2", + "mixer_s:In 1", + "mixer_s:In 2" + ], + "outputs": [ + "mixer_lt:Out", + "mixer_rt:Out" + ] + }, + "capture.props": { + "node.name": "effect_input.dolby_surround", + "media.class": "Audio/Sink", + "audio.channels": 6, + "audio.position": [ + "FL", + "FR", + "FC", + "LFE", + "SL", + "SR" + ] + }, + "playback.props": { + "node.name": "effect_output.dolby_surround", + "node.passive": true, + "audio.channels": 2, + "audio.position": [ + "FL", + "FR" + ] } + } } -] + ] +} From 0e8a8e9844ba2aeb34048ead76eb8b36b45147f6 Mon Sep 17 00:00:00 2001 From: ZZyVSmOzMz OZaAEBlUIZ <1080187-ZZyVSmOzMz@users.noreply.gitlab.freedesktop.org> Date: Thu, 12 Jun 2025 06:38:32 +0000 Subject: [PATCH 0377/1014] filter-chain: add dolby pro logic ii sink --- src/daemon/filter-chain/meson.build | 1 + .../filter-chain/sink-dolby-pro-logic-ii.conf | 145 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/daemon/filter-chain/sink-dolby-pro-logic-ii.conf diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build index 0e6340643..774dcfd7f 100644 --- a/src/daemon/filter-chain/meson.build +++ b/src/daemon/filter-chain/meson.build @@ -6,6 +6,7 @@ conf_files = [ [ 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' ], [ 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' ], [ 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ], + [ 'sink-dolby-pro-logic-ii.conf', 'sink-dolby-pro-logic-ii.conf' ], [ 'sink-eq6.conf', 'sink-eq6.conf' ], [ 'sink-matrix-spatialiser.conf', 'sink-matrix-spatialiser.conf' ], [ 'source-rnnoise.conf', 'source-rnnoise.conf' ], diff --git a/src/daemon/filter-chain/sink-dolby-pro-logic-ii.conf b/src/daemon/filter-chain/sink-dolby-pro-logic-ii.conf new file mode 100644 index 000000000..036c69af4 --- /dev/null +++ b/src/daemon/filter-chain/sink-dolby-pro-logic-ii.conf @@ -0,0 +1,145 @@ +# Dolby Pro Logic II encoder sink +# +# Copy this file into a conf.d/ directory such as +# ~/.config/pipewire/filter-chain.conf.d/ +# +{ + "context.modules": [ + { + "name": "libpipewire-module-filter-chain", + "flags": [ + "nofail" + ], + "args": { + "node.description": "Dolby Pro Logic II Sink", + "media.name": "Dolby Pro Logic II Sink", + "filter.graph": { + "nodes": [ + { + "type": "builtin", + "name": "fc_copy", + "label": "copy" + }, + { + "type": "builtin", + "name": "lfe_copy", + "label": "copy" + }, + { + "type": "builtin", + "name": "sl_phased", + "label": "convolver", + "config": { + "filename": "/hilbert", + "length": 90 + } + }, + { + "type": "builtin", + "name": "sr_phased", + "label": "convolver", + "config": { + "filename": "/hilbert", + "length": 90 + } + }, + { + "type": "builtin", + "name": "mixer_lt", + "label": "mixer", + "control": { + "Gain 1": 1, + "Gain 2": 0, + "Gain 3": 0.7071067811865475, + "Gain 4": 0.7071067811865475, + "Gain 5": -0.8660254037844386, + "Gain 6": -0.5 + } + }, + { + "type": "builtin", + "name": "mixer_rt", + "label": "mixer", + "control": { + "Gain 1": 0, + "Gain 2": 1, + "Gain 3": 0.7071067811865475, + "Gain 4": 0.7071067811865475, + "Gain 5": 0.5, + "Gain 6": 0.8660254037844386 + } + } + ], + "links": [ + { + "output": "fc_copy:Out", + "input": "mixer_lt:In 3" + }, + { + "output": "fc_copy:Out", + "input": "mixer_rt:In 3" + }, + { + "output": "lfe_copy:Out", + "input": "mixer_lt:In 4" + }, + { + "output": "lfe_copy:Out", + "input": "mixer_rt:In 4" + }, + { + "output": "sl_phased:Out", + "input": "mixer_lt:In 5" + }, + { + "output": "sl_phased:Out", + "input": "mixer_rt:In 5" + }, + { + "output": "sr_phased:Out", + "input": "mixer_lt:In 6" + }, + { + "output": "sr_phased:Out", + "input": "mixer_rt:In 6" + } + ], + "inputs": [ + "mixer_lt:In 1", + "mixer_rt:In 2", + "fc_copy:In", + "lfe_copy:In", + "sl_phased:In", + "sr_phased:In" + ], + "outputs": [ + "mixer_lt:Out", + "mixer_rt:Out" + ] + }, + "capture.props": { + "node.name": "effect_input.dolby_pro_logic_ii", + "media.class": "Audio/Sink", + "audio.channels": 6, + "audio.position": [ + "FL", + "FR", + "FC", + "LFE", + "SL", + "SR" + ] + }, + "playback.props": { + "node.name": "effect_output.dolby_pro_logic_ii", + "node.passive": true, + "audio.channels": 2, + "audio.position": [ + "FL", + "FR" + ] + } + } + } + ] +} From d238fc8199cdf3202d95f0b1a50588f64b6fc22d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 09:21:28 +0200 Subject: [PATCH 0378/1014] doc: fix a typo --- doc/dox/internals/latency.dox | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/dox/internals/latency.dox b/doc/dox/internals/latency.dox index af3b5acf9..1a5893184 100644 --- a/doc/dox/internals/latency.dox +++ b/doc/dox/internals/latency.dox @@ -129,7 +129,7 @@ When we have a sink with a ProcessLatency, for example, of 512 samples: ^ | +----------+ +---- FL | - | sink | <- ProcessLatency: [ { "quantum": 0, "rate": 1024, "ns": 0 } ] + | sink | <- ProcessLatency: [ { "quantum": 0, "rate": 512, "ns": 0 } ] +---- FR | | +----------+ v @@ -192,8 +192,8 @@ If we place a node with a 256 sample latency in the above source-sink graph: ``` -See how the output latency propagates and is incremented going downstream and the -input latency is incremented and traveling upstream. +See how the output latency propagates and is increased going downstream and the +input latency is increased and traveling upstream. ## Link a port to two port with different latencies From f6fc307638c1ad826f71d9c23fc64eb4f86425b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 13 Mar 2025 10:59:28 +0100 Subject: [PATCH 0379/1014] bluez5: backend-native: Support legacy audio connection This allows to connect the SCO link with old HFP AG devices which doesn't support the codec negotiation. The audio connection could be done even without an ongoing call. --- spa/plugins/bluez5/backend-native.c | 36 +++++++++++++++++------------ 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index e42963c34..ec03e14e2 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1869,6 +1869,8 @@ static void hfp_hf_send_tones(void *data, const char *tones, enum spa_bt_telepho *err = BT_TELEPHONY_ERROR_NONE; } +static int sco_do_connect(struct spa_bt_transport *t); + static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) { struct rfcomm *rfcomm = data; @@ -1876,26 +1878,30 @@ static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *e char reply[20]; bool res; - if (spa_list_is_empty(&rfcomm->telephony_ag->call_list)) { - spa_log_debug(backend->log, "no ongoing call"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - if (rfcomm->transport->fd > 0) { + if (rfcomm->transport && rfcomm->transport->fd > 0) { spa_log_debug(backend->log, "transport is already active; SCO socket exists"); *err = BT_TELEPHONY_ERROR_INVALID_STATE; return; } - rfcomm_send_cmd(rfcomm, "AT+BCC"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to send AT+BCC"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + if (rfcomm->codec_negotiation_supported) { + rfcomm_send_cmd(rfcomm, "AT+BCC"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to send AT+BCC"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + } else { + if (!rfcomm->transport || rfcomm->transport->codec != HFP_AUDIO_CODEC_CVSD) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } + + sco_do_connect(rfcomm->transport); } *err = BT_TELEPHONY_ERROR_NONE; From c2eb173fdc154d83137c7961920e0d1318b16bc3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 10:02:10 +0200 Subject: [PATCH 0380/1014] spa: make the wave, pattern and ditherType Int Properties of type Id should have a type of the enum with the possible values associated with them. The other types that don't have a fixed enumeration but are usually mapped to some constant/description with PropInfo should be Int. Fixes !2399 --- spa/include/spa/param/props-types.h | 6 +++--- spa/plugins/test/fakesrc.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/param/props-types.h b/spa/include/spa/param/props-types.h index 4da520283..49f01286b 100644 --- a/spa/include/spa/param/props-types.h +++ b/spa/include/spa/param/props-types.h @@ -41,12 +41,12 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec }, { SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL }, - { SPA_PROP_waveType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL }, + { SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL }, { SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL }, { SPA_PROP_volume, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volume", NULL }, { SPA_PROP_mute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "mute", NULL }, - { SPA_PROP_patternType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "patternType", NULL }, - { SPA_PROP_ditherType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "ditherType", NULL }, + { SPA_PROP_patternType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "patternType", NULL }, + { SPA_PROP_ditherType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "ditherType", NULL }, { SPA_PROP_truncate, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "truncate", NULL }, { SPA_PROP_channelVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelVolumes", spa_type_prop_float_array }, { SPA_PROP_volumeBase, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeBase", NULL }, diff --git a/spa/plugins/test/fakesrc.c b/spa/plugins/test/fakesrc.c index f411359a1..28b37dab4 100644 --- a/spa/plugins/test/fakesrc.c +++ b/spa/plugins/test/fakesrc.c @@ -130,7 +130,7 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(p->live), - SPA_PROP_patternType, SPA_POD_CHOICE_ENUM_Id(2, p->pattern, p->pattern)); + SPA_PROP_patternType, SPA_POD_CHOICE_ENUM_Int(2, p->pattern, p->pattern)); break; } default: @@ -173,7 +173,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&p->live), - SPA_PROP_patternType, SPA_POD_OPT_Id(&p->pattern)); + SPA_PROP_patternType, SPA_POD_OPT_Int(&p->pattern)); if (p->live) port->info.flags |= SPA_PORT_FLAG_LIVE; From c1b5fb19eae85df50e2bc50697f9b97c6ad78b99 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 10:04:20 +0200 Subject: [PATCH 0381/1014] spa: add volume_ramp_scale enum type info And link it to the SPA_PROP_volumeRampScale property --- spa/include/spa/param/audio/raw-types.h | 9 +++++++++ spa/include/spa/param/props-types.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index 257789341..b3de74f86 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -274,6 +274,15 @@ SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32 return spa_type_to_short_name(type, spa_type_audio_channel, "UNK"); } +#define SPA_TYPE_INFO_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale" +#define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":" + +static const struct spa_type_info spa_type_audio_volume_ramp_scale[] = { + { SPA_AUDIO_VOLUME_RAMP_INVALID, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "INVALID", NULL }, + { SPA_AUDIO_VOLUME_RAMP_LINEAR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "LINEAR", NULL }, + { SPA_AUDIO_VOLUME_RAMP_CUBIC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "CUBIC", NULL }, + { 0, 0, NULL, NULL }, +}; /** * \} diff --git a/spa/include/spa/param/props-types.h b/spa/include/spa/param/props-types.h index 49f01286b..54d17339f 100644 --- a/spa/include/spa/param/props-types.h +++ b/spa/include/spa/param/props-types.h @@ -62,7 +62,7 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_volumeRampStepSamples, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepSamples", NULL }, { SPA_PROP_volumeRampTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampTime", NULL }, { SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL }, - { SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", NULL }, + { SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", spa_type_audio_volume_ramp_scale }, { SPA_PROP_brightness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, { SPA_PROP_contrast, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, From baf0b0b9e1dd07762d483b1726b1622fad2016d1 Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Fri, 13 Jun 2025 09:40:36 +0200 Subject: [PATCH 0382/1014] 50-raop.conf.in: Add a condition that allows for disabling module --- src/daemon/pipewire.conf.avail/50-raop.conf.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/daemon/pipewire.conf.avail/50-raop.conf.in b/src/daemon/pipewire.conf.avail/50-raop.conf.in index 088f4a597..22050b2a8 100644 --- a/src/daemon/pipewire.conf.avail/50-raop.conf.in +++ b/src/daemon/pipewire.conf.avail/50-raop.conf.in @@ -1,4 +1,6 @@ context.modules = [ # Use mDNS to detect and load module-raop-sink - { name = libpipewire-module-raop-discover } + { name = libpipewire-module-raop-discover + condition = [ { module.raop = !false } ] + } ] From 72e0fe0479dee9857ca67254d4b484e3546619f4 Mon Sep 17 00:00:00 2001 From: Elliot Chen Date: Fri, 13 Jun 2025 16:52:40 +0900 Subject: [PATCH 0383/1014] pipewiresrc: add provide clock property --- src/gst/gstpipewiresrc.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 95a724f46..0e9cab853 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -48,6 +48,7 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define DEFAULT_AUTOCONNECT true #define DEFAULT_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO #define DEFAULT_ON_DISCONNECT GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE +#define DEFAULT_PROVIDE_CLOCK TRUE enum { @@ -66,6 +67,7 @@ enum PROP_AUTOCONNECT, PROP_USE_BUFFERPOOL, PROP_ON_DISCONNECT, + PROP_PROVIDE_CLOCK, }; GType @@ -195,6 +197,16 @@ gst_pipewire_src_set_property (GObject * object, guint prop_id, pwsrc->on_disconnect = g_value_get_enum (value); break; + case PROP_PROVIDE_CLOCK: + gboolean provide = g_value_get_boolean (value); + GST_OBJECT_LOCK (pwsrc); + if (provide) + GST_OBJECT_FLAG_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + else + GST_OBJECT_FLAG_UNSET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + GST_OBJECT_UNLOCK (pwsrc); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -264,6 +276,14 @@ gst_pipewire_src_get_property (GObject * object, guint prop_id, g_value_set_enum (value, pwsrc->on_disconnect); break; + case PROP_PROVIDE_CLOCK: + gboolean result; + GST_OBJECT_LOCK (pwsrc); + result = GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + GST_OBJECT_UNLOCK (pwsrc); + g_value_set_boolean (value, result); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -453,6 +473,15 @@ gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_PROVIDE_CLOCK, + g_param_spec_boolean ("provide-clock", + "Provide Clock", + "Provide a clock to be used as the global pipeline clock", + DEFAULT_PROVIDE_CLOCK, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gstelement_class->provide_clock = gst_pipewire_src_provide_clock; gstelement_class->change_state = gst_pipewire_src_change_state; gstelement_class->send_event = gst_pipewire_src_send_event; From 3d8a19af33426ae22adc366e9db03e674aa1505c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 11:58:13 +0200 Subject: [PATCH 0384/1014] filter-chain: we manage the state from the playback stream --- src/modules/module-filter-chain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index e2a8f0683..a13bbbba7 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1311,7 +1311,7 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod * spa_zero(info); if (param == NULL) { pw_log_info("module %p: filter deactivate", impl); - if (direction == SPA_DIRECTION_INPUT) + if (direction == SPA_DIRECTION_OUTPUT) deactivate_graph(impl); impl->rate = 0; } else { From 3d1c9f1cce9bff67dbd722a47073e3cd54d8ef5a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 12:46:40 +0200 Subject: [PATCH 0385/1014] modules: update Props and ProcessLatency When setting Props or ProcessLatency on a stream, it doesn't actually remember the values that were set so we need to manually update them. See #4731 --- src/modules/module-filter-chain.c | 26 ++++++++++++++++---------- src/modules/module-loopback.c | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index a13bbbba7..2356cd4a3 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1198,26 +1198,32 @@ static int reset_graph(struct impl *impl) return res; } -static void update_latency(struct impl *impl, enum spa_direction direction) +static void update_latency(struct impl *impl, enum spa_direction direction, bool process) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *params[1]; + const struct spa_pod *params[2]; + uint32_t n_params = 0; struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? impl->playback : impl->capture; spa_pod_builder_init(&b, buffer, sizeof(buffer)); latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - pw_stream_update_params(s, params, 1); + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + + if (process) { + params[n_params++] = spa_process_latency_build(&b, + SPA_PARAM_ProcessLatency, &impl->process_latency); + } + pw_stream_update_params(s, params, n_params); } -static void update_latencies(struct impl *impl) +static void update_latencies(struct impl *impl, bool process) { - update_latency(impl, SPA_DIRECTION_INPUT); - update_latency(impl, SPA_DIRECTION_OUTPUT); + update_latency(impl, SPA_DIRECTION_INPUT, process); + update_latency(impl, SPA_DIRECTION_OUTPUT, process); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param, @@ -1229,7 +1235,7 @@ static void param_latency_changed(struct impl *impl, const struct spa_pod *param return; impl->latency[latency.direction] = latency; - update_latency(impl, latency.direction); + update_latency(impl, latency.direction, false); } static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, @@ -1245,7 +1251,7 @@ static void param_process_latency_changed(struct impl *impl, const struct spa_po return; impl->process_latency = process_latency; - update_latencies(impl); + update_latencies(impl, true); } static void param_tag_changed(struct impl *impl, const struct spa_pod *param, @@ -1567,7 +1573,7 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) if (spa_atod(s, &latency)) { if (impl->process_latency.rate != (int32_t)latency) { impl->process_latency.rate = (int32_t)latency; - update_latencies(impl); + update_latencies(impl, true); } } } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 0b552222f..b23d167c1 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -501,7 +501,7 @@ static void param_process_latency_changed(struct impl *impl, const struct spa_po return; impl->process_latency = info; - update_latencies(impl, true, false); + update_latencies(impl, true, true); } static void param_props_changed(struct impl *impl, const struct spa_pod *param) @@ -518,7 +518,7 @@ static void param_props_changed(struct impl *impl, const struct spa_pod *param) if (impl->process_latency.ns == nsec) return; impl->process_latency.ns = nsec; - update_latencies(impl, false, true); + update_latencies(impl, true, true); } static void param_tag_changed(struct impl *impl, const struct spa_pod *param, From 83c644fe090a86214b355bd5cfb2e46bb39068a3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 13 Jun 2025 18:07:55 +0200 Subject: [PATCH 0386/1014] pulse-server: mark empty buffers This makes it use some more optimal paths in the mixer. --- src/modules/module-protocol-pulse/pulse-server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 2f775fd8d..12ffed1c1 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1379,6 +1379,7 @@ static void stream_process(void *data) if (stream->direction == PW_DIRECTION_OUTPUT) { int32_t avail = spa_ringbuffer_get_read_index(&stream->ring, &index); + bool empty = false; minreq = buffer->requested * stream->frame_size; if (minreq == 0) @@ -1391,6 +1392,7 @@ static void stream_process(void *data) /* underrun, produce a silence buffer */ size = SPA_MIN(d->maxsize, minreq); sample_spec_silence(&stream->ss, p, size); + empty = true; if (stream->draining && !stream->corked) { stream->draining = false; @@ -1406,6 +1408,7 @@ static void stream_process(void *data) stream->buffer, MAXLENGTH, index % MAXLENGTH, p, avail); + empty = false; } index += size; pd.read_inc = size; @@ -1446,6 +1449,7 @@ static void stream_process(void *data) d->chunk->offset = 0; d->chunk->stride = stream->frame_size; d->chunk->size = size; + SPA_FLAG_UPDATE(d->chunk->flags, SPA_CHUNK_FLAG_EMPTY, empty); buffer->size = size / stream->frame_size; } else { int32_t filled = spa_ringbuffer_get_write_index(&stream->ring, &index); From 4e0d0c5f0befd3973b9ed22a7a45f2a9b3b7e449 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:18:49 +0300 Subject: [PATCH 0387/1014] bluez5: replace codec->bap/asha flags with codec->kind enum Indicate codec type with enum instead of bool flags. This is in preparation of moving also HFP to media codecs. --- spa/plugins/bluez5/a2dp-codec-aac.c | 2 + spa/plugins/bluez5/a2dp-codec-aptx.c | 3 ++ spa/plugins/bluez5/a2dp-codec-faststream.c | 1 + spa/plugins/bluez5/a2dp-codec-lc3plus.c | 1 + spa/plugins/bluez5/a2dp-codec-ldac.c | 1 + spa/plugins/bluez5/a2dp-codec-opus-g.c | 1 + spa/plugins/bluez5/a2dp-codec-opus.c | 1 + spa/plugins/bluez5/a2dp-codec-sbc.c | 2 + spa/plugins/bluez5/asha-codec-g722.c | 2 +- spa/plugins/bluez5/bap-codec-lc3.c | 2 +- spa/plugins/bluez5/bluez5-dbus.c | 43 ++++++++++++---------- spa/plugins/bluez5/codec-loader.c | 11 ++++++ spa/plugins/bluez5/iso-io.c | 2 +- spa/plugins/bluez5/media-codecs.c | 2 +- spa/plugins/bluez5/media-codecs.h | 29 +++++++++++++-- spa/plugins/bluez5/media-sink.c | 30 ++++++++------- spa/plugins/bluez5/media-source.c | 10 ++--- 17 files changed, 97 insertions(+), 46 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 7522f3bb7..4e50cae45 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -683,6 +683,7 @@ static void codec_set_log(struct spa_log *global_log) const struct media_codec a2dp_codec_aac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_MPEG24, .name = "aac", .description = "AAC", @@ -708,6 +709,7 @@ const struct media_codec a2dp_codec_aac = { const struct media_codec a2dp_codec_aac_eld = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_MPEG24, .name = "aac_eld", .description = "AAC-ELD", diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index e10691e78..d74238378 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -617,6 +617,7 @@ static int msbc_decode(void *data, const struct media_codec a2dp_codec_aptx = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_VENDOR_ID, .codec_id = APTX_CODEC_ID }, @@ -641,6 +642,7 @@ const struct media_codec a2dp_codec_aptx = { const struct media_codec a2dp_codec_aptx_hd = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_HD_VENDOR_ID, .codec_id = APTX_HD_CODEC_ID }, @@ -663,6 +665,7 @@ const struct media_codec a2dp_codec_aptx_hd = { }; #define APTX_LL_COMMON_DEFS \ + .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .description = "aptX-LL", \ .fill_caps = codec_fill_caps, \ diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index bbf9e96ab..1f1875171 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -584,6 +584,7 @@ static const struct media_codec duplex_codec = { }; #define FASTSTREAM_COMMON_DEFS \ + .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .vendor = { .vendor_id = FASTSTREAM_VENDOR_ID, \ .codec_id = FASTSTREAM_CODEC_ID }, \ diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index 05a3aa209..808a6b721 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -740,6 +740,7 @@ static int codec_increase_bitpool(void *data) const struct media_codec a2dp_codec_lc3plus_hr = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, + .kind = MEDIA_CODEC_A2DP, .name = "lc3plus_hr", .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LC3PLUS_HR_VENDOR_ID, diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index 3abc8e36c..4ae86b571 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -748,6 +748,7 @@ static void codec_set_log(struct spa_log *global_log) const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LDAC_VENDOR_ID, .codec_id = LDAC_CODEC_ID }, diff --git a/spa/plugins/bluez5/a2dp-codec-opus-g.c b/spa/plugins/bluez5/a2dp-codec-opus-g.c index 85a36bf8d..018a5a9ce 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus-g.c +++ b/spa/plugins/bluez5/a2dp-codec-opus-g.c @@ -512,6 +512,7 @@ static void codec_set_log(struct spa_log *global_log) const struct media_codec a2dp_codec_opus_g = { .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = OPUS_G_VENDOR_ID, .codec_id = OPUS_G_CODEC_ID }, diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index e65b62fac..42cb1caba 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -1349,6 +1349,7 @@ static void codec_set_log(struct spa_log *global_log) } #define OPUS_05_COMMON_DEFS \ + .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .vendor = { .vendor_id = OPUS_05_VENDOR_ID, \ .codec_id = OPUS_05_CODEC_ID }, \ diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index fc55a031d..5c178c87b 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -634,6 +634,7 @@ static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) const struct media_codec a2dp_codec_sbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_SBC, .name = "sbc", .description = "SBC", @@ -657,6 +658,7 @@ const struct media_codec a2dp_codec_sbc = { const struct media_codec a2dp_codec_sbc_xq = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, + .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_SBC, .name = "sbc_xq", .description = "SBC-XQ", diff --git a/spa/plugins/bluez5/asha-codec-g722.c b/spa/plugins/bluez5/asha-codec-g722.c index 8faa920cb..3aaed84f2 100644 --- a/spa/plugins/bluez5/asha-codec-g722.c +++ b/spa/plugins/bluez5/asha-codec-g722.c @@ -155,9 +155,9 @@ static void codec_set_log(struct spa_log *global_log) const struct media_codec asha_codec_g722 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_G722, + .kind = MEDIA_CODEC_ASHA, .codec_id = ASHA_CODEC_G722, .name = "g722", - .asha = true, .description = "G722", .fill_caps = NULL, .enum_config = codec_enum_config, diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 2f1117ef8..80174dd3d 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -1401,9 +1401,9 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps, const struct media_codec bap_codec_lc3 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3, + .kind = MEDIA_CODEC_BAP, .name = "lc3", .codec_id = BAP_CODEC_LC3, - .bap = true, .description = "LC3", .fill_caps = codec_fill_caps, .select_config = codec_select_config, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 61491056f..ba2ac0bbc 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -458,9 +458,9 @@ static int media_codec_to_endpoint(const struct media_codec *codec, const char * endpoint; if (direction == SPA_BT_MEDIA_SOURCE) - endpoint = codec->bap ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT; + endpoint = codec->kind == MEDIA_CODEC_BAP ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT; else if (direction == SPA_BT_MEDIA_SINK) - endpoint = codec->bap ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT; + endpoint = codec->kind == MEDIA_CODEC_BAP ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT; else if (direction == SPA_BT_MEDIA_SOURCE_BROADCAST) endpoint = BAP_BROADCAST_SOURCE_ENDPOINT; else if (direction == SPA_BT_MEDIA_SINK_BROADCAST) @@ -568,11 +568,14 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, { switch (direction) { case SPA_BT_MEDIA_SOURCE: - return codec->bap ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; + return codec->kind == MEDIA_CODEC_BAP ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_MEDIA_SINK: - if (codec->asha) + if (codec->kind == MEDIA_CODEC_ASHA) return SPA_BT_PROFILE_ASHA_SINK; - return codec->bap ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_A2DP_SINK; + else if (codec->kind == MEDIA_CODEC_BAP) + return SPA_BT_PROFILE_BAP_SINK; + else + return SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_MEDIA_SOURCE_BROADCAST: return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; case SPA_BT_MEDIA_SINK_BROADCAST: @@ -971,7 +974,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe */ codec = media_endpoint_to_codec(monitor, path, &sink, NULL); spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : ""); - if (!codec || !codec->bap || !codec->get_qos) { + if (!codec || codec->kind != MEDIA_CODEC_BAP || !codec->get_qos) { spa_log_error(monitor->log, "Unsupported codec"); err_msg = "Unsupported codec"; goto error; @@ -2537,7 +2540,7 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, }; - bool is_a2dp = !codec->bap && !codec->asha; + bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP; size_t i; if (!is_media_codec_enabled(device->monitor, codec)) @@ -2548,7 +2551,7 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru return (codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc") && device->adapter->legacy_endpoints_registered); } - if (!device->adapter->bap_application_registered && codec->bap) + if (!device->adapter->bap_application_registered && codec->kind == MEDIA_CODEC_BAP) return false; /* Check codec quirks */ @@ -4178,7 +4181,7 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, for (int i = 0; media_codecs[i]; i++) { const struct media_codec *mcodec = media_codecs[i]; - if (!mcodec->asha) + if (mcodec->kind != MEDIA_CODEC_ASHA) continue; if (!spa_streq(mcodec->name, "g722")) continue; @@ -4577,7 +4580,7 @@ static int media_codec_switch_cmp(const void *a, const void *b) else if (ep2 == NULL) return -1; - if (codec->bap) + if (codec->kind == MEDIA_CODEC_BAP) flags = spa_streq(ep1->uuid, SPA_BT_UUID_BAP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; else flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; @@ -5178,10 +5181,9 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * uint8_t caps[A2DP_MAX_CAPS_SIZE]; int caps_size, ret; uint16_t codec_id = codec->codec_id; + enum media_codec_kind kind = is_bap ? MEDIA_CODEC_BAP : MEDIA_CODEC_A2DP; - if (codec->bap != is_bap) - continue; - if (codec->asha) + if (codec->kind != kind) continue; if (!is_media_codec_enabled(monitor, codec)) @@ -5197,7 +5199,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * if (ret == 0) { spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, - codec->bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, + is_bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, codec_id, caps, caps_size); } } @@ -5212,12 +5214,12 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * if (ret == 0) { spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, - codec->bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, + is_bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, codec_id, caps, caps_size); } } - if (codec->bap && register_bcast) { + if (is_bap && register_bcast) { if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) { caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps); if (caps_size < 0) @@ -5375,7 +5377,7 @@ static int register_media_application(struct spa_bt_monitor * monitor) register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); - if (codec->bap) { + if (codec->kind == MEDIA_CODEC_BAP) { register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST); register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST); } @@ -5411,7 +5413,7 @@ static void unregister_media_application(struct spa_bt_monitor * monitor) unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); - if (codec->bap) { + if (codec->kind == MEDIA_CODEC_BAP) { unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST); unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST); } @@ -5428,8 +5430,9 @@ static bool have_codec_endpoints(struct spa_bt_monitor *monitor, bool bap) for (i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; + enum media_codec_kind kind = bap ? MEDIA_CODEC_BAP : MEDIA_CODEC_A2DP; - if (codec->bap != bap) + if (codec->kind != kind) continue; if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK) || endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE) || @@ -5779,7 +5782,7 @@ static void interface_added(struct spa_bt_monitor *monitor, /* get local endpoint */ for (i = 0; monitor->media_codecs[i]; i++) { - if (!monitor->media_codecs[i]->bap) + if (monitor->media_codecs[i]->kind != MEDIA_CODEC_BAP) continue; if (!is_media_codec_enabled(monitor, monitor->media_codecs[i])) continue; diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 6fd1d0430..ba2c1aa5e 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -120,6 +120,17 @@ static int load_media_codecs_from(struct impl *impl, const char *factory_name, c break; } + switch (c->kind) { + case MEDIA_CODEC_A2DP: + case MEDIA_CODEC_BAP: + case MEDIA_CODEC_ASHA: + break; + default: + spa_log_warn(impl->log, "codec plugin %s: unknown codec %s kind %d", + factory_name, c->name, c->kind); + continue; + } + /* Don't load duplicate endpoints */ for (j = 0; j < impl->n_codecs; ++j) { const struct media_codec *c2 = impl->codecs[j]; diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index cdfc6ec4f..8fc488eec 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -417,7 +417,7 @@ static struct stream *stream_create(struct spa_bt_transport *t, struct group *gr sink = false; } - if (!t->media_codec->bap || !t->media_codec->get_interval) { + if (t->media_codec->kind != MEDIA_CODEC_BAP || !t->media_codec->get_interval) { res = -EINVAL; goto fail; } diff --git a/spa/plugins/bluez5/media-codecs.c b/spa/plugins/bluez5/media-codecs.c index 718e54ea2..00a0bbf08 100644 --- a/spa/plugins/bluez5/media-codecs.c +++ b/spa/plugins/bluez5/media-codecs.c @@ -91,7 +91,7 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_ if (res < 0) return false; - if (codec->bap) + if (codec->kind == MEDIA_CODEC_BAP) return true; else return ((size_t)res == caps_size); diff --git a/spa/plugins/bluez5/media-codecs.h b/spa/plugins/bluez5/media-codecs.h index ed62cd131..0030ee615 100644 --- a/spa/plugins/bluez5/media-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -26,7 +26,7 @@ #define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 13 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 14 struct spa_bluez5_codec_a2dp { struct spa_interface iface; @@ -60,6 +60,13 @@ enum { NEED_FLUSH_FRAGMENT = 2, }; +enum media_codec_kind { + MEDIA_CODEC_A2DP, + MEDIA_CODEC_BAP, + MEDIA_CODEC_ASHA, + MEDIA_CODEC_HFP, +}; + struct media_codec_audio_info { uint32_t rate; uint32_t channels; @@ -67,12 +74,11 @@ struct media_codec_audio_info { struct media_codec { enum spa_bluetooth_audio_codec id; + enum media_codec_kind kind; + uint8_t codec_id; a2dp_vendor_codec_t vendor; - bool bap; - bool asha; - const char *name; const char *description; const char *endpoint_name; /**< Endpoint name. If NULL, same as name */ @@ -219,6 +225,21 @@ struct media_codec_config { unsigned int priority; }; +static inline const char *media_codec_kind_str(const struct media_codec *codec) +{ + switch (codec->kind) { + case MEDIA_CODEC_A2DP: + return "A2DP"; + case MEDIA_CODEC_BAP: + return "BAP"; + case MEDIA_CODEC_ASHA: + return "ASHA"; + case MEDIA_CODEC_HFP: + return "HFP"; + } + return "unknown"; +} + int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value); diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 374db005a..22dbad068 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -649,8 +649,12 @@ static int reset_buffer(struct impl *this) this->need_flush = 0; this->block_count = 0; this->fragment = false; - this->timestamp = (this->codec->bap || this->codec->asha) ? (get_reference_time(this, NULL) / SPA_NSEC_PER_USEC) - : this->sample_count; + + if (this->codec->kind == MEDIA_CODEC_BAP || this->codec->kind == MEDIA_CODEC_ASHA) + this->timestamp = get_reference_time(this, NULL) / SPA_NSEC_PER_USEC; + else + this->timestamp = this->sample_count; + this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer, sizeof(this->buffer), ++this->seqnum, this->timestamp); @@ -865,7 +869,7 @@ static void enable_flush_timer(struct impl *this, bool enabled) static int flush_data(struct impl *this, uint64_t now_time) { struct port *port = &this->port; - bool is_asha = this->codec->asha; + bool is_asha = this->codec->kind == MEDIA_CODEC_ASHA; uint32_t total_frames; int written; int unsent_buffer; @@ -1447,7 +1451,7 @@ static int transport_start(struct impl *this) conf = this->transport->configuration; size = this->transport->configuration_len; - is_asha = this->codec->asha; + is_asha = this->codec->kind == MEDIA_CODEC_ASHA; spa_log_debug(this->log, "Transport configuration:"); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); @@ -1478,7 +1482,7 @@ static int transport_start(struct impl *this) if (this->codec->get_delay) this->codec->get_delay(this->codec_data, &this->encoder_delay, NULL); - const char *codec_profile = this->codec->asha ? "ASHA" : (this->codec->bap ? "BAP" : "A2DP"); + const char *codec_profile = media_codec_kind_str(this->codec); spa_log_info(this->log, "%p: using %s codec %s, delay:%.2f ms, codec-delay:%.2f ms", this, codec_profile, this->codec->description, (double)spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC, @@ -1653,7 +1657,7 @@ static int do_remove_transport_source(struct spa_loop *loop, if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); - if (this->codec->asha) { + if (this->codec->kind == MEDIA_CODEC_ASHA) { if (this->asha->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->asha->timer_source); if (this->asha->flush_source.loop) @@ -1761,7 +1765,7 @@ static void emit_node_info(struct impl *this, bool full) node_group = node_group_buf; } - const char *codec_profile = this->codec->asha ? "ASHA" : (this->codec->bap ? "BAP" : "A2DP"); + const char *codec_profile = media_codec_kind_str(this->codec); struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : @@ -1945,7 +1949,7 @@ impl_node_port_enum_params(void *object, int seq, SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: - if (!this->codec->bap) + if (this->codec->kind != MEDIA_CODEC_BAP) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, @@ -2158,7 +2162,7 @@ impl_node_port_set_io(void *object, port->io = data; break; case SPA_IO_RateMatch: - if (!this->codec->bap) + if (this->codec->kind != MEDIA_CODEC_BAP) return -ENOENT; port->rate_match = data; break; @@ -2244,7 +2248,7 @@ static int impl_node_process(void *object) setup_matching(this); - if (this->codec->asha && !this->asha->set_timer) { + if (this->codec->kind == MEDIA_CODEC_ASHA && !this->asha->set_timer) { struct impl *other = find_other_asha(this); if (other && other->asha->ref_t0 != 0) { this->asha->ref_t0 = other->asha->ref_t0; @@ -2390,7 +2394,7 @@ static int impl_clear(struct spa_handle *handle) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); spa_system_close(this->data_system, this->flush_timerfd); - if (this->codec->asha) { + if (this->codec->kind == MEDIA_CODEC_ASHA) { spa_system_close(this->data_system, this->asha->timerfd); free(this->asha); } @@ -2519,7 +2523,7 @@ impl_init(const struct spa_handle_factory *factory, this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->device->settings); - if (this->codec->bap) + if (this->codec->kind == MEDIA_CODEC_BAP) this->is_output = this->transport->bap_initiator; else this->is_output = true; @@ -2537,7 +2541,7 @@ impl_init(const struct spa_handle_factory *factory, this->flush_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - if (this->codec->asha) { + if (this->codec->kind == MEDIA_CODEC_ASHA) { this->asha = calloc(1, sizeof(struct spa_bt_asha)); if (this->asha == NULL) return -ENOMEM; diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index e8d77c1d3..c53ee9164 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -324,7 +324,7 @@ static void emit_node_info(struct impl *this, bool full); static void set_latency(struct impl *this, bool emit_latency) { - if (this->codec->bap && !this->is_input && this->transport && + if (this->codec->kind == MEDIA_CODEC_BAP && !this->is_input && this->transport && this->transport->delay_us != SPA_BT_UNKNOWN_DELAY) { struct port *port = &this->port; unsigned int node_latency = 2048; @@ -751,7 +751,7 @@ static int transport_start(struct impl *this) return -EIO; spa_log_info(this->log, "%p: using %s codec %s", this, - this->codec->bap ? "BAP" : "A2DP", this->codec->description); + media_codec_kind_str(this->codec), this->codec->description); /* * If the link is bidirectional, media-sink may also be polling the same FD, @@ -983,7 +983,7 @@ static void emit_node_info(struct impl *this, bool full) sizeof(media_name), "%s (codec %s)", ((this->transport && this->transport->device->name) ? - this->transport->device->name : this->codec->bap ? "BAP" : "A2DP"), + this->transport->device->name : media_codec_kind_str(this->codec)), this->codec->description ); @@ -1465,7 +1465,7 @@ static void update_target_latency(struct impl *this) if (this->transport == NULL || !port->have_format) return; - if (!this->codec->bap || this->is_input || + if (this->codec->kind != MEDIA_CODEC_BAP || this->is_input || this->transport->delay_us == SPA_BT_UNKNOWN_DELAY) return; @@ -1905,7 +1905,7 @@ impl_init(const struct spa_handle_factory *factory, this->is_input = true; } - if (this->codec->bap) + if (this->codec->kind == MEDIA_CODEC_BAP) this->is_input = this->transport->bap_initiator; if (this->codec->init_props != NULL) From e6f5fb12a355ec5cf0f9a052d3216ee571a39eea Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:31:44 +0300 Subject: [PATCH 0388/1014] bluez5: add HFP codecs in the media codec API Add copy of HFP codec implementations in the media codec API. --- spa/plugins/bluez5/codec-loader.c | 26 ++- spa/plugins/bluez5/hfp-codec-cvsd.c | 199 ++++++++++++++++++++ spa/plugins/bluez5/hfp-codec-lc3-swb.c | 243 +++++++++++++++++++++++++ spa/plugins/bluez5/hfp-codec-msbc.c | 230 +++++++++++++++++++++++ spa/plugins/bluez5/hfp-h2.h | 128 +++++++++++++ spa/plugins/bluez5/media-codecs.c | 3 + spa/plugins/bluez5/meson.build | 24 +++ 7 files changed, 845 insertions(+), 8 deletions(-) create mode 100644 spa/plugins/bluez5/hfp-codec-cvsd.c create mode 100644 spa/plugins/bluez5/hfp-codec-lc3-swb.c create mode 100644 spa/plugins/bluez5/hfp-codec-msbc.c create mode 100644 spa/plugins/bluez5/hfp-h2.h diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index ba2c1aa5e..1557d1f7c 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -52,6 +52,9 @@ static int codec_order(const struct media_codec *c) SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_BLUETOOTH_AUDIO_CODEC_G722, + SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + SPA_BLUETOOTH_AUDIO_CODEC_CVSD, }; size_t i; for (i = 0; i < SPA_N_ELEMENTS(order); ++i) @@ -124,6 +127,7 @@ static int load_media_codecs_from(struct impl *impl, const char *factory_name, c case MEDIA_CODEC_A2DP: case MEDIA_CODEC_BAP: case MEDIA_CODEC_ASHA: + case MEDIA_CODEC_HFP: break; default: spa_log_warn(impl->log, "codec plugin %s: unknown codec %s kind %d", @@ -171,7 +175,6 @@ fail: const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) { struct impl *impl; - bool has_sbc; size_t i; const struct { const char *factory; const char *lib; } plugins[] = { #define MEDIA_CODEC_FACTORY_LIB(basename) \ @@ -185,7 +188,10 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo MEDIA_CODEC_FACTORY_LIB("opus"), MEDIA_CODEC_FACTORY_LIB("opus-g"), MEDIA_CODEC_FACTORY_LIB("lc3"), - MEDIA_CODEC_FACTORY_LIB("g722") + MEDIA_CODEC_FACTORY_LIB("g722"), + MEDIA_CODEC_FACTORY_LIB("hfp-cvsd"), + MEDIA_CODEC_FACTORY_LIB("hfp-msbc"), + MEDIA_CODEC_FACTORY_LIB("hfp-lc3-swb"), #undef MEDIA_CODEC_FACTORY_LIB }; @@ -201,13 +207,17 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); - has_sbc = false; - for (i = 0; i < impl->n_codecs; ++i) - if (impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC) - has_sbc = true; + bool has_sbc = false, has_cvsd = false; + for (i = 0; i < impl->n_codecs; ++i) { + has_sbc |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC; + has_cvsd |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD; + } - if (!has_sbc) { - spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); + if (!has_sbc || !has_cvsd) { + if (!has_sbc) + spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); + if (!has_cvsd) + spa_log_error(impl->log, "failed to load HFP CVSD codec from plugins"); free_media_codecs(impl->codecs); errno = ENOENT; return NULL; diff --git a/spa/plugins/bluez5/hfp-codec-cvsd.c b/spa/plugins/bluez5/hfp-codec-cvsd.c new file mode 100644 index 000000000..2b776b540 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-cvsd.c @@ -0,0 +1,199 @@ +/* Spa HFP CVSD Codec */ +/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +static struct spa_log *log; + +struct impl { + size_t block_size; + uint16_t seq; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16_LE), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 8000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; + info->info.raw.rate = 8000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct impl *this = NULL; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + if (mtu < 2) + return NULL; + + this = calloc(1, sizeof(struct impl)); + if (!this) + return NULL; + + this->block_size = 2 * (mtu/2); + return this; +} + +static void codec_deinit(void *data) +{ + free(data); +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + + return this->block_size; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + + if (src_size < this->block_size) + return -EINVAL; + if (dst_size < this->block_size) + return -EINVAL; + + spa_memmove(dst, src, this->block_size); + *dst_out = this->block_size; + *need_flush = NEED_FLUSH_ALL; + return this->block_size; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + + if (src_size != 48 && is_zero_packet(src, src_size)) { + /* Adapter is returning non-standard CVSD stream. For example + * Intel 8087:0029 at Firmware revision 0.0 build 191 week 21 2021 + * on kernel 5.13.19 produces such data. + */ + return -EINVAL; + } + + if (src_size % 2 != 0) { + /* Unaligned data: reception or adapter problem. + * Consider the whole packet lost and report. + */ + return -EINVAL; + } + + if (seqnum) + *seqnum = this->seq; + + if (timestamp) + *timestamp = 0; + + this->seq++; + return 0; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int dummy; + + return codec_encode(this, src, src_size, dst, dst_size, dst_out, &dummy); +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_cvsd = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_CVSD, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x01, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "cvsd", + .description = "CVSD", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-cvsd", + &hfp_codec_cvsd +); diff --git a/spa/plugins/bluez5/hfp-codec-lc3-swb.c b/spa/plugins/bluez5/hfp-codec-lc3-swb.c new file mode 100644 index 000000000..487c1dfa5 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-lc3-swb.c @@ -0,0 +1,243 @@ +/* Spa HFP LC3-SWB Codec */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +#include + +#define LC3_SWB_BLOCK_SIZE 960 + +static struct spa_log *log; + +struct impl { + lc3_encoder_t enc; + lc3_decoder_t dec; + struct h2_reader h2; + uint16_t seq; + + void *data; + size_t avail; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 32000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_F32; + info->info.raw.rate = 32000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct impl *this = NULL; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + this = calloc(1, sizeof(*this)); + if (!this) + goto fail; + + this->enc = lc3_setup_encoder(7500, 32000, 0, + calloc(1, lc3_encoder_size(7500, 32000))); + if (!this->enc) + goto fail; + + this->dec = lc3_setup_decoder(7500, 32000, 0, + calloc(1, lc3_decoder_size(7500, 32000))); + if (!this->dec) + goto fail; + + spa_assert(lc3_frame_samples(7500, 32000) * sizeof(float) == LC3_SWB_BLOCK_SIZE); + + h2_reader_init(&this->h2, false); + + return spa_steal_ptr(this); + +fail: + if (this) { + free(this->enc); + free(this->dec); + free(this); + } + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + + free(this->enc); + free(this->dec); + free(this); +} + +static int codec_get_block_size(void *data) +{ + return LC3_SWB_BLOCK_SIZE; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + + this->seq = seqnum; + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + int res; + + if (src_size < LC3_SWB_BLOCK_SIZE) + return -EINVAL; + if (dst_size < H2_PACKET_SIZE) + return -EINVAL; + + h2_write(dst, this->seq); + + res = lc3_encode(this->enc, LC3_PCM_FORMAT_FLOAT, src, 1, + H2_PACKET_SIZE - 2, SPA_PTROFF(dst, 2, void)); + if (res != 0) + return -EINVAL; + + *dst_out = H2_PACKET_SIZE; + *need_flush = NEED_FLUSH_ALL; + return LC3_SWB_BLOCK_SIZE; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + size_t consumed; + + if (is_zero_packet(src, src_size)) + return -EINVAL; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + + if (seqnum) + *seqnum = this->h2.seq; + if (timestamp) + *timestamp = 0; + return consumed; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + size_t consumed = 0; + int res; + + *dst_out = 0; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + if (!this->data) + return consumed; + + res = lc3_decode(this->dec, this->data, this->avail, LC3_PCM_FORMAT_FLOAT, dst, 1); + this->data = NULL; + + if (res) { + h2_reader_init(&this->h2, true); + return -EINVAL; + } + + *dst_out = LC3_SWB_BLOCK_SIZE; + return consumed; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_msbc = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x03, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "lc3_swb", + .description = "LC3-SWB", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-lc3-swb", + &hfp_codec_msbc +); diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c new file mode 100644 index 000000000..3a9112b6d --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -0,0 +1,230 @@ +/* Spa HFP MSBC Codec */ +/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +#define MSBC_BLOCK_SIZE 240 + +static struct spa_log *log; + +struct impl { + sbc_t msbc; + struct h2_reader h2; + uint16_t seq; + + void *data; + size_t avail; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 16000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; + info->info.raw.rate = 16000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + spa_autofree struct impl *this = NULL; + int res; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + this = calloc(1, sizeof(*this)); + if (!this) + return NULL; + + res = sbc_init_msbc(&this->msbc, 0); + if (res < 0) + return NULL; + + /* Libsbc expects audio samples by default in host endianness, mSBC requires little endian */ + this->msbc.endian = SBC_LE; + + h2_reader_init(&this->h2, true); + + return spa_steal_ptr(this); +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + + sbc_finish(&this->msbc); + free(this); +} + +static int codec_get_block_size(void *data) +{ + return MSBC_BLOCK_SIZE; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + + this->seq = seqnum; + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + ssize_t written = 0; + int res; + + if (src_size < MSBC_BLOCK_SIZE) + return -EINVAL; + if (dst_size < H2_PACKET_SIZE) + return -EINVAL; + + h2_write(dst, this->seq); + + res = sbc_encode(&this->msbc, src, src_size, SPA_PTROFF(dst, 2, void), H2_PACKET_SIZE - 3, &written); + if (res < 0) + return -EINVAL; + + *dst_out = H2_PACKET_SIZE; + *need_flush = NEED_FLUSH_ALL; + return res; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + size_t consumed; + + if (is_zero_packet(src, src_size)) + return -EINVAL; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + + if (seqnum) + *seqnum = this->h2.seq; + if (timestamp) + *timestamp = 0; + return consumed; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + size_t consumed = 0; + int res; + + *dst_out = 0; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + if (!this->data) + return consumed; + + res = sbc_decode(&this->msbc, this->data, this->avail, dst, dst_size, dst_out); + this->data = NULL; + + if (res < 0) { + h2_reader_init(&this->h2, true); + return res; + } + + return consumed; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_msbc = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x02, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "msbc", + .description = "MSBC", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-msbc", + &hfp_codec_msbc +); diff --git a/spa/plugins/bluez5/hfp-h2.h b/spa/plugins/bluez5/hfp-h2.h new file mode 100644 index 000000000..064d5249d --- /dev/null +++ b/spa/plugins/bluez5/hfp-h2.h @@ -0,0 +1,128 @@ +/* Spa HFP Codecs */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_HFP_H2_H +#define SPA_BLUEZ5_HFP_H2_H + +#define H2_PACKET_SIZE 60 + +struct h2_reader { + uint8_t buf[H2_PACKET_SIZE]; + uint8_t pos; + bool msbc; + uint16_t seq; + bool started; +}; + +struct h2_writer { + uint8_t seq; +}; + +static inline void h2_reader_init(struct h2_reader *this, bool msbc) +{ + this->pos = 0; + this->msbc = msbc; + this->seq = 0; + this->started = false; +} + +static inline void h2_reader_append_byte(struct h2_reader *this, uint8_t byte) +{ + /* Parse H2 sync header */ + if (this->pos == 0) { + if (byte != 0x01) { + this->pos = 0; + return; + } + } else if (this->pos == 1) { + if (!((byte & 0x0F) == 0x08 && + ((byte >> 4) & 1) == ((byte >> 5) & 1) && + ((byte >> 6) & 1) == ((byte >> 7) & 1))) { + this->pos = 0; + return; + } + } else if (this->msbc) { + /* Beginning of MSBC frame: SYNCWORD + 2 nul bytes */ + if (this->pos == 2) { + if (byte != 0xAD) { + this->pos = 0; + return; + } + } + else if (this->pos == 3) { + if (byte != 0x00) { + this->pos = 0; + return; + } + } + else if (this->pos == 4) { + if (byte != 0x00) { + this->pos = 0; + return; + } + } + } + + if (this->pos >= H2_PACKET_SIZE) { + /* Packet completed. Reset. */ + this->pos = 0; + h2_reader_append_byte(this, byte); + return; + } + + this->buf[this->pos] = byte; + ++this->pos; +} + +static inline void *h2_reader_read(struct h2_reader *this, const uint8_t *src, size_t src_size, size_t *consumed, size_t *avail) +{ + int seq; + size_t i; + + for (i = 0; i < src_size && this->pos < H2_PACKET_SIZE; ++i) + h2_reader_append_byte(this, src[i]); + + *consumed = i; + *avail = 0; + + if (this->pos < H2_PACKET_SIZE) + return NULL; + + this->pos = 0; + + seq = ((this->buf[1] >> 4) & 1) | ((this->buf[1] >> 6) & 2); + if (!this->started) { + this->seq = seq; + this->started = true; + } + + this->seq++; + while (seq != this->seq % 4) + this->seq++; + + *avail = H2_PACKET_SIZE - 2; + return &this->buf[2]; +} + +static inline void h2_write(uint8_t *buf, uint8_t seq) +{ + static const uint8_t sntable[4] = { 0x08, 0x38, 0xc8, 0xf8 }; + + buf[0] = 0x01; + buf[1] = sntable[seq % 4]; + buf[59] = 0; +} + +static inline bool is_zero_packet(const uint8_t *data, size_t size) +{ + size_t i; + + for (i = 0; i < size; ++i) + if (data[i]) + return false; + + return true; +} + +#endif diff --git a/spa/plugins/bluez5/media-codecs.c b/spa/plugins/bluez5/media-codecs.c index 00a0bbf08..bd24d08f8 100644 --- a/spa/plugins/bluez5/media-codecs.c +++ b/spa/plugins/bluez5/media-codecs.c @@ -81,6 +81,9 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_ uint8_t config[A2DP_MAX_CAPS_SIZE]; int res; + if (codec->kind == MEDIA_CODEC_HFP) + return true; + if (codec_id != codec->codec_id) return false; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 154dc9fe9..13c46ad87 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -100,6 +100,22 @@ bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', install : true, install_dir : spa_plugindir / 'bluez5') +bluez_codec_hfp_cvsd = shared_library('spa-codec-bluez5-hfp-cvsd', + [ 'hfp-codec-cvsd.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') + +bluez_codec_hfp_msbc = shared_library('spa-codec-bluez5-hfp-msbc', + [ 'hfp-codec-msbc.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, sbc_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') + if fdk_aac_dep.found() bluez_codec_aac = shared_library('spa-codec-bluez5-aac', [ 'a2dp-codec-aac.c', 'media-codecs.c' ], @@ -175,6 +191,14 @@ if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') + + bluez_codec_hfp_lc3_swb = shared_library('spa-codec-bluez5-hfp-lc3-swb', + [ 'hfp-codec-lc3-swb.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, lc3_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-g722').allowed() From 62bec49c279685d242ab3cb92ef52f9be97929df Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:56:04 +0300 Subject: [PATCH 0389/1014] bluez5: add spa_bt_get_hfp_codec() / spa_bt_get_media_codecs() These are for HFP backends to get media codecs. --- spa/plugins/bluez5/bluez5-dbus.c | 24 ++++++++++++++++++++++++ spa/plugins/bluez5/defs.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index ba2ac0bbc..143c65909 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -451,6 +451,11 @@ static void register_battery_provider(struct spa_bt_device *device) } } +const struct media_codec * const * spa_bt_get_media_codecs(struct spa_bt_monitor *monitor) +{ + return monitor->media_codecs; +} + static int media_codec_to_endpoint(const struct media_codec *codec, enum spa_bt_media_direction direction, char** object_path) @@ -2642,6 +2647,25 @@ const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_b return spa_steal_ptr(supported_codecs); } +const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, unsigned int hfp_codec_id) +{ + const struct media_codec * const * const media_codecs = monitor->media_codecs; + size_t i; + + for (i = 0; media_codecs[i] != NULL; ++i) { + const struct media_codec *codec = media_codecs[i]; + + if (codec->kind != MEDIA_CODEC_HFP) + continue; + if (!is_media_codec_enabled(monitor, codec)) + continue; + if (codec->codec_id == hfp_codec_id) + return codec; + } + + return NULL; +} + static struct spa_bt_remote_endpoint *device_remote_endpoint_find(struct spa_bt_device *device, const char *path) { struct spa_bt_remote_endpoint *ep; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 75ddd109c..4ae6a34ce 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -593,6 +593,8 @@ int spa_bt_device_supports_hfp_codec(struct spa_bt_device *device, unsigned int int spa_bt_device_release_transports(struct spa_bt_device *device); int spa_bt_device_report_battery_level(struct spa_bt_device *device, uint8_t percentage); void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device); +const struct media_codec * const * spa_bt_get_media_codecs(struct spa_bt_monitor *monitor); +const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, unsigned int hfp_codec_id); #define spa_bt_device_emit(d,m,v,...) spa_hook_list_call(&(d)->listener_list, \ struct spa_bt_device_events, \ From 13256e9083788dfc41c8da74bc942c4fb99faf79 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 21:04:44 +0300 Subject: [PATCH 0390/1014] bluez5: backend-native: set media_codec on created transports Set media_codec on created transports. Also avoid using the HFP codec id in spa_bt_transport::codec --- spa/plugins/bluez5/backend-native.c | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index ec03e14e2..91fdb3ba8 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -32,6 +32,7 @@ #include #include "defs.h" +#include "media-codecs.h" #ifdef HAVE_LIBUSB #include @@ -244,9 +245,10 @@ static const struct spa_bt_transport_events transport_events = { static const struct spa_bt_transport_implementation sco_transport_impl; -static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) +static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec_id) { struct impl *backend = rfcomm->backend; + const struct media_codec *codec; struct spa_bt_transport *t = NULL; struct transport_data *td; char* pathfd; @@ -257,6 +259,12 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) rfcomm->transport = NULL; } + codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); + if (!codec) { + spa_log_warn(backend->log, "failed to get HFP codec %d", codec_id); + goto fail; + } + if ((pathfd = spa_aprintf("%s/fd%d", rfcomm->path, rfcomm->source.fd)) == NULL) goto fail; @@ -273,7 +281,8 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) t->backend = &backend->this; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; - t->codec = codec; + t->codec = codec_id; + t->media_codec = codec; td = t->user_data; td->rfcomm = rfcomm; @@ -297,7 +306,7 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) spa_bt_transport_add_listener(t, &rfcomm->transport_listener, &transport_events, rfcomm); if (rfcomm->telephony_ag) { - rfcomm->telephony_ag->transport.codec = codec; + rfcomm->telephony_ag->transport.codec = codec_id; rfcomm->telephony_ag->transport.state = SPA_BT_TRANSPORT_STATE_IDLE; telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); } @@ -2010,7 +2019,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) /* send codec selection to AG */ rfcomm_send_cmd(rfcomm, "AT+BCS=%u", selected_codec); - if (!rfcomm->transport || (rfcomm->transport->codec != selected_codec) ) { + if (!rfcomm->transport || (rfcomm->codec != selected_codec) ) { if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport } else { @@ -2420,7 +2429,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) telephony_ag_set_callbacks(rfcomm->telephony_ag, &telephony_ag_callbacks, rfcomm); if (rfcomm->transport) { - rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.codec = rfcomm->transport->media_codec->codec_id; rfcomm->telephony_ag->transport.state = rfcomm->transport->state; } telephony_ag_register(rfcomm->telephony_ag); @@ -2604,8 +2613,8 @@ static int sco_do_connect(struct spa_bt_transport *t) struct sockaddr_sco addr; int err; - spa_log_debug(backend->log, "transport %p: enter sco_do_connect, codec=%u", - t, t->codec); + spa_log_debug(backend->log, "transport %p: enter sco_do_connect, codec=%s", + t, t->media_codec->description); td->err = -EIO; @@ -2617,8 +2626,8 @@ static int sco_do_connect(struct spa_bt_transport *t) str2ba(d->address, &addr.sco_bdaddr); for (int retry = 2;;) { - bool transparent = (t->codec == HFP_AUDIO_CODEC_MSBC || t->codec == HFP_AUDIO_CODEC_LC3_SWB); - spa_autoclose int sock = sco_create_socket(backend, d->adapter, transparent); + bool encoded = t->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD; + spa_autoclose int sock = sco_create_socket(backend, d->adapter, encoded); if (sock < 0) return -1; @@ -2631,7 +2640,7 @@ static int sco_do_connect(struct spa_bt_transport *t) } else if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { spa_log_error(backend->log, "connect(): %s", strerror(errno)); #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE - if (errno == EOPNOTSUPP && t->codec != HFP_AUDIO_CODEC_CVSD && + if (errno == EOPNOTSUPP && encoded && td->rfcomm->codec_negotiation_supported) { /* Adapter doesn't support msbc/lc3. Renegotiate. */ d->adapter->msbc_probed = true; @@ -2962,14 +2971,14 @@ static void sco_listen_event(struct spa_source *source) return; } - spa_log_debug(backend->log, "transport %p: codec=%u", t, t->codec); + spa_log_debug(backend->log, "transport %p: codec=%s", t, t->media_codec->description); if (backend->defer_setup_enabled) { /* In BT_DEFER_SETUP mode, when a connection is accepted, the listening socket is unblocked but * the effective connection setup happens only on first receive, allowing to configure the * accepted socket. */ char buff; - if (t->codec == HFP_AUDIO_CODEC_MSBC || t->codec == HFP_AUDIO_CODEC_LC3_SWB) { + if (t->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { /* set correct socket options for mSBC/LC3 */ struct bt_voice voice_config; memset(&voice_config, 0, sizeof(voice_config)); From 61b0ea4589711d5b380632d3950f51fa0620a172 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:53:22 +0300 Subject: [PATCH 0391/1014] bluez5: backend-ofono: set media_codec on created transports Set media_codec on created transports. Also avoid using the HFP codec id in spa_bt_transport::codec --- spa/plugins/bluez5/backend-ofono.c | 66 ++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/spa/plugins/bluez5/backend-ofono.c b/spa/plugins/bluez5/backend-ofono.c index 42dd0eceb..5370eb830 100644 --- a/spa/plugins/bluez5/backend-ofono.c +++ b/spa/plugins/bluez5/backend-ofono.c @@ -23,6 +23,7 @@ #include #include "defs.h" +#include "media-codecs.h" #define INITIAL_INTERVAL_NSEC (500 * SPA_NSEC_PER_MSEC) #define ACTION_INTERVAL_NSEC (3000 * SPA_NSEC_PER_MSEC) @@ -53,6 +54,7 @@ struct impl { struct transport_data { struct spa_source sco; + unsigned int codec_id; unsigned int broken:1; unsigned int activated:1; }; @@ -112,17 +114,26 @@ static struct spa_bt_transport *_transport_create(struct impl *backend, const char *path, struct spa_bt_device *device, enum spa_bt_profile profile, - int codec, + int codec_id, struct spa_callbacks *impl) { struct spa_bt_transport *t = NULL; - char *t_path = strdup(path); + const struct media_codec *codec; + struct transport_data *td; + char *t_path; + codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); + if (!codec) { + spa_log_warn(backend->log, "can't create transport: no HFP codec %d", codec_id); + return NULL; + } + + t_path = strdup(path); t = spa_bt_transport_create(backend->monitor, t_path, sizeof(struct transport_data)); if (t == NULL) { spa_log_warn(backend->log, "can't create transport: %m"); free(t_path); - goto finish; + return NULL; } spa_bt_transport_set_implementation(t, impl, t); @@ -130,11 +141,14 @@ static struct spa_bt_transport *_transport_create(struct impl *backend, spa_list_append(&t->device->transport_list, &t->device_link); t->backend = &backend->this; t->profile = profile; - t->codec = codec; + t->media_codec = codec; + t->codec = codec_id; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; -finish: + td = t->user_data; + td->codec_id = codec_id; + return t; } @@ -186,7 +200,7 @@ static int ofono_audio_acquire(void *data, bool optional) struct spa_bt_transport *transport = data; struct transport_data *td = transport->user_data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); - uint8_t codec; + uint8_t codec_id; int ret = 0; if (transport->fd >= 0) @@ -198,17 +212,17 @@ static int ofono_audio_acquire(void *data, bool optional) spa_bt_device_update_last_bluez_action_time(transport->device); - ret = _audio_acquire(backend, transport->path, &codec); + ret = _audio_acquire(backend, transport->path, &codec_id); if (ret < 0) goto finish; transport->fd = ret; - if (transport->codec != codec) { + if (transport->media_codec->codec_id != codec_id) { struct timespec ts; spa_log_info(backend->log, "transport %p: acquired codec (%d) differs from transport one (%d)", - transport, codec, transport->codec); + transport, codec_id, transport->media_codec->codec_id); /* shutdown to make sure connection is dropped immediately */ shutdown(transport->fd, SHUT_RDWR); @@ -216,7 +230,7 @@ static int ofono_audio_acquire(void *data, bool optional) transport->fd = -1; /* schedule immediate profile update, from main loop */ - transport->codec = codec; + td->codec_id = codec_id; td->broken = true; ts.tv_sec = 0; ts.tv_nsec = 1; @@ -229,8 +243,8 @@ static int ofono_audio_acquire(void *data, bool optional) td->broken = false; - spa_log_debug(backend->log, "transport %p: Acquire %s, fd %d codec %d", transport, - transport->path, transport->fd, transport->codec); + spa_log_debug(backend->log, "transport %p: Acquire %s, fd %d codec %s", transport, + transport->path, transport->fd, transport->media_codec->description); ofono_transport_get_mtu(backend, transport); ret = 0; @@ -332,7 +346,7 @@ static bool activate_transport(struct spa_bt_transport *t, const void *data) struct spa_bt_transport *t_copy; t_copy = _transport_create(backend, t->path, t->device, - t->profile, t->codec, (struct spa_callbacks *)&ofono_transport_impl); + t->profile, td->codec_id, (struct spa_callbacks *)&ofono_transport_impl); spa_bt_transport_free(t); if (t_copy) @@ -364,7 +378,7 @@ static DBusHandlerResult ofono_audio_card_found(struct impl *backend, char *path struct spa_bt_transport *t; struct transport_data *td; enum spa_bt_profile profile = SPA_BT_PROFILE_HFP_AG; - uint8_t codec = backend->msbc_supported ? + uint8_t codec_id = backend->msbc_supported ? HFP_AUDIO_CODEC_MSBC : HFP_AUDIO_CODEC_CVSD; spa_assert(backend); @@ -417,7 +431,7 @@ static DBusHandlerResult ofono_audio_card_found(struct impl *backend, char *path } spa_bt_device_add_profile(d, profile); - t = _transport_create(backend, path, d, profile, codec, (struct spa_callbacks *)&ofono_transport_impl); + t = _transport_create(backend, path, d, profile, codec_id, (struct spa_callbacks *)&ofono_transport_impl); if (t == NULL) { spa_log_error(backend->log, "failed to create transport: %s", spa_strerror(-errno)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; @@ -444,7 +458,7 @@ static DBusHandlerResult ofono_audio_card_found(struct impl *backend, char *path spa_bt_device_connect_profile(t->device, t->profile); } - spa_log_debug(backend->log, "Transport %s available, codec %d", t->path, t->codec); + spa_log_debug(backend->log, "Transport %s available, codec %s", t->path, t->media_codec->description); return DBUS_HANDLER_RESULT_HANDLED; } @@ -513,7 +527,7 @@ static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMe struct impl *backend = userdata; const char *path; int fd; - uint8_t codec; + uint8_t codec_id; struct spa_bt_transport *t; struct transport_data *td; spa_autoptr(DBusMessage) r = NULL; @@ -521,7 +535,7 @@ static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMe if (dbus_message_get_args(m, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_UNIX_FD, &fd, - DBUS_TYPE_BYTE, &codec, + DBUS_TYPE_BYTE, &codec_id, DBUS_TYPE_INVALID) == FALSE) { r = dbus_message_new_error(m, OFONO_ERROR_INVALID_ARGUMENTS, "Invalid arguments in method call"); goto fail; @@ -530,6 +544,15 @@ static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMe t = spa_bt_transport_find(backend->monitor, path); if (t && (t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) { int err; + const struct media_codec *codec; + + codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); + if (!codec) { + spa_log_error(backend->log, "transport %p: Couldn't find HFP codec %d", t, codec_id); + shutdown(fd, SHUT_RDWR); + close(fd); + goto fail; + } err = enable_sco_socket(fd); if (err) { @@ -541,10 +564,11 @@ static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMe } t->fd = fd; - t->codec = codec; + t->codec = codec_id; + t->media_codec = codec; - spa_log_debug(backend->log, "transport %p: NewConnection %s, fd %d codec %d", - t, t->path, t->fd, t->codec); + spa_log_debug(backend->log, "transport %p: NewConnection %s, fd %d codec %s", + t, t->path, t->fd, t->media_codec->description); td = t->user_data; td->sco.func = sco_event; From 123f93770110ebf6ddd0255bb0fcabf8d22e4bf8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 21:04:19 +0300 Subject: [PATCH 0392/1014] bluez5: backend-hsphfpd: set media_codec on created transports Set media_codec on created transports. Also avoid using the HFP codec id in spa_bt_transport::codec --- spa/plugins/bluez5/backend-hsphfpd.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/spa/plugins/bluez5/backend-hsphfpd.c b/spa/plugins/bluez5/backend-hsphfpd.c index ddbae1c3c..39b4916c8 100644 --- a/spa/plugins/bluez5/backend-hsphfpd.c +++ b/spa/plugins/bluez5/backend-hsphfpd.c @@ -19,6 +19,7 @@ #include #include "defs.h" +#include "media-codecs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.hsphfpd"); #undef SPA_LOG_TOPIC_DEFAULT @@ -659,8 +660,11 @@ static DBusHandlerResult hsphfpd_new_audio_connection(DBusConnection *conn, DBus goto fail; } - if (transport->codec != codec) - spa_log_warn(backend->log, "Expecting codec to be %d, got %d", transport->codec, codec); + if (transport->media_codec->codec_id != codec) { + spa_log_warn(backend->log, "Expecting codec to be %d, got %d", transport->media_codec->codec_id, codec); + r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s has wrong codec", endpoint_path); + goto fail; + } if (transport->fd >= 0) { spa_log_error(backend->log, "Endpoint %s has already active transport", endpoint_path); @@ -879,7 +883,7 @@ static int hsphfpd_audio_acquire(void *data, bool optional) if (backend->acquire_in_progress) return -EINPROGRESS; - if (transport->codec == HFP_AUDIO_CODEC_MSBC) { + if (transport->media_codec->codec_id == HFP_AUDIO_CODEC_MSBC) { air_codec = HSPHFP_AIR_CODEC_MSBC; agent_codec = HSPHFP_AGENT_CODEC_MSBC; } @@ -953,6 +957,7 @@ static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, DBusMessageIter element_i; struct spa_bt_device *d; struct spa_bt_transport *t; + const struct media_codec *codec; dbus_message_iter_recurse(i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { @@ -1046,7 +1051,8 @@ static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, if ((t = spa_bt_transport_find(backend->monitor, endpoint->path)) != NULL) { /* Release transport on disconnection, or when mSBC is supported if there is an update of the remote codecs */ - if (!endpoint->connected || (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC) && t->codec == HFP_AUDIO_CODEC_CVSD)) { + if (!endpoint->connected || (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC) && + t->media_codec->codec_id == HFP_AUDIO_CODEC_CVSD)) { spa_bt_transport_free(t); spa_bt_device_check_profiles(d, false); spa_log_debug(backend->log, "Transport released for %s", endpoint->path); @@ -1059,6 +1065,15 @@ static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, if (!endpoint->valid || !endpoint->connected) return DBUS_HANDLER_RESULT_HANDLED; + if (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC)) + codec = spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC); + else + codec = spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_CVSD); + if (!codec) { + spa_log_error(backend->log, "cannot get codec for %s", endpoint->path); + return DBUS_HANDLER_RESULT_HANDLED; + } + char *t_path = strdup(endpoint->path); t = spa_bt_transport_create(backend->monitor, t_path, sizeof(struct hsphfpd_transport_data)); if (t == NULL) { @@ -1083,11 +1098,8 @@ static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, else if (endpoint->role == HSPHFPD_ROLE_GATEWAY) t->profile = SPA_BT_PROFILE_HFP_AG; } - if (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC)) - t->codec = HFP_AUDIO_CODEC_MSBC; - else - t->codec = HFP_AUDIO_CODEC_CVSD; + t->media_codec = codec; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; From 1c5895f6252ebdb137d7417ae55597c0f2f56f67 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:38:01 +0300 Subject: [PATCH 0393/1014] bluez5: convert sco-source to use media_codec API Use codecs via media_codec in sco-source instead of implementing the decoding in-place. Also slightly adjust media-source decode semantics. In future, media-source could replace sco-source to reduce code duplication. --- spa/plugins/bluez5/media-source.c | 8 +- spa/plugins/bluez5/sco-source.c | 341 +++++++----------------------- 2 files changed, 77 insertions(+), 272 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index c53ee9164..5634fa3c1 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -465,9 +465,10 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, /* decode */ avail = dst_size; - while (src_size > 0) { + do { + written = 0; if ((processed = this->codec->decode(this->codec_data, - src, src_size, dst, avail, &written)) <= 0) + src, src_size, dst, avail, &written)) < 0) return processed; /* update source and dest pointers */ @@ -476,7 +477,8 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, src += processed; avail -= written; dst += written; - } + } while (src_size && (processed || written)); + return dst_size - avail; } diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 3e0e6bf56..ca3c75047 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -31,13 +31,8 @@ #include #include -#include - #include "defs.h" - -#ifdef HAVE_LC3 -#include -#endif +#include "media-codecs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.source.sco"); #undef SPA_LOG_TOPIC_DEFAULT @@ -135,23 +130,8 @@ struct impl { uint64_t current_time; uint64_t next_time; - /* Codecs */ - bool h2_seq_initialized; - uint8_t h2_seq; - - /* mSBC/LC3 frame parsing */ - uint8_t recv_buffer[HFP_CODEC_PACKET_SIZE]; - uint8_t recv_buffer_pos; - - /* mSBC */ - sbc_t msbc; - - /* LC3 */ -#ifdef HAVE_LC3 - lc3_decoder_t lc3; -#else - void *lc3; -#endif + const struct media_codec *codec; + void *codec_data; uint64_t now; }; @@ -358,55 +338,6 @@ static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer } } -/* Append data to recv buffer, syncing buffer start to headers */ -static void recv_buffer_append_byte(struct impl *this, uint8_t byte) -{ - /* Parse H2 sync header */ - if (this->recv_buffer_pos == 0) { - if (byte != 0x01) { - this->recv_buffer_pos = 0; - return; - } - } else if (this->recv_buffer_pos == 1) { - if (!((byte & 0x0F) == 0x08 && - ((byte >> 4) & 1) == ((byte >> 5) & 1) && - ((byte >> 6) & 1) == ((byte >> 7) & 1))) { - this->recv_buffer_pos = 0; - return; - } - } else if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - /* Beginning of MSBC frame: SYNCWORD + 2 nul bytes */ - if (this->recv_buffer_pos == 2) { - if (byte != 0xAD) { - this->recv_buffer_pos = 0; - return; - } - } - else if (this->recv_buffer_pos == 3) { - if (byte != 0x00) { - this->recv_buffer_pos = 0; - return; - } - } - else if (this->recv_buffer_pos == 4) { - if (byte != 0x00) { - this->recv_buffer_pos = 0; - return; - } - } - } - - if (this->recv_buffer_pos >= HFP_CODEC_PACKET_SIZE) { - /* Packet completed. Reset. */ - this->recv_buffer_pos = 0; - recv_buffer_append_byte(this, byte); - return; - } - - this->recv_buffer[this->recv_buffer_pos] = byte; - ++this->recv_buffer_pos; -} - /* Helper function for debugging */ static SPA_UNUSED void hexdump_to_log(struct impl *this, uint8_t *data, size_t size) { @@ -425,124 +356,53 @@ static SPA_UNUSED void hexdump_to_log(struct impl *this, uint8_t *data, size_t s spa_log_trace(this->log, "hexdump (%d bytes):%s", (int)size, buf); } -/* helper function to detect if a packet consists only of zeros */ -static bool is_zero_packet(uint8_t *data, int size) +static ssize_t decode_data(struct impl *this, uint8_t *src, size_t src_size, uint64_t now) { - for (int i = 0; i < size; ++i) { - if (data[i] != 0) { - return false; - } - } - return true; -} - -static int lc3_decode_frame(struct impl *this, const void *src, size_t src_size, void *dst, - size_t dst_size, size_t *dst_out) -{ -#ifdef HAVE_LC3 + struct port *port = &this->port; + uint16_t seqnum = 0; + uint32_t timestamp = 0; + size_t total = 0; + size_t written; int res; - if (src_size != LC3_SWB_PAYLOAD_SIZE) - return -EINVAL; - if (dst_size < LC3_SWB_DECODED_SIZE) - return -EINVAL; + res = this->codec->start_decode(this->codec_data, src, src_size, &seqnum, ×tamp); + if (res < 0) + return res; - res = lc3_decode(this->lc3, src, src_size, LC3_PCM_FORMAT_S24, dst, 1); - if (res != 0) - return -EINVAL; + /* TODO: check seqnum and handle PLC */ - *dst_out = LC3_SWB_DECODED_SIZE; - return LC3_SWB_DECODED_SIZE; -#else - return -EOPNOTSUPP; -#endif -} + spa_assert((size_t)res <= src_size); + src = SPA_PTROFF(src, res, void); + src_size -= res; -static uint32_t preprocess_and_decode_codec_data(void *userdata, uint8_t *read_data, int size_read, uint64_t now) -{ - struct impl *this = userdata; - struct port *port = &this->port; - uint32_t decoded = 0; - int i; - uint32_t decoded_size = (this->transport->codec == HFP_AUDIO_CODEC_MSBC) ? MSBC_DECODED_SIZE : - LC3_SWB_DECODED_SIZE; + do { + void *dst; + uint32_t dst_size; - spa_log_trace(this->log, "handling mSBC/LC3 data"); + dst = spa_bt_decode_buffer_get_write(&port->buffer, &dst_size); - /* - * Check if the packet contains only zeros - if so ignore the packet. - * This is necessary, because some kernels insert bogus "all-zero" packets - * into the datastream. - * See https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/549 - */ - if (is_zero_packet(read_data, size_read)) - return 0; + written = 0; + res = this->codec->decode(this->codec_data, src, src_size, dst, dst_size, &written); + if (res < 0) + return res; - for (i = 0; i < size_read; ++i) { - void *buf; - uint32_t avail; - int seq, processed; - size_t written; + spa_assert((size_t)res <= src_size); + src = SPA_PTROFF(src, res, void); + src_size -= res; + total += written; - recv_buffer_append_byte(this, read_data[i]); + if (written) + spa_bt_decode_buffer_write_packet(&port->buffer, written, this->now); + } while (src_size && (res || written)); - if (this->recv_buffer_pos != HFP_CODEC_PACKET_SIZE) - continue; - - /* - * Handle found mSBC/LC3 packet - */ - - buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); - - /* Check sequence number */ - seq = ((this->recv_buffer[1] >> 4) & 1) | - ((this->recv_buffer[1] >> 6) & 2); - - spa_log_trace(this->log, "mSBC/LC3 packet seq=%u", seq); - if (!this->h2_seq_initialized) { - this->h2_seq_initialized = true; - this->h2_seq = seq; - } else if (seq != this->h2_seq) { - /* TODO: PLC (too late to insert data now) */ - spa_log_info(this->log, - "missing mSBC/LC3 packet: %u != %u", seq, this->h2_seq); - this->h2_seq = seq; - } - - this->h2_seq = (this->h2_seq + 1) % 4; - - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - if (avail < decoded_size) - spa_log_warn(this->log, "Output buffer full, dropping msbc data"); - - /* decode frame */ - processed = sbc_decode( - &this->msbc, this->recv_buffer + 2, HFP_CODEC_PACKET_SIZE - 3, - buf, avail, &written); - } else { - processed = lc3_decode_frame(this, this->recv_buffer + 2, HFP_CODEC_PACKET_SIZE - 2, - buf, avail, &written); - } - - if (processed < 0) { - spa_log_warn(this->log, "decode failed: %d", processed); - /* TODO: manage errors */ - continue; - } - - spa_bt_decode_buffer_write_packet(&port->buffer, written, now); - decoded += written; - } - - return decoded; + return total; } static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read, uint64_t rx_time) { struct impl *this = userdata; struct port *port = &this->port; - uint32_t decoded; + ssize_t decoded; uint64_t dt; /* Drop data when not started */ @@ -563,40 +423,14 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read, uint hexdump_to_log(this, read_data, size_read); #endif - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC || - this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { - decoded = preprocess_and_decode_codec_data(userdata, read_data, size_read, this->now); - } else { - uint32_t avail; - uint8_t *packet; - - if (size_read != 48 && is_zero_packet(read_data, size_read)) { - /* Adapter is returning non-standard CVSD stream. For example - * Intel 8087:0029 at Firmware revision 0.0 build 191 week 21 2021 - * on kernel 5.13.19 produces such data. - */ - return 0; - } - - if (size_read % port->frame_size != 0) { - /* Unaligned data: reception or adapter problem. - * Consider the whole packet lost and report. - */ - spa_log_debug(this->log, - "received bad Bluetooth SCO CVSD packet"); - return 0; - } - - packet = spa_bt_decode_buffer_get_write(&port->buffer, &avail); - avail = SPA_MIN(avail, (uint32_t)size_read); - spa_memmove(packet, read_data, avail); - spa_bt_decode_buffer_write_packet(&port->buffer, avail, this->now); - - decoded = avail; + decoded = decode_data(this, read_data, size_read, this->now); + if (decoded < 0) { + spa_log_debug(this->log, "failed to decode data: %d", (int)decoded); + return 0; } spa_log_trace(this->log, "read socket data size:%d decoded frames:%d dt:%d dms", - size_read, decoded / port->frame_size, + size_read, (int)decoded / port->frame_size, (int)(dt / 100000)); return 0; @@ -731,32 +565,12 @@ static int transport_start(struct impl *this) spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 40 / 1000); - /* Init mSBC/LC3 if needed */ - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - res = sbc_init_msbc(&this->msbc, 0); - if (res < 0) - return res; - - /* Libsbc expects audio samples by default in host endianness, mSBC requires little endian */ - this->msbc.endian = SBC_LE; - this->h2_seq_initialized = false; - - this->recv_buffer_pos = 0; - } else if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { -#ifdef HAVE_LC3 - this->lc3 = lc3_setup_decoder(7500, 32000, 0, - calloc(1, lc3_decoder_size(7500, 32000))); - if (!this->lc3) - return -EINVAL; - - spa_assert(lc3_frame_samples(7500, 32000) * port->frame_size == LC3_SWB_DECODED_SIZE); - - this->h2_seq_initialized = false; - this->recv_buffer_pos = 0; -#else + /* init codec */ + this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, 0); + if (!this->codec_data) { + spa_log_error(this->log, "codec init failed"); res = -EINVAL; goto fail; -#endif } this->io_error = false; @@ -772,9 +586,10 @@ static int transport_start(struct impl *this) return 0; fail: - sbc_finish(&this->msbc); - free(this->lc3); - this->lc3 = NULL; + if (this->codec_data) { + this->codec->deinit(this->codec_data); + this->codec_data = NULL; + } return res; } @@ -866,9 +681,8 @@ static void transport_stop(struct impl *this) spa_bt_decode_buffer_clear(&port->buffer); - sbc_finish(&this->msbc); - free(this->lc3); - this->lc3 = NULL; + this->codec->deinit(this->codec_data); + this->codec_data = NULL; } static int do_stop(struct impl *this) @@ -1039,6 +853,7 @@ impl_node_port_enum_params(void *object, int seq, uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -1060,28 +875,10 @@ impl_node_port_enum_params(void *object, int seq, if (this->transport == NULL) return -EIO; - /* set the info structure */ - struct spa_audio_info_raw info = { 0, }; - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - info.format = SPA_AUDIO_FORMAT_S24_32_LE; - else - info.format = SPA_AUDIO_FORMAT_S16_LE; - info.channels = 1; - info.position[0] = SPA_AUDIO_CHANNEL_MONO; - - /* CVSD format has a rate of 8kHz - * MSBC format has a rate of 16kHz - * LC3-SWB format has a rate of 32kHz - */ - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - info.rate = 32000; - else if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) - info.rate = 16000; - else - info.rate = 8000; - - /* build the param */ - param = spa_format_audio_raw_build(&b, id, &info); + if ((res = this->codec->enum_config(this->codec, + 0, NULL, 0, + id, result.index, &b, ¶m)) != 1) + return res; break; case SPA_PARAM_Format: @@ -1191,9 +988,6 @@ static int port_set_format(struct impl *this, struct port *port, } else { struct spa_audio_info info = { 0 }; - if (!this->transport) - return -EIO; - if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; @@ -1205,19 +999,24 @@ static int port_set_format(struct impl *this, struct port *port, return -EINVAL; if (info.info.raw.rate == 0 || - info.info.raw.channels != 1) + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) return -EINVAL; + port->frame_size = info.info.raw.channels; + switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16_LE: - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - return -EINVAL; - port->frame_size = info.info.raw.channels * 2; + case SPA_AUDIO_FORMAT_S16_BE: + port->frame_size *= 2; break; - case SPA_AUDIO_FORMAT_S24_32_LE: - if (this->transport->codec != HFP_AUDIO_CODEC_LC3_SWB) - return -EINVAL; - port->frame_size = info.info.raw.channels * 4; + case SPA_AUDIO_FORMAT_S24: + port->frame_size *= 3; + break; + case SPA_AUDIO_FORMAT_S24_32: + case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_F32: + port->frame_size *= 4; break; default: return -EINVAL; @@ -1724,10 +1523,14 @@ impl_init(const struct spa_handle_factory *factory, if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); - if (this->transport == NULL) { - spa_log_error(this->log, "a transport is needed"); + if (this->transport == NULL || this->transport->media_codec == NULL || + this->transport->media_codec->kind != MEDIA_CODEC_HFP) { + spa_log_error(this->log, "a transport with HFP codec is needed"); return -EINVAL; } + + this->codec = this->transport->media_codec; + spa_bt_transport_add_listener(this->transport, &this->transport_listener, &transport_events, this); From defcea02fab159a5bb907a8c6d7de8a82cd92a6c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 20:43:01 +0300 Subject: [PATCH 0394/1014] bluez5: convert sco-sink to media_codec API Use codecs via media_codec in sco-sink instead of implementing the encoding in-place. In future, media-sink could replace sco-source to reduce code duplication. --- spa/plugins/bluez5/sco-sink.c | 313 ++++++++++------------------------ 1 file changed, 92 insertions(+), 221 deletions(-) diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c index 298b9dcbd..c3b8dbaa3 100644 --- a/spa/plugins/bluez5/sco-sink.c +++ b/spa/plugins/bluez5/sco-sink.c @@ -29,13 +29,8 @@ #include #include -#include - #include "defs.h" - -#ifdef HAVE_LC3 -#include -#endif +#include "media-codecs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sink.sco"); #undef SPA_LOG_TOPIC_DEFAULT @@ -50,9 +45,6 @@ struct props { #define MAX_BUFFERS 32 -#define ALT1_PACKET_SIZE 24 -#define ALT6_PACKET_SIZE 60 - struct buffer { uint32_t id; unsigned int outstanding:1; @@ -148,28 +140,17 @@ struct impl { uint64_t prev_flush_time; uint64_t next_flush_time; - /* Codecs */ - uint8_t *buffer; - uint8_t *buffer_head; - uint8_t *buffer_next; - int buffer_size; - int h2_seq; + uint8_t buffer[7200]; + size_t buffer_size; - /* mSBC */ - sbc_t msbc; + uint16_t seqnum; - /* LC3 */ -#ifdef HAVE_LC3 - lc3_encoder_t lc3; -#else - void *lc3; -#endif + const struct media_codec *codec; + void *codec_data; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) -static const char sntable[4] = { 0x08, 0x38, 0xC8, 0xF8 }; - static void reset_props(struct props *props) { props->latency_offset = 0; @@ -335,9 +316,9 @@ static void set_latency(struct impl *this, bool emit_latency) * tell us, so not included but hopefully in < 20 ms range. */ - switch (this->transport->codec) { - case HFP_AUDIO_CODEC_MSBC: - case HFP_AUDIO_CODEC_LC3_SWB: + switch (this->codec->id) { + case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: + case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: delay = 7500 * SPA_NSEC_PER_USEC; break; default: @@ -445,32 +426,49 @@ static uint32_t get_queued_frames(struct impl *this) return bytes / port->frame_size; } -static int lc3_encode_frame(struct impl *this, const void *src, size_t src_size, void *dst, size_t dst_size, - ssize_t *dst_out) +static int encode_data(struct impl *this) { -#ifdef HAVE_LC3 + struct port *port = &this->port; int res; + int need_flush; + size_t written = 0; + size_t avail; - if (src_size < LC3_SWB_DECODED_SIZE) - return -EINVAL; - if (dst_size < LC3_SWB_PAYLOAD_SIZE) - return -EINVAL; + avail = sizeof(this->buffer) - this->buffer_size; + res = this->codec->start_encode(this->codec_data, + this->buffer + this->buffer_size, avail, + this->seqnum, 0); + if (res < 0) { + spa_log_warn(this->log, "encode failed: %d", res); + goto fail; + } - res = lc3_encode(this->lc3, LC3_PCM_FORMAT_S24, src, 1, LC3_SWB_PAYLOAD_SIZE, dst); - if (res != 0) - return -EINVAL; + this->seqnum++; + this->buffer_size += res; - *dst_out = LC3_SWB_PAYLOAD_SIZE; - return LC3_SWB_DECODED_SIZE; -#else - return -EOPNOTSUPP; -#endif + avail = sizeof(this->buffer) - this->buffer_size; + res = this->codec->encode(this->codec_data, + port->write_buffer, port->write_buffer_size, + this->buffer + this->buffer_size, avail, + &written, &need_flush); + if (res < 0) { + spa_log_warn(this->log, "encode failed: %d", res); + goto fail; + } + + this->buffer_size += written; + port->write_buffer_size = 0; + + return written; + +fail: + this->buffer_size = 0; + return res; } static int flush_data(struct impl *this) { struct port *port = &this->port; - int processed = 0; int written; spa_assert(this->transport_started); @@ -478,9 +476,7 @@ static int flush_data(struct impl *this) if (this->transport == NULL || this->transport->sco_io == NULL || !this->flush_timer_source.loop) return -EIO; - const uint32_t min_in_size = (this->transport->codec == HFP_AUDIO_CODEC_MSBC) ? MSBC_DECODED_SIZE : - (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) ? LC3_SWB_DECODED_SIZE : - this->transport->write_mtu; + const uint32_t min_in_size = this->codec->get_block_size(this->codec_data); const uint32_t packet_samples = min_in_size / port->frame_size; const uint64_t packet_time = (uint64_t)packet_samples * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; @@ -535,78 +531,23 @@ static int flush_data(struct impl *this) return 0; } - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC || - this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { - ssize_t out_encoded; - /* Encode */ - if (this->buffer_next + HFP_CODEC_PACKET_SIZE > this->buffer + this->buffer_size) { - /* Buffer overrun; shouldn't usually happen. Drop data and reset. */ - this->buffer_head = this->buffer_next = this->buffer; - spa_log_warn(this->log, "sco-sink: mSBC/LC3 buffer overrun, dropping data"); - } + /* Encode */ + if (encode_data(this) < 0) + goto stop; - /* H2 sync header */ - this->buffer_next[0] = 0x01; - this->buffer_next[1] = sntable[this->h2_seq % 4]; - this->buffer_next[59] = 0x00; - this->h2_seq = (this->h2_seq + 1) % 4; + /* Write */ + written = spa_bt_sco_io_write(this->transport->sco_io, this->buffer, this->buffer_size); + if (written < 0) { + spa_log_warn(this->log, "failed to write data: %d (%s)", + written, spa_strerror(written)); + goto stop; + } - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - processed = sbc_encode(&this->msbc, port->write_buffer, port->write_buffer_size, - this->buffer_next + 2, HFP_CODEC_PACKET_SIZE - 3, &out_encoded); - out_encoded += 1; /* pad */ - } else { - processed = lc3_encode_frame(this, port->write_buffer, port->write_buffer_size, - this->buffer_next + 2, HFP_CODEC_PACKET_SIZE - 2, &out_encoded); - } - - if (processed < 0) { - spa_log_warn(this->log, "encode failed: %d", processed); - return -EINVAL; - } - this->buffer_next += out_encoded + 2; - port->write_buffer_size = 0; - - /* Write */ - written = spa_bt_sco_io_write(this->transport->sco_io, this->buffer_head, - this->buffer_next - this->buffer_head); - if (written < 0) { - spa_log_warn(this->log, "failed to write data: %d (%s)", - written, spa_strerror(written)); - goto stop; - } - - this->buffer_head += written; - - if (this->buffer_head == this->buffer_next) - this->buffer_head = this->buffer_next = this->buffer; - else if (this->buffer_next + HFP_CODEC_PACKET_SIZE > this->buffer + this->buffer_size) { - /* Written bytes is not necessarily commensurate - * with HFP_CODEC_PACKET_SIZE. If this occurs, copy data. - */ - int size = this->buffer_next - this->buffer_head; - spa_memmove(this->buffer, this->buffer_head, size); - this->buffer_next = this->buffer + size; - this->buffer_head = this->buffer; - } + if ((size_t)written == this->buffer_size) { + this->buffer_size = 0; } else { - written = spa_bt_sco_io_write(this->transport->sco_io, port->write_buffer, - port->write_buffer_size); - if (written < 0) { - spa_log_warn(this->log, "sco-sink: write failure: %d (%s)", - written, spa_strerror(written)); - goto stop; - } else if (written == 0) { - /* EAGAIN or similar, just skip ahead */ - written = SPA_MIN(port->write_buffer_size, (uint32_t)48); - } - - processed = written; - port->write_buffer_size -= written; - - if (port->write_buffer_size > 0 && written > 0) { - spa_memmove(port->write_buffer, port->write_buffer + written, port->write_buffer_size); - } + spa_memmove(this->buffer, this->buffer + written, this->buffer_size - written); + this->buffer_size -= written; } if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { @@ -744,20 +685,6 @@ static void sco_on_timeout(struct spa_source *source) set_timeout(this, this->next_time); } -/* greater common divider */ -static int gcd(int a, int b) { - while(b) { - int c = b; - b = a % b; - a = c; - } - return a; -} -/* least common multiple */ -static int lcm(int a, int b) { - return (a*b)/gcd(a,b); -} - static int transport_start(struct impl *this) { int res; @@ -775,51 +702,11 @@ static int transport_start(struct impl *this) spa_log_debug(this->log, "%p: start transport", this); - /* Init mSBC/LC3 if needed */ - if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) { - res = sbc_init_msbc(&this->msbc, 0); - if (res < 0) - return res; - /* Libsbc expects audio samples by default in host endianness, mSBC requires little endian */ - this->msbc.endian = SBC_LE; + this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, this->transport->write_mtu); + if (!this->codec_data) + return -EINVAL; - /* write_mtu might not be correct at this point, so we'll throw - * in some common ones, at the cost of a potentially larger - * allocation (size <= 120 * write_mtu). If it still fails to be - * commensurate, we may end up doing memmoves, but nothing worse - * is going to happen. - */ - this->buffer_size = lcm(ALT1_PACKET_SIZE, lcm(ALT6_PACKET_SIZE, lcm(this->transport->write_mtu, 2 * HFP_CODEC_PACKET_SIZE))); - } else if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { -#ifdef HAVE_LC3 - this->lc3 = lc3_setup_encoder(7500, 32000, 0, - calloc(1, lc3_encoder_size(7500, 32000))); - if (!this->lc3) - return -EINVAL; - - spa_assert(lc3_frame_samples(7500, 32000) * this->port.frame_size == LC3_SWB_DECODED_SIZE); - - this->buffer_size = lcm(ALT1_PACKET_SIZE, lcm(ALT6_PACKET_SIZE, lcm(this->transport->write_mtu, 2 * HFP_CODEC_PACKET_SIZE))); -#else - res = -EOPNOTSUPP; - goto fail; -#endif - } else { - this->buffer_size = 0; - } - - if (this->buffer_size) { - this->buffer = calloc(this->buffer_size, sizeof(uint8_t)); - this->buffer_head = this->buffer_next = this->buffer; - if (this->buffer == NULL) { - res = -errno; - goto fail; - } - } - - spa_return_val_if_fail(this->transport->write_mtu <= sizeof(this->port.write_buffer), -EINVAL); - - spa_log_info(this->log, "%p: using codec %d, delay:%"PRIi64" ms", this, this->transport->codec, + spa_log_info(this->log, "%p: using codec %s, delay:%"PRIi64" ms", this, this->codec->description, (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); /* start socket i/o */ @@ -842,11 +729,10 @@ static int transport_start(struct impl *this) return 0; fail: - free(this->buffer); - this->buffer = NULL; - sbc_finish(&this->msbc); - free(this->lc3); - this->lc3 = NULL; + if (this->codec_data) { + this->codec->deinit(this->codec_data); + this->codec_data = NULL; + } return res; } @@ -956,15 +842,12 @@ static void transport_stop(struct impl *this) spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); - if (this->buffer) { - free(this->buffer); - this->buffer = NULL; - this->buffer_head = this->buffer_next = this->buffer; - } + this->buffer_size = 0; - sbc_finish(&this->msbc); - free(this->lc3); - this->lc3 = NULL; + if (this->codec_data) { + this->codec->deinit(this->codec_data); + this->codec_data = NULL; + } } static int do_stop(struct impl *this) @@ -1136,6 +1019,7 @@ impl_node_port_enum_params(void *object, int seq, uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); @@ -1157,30 +1041,10 @@ impl_node_port_enum_params(void *object, int seq, if (this->transport == NULL) return -EIO; - /* set the info structure */ - struct spa_audio_info_raw info = { 0, }; - - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - info.format = SPA_AUDIO_FORMAT_S24_32_LE; - else - info.format = SPA_AUDIO_FORMAT_S16_LE; - info.channels = 1; - info.position[0] = SPA_AUDIO_CHANNEL_MONO; - - /* CVSD format has a rate of 8kHz - * MSBC format has a rate of 16kHz - * LC3-SWB format has a rate of 32kHz - */ - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - info.rate = 32000; - else if (this->transport->codec == HFP_AUDIO_CODEC_MSBC) - info.rate = 16000; - else - info.rate = 8000; - - /* build the param */ - param = spa_format_audio_raw_build(&b, id, &info); - + if ((res = this->codec->enum_config(this->codec, + 0, NULL, 0, + id, result.index, &b, ¶m)) != 1) + return res; break; case SPA_PARAM_Format: @@ -1303,19 +1167,23 @@ static int port_set_format(struct impl *this, struct port *port, return -EINVAL; if (info.info.raw.rate == 0 || - info.info.raw.channels != 1) + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) return -EINVAL; + port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16_LE: - if (this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) - return -EINVAL; - port->frame_size = info.info.raw.channels * 2; + case SPA_AUDIO_FORMAT_S16_BE: + port->frame_size *= 2; break; - case SPA_AUDIO_FORMAT_S24_32_LE: - if (this->transport->codec != HFP_AUDIO_CODEC_LC3_SWB) - return -EINVAL; - port->frame_size = info.info.raw.channels * 4; + case SPA_AUDIO_FORMAT_S24: + port->frame_size *= 3; + break; + case SPA_AUDIO_FORMAT_S24_32: + case SPA_AUDIO_FORMAT_S32: + case SPA_AUDIO_FORMAT_F32: + port->frame_size *= 4; break; default: return -EINVAL; @@ -1703,11 +1571,14 @@ impl_init(const struct spa_handle_factory *factory, if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); - if (this->transport == NULL) { - spa_log_error(this->log, "a transport is needed"); + if (this->transport == NULL || this->transport->media_codec == NULL || + this->transport->media_codec->kind != MEDIA_CODEC_HFP) { + spa_log_error(this->log, "a transport with HFP codec is needed"); return -EINVAL; } + this->codec = this->transport->media_codec; + set_latency(this, false); spa_bt_transport_add_listener(this->transport, From 3f9fb8d66420e2772b59e9606f1a8d620a29fa13 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 22:14:40 +0300 Subject: [PATCH 0395/1014] bluez5: bluez5-device: reduce special casing of HFP codec related things Get most information items out from media_codec properties, avoid referring to HFP codec ids. --- spa/plugins/bluez5/bluez5-dbus.c | 25 ++-- spa/plugins/bluez5/bluez5-device.c | 203 ++++++++++------------------- spa/plugins/bluez5/defs.h | 3 +- 3 files changed, 83 insertions(+), 148 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 143c65909..c46209b07 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -2551,6 +2551,12 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru if (!is_media_codec_enabled(device->monitor, codec)) return false; + if (codec->kind == MEDIA_CODEC_HFP) { + if (!(profile & SPA_BT_PROFILE_HEADSET_AUDIO)) + return false; + return spa_bt_backend_supports_codec(monitor->backend, device, codec->codec_id) == 1; + } + if (!device->adapter->a2dp_application_registered && is_a2dp) { /* Codec switching not supported: only plain SBC allowed */ return (codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc") && @@ -3370,9 +3376,6 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) /* Fallback values when device does not provide information */ - if (t->media_codec == NULL) - return 20 * SPA_NSEC_PER_MSEC; - switch (t->media_codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_SBC: case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ: @@ -3389,6 +3392,10 @@ int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX: case SPA_BLUETOOTH_AUDIO_CODEC_LC3: return 40 * SPA_NSEC_PER_MSEC; + case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: + case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: + case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: + return 20 * SPA_NSEC_PER_MSEC; default: break; }; @@ -4726,16 +4733,14 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct return 0; } -int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, unsigned int codec) +int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec) { struct spa_bt_monitor *monitor = device->monitor; - return spa_bt_backend_ensure_codec(monitor->backend, device, codec); -} -int spa_bt_device_supports_hfp_codec(struct spa_bt_device *device, unsigned int codec) -{ - struct spa_bt_monitor *monitor = device->monitor; - return spa_bt_backend_supports_codec(monitor->backend, device, codec); + if (!codec || codec->kind != MEDIA_CODEC_HFP) + return -EINVAL; + + return spa_bt_backend_ensure_codec(monitor->backend, device, codec->codec_id); } static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 673dd1401..7884a06dc 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -193,6 +193,9 @@ static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec i spa_assert(this->supported_codecs); for (c = this->supported_codecs; *c && size > 1; ++c) { + if ((*c)->kind == MEDIA_CODEC_HFP) + continue; + if ((*c)->id == id || id == 0) { *codecs++ = *c; --size; @@ -203,7 +206,7 @@ static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec i } static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id, - size_t *idx, enum spa_bt_profile profile) + int *priority, enum spa_bt_profile profile) { const struct media_codec *media_codec = NULL; size_t i; @@ -211,8 +214,7 @@ static const struct media_codec *get_supported_media_codec(struct impl *this, en for (i = 0; i < this->supported_codec_count; ++i) { if (this->supported_codecs[i]->id == id) { media_codec = this->supported_codecs[i]; - if (idx) - *idx = i; + break; } } @@ -222,6 +224,16 @@ static const struct media_codec *get_supported_media_codec(struct impl *this, en if (!spa_bt_device_supports_media_codec(this->bt_dev, media_codec, profile)) return NULL; + if (priority) { + *priority = 0; + for (i = 0; i < this->supported_codec_count; ++i) { + if (this->supported_codecs[i] == media_codec) + break; + if (this->supported_codecs[i]->kind == media_codec->kind) + ++(*priority); + } + } + return media_codec; } @@ -252,67 +264,11 @@ static bool can_bap_codec_switch(struct impl *this) return true; } -static unsigned int get_hfp_codec(enum spa_bluetooth_audio_codec id) -{ - switch (id) { - case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: - return HFP_AUDIO_CODEC_CVSD; - case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: - return HFP_AUDIO_CODEC_MSBC; - case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: - return HFP_AUDIO_CODEC_LC3_SWB; - default: - return 0; - } -} - -static enum spa_bluetooth_audio_codec get_hfp_codec_id(unsigned int codec) -{ - switch (codec) { - case HFP_AUDIO_CODEC_MSBC: - return SPA_BLUETOOTH_AUDIO_CODEC_MSBC; - case HFP_AUDIO_CODEC_LC3_SWB: - return SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB; - case HFP_AUDIO_CODEC_CVSD: - return SPA_BLUETOOTH_AUDIO_CODEC_CVSD; - } - return SPA_ID_INVALID; -} - -static const char *get_hfp_codec_description(unsigned int codec) -{ - switch (codec) { - case HFP_AUDIO_CODEC_MSBC: - return "mSBC"; - case HFP_AUDIO_CODEC_LC3_SWB: - return "LC3-SWB"; - case HFP_AUDIO_CODEC_CVSD: - return "CVSD"; - } - return "unknown"; -} - -static const char *get_hfp_codec_name(unsigned int codec) -{ - switch (codec) { - case HFP_AUDIO_CODEC_MSBC: - return "msbc"; - case HFP_AUDIO_CODEC_LC3_SWB: - return "lc3_swb"; - case HFP_AUDIO_CODEC_CVSD: - return "cvsd"; - } - return "unknown"; -} - static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex) { - if (t->media_codec != NULL) { - if (a2dp_duplex && t->media_codec->duplex_codec) - return t->media_codec->duplex_codec->name; - return t->media_codec->name; - } - return get_hfp_codec_name(t->codec); + if (a2dp_duplex && t->media_codec->duplex_codec) + return t->media_codec->duplex_codec->name; + return t->media_codec->name; } static void transport_destroy(void *userdata) @@ -1213,7 +1169,7 @@ static int emit_nodes(struct impl *this) if (!t) t = find_transport(this, SPA_BT_PROFILE_HSP_AG); if (t) { - this->props.codec = get_hfp_codec_id(t->codec); + this->props.codec = t->media_codec->id; emit_dynamic_node(this, t, 0, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false); emit_dynamic_node(this, t, 1, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } @@ -1346,7 +1302,7 @@ static int emit_nodes(struct impl *this) if (!t) t = find_transport(this, SPA_BT_PROFILE_HSP_HS); if (t) { - this->props.codec = get_hfp_codec_id(t->codec); + this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false); emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } @@ -1459,12 +1415,14 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a } else { return 0; } - } else if (profile == DEVICE_PROFILE_HSP_HFP && get_hfp_codec(codec)) { + } else if (profile == DEVICE_PROFILE_HSP_HFP) { int ret; + const struct media_codec *media_codec = get_supported_media_codec(this, codec, NULL, + SPA_BT_PROFILE_HEADSET_AUDIO); this->switching_codec = true; - ret = spa_bt_device_ensure_hfp_codec(this->bt_dev, get_hfp_codec(codec)); + ret = spa_bt_device_ensure_hfp_codec(this->bt_dev, media_codec); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); @@ -1833,7 +1791,7 @@ static bool set_initial_hsp_hfp_profile(struct impl *this) if (t) { this->profile = (i & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) ? DEVICE_PROFILE_AG : DEVICE_PROFILE_HSP_HFP; - this->props.codec = get_hfp_codec_id(t->codec); + this->props.codec = t->media_codec->id; spa_log_debug(this->log, "initial profile HSP/HFP profile:%d codec:%d", this->profile, this->props.codec); @@ -1938,7 +1896,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * */ if ((device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) && (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) - priority = 15; + priority = 127; else priority = 256; break; @@ -1983,8 +1941,8 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * name = spa_bt_profile_name(profile); n_sink++; if (codec) { - size_t idx; - const struct media_codec *media_codec = get_supported_media_codec(this, codec, &idx, profile); + int prio; + const struct media_codec *media_codec = get_supported_media_codec(this, codec, &prio, profile); if (media_codec == NULL) { errno = EINVAL; return NULL; @@ -1996,7 +1954,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * * selected at command line with out knowing which codecs are actually * supported */ - if (idx != 0) + if (prio != 0) name = name_and_codec; if (profile == SPA_BT_PROFILE_A2DP_SINK && !media_codec->duplex_codec) { @@ -2008,14 +1966,14 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * } desc = desc_and_codec; - priority = 16 + this->supported_codec_count - idx; /* order as in codec list */ + priority = 128 + this->supported_codec_count - prio; /* order as in codec list */ } else { if (profile == SPA_BT_PROFILE_A2DP_SINK) { desc = _("High Fidelity Playback (A2DP Sink)"); } else { desc = _("High Fidelity Duplex (A2DP Source/Sink)"); } - priority = 16; + priority = 128; } break; } @@ -2025,7 +1983,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SINK); - size_t idx; + int idx; const struct media_codec *media_codec; int n_set_sink, n_set_source; @@ -2077,7 +2035,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * media_codec->description); } desc = desc_and_codec; - priority = 128 + this->supported_codec_count - idx; /* order as in codec list */ + priority = 512 + this->supported_codec_count - idx; /* order as in codec list */ } else { switch (profile) { case SPA_BT_PROFILE_BAP_SINK: @@ -2091,7 +2049,7 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * default: desc = _("High Fidelity Duplex (BAP Source/Sink)"); } - priority = 128; + priority = 512; } device_set_get_info(this, DEVICE_PROFILE_BAP, &n_set_sink, &n_set_source); @@ -2103,43 +2061,36 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * } case DEVICE_PROFILE_HSP_HFP: { - /* make this device profile visible only if there is a head unit */ uint32_t profile = device->connected_profiles & - SPA_BT_PROFILE_HEADSET_HEAD_UNIT; - unsigned int hfp_codec = get_hfp_codec(codec); - unsigned int idx; + SPA_BT_PROFILE_HEADSET_HEAD_UNIT; + int prio; + const struct media_codec *media_codec = get_supported_media_codec(this, codec, &prio, profile); - if (profile == 0) + if (!profile) return NULL; - /* HFP will only enlist codec profiles */ - if (codec == 0) - return NULL; - if (codec != SPA_BLUETOOTH_AUDIO_CODEC_CVSD && - spa_bt_device_supports_hfp_codec(this->bt_dev, hfp_codec) != 1) + /* Only list codec profiles */ + if (!codec || !media_codec) return NULL; name = spa_bt_profile_name(profile); n_source++; n_sink++; - name_and_codec = spa_aprintf("%s-%s", name, get_hfp_codec_name(hfp_codec)); + name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); /* * Give base name to highest priority profile, so that best codec can be * selected at command line with out knowing which codecs are actually * supported */ - for (idx = HFP_AUDIO_CODEC_LC3_SWB; idx > 0; --idx) - if (spa_bt_device_supports_hfp_codec(this->bt_dev, idx) == 1) - break; - if (hfp_codec < idx) + if (prio != 0) name = name_and_codec; desc_and_codec = spa_aprintf(_("Headset Head Unit (HSP/HFP, codec %s)"), - get_hfp_codec_description(hfp_codec)); + media_codec->description); desc = desc_and_codec; - priority = 1 + hfp_codec; /* prefer lc3_swb > msbc > cvsd */ + priority = 1 + this->supported_codec_count - prio; break; } default: @@ -2514,6 +2465,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, static bool iterate_supported_media_codecs(struct impl *this, int *j, const struct media_codec **codec) { int i; + const struct media_codec *c; next: *j = *j + 1; @@ -2521,11 +2473,20 @@ next: if ((size_t)*j >= this->supported_codec_count) return false; - for (i = 0; i < *j; ++i) - if (this->supported_codecs[i]->id == this->supported_codecs[*j]->id) - goto next; + c = this->supported_codecs[*j]; - *codec = this->supported_codecs[*j]; + if (!(this->profile == DEVICE_PROFILE_A2DP && c->kind == MEDIA_CODEC_A2DP) && + !(this->profile == DEVICE_PROFILE_BAP && c->kind == MEDIA_CODEC_BAP) && + !(this->profile == DEVICE_PROFILE_HSP_HFP && c->kind == MEDIA_CODEC_HFP) && + !(this->profile == DEVICE_PROFILE_ASHA && c->kind == MEDIA_CODEC_ASHA)) + goto next; + + /* skip endpoint aliases */ + for (i = 0; i < *j; ++i) + if (this->supported_codecs[i]->id == c->id) + goto next; + + *codec = c; return true; } @@ -2539,9 +2500,6 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b #define FOR_EACH_MEDIA_CODEC(j, codec) \ for (j = -1; iterate_supported_media_codecs(this, &j, &codec);) -#define FOR_EACH_HFP_CODEC(j) \ - for (j = HFP_AUDIO_CODEC_LC3_SWB; j >= HFP_AUDIO_CODEC_CVSD; --j) \ - if (spa_bt_device_supports_hfp_codec(this->bt_dev, j) == 1) spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); @@ -2559,42 +2517,25 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]); n = 0; - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { - FOR_EACH_MEDIA_CODEC(j, codec) { - if (n == 0) - spa_pod_builder_int(b, codec->id); + FOR_EACH_MEDIA_CODEC(j, codec) { + if (n == 0) spa_pod_builder_int(b, codec->id); - ++n; - } - } else if (this->profile == DEVICE_PROFILE_HSP_HFP) { - FOR_EACH_HFP_CODEC(j) { - if (n == 0) - spa_pod_builder_int(b, get_hfp_codec_id(j)); - spa_pod_builder_int(b, get_hfp_codec_id(j)); - ++n; - } + spa_pod_builder_int(b, codec->id); + ++n; } if (n == 0) choice->body.type = SPA_CHOICE_None; spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { - FOR_EACH_MEDIA_CODEC(j, codec) { - spa_pod_builder_int(b, codec->id); - spa_pod_builder_string(b, codec->description); - } - } else if (this->profile == DEVICE_PROFILE_HSP_HFP) { - FOR_EACH_HFP_CODEC(j) { - spa_pod_builder_int(b, get_hfp_codec_id(j)); - spa_pod_builder_string(b, get_hfp_codec_description(j)); - } + FOR_EACH_MEDIA_CODEC(j, codec) { + spa_pod_builder_int(b, codec->id); + spa_pod_builder_string(b, codec->description); } spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); #undef FOR_EACH_MEDIA_CODEC -#undef FOR_EACH_HFP_CODEC } static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id) @@ -3037,24 +2978,14 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || + this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { return set_profile(this, this->profile, codec_id, true); } } - } else if (this->profile == DEVICE_PROFILE_HSP_HFP) { - if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD && - spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_CVSD) == 1) { - return set_profile(this, this->profile, codec_id, true); - } else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_MSBC && - spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_MSBC) == 1) { - return set_profile(this, this->profile, codec_id, true); - } else if (codec_id == SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB && - spa_bt_device_supports_hfp_codec(this->bt_dev, HFP_AUDIO_CODEC_LC3_SWB) == 1) { - return set_profile(this, this->profile, codec_id, true); - } } return -EINVAL; } diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 4ae6a34ce..c6aef0d6a 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -586,10 +586,9 @@ int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs); +int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec); bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, enum spa_bt_profile profile); const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count); -int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, unsigned int codec); -int spa_bt_device_supports_hfp_codec(struct spa_bt_device *device, unsigned int codec); int spa_bt_device_release_transports(struct spa_bt_device *device); int spa_bt_device_report_battery_level(struct spa_bt_device *device, uint8_t percentage); void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device); From 83f6d719b1615d94055c661f3960cc2b26736879 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 7 Jun 2025 22:17:56 +0300 Subject: [PATCH 0396/1014] bluez5: remove HFP codec id from transports Make HFP codec id backend/codec internal detail. Remove spa_bt_transport::codec field which is now unused. --- spa/plugins/bluez5/backend-hsphfpd.c | 1 + spa/plugins/bluez5/backend-native.c | 2 +- spa/plugins/bluez5/backend-ofono.c | 3 +-- spa/plugins/bluez5/bluez5-dbus.c | 11 ----------- spa/plugins/bluez5/defs.h | 13 ------------- spa/plugins/bluez5/hfp-codec-caps.h | 16 ++++++++++++++++ spa/plugins/bluez5/sco-io.c | 10 +++++----- 7 files changed, 24 insertions(+), 32 deletions(-) create mode 100644 spa/plugins/bluez5/hfp-codec-caps.h diff --git a/spa/plugins/bluez5/backend-hsphfpd.c b/spa/plugins/bluez5/backend-hsphfpd.c index 39b4916c8..6f858e22c 100644 --- a/spa/plugins/bluez5/backend-hsphfpd.c +++ b/spa/plugins/bluez5/backend-hsphfpd.c @@ -20,6 +20,7 @@ #include "defs.h" #include "media-codecs.h" +#include "hfp-codec-caps.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.hsphfpd"); #undef SPA_LOG_TOPIC_DEFAULT diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 91fdb3ba8..c8e1fcf92 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -33,6 +33,7 @@ #include "defs.h" #include "media-codecs.h" +#include "hfp-codec-caps.h" #ifdef HAVE_LIBUSB #include @@ -281,7 +282,6 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec_id) t->backend = &backend->this; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; - t->codec = codec_id; t->media_codec = codec; td = t->user_data; diff --git a/spa/plugins/bluez5/backend-ofono.c b/spa/plugins/bluez5/backend-ofono.c index 5370eb830..7cf129017 100644 --- a/spa/plugins/bluez5/backend-ofono.c +++ b/spa/plugins/bluez5/backend-ofono.c @@ -24,6 +24,7 @@ #include "defs.h" #include "media-codecs.h" +#include "hfp-codec-caps.h" #define INITIAL_INTERVAL_NSEC (500 * SPA_NSEC_PER_MSEC) #define ACTION_INTERVAL_NSEC (3000 * SPA_NSEC_PER_MSEC) @@ -142,7 +143,6 @@ static struct spa_bt_transport *_transport_create(struct impl *backend, t->backend = &backend->this; t->profile = profile; t->media_codec = codec; - t->codec = codec_id; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; @@ -564,7 +564,6 @@ static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMe } t->fd = fd; - t->codec = codec_id; t->media_codec = codec; spa_log_debug(backend->log, "transport %p: NewConnection %s, fd %d codec %s", diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index c46209b07..3326aee56 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -3500,17 +3500,6 @@ static int transport_update_props(struct spa_bt_transport *transport, transport->bap_initiator = ep->acceptor; } } - else if (spa_streq(key, "Codec")) { - uint8_t value; - - if (type != DBUS_TYPE_BYTE) - goto next; - dbus_message_iter_get_basic(&it[1], &value); - - spa_log_debug(monitor->log, "transport %p: %s=%02x", transport, key, value); - - transport->codec = value; - } else if (spa_streq(key, "Configuration")) { DBusMessageIter iter; uint8_t *value; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index c6aef0d6a..b64b02141 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -143,10 +143,6 @@ extern "C" { #define BUS_TYPE_USB 1 #define BUS_TYPE_OTHER 255 -#define HFP_AUDIO_CODEC_CVSD 0x01 -#define HFP_AUDIO_CODEC_MSBC 0x02 -#define HFP_AUDIO_CODEC_LC3_SWB 0x03 - #define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint" #define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink" #define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource" @@ -161,14 +157,6 @@ extern "C" { #define SPA_BT_NO_BATTERY ((uint8_t)255) -/* HFP uses SBC encoding with precisely defined parameters. Hence, the size - * of the input (number of PCM samples) and output is known up front. */ -#define MSBC_DECODED_SIZE 240 -#define MSBC_PAYLOAD_SIZE 57 /* 1 byte padding follows payload */ -#define LC3_SWB_DECODED_SIZE 960 /* 32 kHz mono S24_32 @ 7.5 ms */ -#define LC3_SWB_PAYLOAD_SIZE 58 -#define HFP_CODEC_PACKET_SIZE 60 /* 2 bytes header + payload */ - enum spa_bt_media_direction { SPA_BT_MEDIA_SOURCE, SPA_BT_MEDIA_SINK, @@ -676,7 +664,6 @@ struct spa_bt_transport { enum spa_bt_profile profile; enum spa_bt_transport_state state; const struct media_codec *media_codec; - unsigned int codec; void *configuration; int configuration_len; char *endpoint_path; diff --git a/spa/plugins/bluez5/hfp-codec-caps.h b/spa/plugins/bluez5/hfp-codec-caps.h new file mode 100644 index 000000000..abbd0bbd5 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-caps.h @@ -0,0 +1,16 @@ +/* Spa HFP codec API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_HFP_CODEC_CAPS_H_ +#define SPA_BLUEZ5_HFP_CODEC_CAPS_H_ + +#include + +#define HFP_AUDIO_CODEC_CVSD 0x01 +#define HFP_AUDIO_CODEC_MSBC 0x02 +#define HFP_AUDIO_CODEC_LC3_SWB 0x03 + +#define HFP_H2_PACKET_SIZE 60 + +#endif diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index b67de8bbf..9cb2dcf4d 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -29,6 +29,8 @@ #include #include "defs.h" +#include "media-codecs.h" +#include "hfp-codec-caps.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sco-io"); #undef SPA_LOG_TOPIC_DEFAULT @@ -224,14 +226,12 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s io->read_size = 0; } else { /* Set some sensible initial packet size */ - switch (transport->codec) { - case HFP_AUDIO_CODEC_CVSD: + switch (transport->media_codec->id) { + case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: io->read_size = 48; /* 3ms S16_LE 8000 Hz */ break; - case HFP_AUDIO_CODEC_MSBC: - case HFP_AUDIO_CODEC_LC3_SWB: default: - io->read_size = HFP_CODEC_PACKET_SIZE; + io->read_size = HFP_H2_PACKET_SIZE; break; } } From 9f34e962a660602682925468f04eca903383a7b0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 8 Jun 2025 01:07:39 +0300 Subject: [PATCH 0397/1014] bluez5: backend-native: don't hardcode available HFP codecs Remove most hardcoding of possible HFP codecs. Instead, get what is available from codec lists. --- spa/meson.build | 2 - spa/plugins/bluez5/backend-native.c | 227 ++++++++++++++++++---------- 2 files changed, 150 insertions(+), 79 deletions(-) diff --git a/spa/meson.build b/spa/meson.build index fd65269bb..48a0000a1 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -109,10 +109,8 @@ if get_option('spa-plugins').allowed() mm_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-backend-native-mm')) summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends') endif - cdata.set('HAVE_LC3', get_option('bluez5-codec-lc3').allowed() and lc3_dep.found()) g722_codec_option = get_option('bluez5-codec-g722') summary({'G722': g722_codec_option.allowed()}, bool_yn: true, section: 'Bluetooth audio codecs') - cdata.set('HAVE_G722', g722_codec_option.allowed()) endif have_vulkan = false diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index c8e1fcf92..b7fd8f45c 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -101,6 +101,8 @@ struct impl { struct spa_dbus *dbus; DBusConnection *conn; + const struct media_codec * const * codecs; + #define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; bool hfp_disable_nrec; @@ -168,6 +170,11 @@ struct rfcomm_cmd { char* cmd; }; +struct codec_item { + struct spa_list link; + const struct media_codec *codec; +}; + struct rfcomm { struct spa_list link; struct spa_source source; @@ -184,10 +191,10 @@ struct rfcomm { struct rfcomm_volume volumes[SPA_BT_VOLUME_ID_TERM]; unsigned int broken_mic_hw_volume:1; #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE + struct spa_list available_codec_list; + struct spa_list supported_codec_list; unsigned int slc_configured:1; unsigned int codec_negotiation_supported:1; - unsigned int msbc_supported_by_hfp:1; - unsigned int lc3_supported_by_hfp:1; unsigned int hfp_ag_switching_codec:1; unsigned int hfp_ag_initial_codec_setup:2; unsigned int cind_call_active:1; @@ -211,6 +218,66 @@ struct rfcomm { #endif }; +static const struct media_codec *codec_list_get(struct impl *backend, struct spa_list *list, unsigned int codec_id) +{ + struct codec_item *item; + + /* CVSD is always supported: not included in the list */ + if (codec_id == HFP_AUDIO_CODEC_CVSD) + return spa_bt_get_hfp_codec(backend->monitor, codec_id); + + spa_list_for_each(item, list, link) + if (item->codec->codec_id == codec_id) + return item->codec; + + return NULL; +} + +static bool codec_list_add(struct spa_list *list, const struct media_codec *codec) +{ + struct codec_item *item; + + if (codec->codec_id == HFP_AUDIO_CODEC_CVSD) + return true; + + spa_list_for_each(item, list, link) + if (item->codec == codec) + return true; + + item = calloc(1, sizeof(*item)); + if (!item) + return false; + + item->codec = codec; + spa_list_append(list, &item->link); + return true; +} + +static void codec_list_clear(struct spa_list *list) +{ + struct codec_item *item; + + spa_list_consume(item, list, link) { + spa_list_remove(&item->link); + free(item); + } +} + +static const struct media_codec *codec_list_best(struct impl *backend, struct spa_list *list) +{ + size_t i; + + /* Codec list is in 'best' order */ + for (i = 0; backend->codecs[i]; ++i) { + const struct media_codec *c = backend->codecs[i]; + if (c->kind == MEDIA_CODEC_HFP && codec_list_get(backend, list, c->codec_id)) + return c; + } + + spa_assert_not_reached(); + return NULL; +} + static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata) { if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented")) @@ -356,6 +423,8 @@ static void rfcomm_free(struct rfcomm *rfcomm) } if (rfcomm->volume_sync_timer) spa_loop_utils_destroy_source(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer); + codec_list_clear(&rfcomm->available_codec_list); + codec_list_clear(&rfcomm->supported_codec_list); free(rfcomm); } @@ -702,7 +771,8 @@ fail: #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE -static bool device_supports_codec(struct impl *backend, struct spa_bt_device *device, int codec) +static bool device_supports_codec(struct impl *backend, struct spa_bt_device *device, + enum spa_bluetooth_audio_codec codec) { int res; bool alt6_ok = true, alt1_ok = true; @@ -718,14 +788,14 @@ static bool device_supports_codec(struct impl *backend, struct spa_bt_device *de } switch (codec) { - case HFP_AUDIO_CODEC_CVSD: + case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: return true; - case HFP_AUDIO_CODEC_MSBC: + case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: alt1_ok = msbc_alt1_ok; alt6_ok = msbc_alt6_ok; break; - case HFP_AUDIO_CODEC_LC3_SWB: -#ifdef HAVE_LC3 + case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: + default: /* LC3-SWB has same transport requirements as msbc. * However, ALT1/ALT5 modes don't appear to work, seem * to lose frame sync so output is garbled. @@ -733,11 +803,6 @@ static bool device_supports_codec(struct impl *backend, struct spa_bt_device *de alt1_ok = false; alt6_ok = msbc_alt6_ok; break; -#else - return false; -#endif - default: - return false; } spa_log_info(backend->log, @@ -785,6 +850,21 @@ static bool device_supports_codec(struct impl *backend, struct spa_bt_device *de return alt6_ok || alt1_ok; } +static void make_available_codec_list(struct impl *backend, struct spa_bt_device *device, struct spa_list *codec_list) +{ + size_t i; + + codec_list_clear(codec_list); + + for (i = 0; backend->codecs[i]; ++i) { + const struct media_codec *codec = backend->codecs[i]; + if (codec->kind != MEDIA_CODEC_HFP) + continue; + if (device_supports_codec(backend, device, codec->id)) + codec_list_add(codec_list, codec); + } +} + static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec); static void process_xevent_indicator(struct rfcomm *rfcomm, unsigned int level, unsigned int nlevels) @@ -886,8 +966,6 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) if (sscanf(buf, "AT+BRSF=%u", &features) == 1) { unsigned int ag_features = SPA_BT_HFP_AG_FEATURE_NONE; - bool codecs = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC) || - device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3_SWB); /* * Determine device volume control. Some headsets only support control of @@ -898,7 +976,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) /* Decide if we want to signal that the computer supports codec negotiation This should be done when the computers bluetooth adapter supports the necessary transport mode */ - if (codecs) { + if (!spa_list_is_empty(&rfcomm->available_codec_list)) { /* set the feature bit that indicates AG (=computer) supports codec negotiation */ ag_features |= SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION; @@ -909,8 +987,6 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) features); /* Prepare reply: Audio Gateway (=computer) supports codec negotiation */ rfcomm->codec_negotiation_supported = true; - rfcomm->msbc_supported_by_hfp = false; - rfcomm->lc3_supported_by_hfp = false; } else { /* Codec negotiation not supported */ spa_log_debug(backend->log, @@ -918,8 +994,6 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) features); rfcomm->codec_negotiation_supported = false; - rfcomm->msbc_supported_by_hfp = false; - rfcomm->lc3_supported_by_hfp = false; } } @@ -936,28 +1010,28 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) char* token; int cntr = 0; + codec_list_clear(&rfcomm->supported_codec_list); + while ((token = strsep(&buf, "=,"))) { unsigned int codec_id; /* skip token 0 i.e. the "AT+BAC=" part */ if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) { + const struct media_codec *codec; + spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id); - if (codec_id == HFP_AUDIO_CODEC_MSBC) - rfcomm->msbc_supported_by_hfp = - device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC); - else if (codec_id == HFP_AUDIO_CODEC_LC3_SWB) - rfcomm->lc3_supported_by_hfp = - device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3_SWB); + codec = codec_list_get(backend, &rfcomm->available_codec_list, codec_id); + if (codec) { + spa_log_debug(backend->log, "RFCOMM AT+BAC codec %s supported", codec->description); + codec_list_add(&rfcomm->supported_codec_list, codec); + } else { + spa_log_debug(backend->log, "RFCOMM AT+BAC codec %u not supported", codec_id); + } } cntr++; } - if (rfcomm->msbc_supported_by_hfp) - spa_log_debug(backend->log, "mSBC codec is supported"); - if (rfcomm->lc3_supported_by_hfp) - spa_log_debug(backend->log, "LC3 codec is supported"); - rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND=?")) { rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS); @@ -968,8 +1042,8 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) backend->modem.network_is_roaming, backend->battery_level); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CMER")) { + const struct media_codec *best_codec = codec_list_best(backend, &rfcomm->supported_codec_list); int mode, keyp, disp, ind; - bool have_codecs = rfcomm->msbc_supported_by_hfp || rfcomm->lc3_supported_by_hfp; rfcomm->slc_configured = true; rfcomm_send_reply(rfcomm, "OK"); @@ -980,14 +1054,12 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) else rfcomm->cind_call_notify = false; - /* switch codec to mSBC by sending unsolicited +BCS message */ - if (rfcomm->codec_negotiation_supported && have_codecs) { + /* switch to better codec by sending unsolicited +BCS message */ + if (rfcomm->codec_negotiation_supported && best_codec && + best_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { spa_log_debug(backend->log, "RFCOMM initial codec setup"); rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_SEND; - if (rfcomm->lc3_supported_by_hfp) - rfcomm_send_reply(rfcomm, "+BCS: 3"); - else - rfcomm_send_reply(rfcomm, "+BCS: 2"); + rfcomm_send_reply(rfcomm, "+BCS: %u", best_codec->codec_id); codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC); } else { if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { @@ -1012,13 +1084,14 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) } else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) { /* parse BCS(=Bluetooth Codec Selection) reply */ bool was_switching_codec = rfcomm->hfp_ag_switching_codec && (rfcomm->device != NULL); + const struct media_codec *codec = codec_list_get(backend, &rfcomm->supported_codec_list, selected_codec); + rfcomm->hfp_ag_switching_codec = false; rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; codec_switch_stop_timer(rfcomm); volume_sync_stop_timer(rfcomm); - if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC && - selected_codec != HFP_AUDIO_CODEC_LC3_SWB) { + if (!codec) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) @@ -2003,15 +2076,16 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (sscanf(token, "+BRSF:%u", &features) == 1) { if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) && - (rfcomm->msbc_supported_by_hfp || rfcomm->lc3_supported_by_hfp)) + !spa_list_is_empty(&rfcomm->available_codec_list)) rfcomm->codec_negotiation_supported = true; rfcomm->hfp_hf_3way = (features & SPA_BT_HFP_AG_FEATURE_3WAY) != 0; rfcomm->hfp_hf_nrec = (features & SPA_BT_HFP_AG_FEATURE_ECNR) != 0; rfcomm->hfp_hf_clcc = (features & SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS) != 0; rfcomm->hfp_hf_cme = (features & SPA_BT_HFP_AG_FEATURE_EXTENDED_RES_CODE) != 0; } else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) { - if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC && - selected_codec != HFP_AUDIO_CODEC_LC3_SWB) { + const struct media_codec *codec = codec_list_get(backend, &rfcomm->available_codec_list, selected_codec); + + if (!codec) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); } else { spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); @@ -2377,13 +2451,12 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (rfcomm->codec_negotiation_supported) { char buf[64]; struct spa_strbuf str; + struct codec_item *item; spa_strbuf_init(&str, buf, sizeof(buf)); spa_strbuf_append(&str, "1"); - if (rfcomm->msbc_supported_by_hfp) - spa_strbuf_append(&str, ",2"); - if (rfcomm->lc3_supported_by_hfp) - spa_strbuf_append(&str, ",3"); + spa_list_for_each(item, &rfcomm->available_codec_list, link) + spa_strbuf_append(&str, ",%u", item->codec->codec_id); rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); rfcomm->hf_state = hfp_hf_bac; @@ -2642,11 +2715,13 @@ static int sco_do_connect(struct spa_bt_transport *t) #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (errno == EOPNOTSUPP && encoded && td->rfcomm->codec_negotiation_supported) { - /* Adapter doesn't support msbc/lc3. Renegotiate. */ + /* Adapter doesn't support msbc/lc3/etc. Renegotiate. */ d->adapter->msbc_probed = true; d->adapter->has_msbc = false; - td->rfcomm->msbc_supported_by_hfp = false; - td->rfcomm->lc3_supported_by_hfp = false; + + codec_list_clear(&td->rfcomm->available_codec_list); + codec_list_clear(&td->rfcomm->supported_codec_list); + if (t->profile == SPA_BT_PROFILE_HFP_HF) { td->rfcomm->hfp_ag_switching_codec = true; rfcomm_send_reply(td->rfcomm, "+BCS: 1"); @@ -3152,12 +3227,7 @@ static int backend_native_supports_codec(void *data, struct spa_bt_device *devic if (!rfcomm->codec_negotiation_supported) return 0; - if (codec == HFP_AUDIO_CODEC_MSBC) - return rfcomm->msbc_supported_by_hfp; - else if (codec == HFP_AUDIO_CODEC_LC3_SWB) - return rfcomm->lc3_supported_by_hfp; - - return 0; + return codec_list_get(backend, &rfcomm->supported_codec_list, codec) ? 1 : 0; #else return -ENOTSUP; #endif @@ -3425,6 +3495,9 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->cind_enabled_indicators = 0xFFFFFFFF; memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); + spa_list_init(&rfcomm->available_codec_list); + spa_list_init(&rfcomm->supported_codec_list); + for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { if (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) rfcomm->volumes[i].active = true; @@ -3455,22 +3528,14 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag SPA_BT_HFP_HF_FEATURE_ECNR | SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS | SPA_BT_HFP_HF_FEATURE_ESCO_S4; - bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC); - bool has_lc3 = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3_SWB); + + make_available_codec_list(backend, rfcomm->device, &rfcomm->available_codec_list); + rfcomm->codec_negotiation_supported = false; /* Decide if we want to signal that the HF supports mSBC/LC3 negotiation This should be done when the bluetooth adapter supports the necessary transport mode */ - if (has_msbc || has_lc3) { - /* set the feature bit that indicates HF supports codec negotiation */ + if (!spa_list_is_empty(&rfcomm->available_codec_list)) hf_features |= SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION; - rfcomm->msbc_supported_by_hfp = has_msbc; - rfcomm->lc3_supported_by_hfp = has_lc3; - rfcomm->codec_negotiation_supported = false; - } else { - rfcomm->msbc_supported_by_hfp = false; - rfcomm->lc3_supported_by_hfp = false; - rfcomm->codec_negotiation_supported = false; - } rfcomm->has_volume = true; hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; @@ -3479,6 +3544,8 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm_send_cmd(rfcomm, "AT+BRSF=%u", hf_features); rfcomm->hf_state = hfp_hf_brsf; + } else if (profile == SPA_BT_PROFILE_HFP_HF) { + make_available_codec_list(backend, rfcomm->device, &rfcomm->available_codec_list); } if (rfcomm_hw_volume_enabled(rfcomm) && (profile == SPA_BT_PROFILE_HFP_HF || profile == SPA_BT_PROFILE_HSP_HS)) { @@ -3666,11 +3733,13 @@ static int register_profile(struct impl *backend, const char *profile, const cha } else if (spa_streq(uuid, SPA_BT_UUID_HFP_AG)) { str = "Features"; - /* We announce wideband speech support anyway */ - features = SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH; -#ifdef HAVE_LC3 - features |= SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH; -#endif + features = 0; + + if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC)) + features |= SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH; + if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_LC3_SWB)) + features |= SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH; + dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -3690,11 +3759,13 @@ static int register_profile(struct impl *backend, const char *profile, const cha } else if (spa_streq(uuid, SPA_BT_UUID_HFP_HF)) { str = "Features"; - /* We announce wideband speech support anyway */ - features = SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH; -#ifdef HAVE_LC3 - features |= SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH; -#endif + features = 0; + + if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC)) + features |= SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH; + if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_LC3_SWB)) + features |= SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH; + dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -4104,6 +4175,8 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, backend->conn = dbus_connection; backend->sco.fd = -1; + backend->codecs = spa_bt_get_media_codecs(monitor); + spa_log_topic_init(backend->log, &log_topic); spa_list_init(&backend->rfcomm_list); From 52fc22a76b250c359a3daf27f1fbc49ed805289f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 9 Jun 2025 20:55:43 +0300 Subject: [PATCH 0398/1014] bluez5: make sure mandatory codecs are always enabled It should not be possible to disable mandatory codecs: csvd, sbc, lc3 --- spa/plugins/bluez5/bluez5-dbus.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 3326aee56..e2dc3e8df 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -551,7 +551,15 @@ static int media_endpoint_to_profile(const char *endpoint) static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec) { - return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL; + /* Mandatory codecs are always enabled */ + switch (codec->id) { + case SPA_BLUETOOTH_AUDIO_CODEC_SBC: + case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: + case SPA_BLUETOOTH_AUDIO_CODEC_LC3: + return true; + default: + return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL; + } } static bool codec_has_direction(const struct media_codec *codec, enum spa_bt_media_direction direction) @@ -6606,8 +6614,6 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict if (spa_dict_lookup_item(&this->enabled_codecs, codec->name) != NULL) continue; - spa_log_debug(this->log, "enabling codec %s", codec->name); - spa_assert(this->enabled_codecs.n_items < num_codecs); codecs[this->enabled_codecs.n_items].key = codec->name; @@ -6622,8 +6628,8 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict for (i = 0; media_codecs[i]; ++i) { const struct media_codec *codec = media_codecs[i]; - if (!is_media_codec_enabled(this, codec)) - spa_log_debug(this->log, "disabling codec %s", codec->name); + spa_log_debug(this->log, "codec %s: %s", codec->name, + is_media_codec_enabled(this, codec) ? "enabled" : "disabled"); } return 0; From df591638a59efa59d89566467f93968358f55dd6 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 11 Jun 2025 21:03:25 +0300 Subject: [PATCH 0399/1014] bluez5: indicate codec support status for ofono/hsphfpd Indicates codecs properly. --- spa/plugins/bluez5/backend-hsphfpd.c | 17 +++++++++++++++++ spa/plugins/bluez5/backend-ofono.c | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/spa/plugins/bluez5/backend-hsphfpd.c b/spa/plugins/bluez5/backend-hsphfpd.c index 6f858e22c..01d81e1a3 100644 --- a/spa/plugins/bluez5/backend-hsphfpd.c +++ b/spa/plugins/bluez5/backend-hsphfpd.c @@ -1433,11 +1433,25 @@ static int backend_hsphfpd_free(void *data) return 0; } +static int backend_hsphfpd_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec) +{ + struct impl *backend = data; + + switch (codec) { + case HFP_AUDIO_CODEC_CVSD: + return 1; + case HFP_AUDIO_CODEC_MSBC: + return backend->msbc_supported; + } + return 0; +} + static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_hsphfpd_free, .register_profiles = backend_hsphfpd_register, .unregister_profiles = backend_hsphfpd_unregistered, + .supports_codec = backend_hsphfpd_supports_codec, }; static bool is_available(struct impl *backend) @@ -1492,6 +1506,9 @@ struct spa_bt_backend *backend_hsphfpd_new(struct spa_bt_monitor *monitor, else backend->msbc_supported = false; + if (!spa_bt_get_hfp_codec(monitor, HFP_AUDIO_CODEC_MSBC)) + backend->msbc_supported = false; + spa_log_topic_init(backend->log, &log_topic); spa_list_init(&backend->endpoint_list); diff --git a/spa/plugins/bluez5/backend-ofono.c b/spa/plugins/bluez5/backend-ofono.c index 7cf129017..d0581ddf4 100644 --- a/spa/plugins/bluez5/backend-ofono.c +++ b/spa/plugins/bluez5/backend-ofono.c @@ -840,10 +840,24 @@ static int backend_ofono_free(void *data) return 0; } +static int backend_ofono_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec) +{ + struct impl *backend = data; + + switch (codec) { + case HFP_AUDIO_CODEC_CVSD: + return 1; + case HFP_AUDIO_CODEC_MSBC: + return backend->msbc_supported; + } + return 0; +} + static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_ofono_free, .register_profiles = backend_ofono_register, + .supports_codec = backend_ofono_supports_codec, }; static bool is_available(struct impl *backend) @@ -897,6 +911,9 @@ struct spa_bt_backend *backend_ofono_new(struct spa_bt_monitor *monitor, else backend->msbc_supported = false; + if (!spa_bt_get_hfp_codec(monitor, HFP_AUDIO_CODEC_MSBC)) + backend->msbc_supported = false; + spa_log_topic_init(backend->log, &log_topic); backend->timer = spa_loop_utils_add_timer(backend->loop_utils, activate_timer_event, backend); From 7e135a52357812ca4d1ba4ae3f41764215a031df Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 13 Jun 2025 21:06:08 +0300 Subject: [PATCH 0400/1014] bluez5: fix compilation --- spa/plugins/bluez5/backend-native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index b7fd8f45c..38976c30a 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1978,7 +1978,7 @@ static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *e return; } } else { - if (!rfcomm->transport || rfcomm->transport->codec != HFP_AUDIO_CODEC_CVSD) { + if (!rfcomm->transport || rfcomm->transport->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; return; } From d04ee917143f2b62ddd86a02fbfd9c341109f3b2 Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Fri, 13 Jun 2025 12:33:35 +0200 Subject: [PATCH 0401/1014] plugins: alsa: increase follower write synchronization when htimestamps are enabled alsa_write_sync can insert or remove some data from alsa when resynchronization is needed. Avail and delay are equal when high precision timestamps are not allowed. When the high precision timestamps are enabled, the delay is avail adjusted to current_time. Signed-off-by: Martin Geier --- spa/plugins/alsa/alsa-pcm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 9d823d9d2..73df819ba 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3020,10 +3020,10 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) state->name, avail, delay, target, state->threshold, suppressed); - if (avail > target) - snd_pcm_rewind(state->hndl, avail - target); - else if (avail < target) - spa_alsa_silence(state, target - avail); + if (delay > target) + snd_pcm_rewind(state->hndl, delay - target); + else if (delay < target) + spa_alsa_silence(state, target - delay); avail = target; } spa_dll_init(&state->dll); From 6982bb8c7fcc65370b6ad5bd112c149df9032398 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 12 Jun 2025 20:48:21 +0300 Subject: [PATCH 0402/1014] bluez5: backend-native: set best codec also when retrying on timeout Try again setting best available codec, not MSBC, when retrying if no response to previous +BCS: command. --- spa/plugins/bluez5/backend-native.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 38976c30a..d55af650a 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -3310,6 +3310,7 @@ static void codec_switch_timer_event(struct spa_source *source) { struct rfcomm *rfcomm = source->data; struct impl *backend = rfcomm->backend; + const struct media_codec *best_codec; uint64_t exp; if (spa_system_timerfd_read(backend->main_system, source->fd, &exp) < 0) @@ -3322,10 +3323,14 @@ static void codec_switch_timer_event(struct spa_source *source) switch (rfcomm->hfp_ag_initial_codec_setup) { case HFP_AG_INITIAL_CODEC_SETUP_SEND: /* Retry codec selection */ - rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_WAIT; - rfcomm_send_reply(rfcomm, "+BCS: 2"); - codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC); - return; + best_codec = codec_list_best(backend, &rfcomm->supported_codec_list); + if (best_codec && best_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { + rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_WAIT; + rfcomm_send_reply(rfcomm, "+BCS: %u", best_codec->codec_id); + codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC); + return; + } + SPA_FALLTHROUGH; case HFP_AG_INITIAL_CODEC_SETUP_WAIT: /* Failure, try falling back to CVSD. */ rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; From 64f2f38ec492eb377aa9175e7fe6594f7fa905ac Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 8 Jun 2025 01:35:56 +0300 Subject: [PATCH 0403/1014] bluez5: support LC3-24kHz HFP codec available on some Apple devices Add support of HFP codec used on eg Apple AirPods 3+, transporting LC3 mono @ 24kHz --- spa/include/spa/param/bluetooth/audio.h | 1 + spa/include/spa/param/bluetooth/type-info.h | 1 + spa/plugins/bluez5/codec-loader.c | 2 + spa/plugins/bluez5/hfp-codec-lc3-a127.c | 243 ++++++++++++++++++++ spa/plugins/bluez5/meson.build | 8 + 5 files changed, 255 insertions(+) create mode 100644 spa/plugins/bluez5/hfp-codec-lc3-a127.c diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index c95e22d6d..34b45e297 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -41,6 +41,7 @@ enum spa_bluetooth_audio_codec { SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, /* BAP */ SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200, diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index a936cca5f..7bf1da52c 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -44,6 +44,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3_swb", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3_a127", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 1557d1f7c..68e6af756 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -53,6 +53,7 @@ static int codec_order(const struct media_codec *c) SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_BLUETOOTH_AUDIO_CODEC_G722, SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_BLUETOOTH_AUDIO_CODEC_CVSD, }; @@ -192,6 +193,7 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo MEDIA_CODEC_FACTORY_LIB("hfp-cvsd"), MEDIA_CODEC_FACTORY_LIB("hfp-msbc"), MEDIA_CODEC_FACTORY_LIB("hfp-lc3-swb"), + MEDIA_CODEC_FACTORY_LIB("hfp-lc3-a127"), #undef MEDIA_CODEC_FACTORY_LIB }; diff --git a/spa/plugins/bluez5/hfp-codec-lc3-a127.c b/spa/plugins/bluez5/hfp-codec-lc3-a127.c new file mode 100644 index 000000000..e3f077930 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-lc3-a127.c @@ -0,0 +1,243 @@ +/* Spa HFP LC3-24kHz (Apple) Codec */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +#include + +#define LC3_A127_BLOCK_SIZE 720 + +static struct spa_log *log; + +struct impl { + lc3_encoder_t enc; + lc3_decoder_t dec; + + int prev_hwseq; + uint16_t seq; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 24000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_F32; + info->info.raw.rate = 24000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct impl *this = NULL; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + this = calloc(1, sizeof(*this)); + if (!this) + goto fail; + + this->enc = lc3_setup_encoder(7500, 24000, 0, + calloc(1, lc3_encoder_size(7500, 24000))); + if (!this->enc) + goto fail; + + this->dec = lc3_setup_decoder(7500, 24000, 0, + calloc(1, lc3_decoder_size(7500, 24000))); + if (!this->dec) + goto fail; + + spa_assert(lc3_frame_samples(7500, 24000) * sizeof(float) == LC3_A127_BLOCK_SIZE); + + this->prev_hwseq = -1; + return this; + +fail: + if (this) { + free(this->enc); + free(this->dec); + free(this); + } + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + + free(this->enc); + free(this->dec); + free(this); +} + +static int codec_get_block_size(void *data) +{ + return LC3_A127_BLOCK_SIZE; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + + this->seq = seqnum; + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + uint8_t *buf = dst; + int res; + + if (src_size < LC3_A127_BLOCK_SIZE) + return -EINVAL; + if (dst_size < H2_PACKET_SIZE) + return -EINVAL; + + buf[0] = this->seq; + buf[1] = H2_PACKET_SIZE - 2; + this->seq++; + + res = lc3_encode(this->enc, LC3_PCM_FORMAT_FLOAT, src, 1, + H2_PACKET_SIZE - 2, SPA_PTROFF(buf, 2, void)); + if (res != 0) + return -EINVAL; + + *dst_out = H2_PACKET_SIZE; + *need_flush = NEED_FLUSH_ALL; + return LC3_A127_BLOCK_SIZE; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + const uint8_t *buf = src; + + if (is_zero_packet(src, src_size)) + return -EINVAL; + if (src_size < 2) + return -EINVAL; + if (buf[1] != H2_PACKET_SIZE - 2) + return -EINVAL; + + if (this->prev_hwseq >= 0) + this->seq += (uint8_t)(buf[0] - this->prev_hwseq); + this->prev_hwseq = buf[0]; + + if (seqnum) + *seqnum = this->seq; + if (timestamp) + *timestamp = 0; + + return 2; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int res; + + *dst_out = 0; + + if (src_size < H2_PACKET_SIZE - 2) + return -EINVAL; + if (dst_size < LC3_A127_BLOCK_SIZE) + return -EINVAL; + + res = lc3_decode(this->dec, src, H2_PACKET_SIZE - 2, LC3_PCM_FORMAT_FLOAT, dst, 1); + if (res) + return -EINVAL; + + *dst_out = LC3_A127_BLOCK_SIZE; + return H2_PACKET_SIZE - 2; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +static const struct media_codec hfp_codec_a127 = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, + .kind = MEDIA_CODEC_HFP, + .codec_id = 127, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "lc3_a127", + .description = "LC3-24kHz", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-lc3-a127", + &hfp_codec_a127 +); diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 13c46ad87..97eae0d79 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -199,6 +199,14 @@ if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') + + bluez_codec_hfp_lc3_a127 = shared_library('spa-codec-bluez5-hfp-lc3-a127', + [ 'hfp-codec-lc3-a127.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, lc3_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-g722').allowed() From a30d385636023e2d8f46f65ed7046193251c608a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 13 Jun 2025 00:35:44 +0300 Subject: [PATCH 0404/1014] bluez5: reduce debug spam --- spa/plugins/bluez5/bluez5-device.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 7884a06dc..4b0048cc5 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -981,8 +981,9 @@ static void device_set_update_asha(struct impl *this, struct device_set *dset) ++num_devices; } - spa_log_debug(this->log, "%p: %s belongs to ASHA set %s leader:%d", this, - device->path, set->path, set->leader); + if (dset == &this->device_set) + spa_log_debug(this->log, "%p: %s belongs to ASHA set %s leader:%d", this, + device->path, set->path, set->leader); if (num_devices > 1) break; @@ -1069,8 +1070,9 @@ static void device_set_update_bap(struct impl *this, struct device_set *dset) ++num_devices; } - spa_log_debug(this->log, "%p: %s belongs to set %s leader:%d", this, - device->path, set->path, set->leader); + if (dset == &this->device_set) + spa_log_debug(this->log, "%p: %s belongs to set %s leader:%d", this, + device->path, set->path, set->leader); if (is_bap_client(this)) { dset->path = strdup(set->path); From 6e0970c14b1602012fccaee5bbe7bd7e98c42590 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Jun 2024 16:28:54 +0300 Subject: [PATCH 0405/1014] bluez5: simplify codec switch code Simplify codec switching code: determine what switch to perform immediately in spa_bt_device_ensure_media_codec(). The previous code doing "fallback" switching to various codecs is not useful, as A2DP generally disconnects on the first failure and all remote endpoints disappear. Add iteration over multiple endpoints, for reconfiguring both source and sink directions at the same time. This is in preparation of supporting BAP reconfiguration (for A2DP there's usually only one direction connected at a time). --- spa/plugins/bluez5/bluez5-dbus.c | 705 +++++++++++++++---------------- spa/plugins/bluez5/defs.h | 1 - 2 files changed, 343 insertions(+), 363 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index e2dc3e8df..407ea1fed 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -69,8 +69,6 @@ enum backend_selection { */ #define BLUEZ_ACTION_RATE_MSEC 3000 -#define CODEC_SWITCH_RETRIES 1 - /* How many times to retry acquire on errors, and how long delay to require before we can * try again. */ @@ -85,6 +83,7 @@ struct spa_bt_monitor { struct spa_log *log; struct spa_loop *main_loop; struct spa_loop *data_loop; + struct spa_loop_utils *loop_utils; struct spa_system *main_system; struct spa_system *data_system; struct spa_plugin_loader *plugin_loader; @@ -203,33 +202,36 @@ struct spa_bt_big { * with the desired capabilities. * The codec switch struct tracks candidates still to be tried. */ -struct spa_bt_media_codec_switch { + +#define SPA_TYPE_BT_WORK_CODEC_SWITCH SPA_TYPE_INFO_BT_WORK_BASE "CodecSwitch" +#define SPA_TYPE_BT_WORK_RATE_LIMIT SPA_TYPE_INFO_BT_WORK_BASE "RateLimit" + +struct spa_bt_codec_switch { + struct spa_list link; + + bool canceled; + bool failed; + bool waiting; + struct spa_bt_device *device; - struct spa_list device_link; - /* - * Codec switch may be waiting for either DBus reply from BlueZ - * or a timeout (but not both). - */ - struct spa_source timer; + struct spa_source *timer; DBusPendingCall *pending; - uint32_t profile; - /* * Called asynchronously, so endpoint paths instead of pointers (which may be * invalidated in the meantime). */ - const struct media_codec **codecs; + const struct media_codec *codec; char **paths; - - const struct media_codec **codec_iter; /**< outer iterator over codecs */ - char **path_iter; /**< inner iterator over endpoint paths */ - - uint16_t retries; - size_t num_paths; + char **path_iter; }; +static struct spa_bt_codec_switch *codec_switch_cmp_sw; /* global for qsort */ + +static void codec_switch_list_process(struct spa_list *codec_switch_list); +static void codec_switch_destroy(struct spa_bt_codec_switch *sw); + #define DEFAULT_RECONNECT_PROFILES SPA_BT_PROFILE_NULL #define DEFAULT_HW_VOLUME_PROFILES (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_HEADSET_HEAD_UNIT | \ SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK | \ @@ -262,8 +264,6 @@ static void spa_bt_transport_commit_release_timer(struct spa_bt_transport *trans static int device_start_timer(struct spa_bt_device *device); static int device_stop_timer(struct spa_bt_device *device); -static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw); - // Working with BlueZ Battery Provider. // Developed using https://github.com/dgreid/adhd/commit/655b58f as an example of DBus calls. @@ -562,20 +562,6 @@ static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct } } -static bool codec_has_direction(const struct media_codec *codec, enum spa_bt_media_direction direction) -{ - switch (direction) { - case SPA_BT_MEDIA_SOURCE: - case SPA_BT_MEDIA_SOURCE_BROADCAST: - return codec->encode; - case SPA_BT_MEDIA_SINK: - case SPA_BT_MEDIA_SINK_BROADCAST: - return codec->decode; - default: - spa_assert_not_reached(); - } -} - static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, enum spa_bt_media_direction direction) { @@ -598,6 +584,25 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, } } +static bool codec_has_direction(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) +{ + if (!is_media_codec_enabled(monitor, codec)) + return false; + if (!(get_codec_profile(codec, direction) & monitor->enabled_profiles)) + return false; + + switch (direction) { + case SPA_BT_MEDIA_SOURCE: + case SPA_BT_MEDIA_SOURCE_BROADCAST: + return codec->encode; + case SPA_BT_MEDIA_SINK: + case SPA_BT_MEDIA_SINK_BROADCAST: + return codec->decode; + default: + spa_assert_not_reached(); + } +} + static enum spa_bt_profile swap_profile(enum spa_bt_profile profile) { switch (profile) { @@ -618,6 +623,19 @@ static enum spa_bt_profile swap_profile(enum spa_bt_profile profile) } } +static uint32_t get_codec_target_profile(struct spa_bt_monitor *monitor, + const struct media_codec *codec) +{ + enum spa_bt_profile profile = 0; + int i; + + for (i = 0; i < SPA_BT_MEDIA_DIRECTION_LAST; ++i) + if (codec_has_direction(monitor, codec, i)) + profile |= swap_profile(get_codec_profile(codec, i)); + + return profile; +} + static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) @@ -625,10 +643,8 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor, /* Codecs with fill_caps == NULL share endpoint with another codec, * and don't have their own endpoint */ - return is_media_codec_enabled(monitor, codec) && - codec_has_direction(codec, direction) && - codec->fill_caps && - (get_codec_profile(codec, direction) & monitor->enabled_profiles); + return codec_has_direction(monitor, codec, direction) && + codec->fill_caps; } static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) @@ -1603,7 +1619,7 @@ static void device_clear_sub(struct spa_bt_device *device) static void device_free(struct spa_bt_device *device) { struct spa_bt_remote_endpoint *ep, *tep; - struct spa_bt_media_codec_switch *sw; + struct spa_bt_codec_switch *sw; struct spa_bt_transport *t, *tt; struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_set_membership *s; @@ -1633,8 +1649,8 @@ static void device_free(struct spa_bt_device *device) } } - spa_list_consume(sw, &device->codec_switch_list, device_link) - media_codec_switch_free(sw); + spa_list_consume(sw, &device->codec_switch_list, link) + codec_switch_destroy(sw); spa_list_consume(s, &device->set_membership_list, link) { spa_list_remove(&s->link); @@ -2068,6 +2084,11 @@ static int device_stop_timer(struct spa_bt_device *device) return 0; } +static bool has_codec_switch(struct spa_bt_device *device) +{ + return !spa_list_is_empty(&device->codec_switch_list); +} + int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) { struct spa_bt_monitor *monitor = device->monitor; @@ -2109,7 +2130,7 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) device, device->profiles, connected_profiles, connectable_profiles, device->added, all_connected, direction_connected, set_connected); - if (connected_profiles == 0 && spa_list_is_empty(&device->codec_switch_list)) { + if (connected_profiles == 0 && !has_codec_switch(device)) { device_stop_timer(device); device_connected(monitor, device, BT_DEVICE_DISCONNECTED); } else if (force || ((direction_connected || all_connected) && set_connected && connected_profiles)) { @@ -2136,10 +2157,10 @@ static void device_set_connected(struct spa_bt_device *device, int connected) spa_bt_quirks_log_features(monitor->quirks, device->adapter, device); spa_bt_device_check_profiles(device, false); } else { - /* Stop codec switch on disconnect */ - struct spa_bt_media_codec_switch *sw; - spa_list_consume(sw, &device->codec_switch_list, device_link) - media_codec_switch_free(sw); + /* Stop works on disconnect */ + struct spa_bt_codec_switch *sw; + spa_list_consume(sw, &device->codec_switch_list, link) + codec_switch_destroy(sw); if (device->reconnect_state != BT_DEVICE_RECONNECT_INIT) device_stop_timer(device); @@ -2556,7 +2577,8 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP; size_t i; - if (!is_media_codec_enabled(device->monitor, codec)) + codec_target_profile = get_codec_target_profile(monitor, codec); + if (!codec_target_profile) return false; if (codec->kind == MEDIA_CODEC_HFP) { @@ -2587,10 +2609,6 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru return false; } - for (i = 0, codec_target_profile = 0; i < (size_t)SPA_BT_MEDIA_DIRECTION_LAST; ++i) - if (codec_has_direction(codec, i)) - codec_target_profile |= swap_profile(get_codec_profile(codec, i)); - spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { enum spa_bt_profile ep_profile = spa_bt_profile_from_uuid(ep->uuid); @@ -4249,148 +4267,180 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, return 0; } -static void media_codec_switch_reply(DBusPendingCall *pending, void *userdata); - -static int media_codec_switch_cmp(const void *a, const void *b); - -static struct spa_bt_media_codec_switch *media_codec_switch_cmp_sw; /* global for qsort */ - -static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout); - -static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw); - -static void media_codec_switch_free(struct spa_bt_media_codec_switch *sw) +static void codec_switch_resume(struct spa_bt_codec_switch *sw) { - char **p; - - media_codec_switch_stop_timer(sw); - - cancel_and_unref(&sw->pending); - - if (sw->device != NULL) - spa_list_remove(&sw->device_link); - - if (sw->paths != NULL) - for (p = sw->paths; *p != NULL; ++p) - free(*p); - - free(sw->paths); - free(sw->codecs); - free(sw); + spa_assert(sw->waiting); + sw->waiting = false; + codec_switch_list_process(&sw->device->codec_switch_list); } -static void media_codec_switch_next(struct spa_bt_media_codec_switch *sw) +static void codec_switch_rate_limit_event(void *data, uint64_t exp) { - spa_assert(*sw->codec_iter != NULL && *sw->path_iter != NULL); + codec_switch_resume(data); +} - ++sw->path_iter; - if (*sw->path_iter == NULL) { - ++sw->codec_iter; - sw->path_iter = sw->paths; +static bool codec_switch_rate_limit(struct spa_bt_codec_switch *sw) +{ + struct spa_bt_device *device = sw->device; + struct spa_bt_monitor *monitor = device->monitor; + uint64_t now, wakeup; + struct timespec ts; + + now = get_time_now(monitor); + wakeup = device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; + if (now >= wakeup) + return false; + + if (!sw->timer) + sw->timer = spa_loop_utils_add_timer(monitor->loop_utils, + codec_switch_rate_limit_event, sw); + if (!sw->timer) + return false; + + ts.tv_sec = wakeup / SPA_NSEC_PER_SEC; + ts.tv_nsec = wakeup % SPA_NSEC_PER_SEC; + if (spa_loop_utils_update_timer(monitor->loop_utils, sw->timer, &ts, NULL, true) < 0) { + spa_loop_utils_destroy_source(monitor->loop_utils, sw->timer); + sw->timer = NULL; + return false; } - sw->retries = CODEC_SWITCH_RETRIES; + return true; } -static bool media_codec_switch_process_current(struct spa_bt_media_codec_switch *sw) +static bool codec_switch_check_endpoint(struct spa_bt_remote_endpoint *ep, + const struct media_codec *codec, + bool *sink, char **local_endpoint) { + enum spa_bt_media_direction direction; + spa_autofree char *path = NULL; + uint32_t ep_profile; + + if (!ep->uuid || !ep->capabilities || !ep->device) + return false; + + ep_profile = spa_bt_profile_from_uuid(ep->uuid); + if (!(ep_profile & get_codec_target_profile(ep->device->monitor, codec))) + return false; + + if (!media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, + &ep->device->monitor->default_audio_info, + &ep->device->monitor->global_settings)) + return false; + + if (ep_profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK)) { + direction = SPA_BT_MEDIA_SOURCE; + if (sink) + *sink = false; + } else if (ep_profile & (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_BAP_SOURCE)) { + direction = SPA_BT_MEDIA_SINK; + if (sink) + *sink = true; + } else { + return false; + } + + if (!(get_codec_profile(codec, direction) & ep->device->monitor->enabled_profiles)) + return false; + + if (media_codec_to_endpoint(codec, direction, &path) < 0) + return false; + + if (local_endpoint) + *local_endpoint = spa_steal_ptr(path); + + return true; +} + +static void codec_switch_reply(DBusPendingCall *pending, void *user_data) +{ + struct spa_bt_codec_switch *sw = user_data; + struct spa_bt_device *device = sw->device; + struct spa_bt_monitor *monitor = device->monitor; + + spa_assert(sw->pending == pending); + spa_autoptr(DBusMessage) r = steal_reply_and_unref(&sw->pending); + + spa_bt_device_update_last_bluez_action_time(device); + + if (r == NULL) { + spa_log_error(monitor->log, + "media codec switch %p: empty reply from dbus", + sw); + sw->failed = true; + } else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { + spa_log_error(monitor->log, + "media codec switch %p: failed (%s)", + sw, dbus_message_get_error_name(r)); + sw->failed = true; + } + + codec_switch_resume(sw); +} + +static bool codec_switch_configure(struct spa_bt_codec_switch *sw) +{ + struct spa_bt_device *device = sw->device; + struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; const struct media_codec *codec; uint8_t config[A2DP_MAX_CAPS_SIZE]; - enum spa_bt_media_direction direction; - spa_autofree char *local_endpoint = NULL; int res, config_size; + spa_autofree char *local_endpoint = NULL; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter iter, d; - int i; bool sink; - /* Try setting configuration for current codec on current endpoint in list */ + codec = sw->codec; + ep = device_remote_endpoint_find(device, *sw->path_iter); - codec = *sw->codec_iter; - - spa_log_debug(sw->device->monitor->log, "media codec switch %p: consider codec %s for remote endpoint %s", - sw, (*sw->codec_iter)->name, *sw->path_iter); - - ep = device_remote_endpoint_find(sw->device, *sw->path_iter); - - if (ep == NULL || ep->capabilities == NULL || ep->uuid == NULL) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s not valid, try next", - sw, *sw->path_iter); + if (!codec_switch_check_endpoint(ep, codec, &sink, &local_endpoint)) { + spa_log_error(monitor->log, "media codec switch %p: endpoint %s not valid", + sw, *sw->path_iter); return false; } - /* Setup and check compatible configuration */ - if (ep->codec != codec->codec_id) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: different codec, try next", sw); - return false; - } - - if (!(sw->profile & spa_bt_profile_from_uuid(ep->uuid))) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: wrong uuid (%s) for profile, try next", - sw, ep->uuid); - return false; - } - - if ((sw->profile & SPA_BT_PROFILE_A2DP_SINK) || (sw->profile & SPA_BT_PROFILE_BAP_SINK) ) { - direction = SPA_BT_MEDIA_SOURCE; - sink = false; - } else if ((sw->profile & SPA_BT_PROFILE_A2DP_SOURCE) || (sw->profile & SPA_BT_PROFILE_BAP_SOURCE) ) { - direction = SPA_BT_MEDIA_SINK; - sink = true; - } else { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: bad profile (%d), try next", - sw, sw->profile); - return false; - } - - if (media_codec_to_endpoint(codec, direction, &local_endpoint) < 0) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: no endpoint for codec %s, try next", - sw, codec->name); - return false; - } - - /* Each endpoint can be used by only one device at a time (on each adapter) */ - spa_list_for_each(t, &sw->device->monitor->transport_list, link) { - if (t->device == sw->device) - continue; - if (t->device->adapter != sw->device->adapter) - continue; - if (spa_streq(t->endpoint_path, local_endpoint)) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: endpoint %s in use, try next", - sw, local_endpoint); - return false; + if (!codec->bap) { + /* Each A2DP endpoint can be used by only one device at a time (on each adapter) */ + spa_list_for_each(t, &monitor->transport_list, link) { + if (t->device == device) + continue; + if (t->device->adapter != device->adapter) + continue; + if (spa_streq(t->endpoint_path, local_endpoint)) { + spa_log_error(monitor->log, "media codec switch %p: endpoint %s in use", + sw, local_endpoint); + return false; + } } } res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len, - &sw->device->monitor->default_audio_info, - &sw->device->monitor->global_settings, config); + &monitor->default_audio_info, &monitor->global_settings, config); if (res < 0) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: incompatible capabilities (%d), try next", + spa_log_error(monitor->log, "media codec switch %p: incompatible capabilities (%d)", sw, res); return false; } config_size = res; - spa_log_debug(sw->device->monitor->log, "media codec switch %p: configuration %d", sw, config_size); - for (i = 0; i < config_size; i++) - spa_log_debug(sw->device->monitor->log, "media codec switch %p: %d: %02x", sw, i, config[i]); + spa_log_debug(monitor->log, "media codec switch %p: configuration %d", sw, config_size); + spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 4, config, config_size); /* Codecs may share the same endpoint, so indicate which one we are using */ - sw->device->preferred_codec = codec; + device->preferred_codec = codec; /* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */ m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (m == NULL) { - spa_log_debug(sw->device->monitor->log, "media codec switch %p: dbus allocation failure, try next", sw); + spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); return false; } - spa_bt_device_update_last_bluez_action_time(sw->device); + spa_bt_device_update_last_bluez_action_time(device); - spa_log_info(sw->device->monitor->log, "media codec switch %p: trying codec %s for endpoint %s, local endpoint %s", + spa_log_info(monitor->log, "media codec switch %p: set codec %s for endpoint %s, local endpoint %s", sw, codec->name, ep->path, local_endpoint); dbus_message_iter_init_append(m, &iter); @@ -4400,191 +4450,99 @@ static bool media_codec_switch_process_current(struct spa_bt_media_codec_switch dbus_message_iter_close_container(&iter, &d); spa_assert(sw->pending == NULL); - sw->pending = send_with_reply(sw->device->monitor->conn, m, media_codec_switch_reply, sw); + sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); if (!sw->pending) { - spa_log_error(sw->device->monitor->log, "media codec switch %p: dbus call failure, try next", sw); + spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); return false; } return true; } -static void media_codec_switch_process(struct spa_bt_media_codec_switch *sw) +static bool codec_switch_process(struct spa_bt_codec_switch *sw) { - while (*sw->codec_iter != NULL && *sw->path_iter != NULL) { - uint64_t now, threshold; + if (sw->waiting) + return false; + if (sw->canceled) + return true; + if (sw->failed) + goto fail; - /* Rate limit BlueZ calls */ - now = get_time_now(sw->device->monitor); - threshold = sw->device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; - if (now < threshold) { - /* Wait for timeout */ - media_codec_switch_start_timer(sw, threshold - now); - return; - } + if (*sw->path_iter == NULL) { + /* Success */ + spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); + spa_bt_device_emit_codec_switched(sw->device, 0); + spa_bt_device_check_profiles(sw->device, false); + return true; + } - if (sw->path_iter == sw->paths && (*sw->codec_iter)->caps_preference_cmp) { - /* Sort endpoints according to codec preference, when at a new codec. */ - media_codec_switch_cmp_sw = sw; - qsort(sw->paths, sw->num_paths, sizeof(char *), media_codec_switch_cmp); - } + /* Rate limit BlueZ calls */ + if (codec_switch_rate_limit(sw)) + return false; - if (media_codec_switch_process_current(sw)) { - /* Wait for dbus reply */ - return; - } + if (!codec_switch_configure(sw)) + goto fail; - media_codec_switch_next(sw); - }; + /* Configure another endpoint next */ + ++sw->path_iter; - /* Didn't find any suitable endpoint. Report failure. */ - spa_log_info(sw->device->monitor->log, "media codec switch %p: failed to get an endpoint", sw); + /* Wait for dbus reply */ + return false; + +fail: + /* Report failure. */ + spa_log_info(sw->device->monitor->log, "media codec switch %p: failed", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); - media_codec_switch_free(sw); -} - -static bool media_codec_switch_goto_active(struct spa_bt_media_codec_switch *sw) -{ - struct spa_bt_device *device = sw->device; - struct spa_bt_media_codec_switch *active_sw; - - active_sw = spa_list_first(&device->codec_switch_list, struct spa_bt_media_codec_switch, device_link); - - if (active_sw != sw) { - struct spa_bt_media_codec_switch *t; - - /* This codec switch has been canceled. Switch to the newest one. */ - spa_log_debug(sw->device->monitor->log, - "media codec switch %p: canceled, go to new switch", sw); - - spa_list_for_each_safe(sw, t, &device->codec_switch_list, device_link) { - if (sw != active_sw) - media_codec_switch_free(sw); - } - - media_codec_switch_process(active_sw); - return false; - } - return true; } -static void media_codec_switch_timer_event(struct spa_source *source) +static void codec_switch_cancel(struct spa_bt_codec_switch *sw) { - struct spa_bt_media_codec_switch *sw = source->data; - struct spa_bt_device *device = sw->device; - struct spa_bt_monitor *monitor = device->monitor; - uint64_t exp; - - if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) - spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); - - spa_log_debug(monitor->log, "media codec switch %p: rate limit timer event", sw); - - media_codec_switch_stop_timer(sw); - - if (!media_codec_switch_goto_active(sw)) - return; - - media_codec_switch_process(sw); + /* BlueZ does not appear to allow calling dbus_pending_call_cancel on an + * active request, so we have to wait for the reply to arrive. + */ + sw->canceled = true; } -static void media_codec_switch_reply(DBusPendingCall *pending, void *user_data) +static void codec_switch_destroy(struct spa_bt_codec_switch *sw) { - struct spa_bt_media_codec_switch *sw = user_data; - struct spa_bt_device *device = sw->device; + char **p; - spa_assert(sw->pending == pending); - spa_autoptr(DBusMessage) r = steal_reply_and_unref(&sw->pending); + spa_list_remove(&sw->link); - spa_bt_device_update_last_bluez_action_time(device); + cancel_and_unref(&sw->pending); - if (!media_codec_switch_goto_active(sw)) - return; + if (sw->paths != NULL) + for (p = sw->paths; *p != NULL; ++p) + free(*p); - if (r == NULL) { - spa_log_error(sw->device->monitor->log, - "media codec switch %p: empty reply from dbus, trying next", - sw); - goto next; - } + if (sw->timer) + spa_loop_utils_destroy_source(sw->device->monitor->loop_utils, sw->timer); - if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { - spa_log_debug(sw->device->monitor->log, - "media codec switch %p: failed (%s), trying next", - sw, dbus_message_get_error_name(r)); - goto next; - } - - /* Success */ - spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); - spa_bt_device_emit_codec_switched(sw->device, 0); - spa_bt_device_check_profiles(sw->device, false); - media_codec_switch_free(sw); - return; - -next: - if (sw->retries > 0) - --sw->retries; - else - media_codec_switch_next(sw); - - media_codec_switch_process(sw); - return; + free(sw->paths); + free(sw); } -static int media_codec_switch_start_timer(struct spa_bt_media_codec_switch *sw, uint64_t timeout) +static void codec_switch_list_process(struct spa_list *list) { + struct spa_bt_codec_switch *sw; + + spa_list_consume(sw, list, link) { + if (codec_switch_process(sw)) { + codec_switch_destroy(sw); + } else { + sw->waiting = true; + break; + } + } +} + +static int codec_switch_cmp(const void *a, const void *b) +{ + struct spa_bt_codec_switch *sw = codec_switch_cmp_sw; + const struct media_codec *codec = sw->codec; struct spa_bt_monitor *monitor = sw->device->monitor; - struct itimerspec ts; - - spa_assert(sw->timer.data == NULL); - - spa_log_debug(monitor->log, "media codec switch %p: starting rate limit timer", sw); - - if (sw->timer.data == NULL) { - sw->timer.data = sw; - sw->timer.func = media_codec_switch_timer_event; - sw->timer.fd = spa_system_timerfd_create(monitor->main_system, - CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - sw->timer.mask = SPA_IO_IN; - sw->timer.rmask = 0; - spa_loop_add_source(monitor->main_loop, &sw->timer); - } - ts.it_value.tv_sec = timeout / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = timeout % SPA_NSEC_PER_SEC; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(monitor->main_system, sw->timer.fd, 0, &ts, NULL); - return 0; -} - -static int media_codec_switch_stop_timer(struct spa_bt_media_codec_switch *sw) -{ - struct spa_bt_monitor *monitor = sw->device->monitor; - struct itimerspec ts; - - if (sw->timer.data == NULL) - return 0; - - spa_log_debug(monitor->log, "media codec switch %p: stopping rate limit timer", sw); - - spa_loop_remove_source(monitor->main_loop, &sw->timer); - ts.it_value.tv_sec = 0; - ts.it_value.tv_nsec = 0; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(monitor->main_system, sw->timer.fd, 0, &ts, NULL); - spa_system_close(monitor->main_system, sw->timer.fd); - sw->timer.data = NULL; - return 0; -} - -static int media_codec_switch_cmp(const void *a, const void *b) -{ - struct spa_bt_media_codec_switch *sw = media_codec_switch_cmp_sw; - const struct media_codec *codec = *sw->codec_iter; const char *path1 = *(char **)a, *path2 = *(char **)b; struct spa_bt_remote_endpoint *ep1, *ep2; uint32_t flags; @@ -4614,18 +4572,20 @@ static int media_codec_switch_cmp(const void *a, const void *b) flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; return codec->caps_preference_cmp(codec, flags, ep1->capabilities, ep1->capabilities_len, - ep2->capabilities, ep2->capabilities_len, &sw->device->monitor->default_audio_info, - &sw->device->monitor->global_settings); + ep2->capabilities, ep2->capabilities_len, &monitor->default_audio_info, + &monitor->global_settings); } /* Ensure there's a transport for at least one of the listed codecs */ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs) { - struct spa_bt_media_codec_switch *sw; + struct spa_bt_monitor *monitor = device->monitor; + struct spa_bt_codec_switch *sw, *sw2; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; - const struct media_codec *preferred_codec = NULL; - size_t i, j, num_codecs, num_eps; + const struct media_codec *codec = NULL; + size_t i, j, num_eps, res; + uint32_t profiles = 0; if (!device->adapter->a2dp_application_registered && !device->adapter->bap_application_registered) { @@ -4635,18 +4595,21 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct for (i = 0; codecs[i] != NULL; ++i) { if (spa_bt_device_supports_media_codec(device, codecs[i], device->connected_profiles)) { - preferred_codec = codecs[i]; + codec = codecs[i]; break; } } - /* Check if we already have an enabled transport for the most preferred codec. + if (!codec) + return -EINVAL; + + /* Check if we already have an enabled transport for the codec. * However, if there already was a codec switch running, these transports may * disappear soon. In that case, we have to do the full thing. */ - if (spa_list_is_empty(&device->codec_switch_list) && preferred_codec != NULL) { + if (!has_codec_switch(device)) { spa_list_for_each(t, &device->transport_list, device_link) { - if (t->media_codec != preferred_codec) + if (t->media_codec != codec) continue; if ((device->connected_profiles & t->profile) != t->profile) @@ -4657,77 +4620,89 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct } } - /* Setup and start iteration */ - - sw = calloc(1, sizeof(struct spa_bt_media_codec_switch)); + /* Setup */ + sw = calloc(1, sizeof(struct spa_bt_codec_switch)); if (sw == NULL) - return -ENOMEM; + goto error_errno; + sw->codec = codec; + sw->device = device; + + spa_list_append(&device->codec_switch_list, &sw->link); + + /* Find endpoints */ num_eps = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) ++num_eps; - num_codecs = 0; - while (codecs[num_codecs] != NULL) - ++num_codecs; - - sw->codecs = calloc(num_codecs + 1, sizeof(const struct media_codec *)); sw->paths = calloc(num_eps + 1, sizeof(char *)); - sw->num_paths = num_eps; + if (!sw->paths) + goto error_errno; - if (sw->codecs == NULL || sw->paths == NULL) { - media_codec_switch_free(sw); - return -ENOMEM; - } - - for (i = 0, j = 0; i < num_codecs; ++i) { - if (is_media_codec_enabled(device->monitor, codecs[i])) { - sw->codecs[j] = codecs[i]; - ++j; - } - } - sw->codecs[j] = NULL; + sw->path_iter = sw->paths; i = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { sw->paths[i] = strdup(ep->path); - if (sw->paths[i] == NULL) { - media_codec_switch_free(sw); - return -ENOMEM; - } + if (sw->paths[i] == NULL) + goto error_errno; ++i; } - sw->paths[i] = NULL; - sw->codec_iter = sw->codecs; - sw->path_iter = sw->paths; - sw->retries = CODEC_SWITCH_RETRIES; + /* Sort in codec preference order */ + codec_switch_cmp_sw = sw; + qsort(sw->paths, num_eps, sizeof(char *), codec_switch_cmp); - sw->profile = device->connected_profiles; + /* Pick at most one source and one sink endpoint, if corresponding profiles are + * set */ + profiles = device->connected_profiles; + for (i = 0, j = 0; i < num_eps; ++i) { + struct spa_bt_remote_endpoint *ep; + bool sink; + uint32_t mask; - sw->device = device; + ep = remote_endpoint_find(monitor, sw->paths[i]); + if (!codec_switch_check_endpoint(ep, codec, &sink, NULL)) + continue; - if (!spa_list_is_empty(&device->codec_switch_list)) { - /* - * There's a codec switch already running, either waiting for timeout or - * BlueZ reply. - * - * BlueZ does not appear to allow calling dbus_pending_call_cancel on an - * active request, so we have to wait for the reply to arrive first, and - * only then start processing this request. The timeout we would also have - * to wait to pass in any case, so we don't cancel it either. - */ - spa_log_debug(sw->device->monitor->log, - "media codec switch %p: already in progress, canceling previous", - sw); + mask = sink ? SPA_BT_PROFILE_MEDIA_SOURCE : SPA_BT_PROFILE_MEDIA_SINK; + if (!(profiles & mask)) + continue; + SPA_FLAG_CLEAR(profiles, mask); - spa_list_prepend(&device->codec_switch_list, &sw->device_link); - } else { - spa_list_prepend(&device->codec_switch_list, &sw->device_link); - media_codec_switch_process(sw); + spa_log_debug(monitor->log, + "media codec switch %p: select endpoint %s for codec %s", + sw, sw->paths[i], codec->name); + + SPA_SWAP(sw->paths[j], sw->paths[i]); + ++j; + } + for (; j < num_eps; ++j) { + free(sw->paths[j]); + sw->paths[j] = NULL; } + if (!sw->paths[0]) { + spa_log_debug(monitor->log, + "media codec switch %p: no valid endpoints for codec %s", + sw, codec->name); + errno = EINVAL; + goto error_errno; + } + + /* Cancel other codec switches */ + spa_list_for_each(sw2, &device->codec_switch_list, link) + if (sw2 != sw) + codec_switch_cancel(sw2); + + codec_switch_list_process(&device->codec_switch_list); return 0; + +error_errno: + res = -errno; + if (sw) + codec_switch_destroy(sw); + return res; } int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec) @@ -6736,6 +6711,7 @@ impl_init(const struct spa_handle_factory *factory, this->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); this->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->plugin_loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); @@ -6752,6 +6728,11 @@ impl_init(const struct spa_handle_factory *factory, return -EINVAL; } + if (this->loop_utils == NULL) { + spa_log_error(this->log, "loop utils is needed"); + return -EINVAL; + } + this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index b64b02141..a4f1b7168 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -475,7 +475,6 @@ static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t blu return SPA_BT_FORM_FACTOR_UNKNOWN; } -struct spa_bt_media_codec_switch; struct spa_bt_transport; struct spa_bt_device_events { From 592a97a7b0ebcb6e8569e0627d9b31d8c6bb5a24 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 30 Jun 2024 17:53:09 +0300 Subject: [PATCH 0406/1014] bluez5: keep BAP endpoint properties in spa_bt_remote_endpoint Make QoS, Context etc. always up to date in spa_bt_remote_endpoint. --- spa/plugins/bluez5/bluez5-dbus.c | 146 ++++++++++++++++++------------- 1 file changed, 86 insertions(+), 60 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 407ea1fed..b49a23ce5 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -151,11 +151,13 @@ struct spa_bt_remote_endpoint { char *uuid; unsigned int codec; struct spa_bt_device *device; - uint8_t *capabilities; + uint8_t capabilities[A2DP_MAX_CAPS_SIZE]; int capabilities_len; bool delay_reporting; bool acceptor; + struct bap_endpoint_qos qos; + bool asha_right_side; uint64_t hisyncid; }; @@ -876,10 +878,6 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter const char *key = NULL; int type = 0; - memset(caps, 0, A2DP_MAX_CAPS_SIZE); - *endpoint_path = NULL; - memset(qos, 0, sizeof(*qos)); - if (!check_iter_signature(&dict_iter, "{sv}")) { spa_log_warn(monitor->log, "Invalid BAP Endpoint QoS in DBus"); return -EINVAL; @@ -898,6 +896,9 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter if (spa_streq(key, "Capabilities")) { uint8_t *buf; + if (!caps) + goto next; + if (type != DBUS_TYPE_ARRAY) goto bad_property; @@ -916,6 +917,9 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter spa_log_info(monitor->log, "%p: %s size:%d", monitor, key, *caps_size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', caps, (size_t)*caps_size); } else if (spa_streq(key, "Endpoint")) { + if (!endpoint_path) + goto next; + if (type != DBUS_TYPE_OBJECT_PATH) goto bad_property; @@ -923,6 +927,9 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter spa_log_info(monitor->log, "%p: %s %s", monitor, key, *endpoint_path); } else if (spa_streq(key, "QoS")) { + if (!qos) + goto next; + if (!check_iter_signature(&it[1], "a{sv}")) goto bad_property; @@ -931,6 +938,9 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter } else if (spa_streq(key, "Locations") || spa_streq(key, "Location")) { dbus_uint32_t value; + if (!qos) + goto next; + if (type != DBUS_TYPE_UINT32) goto bad_property; @@ -940,14 +950,34 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter } else if (spa_streq(key, "ChannelAllocation")) { dbus_uint32_t value; + if (!qos) + goto next; + if (type != DBUS_TYPE_UINT32) goto bad_property; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); qos->channel_allocation = value; + } else if (spa_streq(key, "Context") || spa_streq(key, "SupportedContext")) { + dbus_uint16_t value; + + if (!qos) + goto next; + + if (type != DBUS_TYPE_UINT16) + goto bad_property; + + dbus_message_iter_get_basic(&it[1], &value); + spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); + + if (spa_streq(key, "Context")) + qos->context = value; + else if (spa_streq(key, "SupportedContext")) + qos->supported_context = value; } +next: dbus_message_iter_next(&dict_iter); } @@ -974,16 +1004,11 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe unsigned int i, j; const char *endpoint_path = NULL; - uint8_t caps[A2DP_MAX_CAPS_SIZE]; uint8_t config[A2DP_MAX_CAPS_SIZE]; char locations[64] = {0}; char channel_allocation[64] = {0}; - int caps_size = 0; int conf_size; DBusMessageIter dict; - struct bap_endpoint_qos endpoint_qos; - - spa_zero(endpoint_qos); if (!dbus_message_iter_init(m, &args) || !spa_streq(dbus_message_get_signature(m), "a{sv}")) { spa_log_error(monitor->log, "Invalid signature for method SelectProperties()"); @@ -1009,27 +1034,34 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe goto error; } - /* Parse endpoint properties */ - if (parse_endpoint_props(monitor, &props, caps, &caps_size, &endpoint_path, &endpoint_qos) < 0) + /* Find endpoint */ + iter = props; + if (parse_endpoint_props(monitor, &iter, NULL, NULL, &endpoint_path, NULL) < 0) goto error_invalid; - if (endpoint_qos.locations) - spa_scnprintf(locations, sizeof(locations), "%"PRIu32, endpoint_qos.locations); - if (endpoint_qos.channel_allocation) - spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, endpoint_qos.channel_allocation); ep = remote_endpoint_find(monitor, endpoint_path); - if (!ep || !ep->device) { + if (!ep || !ep->device || !ep->uuid) { spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", endpoint_path); goto error_invalid; } - duplex = SPA_FLAG_IS_SET(ep->device->profiles, SPA_BT_PROFILE_BAP_DUPLEX); - - /* Call of SelectProperties means that local device acts as an initiator - * and therefore remote endpoint is an acceptor + /* Call of SelectProperties means that local device is BAP Client + * and therefore remote endpoint is BAP Server / acceptor */ ep->acceptor = true; + /* Parse endpoint properties */ + iter = props; + if (parse_endpoint_props(monitor, &iter, ep->capabilities, &ep->capabilities_len, NULL, &ep->qos) < 0) + goto error_invalid; + + if (ep->qos.locations) + spa_scnprintf(locations, sizeof(locations), "%"PRIu32, ep->qos.locations); + if (ep->qos.channel_allocation) + spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, ep->qos.channel_allocation); + + duplex = SPA_FLAG_IS_SET(ep->device->profiles, SPA_BT_PROFILE_BAP_DUPLEX); + i = 0; setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.channel-allocation", channel_allocation); @@ -1041,7 +1073,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe setting_items[i] = ep->device->settings->items[j]; settings = SPA_DICT_INIT(setting_items, i); - conf_size = codec->select_config(codec, 0, caps, caps_size, &monitor->default_audio_info, &settings, config); + conf_size = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len, + &monitor->default_audio_info, &settings, config); if (conf_size < 0) { spa_log_error(monitor->log, "can't select config: %d (%s)", conf_size, spa_strerror(conf_size)); @@ -1070,7 +1103,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe spa_zero(qos); - res = codec->get_qos(codec, config, conf_size, &endpoint_qos, &qos, &settings); + res = codec->get_qos(codec, config, conf_size, &ep->qos, &qos, &settings); if (res < 0) { spa_log_error(monitor->log, "can't select QOS config: %d (%s)", res, spa_strerror(res)); @@ -2756,6 +2789,11 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en DBusMessageIter *invalidated_iter) { struct spa_bt_monitor *monitor = remote_endpoint->monitor; + DBusMessageIter copy_iter = *props_iter; + + parse_endpoint_props(monitor, ©_iter, + remote_endpoint->capabilities, &remote_endpoint->capabilities_len, NULL, + &remote_endpoint->qos); while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; @@ -2769,7 +2807,12 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en type = dbus_message_iter_get_arg_type(&it[1]); - if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { + if (spa_streq(key, "Capabilities") || spa_streq(key, "Locations") || + spa_streq(key, "QoS") || spa_streq(key, "Context") || + spa_streq(key, "SupportedContext")) { + /* parsed by parse_endpoint_props */ + } + else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char *value; dbus_message_iter_get_basic(&it[1], &value); @@ -2790,9 +2833,8 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en struct spa_bt_device *device; device = spa_bt_device_find(monitor, value); - if (device == NULL) { + if (device == NULL) goto next; - } spa_log_debug(monitor->log, "remote_endpoint %p: device -> %p", remote_endpoint, device); @@ -2803,17 +2845,17 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en if (device != NULL) spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); } - } - /* For ASHA */ - else if (spa_streq(key, "Transport")) { + } else if (spa_streq(key, "Transport")) { + /* For ASHA */ free(remote_endpoint->transport_path); remote_endpoint->transport_path = strdup(value); - } - else if (spa_streq(key, "Side")) { + } else if (spa_streq(key, "Side")) { if (spa_streq(value, "right")) remote_endpoint->asha_right_side = true; else remote_endpoint->asha_right_side = false; + } else { + goto unhandled; } } else if (type == DBUS_TYPE_BOOLEAN) { @@ -2825,6 +2867,8 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en if (spa_streq(key, "DelayReporting")) { remote_endpoint->delay_reporting = value; + } else { + goto unhandled; } } else if (type == DBUS_TYPE_BYTE) { @@ -2836,39 +2880,20 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en if (spa_streq(key, "Codec")) { remote_endpoint->codec = value; + } else { + goto unhandled; } } - /* Codecs property is present for ASHA */ else if (type == DBUS_TYPE_UINT16) { + /* Codecs property is present for ASHA */ uint16_t value; dbus_message_iter_get_basic(&it[1], &value); if (spa_streq(key, "Codecs")) { spa_log_debug(monitor->log, "remote_endpoint %p: %s=%02x", remote_endpoint, key, value); - } - } - else if (spa_streq(key, "Capabilities")) { - DBusMessageIter iter; - uint8_t *value; - int len; - - if (!check_iter_signature(&it[1], "ay")) - goto next; - - dbus_message_iter_recurse(&it[1], &iter); - dbus_message_iter_get_fixed_array(&iter, &value, &len); - - spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); - spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); - - free(remote_endpoint->capabilities); - remote_endpoint->capabilities_len = 0; - - remote_endpoint->capabilities = malloc(len); - if (remote_endpoint->capabilities) { - memcpy(remote_endpoint->capabilities, value, len); - remote_endpoint->capabilities_len = len; + } else { + goto unhandled; } } /* @@ -2893,8 +2918,10 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid); } - else + else { +unhandled: spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); + } next: dbus_message_iter_next(props_iter); @@ -2950,7 +2977,6 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint) free(remote_endpoint->path); free(remote_endpoint->transport_path); free(remote_endpoint->uuid); - free(remote_endpoint->capabilities); free(remote_endpoint); } @@ -4316,7 +4342,7 @@ static bool codec_switch_check_endpoint(struct spa_bt_remote_endpoint *ep, spa_autofree char *path = NULL; uint32_t ep_profile; - if (!ep->uuid || !ep->capabilities || !ep->device) + if (!ep->uuid || !ep->device) return false; ep_profile = spa_bt_profile_from_uuid(ep->uuid); @@ -4550,9 +4576,9 @@ static int codec_switch_cmp(const void *a, const void *b) ep1 = device_remote_endpoint_find(sw->device, path1); ep2 = device_remote_endpoint_find(sw->device, path2); - if (ep1 != NULL && (ep1->uuid == NULL || ep1->codec != codec->codec_id || ep1->capabilities == NULL)) + if (ep1 != NULL && (ep1->uuid == NULL || ep1->codec != codec->codec_id)) ep1 = NULL; - if (ep2 != NULL && (ep2->uuid == NULL || ep2->codec != codec->codec_id || ep2->capabilities == NULL)) + if (ep2 != NULL && (ep2->uuid == NULL || ep2->codec != codec->codec_id)) ep2 = NULL; if (ep1 && ep2 && !spa_streq(ep1->uuid, ep2->uuid)) { ep1 = NULL; From 8795298f694213037737ef96f0b18a3e3725dabb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 16 Feb 2025 15:51:39 +0200 Subject: [PATCH 0407/1014] bluez5: implement BAP ucast reconfiguration Use SelectProperties() DBus API to reconfigure BAP unicast setup. Add support to spa_bt_ensure_codec() to select whether to configure as sink/source/duplex. --- spa/plugins/bluez5/bluez5-dbus.c | 266 +++++++++++++++++++++++------ spa/plugins/bluez5/bluez5-device.c | 5 +- spa/plugins/bluez5/defs.h | 8 +- 3 files changed, 223 insertions(+), 56 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index b49a23ce5..8136e6a6c 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -208,6 +208,11 @@ struct spa_bt_big { #define SPA_TYPE_BT_WORK_CODEC_SWITCH SPA_TYPE_INFO_BT_WORK_BASE "CodecSwitch" #define SPA_TYPE_BT_WORK_RATE_LIMIT SPA_TYPE_INFO_BT_WORK_BASE "RateLimit" +struct spa_bt_codec_switch_path { + char *path; + bool clear; +}; + struct spa_bt_codec_switch { struct spa_list link; @@ -215,6 +220,8 @@ struct spa_bt_codec_switch { bool failed; bool waiting; + uint32_t profiles; + struct spa_bt_device *device; struct spa_source *timer; @@ -225,8 +232,8 @@ struct spa_bt_codec_switch { * invalidated in the meantime). */ const struct media_codec *codec; - char **paths; - char **path_iter; + struct spa_bt_codec_switch_path *paths; + unsigned int path_idx; }; static struct spa_bt_codec_switch *codec_switch_cmp_sw; /* global for qsort */ @@ -1060,7 +1067,10 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe if (ep->qos.channel_allocation) spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, ep->qos.channel_allocation); - duplex = SPA_FLAG_IS_SET(ep->device->profiles, SPA_BT_PROFILE_BAP_DUPLEX); + if (!ep->device->preferred_profiles) + ep->device->preferred_profiles = ep->device->profiles; + + duplex = SPA_FLAG_IS_SET(ep->device->preferred_profiles, SPA_BT_PROFILE_BAP_DUPLEX); i = 0; setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); @@ -1647,6 +1657,7 @@ static void device_clear_sub(struct spa_bt_device *device) battery_remove(device); spa_bt_device_release_transports(device); device->preferred_codec = NULL; + device->preferred_profiles = 0; } static void device_free(struct spa_bt_device *device) @@ -2163,7 +2174,9 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) device, device->profiles, connected_profiles, connectable_profiles, device->added, all_connected, direction_connected, set_connected); - if (connected_profiles == 0 && !has_codec_switch(device)) { + if (has_codec_switch(device)) { + /* noop */ + } else if (connected_profiles == 0) { device_stop_timer(device); device_connected(monitor, device, BT_DEVICE_DISCONNECTED); } else if (force || ((direction_connected || all_connected) && set_connected && connected_profiles)) { @@ -3146,6 +3159,7 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) free(transport->configuration); free(transport->endpoint_path); + free(transport->remote_endpoint_path); free(transport->path); free(transport); } @@ -3543,6 +3557,10 @@ static int transport_update_props(struct spa_bt_transport *transport, } else if (spa_streq(key, "Endpoint")) { struct spa_bt_remote_endpoint *ep = remote_endpoint_find(monitor, value); + + free(transport->remote_endpoint_path); + transport->remote_endpoint_path = strdup(value); + if (!ep) { spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", value); goto next; @@ -4261,7 +4279,9 @@ static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, spa_log_debug(monitor->log, "Setting ASHA codec: %s", mcodec->name); } + free(transport->remote_endpoint_path); free(transport->endpoint_path); + transport->remote_endpoint_path = strdup(remote_endpoint->path); transport->endpoint_path = strdup(remote_endpoint->path); transport->profile = SPA_BT_PROFILE_ASHA_SINK; transport->media_codec = codec; @@ -4404,7 +4424,7 @@ static void codec_switch_reply(DBusPendingCall *pending, void *user_data) codec_switch_resume(sw); } -static bool codec_switch_configure(struct spa_bt_codec_switch *sw) +static bool codec_switch_configure_a2dp(struct spa_bt_codec_switch *sw, const char *path) { struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; @@ -4419,26 +4439,24 @@ static bool codec_switch_configure(struct spa_bt_codec_switch *sw) bool sink; codec = sw->codec; - ep = device_remote_endpoint_find(device, *sw->path_iter); + ep = device_remote_endpoint_find(device, path); if (!codec_switch_check_endpoint(ep, codec, &sink, &local_endpoint)) { spa_log_error(monitor->log, "media codec switch %p: endpoint %s not valid", - sw, *sw->path_iter); + sw, path); return false; } - if (!codec->bap) { - /* Each A2DP endpoint can be used by only one device at a time (on each adapter) */ - spa_list_for_each(t, &monitor->transport_list, link) { - if (t->device == device) - continue; - if (t->device->adapter != device->adapter) - continue; - if (spa_streq(t->endpoint_path, local_endpoint)) { - spa_log_error(monitor->log, "media codec switch %p: endpoint %s in use", - sw, local_endpoint); - return false; - } + /* Each A2DP endpoint can be used by only one device at a time (on each adapter) */ + spa_list_for_each(t, &monitor->transport_list, link) { + if (t->device == device) + continue; + if (t->device->adapter != device->adapter) + continue; + if (spa_streq(t->endpoint_path, local_endpoint)) { + spa_log_error(monitor->log, "media codec switch %p: endpoint %s in use", + sw, local_endpoint); + return false; } } @@ -4485,6 +4503,85 @@ static bool codec_switch_configure(struct spa_bt_codec_switch *sw) return true; } +static bool codec_switch_configure_bap(struct spa_bt_codec_switch *sw, const char *path, bool last) +{ + struct spa_bt_device *device = sw->device; + struct spa_bt_monitor *monitor = device->monitor; + struct spa_bt_remote_endpoint *ep; + spa_autoptr(DBusMessage) m = NULL; + DBusMessageIter iter, d; + dbus_bool_t defer = !last; + + ep = device_remote_endpoint_find(device, path); + if (!ep) { + spa_log_error(monitor->log, "media codec switch %p: no endpoint %s", sw, path); + return false; + } + + device->preferred_codec = sw->codec; + device->preferred_profiles = sw->profiles; + + m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Reconfigure"); + if (m == NULL) { + spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); + return false; + } + + spa_bt_device_update_last_bluez_action_time(device); + + spa_log_info(monitor->log, "media codec switch %p: reconfigure endpoint %s, defer:%d", + sw, ep->path, (int)defer); + + dbus_message_iter_init_append(m, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &d); + append_basic_variant_dict_entry(&d, "Defer", DBUS_TYPE_BOOLEAN, "b", &defer); + dbus_message_iter_close_container(&iter, &d); + + spa_assert(sw->pending == NULL); + sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); + if (!sw->pending) { + spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); + return false; + } + + return true; +} + +static bool codec_switch_clear_bap(struct spa_bt_codec_switch *sw, const char *path) +{ + struct spa_bt_device *device = sw->device; + struct spa_bt_monitor *monitor = device->monitor; + struct spa_bt_remote_endpoint *ep; + spa_autoptr(DBusMessage) m = NULL; + DBusMessageIter iter; + + ep = device_remote_endpoint_find(device, path); + if (!ep) + return true; + + m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"); + if (m == NULL) { + spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); + return false; + } + + spa_bt_device_update_last_bluez_action_time(device); + + spa_log_info(monitor->log, "media codec switch %p: clear endpoint %s", sw, ep->path); + + dbus_message_iter_init_append(m, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + spa_assert(sw->pending == NULL); + sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); + if (!sw->pending) { + spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); + return false; + } + + return true; +} + static bool codec_switch_process(struct spa_bt_codec_switch *sw) { if (sw->waiting) @@ -4494,7 +4591,7 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) if (sw->failed) goto fail; - if (*sw->path_iter == NULL) { + if (sw->paths[sw->path_idx].path == NULL) { /* Success */ spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); @@ -4502,15 +4599,42 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) return true; } - /* Rate limit BlueZ calls */ - if (codec_switch_rate_limit(sw)) - return false; + if (sw->profiles & SPA_BT_PROFILE_A2DP_DUPLEX) { + /* Rate limit BlueZ calls */ + if (codec_switch_rate_limit(sw)) + return false; - if (!codec_switch_configure(sw)) - goto fail; + if (!codec_switch_configure_a2dp(sw, sw->paths[sw->path_idx].path)) + goto fail; + } else { + if (sw->path_idx == 0 && codec_switch_rate_limit(sw)) + return false; + + if (sw->path_idx == 0) { + struct spa_bt_transport *t, *t2; + + /* Force CIG inactive */ + spa_list_for_each(t, &sw->device->transport_list, link) { + spa_bt_transport_release_now(t); + spa_list_for_each(t2, &sw->device->monitor->transport_list, link) + if (t2->device != sw->device && transport_in_same_cig(t, t2)) + spa_bt_transport_release_now(t2); + } + } + + if (sw->paths[sw->path_idx].clear) { + if (!codec_switch_clear_bap(sw, sw->paths[sw->path_idx].path)) + goto fail; + } else { + bool last = (sw->paths[sw->path_idx + 1].path == NULL); + + if (!codec_switch_configure_bap(sw, sw->paths[sw->path_idx].path, last)) + goto fail; + } + } /* Configure another endpoint next */ - ++sw->path_iter; + sw->path_idx++; /* Wait for dbus reply */ return false; @@ -4533,15 +4657,15 @@ static void codec_switch_cancel(struct spa_bt_codec_switch *sw) static void codec_switch_destroy(struct spa_bt_codec_switch *sw) { - char **p; + unsigned int i; spa_list_remove(&sw->link); cancel_and_unref(&sw->pending); if (sw->paths != NULL) - for (p = sw->paths; *p != NULL; ++p) - free(*p); + for (i = 0; sw->paths[i].path; ++i) + free(sw->paths[i].path); if (sw->timer) spa_loop_utils_destroy_source(sw->device->monitor->loop_utils, sw->timer); @@ -4569,12 +4693,13 @@ static int codec_switch_cmp(const void *a, const void *b) struct spa_bt_codec_switch *sw = codec_switch_cmp_sw; const struct media_codec *codec = sw->codec; struct spa_bt_monitor *monitor = sw->device->monitor; - const char *path1 = *(char **)a, *path2 = *(char **)b; + const struct spa_bt_codec_switch_path *path1 = a; + const struct spa_bt_codec_switch_path *path2 = b; struct spa_bt_remote_endpoint *ep1, *ep2; uint32_t flags; - ep1 = device_remote_endpoint_find(sw->device, path1); - ep2 = device_remote_endpoint_find(sw->device, path2); + ep1 = device_remote_endpoint_find(sw->device, path1->path); + ep2 = device_remote_endpoint_find(sw->device, path2->path); if (ep1 != NULL && (ep1->uuid == NULL || ep1->codec != codec->codec_id)) ep1 = NULL; @@ -4603,7 +4728,8 @@ static int codec_switch_cmp(const void *a, const void *b) } /* Ensure there's a transport for at least one of the listed codecs */ -int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs) +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs, + uint32_t profiles) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_codec_switch *sw, *sw2; @@ -4611,7 +4737,7 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct struct spa_bt_transport *t; const struct media_codec *codec = NULL; size_t i, j, num_eps, res; - uint32_t profiles = 0; + uint32_t remaining = 0; if (!device->adapter->a2dp_application_registered && !device->adapter->bap_application_registered) { @@ -4626,21 +4752,28 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct } } + if (!profiles) + profiles = device->connected_profiles & (SPA_BT_PROFILE_MEDIA_SOURCE | + SPA_BT_PROFILE_MEDIA_SINK); + if (!codec) return -EINVAL; - /* Check if we already have an enabled transport for the codec. + /* Check if we already have an enabled transports for the profiles. * However, if there already was a codec switch running, these transports may * disappear soon. In that case, we have to do the full thing. */ if (!has_codec_switch(device)) { + uint32_t found_profiles = 0; + spa_list_for_each(t, &device->transport_list, device_link) { if (t->media_codec != codec) continue; - if ((device->connected_profiles & t->profile) != t->profile) - continue; + found_profiles |= t->profile; + } + if (found_profiles == profiles) { spa_bt_device_emit_codec_switched(device, 0); return 0; } @@ -4653,6 +4786,7 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct sw->codec = codec; sw->device = device; + sw->profiles = profiles; spa_list_append(&device->codec_switch_list, &sw->link); @@ -4661,57 +4795,83 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct spa_list_for_each(ep, &device->remote_endpoint_list, device_link) ++num_eps; - sw->paths = calloc(num_eps + 1, sizeof(char *)); + sw->paths = calloc(num_eps + 1, sizeof(*sw->paths)); if (!sw->paths) goto error_errno; - sw->path_iter = sw->paths; + sw->path_idx = 0; i = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { - sw->paths[i] = strdup(ep->path); - if (sw->paths[i] == NULL) + sw->paths[i].path = strdup(ep->path); + if (sw->paths[i].path == NULL) goto error_errno; ++i; } /* Sort in codec preference order */ codec_switch_cmp_sw = sw; - qsort(sw->paths, num_eps, sizeof(char *), codec_switch_cmp); + qsort(sw->paths, num_eps, sizeof(*sw->paths), codec_switch_cmp); /* Pick at most one source and one sink endpoint, if corresponding profiles are * set */ - profiles = device->connected_profiles; + remaining = profiles; for (i = 0, j = 0; i < num_eps; ++i) { struct spa_bt_remote_endpoint *ep; bool sink; uint32_t mask; - ep = remote_endpoint_find(monitor, sw->paths[i]); + ep = remote_endpoint_find(monitor, sw->paths[i].path); if (!codec_switch_check_endpoint(ep, codec, &sink, NULL)) continue; mask = sink ? SPA_BT_PROFILE_MEDIA_SOURCE : SPA_BT_PROFILE_MEDIA_SINK; - if (!(profiles & mask)) + if (!(remaining & mask)) continue; - SPA_FLAG_CLEAR(profiles, mask); + SPA_FLAG_CLEAR(remaining, mask); spa_log_debug(monitor->log, "media codec switch %p: select endpoint %s for codec %s", - sw, sw->paths[i], codec->name); + sw, sw->paths[i].path, codec->name); SPA_SWAP(sw->paths[j], sw->paths[i]); ++j; } + if (profiles & SPA_BT_PROFILE_BAP_AUDIO) { + /* Active unselected endpoints must be cleared */ + for (i = j; i < num_eps; ++i) { + bool active_ep = false; + + spa_list_for_each(t, &device->transport_list, device_link) { + if (spa_streq(t->remote_endpoint_path, sw->paths[i].path)) { + active_ep = true; + break; + } + } + if (!active_ep) + continue; + + spa_log_debug(monitor->log, + "media codec switch %p: select endpoint %s to be cleared", + sw, sw->paths[i].path); + SPA_SWAP(sw->paths[j], sw->paths[i]); + sw->paths[j].clear = true; + ++j; + } + + /* Reverse order so that clears come first */ + for (i = 0; i < j/2; ++i) + SPA_SWAP(sw->paths[i], sw->paths[j - 1 - i]); + } for (; j < num_eps; ++j) { - free(sw->paths[j]); - sw->paths[j] = NULL; + free(sw->paths[j].path); + spa_zero(sw->paths[j]); } - if (!sw->paths[0]) { - spa_log_debug(monitor->log, - "media codec switch %p: no valid endpoints for codec %s", - sw, codec->name); + if (!sw->paths[0].path || remaining) { + spa_log_error(monitor->log, + "media codec switch %p: no valid profile 0x%x endpoints for codec %s", + sw, profiles, codec->name); errno = EINVAL; goto error_errno; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 4b0048cc5..9813d2725 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -1410,7 +1410,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a this->switching_codec = true; - ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs); + ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs, 0); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); @@ -1563,6 +1563,9 @@ static void device_set_changed(void *userdata) this->profile != DEVICE_PROFILE_ASHA) return; + if (this->switching_codec) + return; + if (!device_set_needs_update(this)) { spa_log_debug(this->log, "%p: device set not changed", this); return; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index a4f1b7168..ae6544307 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -195,7 +195,9 @@ enum spa_bt_profile { static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) { - if (strcasecmp(uuid, SPA_BT_UUID_A2DP_SOURCE) == 0) + if (!uuid) + return 0; + else if (strcasecmp(uuid, SPA_BT_UUID_A2DP_SOURCE) == 0) return SPA_BT_PROFILE_A2DP_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_A2DP_SINK) == 0) return SPA_BT_PROFILE_A2DP_SINK; @@ -565,6 +567,7 @@ struct spa_bt_device { DBusPendingCall *battery_pending_call; const struct media_codec *preferred_codec; + uint32_t preferred_profiles; }; struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); @@ -572,7 +575,7 @@ struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monit int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); -int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs); +int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs, uint32_t profiles); int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec); bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, enum spa_bt_profile profile); const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count); @@ -666,6 +669,7 @@ struct spa_bt_transport { void *configuration; int configuration_len; char *endpoint_path; + char *remote_endpoint_path; bool bap_initiator; struct spa_list bap_transport_linked; From 209820bab8fd5f73b3c597c8d118a503d1b8bc21 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 16 Feb 2025 18:13:52 +0200 Subject: [PATCH 0408/1014] bluez5: add separate BAP sink/source/duplex profiles If device supports duplex, show also separate sink-only/source-only profiles. Devices don't necessarily support high-quality audio in duplex profile, so add sink/source only profiles. This is also a workaround for the current situation that devices may signal duplex support, but the attempted duplex configuration fails to work. --- spa/plugins/bluez5/bluez5-device.c | 155 ++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 38 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 9813d2725..f0bbc6bb8 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -53,13 +53,15 @@ static struct spa_i18n *_i18n; #define _(_str) spa_i18n_text(_i18n,(_str)) #define N_(_str) (_str) -enum { +enum device_profile { DEVICE_PROFILE_OFF = 0, - DEVICE_PROFILE_AG = 1, - DEVICE_PROFILE_A2DP = 2, - DEVICE_PROFILE_HSP_HFP = 3, - DEVICE_PROFILE_BAP = 4, - DEVICE_PROFILE_ASHA = 5, + DEVICE_PROFILE_AG, + DEVICE_PROFILE_A2DP, + DEVICE_PROFILE_HSP_HFP, + DEVICE_PROFILE_BAP, + DEVICE_PROFILE_BAP_SINK, + DEVICE_PROFILE_BAP_SOURCE, + DEVICE_PROFILE_ASHA, DEVICE_PROFILE_LAST, }; @@ -185,6 +187,19 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) } } +static bool profile_is_bap(enum device_profile profile) +{ + switch (profile) { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + return true; + default: + break; + } + return false; +} + static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size) { const struct media_codec * const *c; @@ -250,20 +265,6 @@ static bool is_bap_client(struct impl *this) return false; } -static bool can_bap_codec_switch(struct impl *this) -{ - if (!is_bap_client(this)) - return false; - - /* XXX: codec switching for source/duplex is not currently - * XXX: implemented properly. TODO: fix this - */ - if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE) - return false; - - return true; -} - static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex) { if (a2dp_duplex && t->media_codec->duplex_codec) @@ -376,6 +377,8 @@ static bool node_update_volume_from_transport(struct node *node, bool reset) /* PW is the controller for remote device. */ if (impl->profile != DEVICE_PROFILE_A2DP && impl->profile != DEVICE_PROFILE_BAP + && impl->profile != DEVICE_PROFILE_BAP_SINK + && impl->profile != DEVICE_PROFILE_BAP_SOURCE && impl->profile != DEVICE_PROFILE_HSP_HFP) return false; @@ -1095,7 +1098,7 @@ static void device_set_update_bap(struct impl *this, struct device_set *dset) static void device_set_update(struct impl *this, struct device_set *dset, int profile) { - if (profile == DEVICE_PROFILE_BAP) + if (profile_is_bap(this->profile)) device_set_update_bap(this, dset); else if (profile == DEVICE_PROFILE_ASHA) device_set_update_asha(this, dset); @@ -1236,7 +1239,10 @@ static int emit_nodes(struct impl *this) if (!this->props.codec) this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_SBC; break; - case DEVICE_PROFILE_BAP: { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + { struct device_set *set = &this->device_set; unsigned int i; @@ -1379,7 +1385,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a if (this->profile == profile && (this->profile != DEVICE_PROFILE_ASHA || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && - (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) && + (!profile_is_bap(this->profile) || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) return 0; @@ -1401,16 +1407,35 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a * XXX: source-only case, as it will only switch the sink, and we only * XXX: list the sink codecs here. TODO: fix this */ - if ((profile == DEVICE_PROFILE_A2DP || (profile == DEVICE_PROFILE_BAP && can_bap_codec_switch(this))) + if ((profile == DEVICE_PROFILE_A2DP || (profile_is_bap(profile) && is_bap_client(this))) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { int ret; const struct media_codec *codecs[64]; + uint32_t profiles; get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); this->switching_codec = true; - ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs, 0); + switch (profile) { + case DEVICE_PROFILE_BAP_SINK: + profiles = SPA_BT_PROFILE_BAP_SINK; + break; + case DEVICE_PROFILE_BAP_SOURCE: + profiles = SPA_BT_PROFILE_BAP_SOURCE; + break; + case DEVICE_PROFILE_BAP: + profiles = this->bt_dev->profiles & SPA_BT_PROFILE_BAP_DUPLEX; + break; + case DEVICE_PROFILE_A2DP: + profiles = this->bt_dev->profiles & SPA_BT_PROFILE_A2DP_DUPLEX; + break; + default: + profiles = 0; + break; + } + + ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs, profiles); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); @@ -1462,8 +1487,13 @@ static void codec_switched(void *userdata, int status) emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; - if (this->prev_bt_connected_profiles != this->bt_dev->connected_profiles) + if ((this->prev_bt_connected_profiles ^ this->bt_dev->connected_profiles) + & ~SPA_BT_PROFILE_BAP_DUPLEX) { + spa_log_debug(this->log, "profiles changed %x -> %x", + this->prev_bt_connected_profiles, + this->bt_dev->connected_profiles); this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; + } this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; @@ -1477,7 +1507,7 @@ static bool device_set_needs_update(struct impl *this) struct device_set dset = { .impl = this }; bool changed; - if (this->profile != DEVICE_PROFILE_BAP && + if (!profile_is_bap(this->profile) && this->profile != DEVICE_PROFILE_ASHA) return false; @@ -1526,6 +1556,8 @@ static void profiles_changed(void *userdata, uint32_t connected_change) nodes_changed); break; case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: nodes_changed = ((connected_change & SPA_BT_PROFILE_BAP_DUPLEX) && device_set_needs_update(this)) || (connected_change & (SPA_BT_PROFILE_BAP_BROADCAST_SINK | @@ -1559,7 +1591,7 @@ static void device_set_changed(void *userdata) { struct impl *this = userdata; - if (this->profile != DEVICE_PROFILE_BAP && + if (!profile_is_bap(this->profile) && this->profile != DEVICE_PROFILE_ASHA) return; @@ -1683,11 +1715,17 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s have_input = true; break; case DEVICE_PROFILE_BAP: - if (device->connected_profiles & SPA_BT_PROFILE_BAP_SINK) + if (device->profiles & SPA_BT_PROFILE_BAP_SINK) have_output = true; - if (device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE) + if (device->profiles & SPA_BT_PROFILE_BAP_SOURCE) have_input = true; break; + case DEVICE_PROFILE_BAP_SINK: + have_output = true; + break; + case DEVICE_PROFILE_BAP_SOURCE: + have_input = true; + break; case DEVICE_PROFILE_HSP_HFP: if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) have_output = have_input = true; @@ -1726,6 +1764,8 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32 case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_HSP_HFP: case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: *codec = (index & 0xffff); *next = (profile + 1) << 16; @@ -1755,6 +1795,8 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: case DEVICE_PROFILE_HSP_HFP: if (!codec) return SPA_ID_INVALID; @@ -1982,13 +2024,15 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * } break; } + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + /* These are client-only */ + if (!is_bap_client(this)) + return NULL; + SPA_FALLTHROUGH; case DEVICE_PROFILE_BAP: { - uint32_t profile = device->connected_profiles & - (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE - | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE - | SPA_BT_PROFILE_BAP_BROADCAST_SINK); - int idx; + uint32_t profile; const struct media_codec *media_codec; int n_set_sink, n_set_source; @@ -1996,6 +2040,25 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * if (codec == 0) return NULL; + switch (profile_index) { + case DEVICE_PROFILE_BAP: + profile = device->profiles & + (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE + | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE + | SPA_BT_PROFILE_BAP_BROADCAST_SINK); + break; + case DEVICE_PROFILE_BAP_SINK: + if (!(device->profiles & SPA_BT_PROFILE_BAP_SOURCE)) + return NULL; + profile = device->profiles & SPA_BT_PROFILE_BAP_SINK; + break; + case DEVICE_PROFILE_BAP_SOURCE: + if (!(device->profiles & SPA_BT_PROFILE_BAP_SINK)) + return NULL; + profile = device->profiles & SPA_BT_PROFILE_BAP_SOURCE; + break; + } + if (profile == 0) return NULL; @@ -2009,6 +2072,8 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * name = spa_bt_profile_name(profile); if (codec) { + int idx; + media_codec = get_supported_media_codec(this, codec, &idx, profile); if (media_codec == NULL) { errno = EINVAL; @@ -2182,6 +2247,20 @@ static bool profile_has_route(uint32_t profile, uint32_t route) return true; } break; + case DEVICE_PROFILE_BAP_SINK: + switch (route) { + case ROUTE_OUTPUT: + case ROUTE_SET_OUTPUT: + return true; + } + break; + case DEVICE_PROFILE_BAP_SOURCE: + switch (route) { + case ROUTE_INPUT: + case ROUTE_SET_INPUT: + return true; + } + break; case DEVICE_PROFILE_ASHA: switch (route) { case ROUTE_OUTPUT: @@ -2444,7 +2523,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels); - if ((this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) && + if ((this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile)) && (dev & SINK_ID_FLAG)) { spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); spa_pod_builder_long(b, node->latency_offset); @@ -2481,7 +2560,7 @@ next: c = this->supported_codecs[*j]; if (!(this->profile == DEVICE_PROFILE_A2DP && c->kind == MEDIA_CODEC_A2DP) && - !(this->profile == DEVICE_PROFILE_BAP && c->kind == MEDIA_CODEC_BAP) && + !(profile_is_bap(this->profile) && c->kind == MEDIA_CODEC_BAP) && !(this->profile == DEVICE_PROFILE_HSP_HFP && c->kind == MEDIA_CODEC_HFP) && !(this->profile == DEVICE_PROFILE_ASHA && c->kind == MEDIA_CODEC_ASHA)) goto next; @@ -2983,7 +3062,7 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || + if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) || this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { From 3ed969144a226d5c1609b10c428a32596a35f41a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 23 Feb 2025 19:54:02 +0200 Subject: [PATCH 0409/1014] bluez5: bap: prefer 32 kHz in/out for duplex configuration I'm not aware of any devices that support 48 kHz output in duplex configuration, so disable that for now. Doing this properly requires catching errors when on transport Acquire, and switching to another configuration if the error was due to bad configuration. Due to how BAP specification works, it's not necessarily possible to know whether a configuration was really accepted at earlier stage, and anyway there's no proper error -> reconfiguration handling currently on BlueZ side either. --- spa/plugins/bluez5/bap-codec-lc3.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 80174dd3d..8e2183884 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -644,13 +644,17 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp * Frame length is not limited by ISO MTU, as kernel will fragment * and reassemble SDUs as needed. */ - if (pac->settings->sink && pac->settings->duplex) { + if (pac->settings->duplex) { /* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, so prefer - * it for now for input rate in duplex configuration. + * it or 32kHz for now for input rate in duplex configuration. + * + * It appears few devices support 48kHz out + input, so in duplex mode + * try 32 kHz or 16 kHz also for output direction. * * Devices may list other values but not certain they will work properly. */ - found = select_bap_qos(&bap_qos, pac->settings, rate_mask & LC3_FREQ_16KHZ, duration_mask, framelen_min, framelen_max); + found = select_bap_qos(&bap_qos, pac->settings, rate_mask & (LC3_FREQ_16KHZ | LC3_FREQ_32KHZ), + duration_mask, framelen_min, framelen_max); } if (!found) found = select_bap_qos(&bap_qos, pac->settings, rate_mask, duration_mask, framelen_min, framelen_max); @@ -751,8 +755,8 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in PREFER_BOOL(conf->channels & LC3_CHAN_2); PREFER_BOOL(conf->channels & LC3_CHAN_1); - if (conf->sink && conf->duplex) - PREFER_BOOL(conf->rate & LC3_CONFIG_FREQ_16KHZ); + if (conf->duplex) + PREFER_BOOL(conf->rate & (LC3_CONFIG_FREQ_16KHZ | LC3_CONFIG_FREQ_32KHZ)); PREFER_EXPR(conf->priority); From 26b09b0ee38b01f6a3969836ba3565b1525f955d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 12 Jun 2025 23:58:16 +0300 Subject: [PATCH 0410/1014] bluez5: temporarily remove BAP nodes when another device is switching Unicast BAP codec switch requires CIG reconfiguration, which cannot be done if there is an acquired transport. When doing BAP codec switch, disable nodes of other devices sharing the same CIG. To avoid problems with node start/stop, just remove and re-add them. --- spa/plugins/bluez5/bluez5-dbus.c | 39 +++++++++++++++++------- spa/plugins/bluez5/bluez5-device.c | 49 ++++++++++++++++++++++++++++++ spa/plugins/bluez5/defs.h | 4 +++ 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 8136e6a6c..44d221935 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4582,6 +4582,26 @@ static bool codec_switch_clear_bap(struct spa_bt_codec_switch *sw, const char *p return true; } +static void codec_switch_emit_switching(struct spa_bt_monitor *monitor) +{ + struct spa_bt_device *d; + struct spa_bt_codec_switch *sw; + bool found = false; + + spa_list_for_each(d, &monitor->device_list, link) { + spa_list_for_each(sw, &d->codec_switch_list, link) { + if (sw->profiles & SPA_BT_PROFILE_BAP_AUDIO) { + found = true; + goto done; + } + } + } + +done: + spa_list_for_each(d, &monitor->device_list, link) + spa_bt_device_emit_codec_switch_other(d, found); +} + static bool codec_switch_process(struct spa_bt_codec_switch *sw) { if (sw->waiting) @@ -4596,6 +4616,9 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); spa_bt_device_check_profiles(sw->device, false); + + sw->profiles = 0; + codec_switch_emit_switching(sw->device->monitor); return true; } @@ -4610,17 +4633,8 @@ static bool codec_switch_process(struct spa_bt_codec_switch *sw) if (sw->path_idx == 0 && codec_switch_rate_limit(sw)) return false; - if (sw->path_idx == 0) { - struct spa_bt_transport *t, *t2; - - /* Force CIG inactive */ - spa_list_for_each(t, &sw->device->transport_list, link) { - spa_bt_transport_release_now(t); - spa_list_for_each(t2, &sw->device->monitor->transport_list, link) - if (t2->device != sw->device && transport_in_same_cig(t, t2)) - spa_bt_transport_release_now(t2); - } - } + if (sw->path_idx == 0) + codec_switch_emit_switching(sw->device->monitor); if (sw->paths[sw->path_idx].clear) { if (!codec_switch_clear_bap(sw, sw->paths[sw->path_idx].path)) @@ -4644,6 +4658,9 @@ fail: spa_log_info(sw->device->monitor->log, "media codec switch %p: failed", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); + + sw->profiles = 0; + codec_switch_emit_switching(sw->device->monitor); return true; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index f0bbc6bb8..dde098ace 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -158,6 +158,7 @@ struct impl { uint32_t profile; unsigned int switching_codec:1; + unsigned int switching_codec_other:1; unsigned int save_profile:1; uint32_t prev_bt_connected_profiles; @@ -1161,6 +1162,15 @@ static int emit_nodes(struct impl *this) { struct spa_bt_transport *t; + switch (this->profile) { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + if (this->switching_codec_other) + return -EBUSY; + break; + } + this->props.codec = 0; device_set_update(this, &this->device_set, this->profile); @@ -1459,6 +1469,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a } this->switching_codec = false; + emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; @@ -1502,6 +1513,43 @@ static void codec_switched(void *userdata, int status) emit_info(this, false); } +static void codec_switch_other(void *userdata, bool switching) +{ + struct impl *this = userdata; + + this->switching_codec_other = switching; + + switch (this->profile) { + case DEVICE_PROFILE_BAP: + case DEVICE_PROFILE_BAP_SINK: + case DEVICE_PROFILE_BAP_SOURCE: + break; + default: + return; + } + + spa_log_debug(this->log, "%p: BAP codec switching by another device, switching:%d", + this, (int)switching); + + /* + * In unicast BAP, output/input must be halted when another device is + * switching codec, because CIG must be torn down before it can be + * reconfigured. Easiest way to do this and to suspend output/input is to + * remove the nodes. + */ + if (!find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SINK) && + !find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SOURCE)) + return; + + if (switching) { + emit_remove_nodes(this); + spa_bt_device_release_transports(this->bt_dev); + } else { + emit_remove_nodes(this); + emit_nodes(this); + } +} + static bool device_set_needs_update(struct impl *this) { struct device_set dset = { .impl = this }; @@ -1655,6 +1703,7 @@ static const struct spa_bt_device_events bt_dev_events = { SPA_VERSION_BT_DEVICE_EVENTS, .connected = device_connected, .codec_switched = codec_switched, + .codec_switch_other = codec_switch_other, .profiles_changed = profiles_changed, .device_set_changed = device_set_changed, .switch_profile = device_switch_profile, diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index ae6544307..03ac283b2 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -489,6 +489,9 @@ struct spa_bt_device_events { /** Codec switching completed */ void (*codec_switched) (void *data, int status); + /** Codec switching initiated or completed by another device */ + void (*codec_switch_other) (void *data, bool switching); + /** Profile configuration changed */ void (*profiles_changed) (void *data, uint32_t connected_change); @@ -590,6 +593,7 @@ const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, u m, v, ##__VA_ARGS__) #define spa_bt_device_emit_connected(d,...) spa_bt_device_emit(d, connected, 0, __VA_ARGS__) #define spa_bt_device_emit_codec_switched(d,...) spa_bt_device_emit(d, codec_switched, 0, __VA_ARGS__) +#define spa_bt_device_emit_codec_switch_other(d,...) spa_bt_device_emit(d, codec_switch_other, 0, __VA_ARGS__) #define spa_bt_device_emit_profiles_changed(d,...) spa_bt_device_emit(d, profiles_changed, 0, __VA_ARGS__) #define spa_bt_device_emit_device_set_changed(d) spa_bt_device_emit(d, device_set_changed, 0) #define spa_bt_device_emit_switch_profile(d) spa_bt_device_emit(d, switch_profile, 0) From 7f2bdab8ead8d559f26be0531472345db59c3678 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 13:50:51 +0300 Subject: [PATCH 0411/1014] bluez5: fix some coverity issues Missing null pointer checks, wrong array indices, uninitialized/unused variables. --- spa/plugins/bluez5/backend-native.c | 5 +++-- spa/plugins/bluez5/bluez5-dbus.c | 4 ++-- spa/plugins/bluez5/hfp-codec-lc3-swb.c | 2 +- spa/plugins/bluez5/hfp-codec-msbc.c | 2 +- spa/plugins/bluez5/midi-server.c | 3 --- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index d55af650a..c670ec892 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -549,8 +549,9 @@ static bool rfcomm_hw_volume_enabled(struct rfcomm *rfcomm) static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_volume) { struct spa_bt_transport_volume *t_volume; + bool valid_volume = (id == SPA_BT_VOLUME_ID_RX || id == SPA_BT_VOLUME_ID_TX); - if ((id == SPA_BT_VOLUME_ID_RX || id == SPA_BT_VOLUME_ID_TX) && hw_volume >= 0) { + if (valid_volume && hw_volume >= 0) { rfcomm->volumes[id].active = true; rfcomm->volumes[id].hw_volume = hw_volume; } @@ -571,7 +572,7 @@ static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_vol spa_bt_transport_emit_volume_changed(rfcomm->transport); } - if (rfcomm->telephony_ag) { + if (rfcomm->telephony_ag && valid_volume) { rfcomm->telephony_ag->volume[id] = hw_volume; telephony_ag_notify_updated_props(rfcomm->telephony_ag); } diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 44d221935..ddfe7a350 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4204,7 +4204,7 @@ static int transport_set_delay(void *data, int64_t delay_nsec) if (!(transport->profile & SPA_BT_PROFILE_A2DP_DUPLEX)) return -ENOTSUP; - value = SPA_CLAMP(delay_nsec / (100 * SPA_NSEC_PER_USEC), 0, 10 * UINT16_MAX); + value = SPA_CLAMP(delay_nsec / (100 * SPA_NSEC_PER_USEC), 0, UINT16_MAX); if (transport->delay_us == 100 * value) return 0; @@ -4362,7 +4362,7 @@ static bool codec_switch_check_endpoint(struct spa_bt_remote_endpoint *ep, spa_autofree char *path = NULL; uint32_t ep_profile; - if (!ep->uuid || !ep->device) + if (!ep || !ep->uuid || !ep->device) return false; ep_profile = spa_bt_profile_from_uuid(ep->uuid); diff --git a/spa/plugins/bluez5/hfp-codec-lc3-swb.c b/spa/plugins/bluez5/hfp-codec-lc3-swb.c index 487c1dfa5..edc27e87a 100644 --- a/spa/plugins/bluez5/hfp-codec-lc3-swb.c +++ b/spa/plugins/bluez5/hfp-codec-lc3-swb.c @@ -170,7 +170,7 @@ static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; - size_t consumed; + size_t consumed = 0; if (is_zero_packet(src, src_size)) return -EINVAL; diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c index 3a9112b6d..c4e9e94b2 100644 --- a/spa/plugins/bluez5/hfp-codec-msbc.c +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -158,7 +158,7 @@ static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; - size_t consumed; + size_t consumed = 0; if (is_zero_packet(src, src_size)) return -EINVAL; diff --git a/spa/plugins/bluez5/midi-server.c b/spa/plugins/bluez5/midi-server.c index a3a5e9270..fffe5cb27 100644 --- a/spa/plugins/bluez5/midi-server.c +++ b/spa/plugins/bluez5/midi-server.c @@ -177,7 +177,6 @@ static gboolean chr_handle_acquire(Bluez5GattCharacteristic1 *object, int res; GUnixFDList *fd_list = NULL; GVariant *fd_handle = NULL; - GError *err = NULL; if ((write && (impl->cb->acquire_write == NULL)) || (!write && (impl->cb->acquire_notify == NULL))) { @@ -230,8 +229,6 @@ fail: if (fds[1] >= 0) close(fds[1]); - if (err) - g_error_free(err); g_clear_pointer(&fd_handle, g_variant_unref); g_clear_object(&fd_list); g_dbus_method_invocation_return_dbus_error(invocation, From 3539374ba70031cd1e1b6f100f617b5fe08b226b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 13:54:23 +0300 Subject: [PATCH 0412/1014] spa: alsa: fix some coverity warnings NULL checks. Change pa_x* malloc functions act like the pulseaudio ones: assert on failure, as code assumes that. --- spa/plugins/alsa/acp/acp.c | 20 +++++++++++++++----- spa/plugins/alsa/acp/alsa-ucm.c | 2 +- spa/plugins/alsa/acp/compat.h | 21 +++++++++++++-------- spa/plugins/alsa/alsa-pcm.c | 2 +- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 07e9080d9..5b3de930b 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -8,6 +8,7 @@ #include #include +#include #include int _acp_log_level = 1; @@ -372,7 +373,7 @@ static int add_pro_profile(pa_card *impl, uint32_t index) dev = -1; while (1) { - char desc[128], devstr[128], *name; + char desc[128], devstr[128]; if ((err = snd_ctl_pcm_next_device(ctl_hndl, &dev)) < 0) { pa_log_error("error iterating devices: %s", snd_strerror(err)); @@ -396,8 +397,13 @@ static int add_pro_profile(pa_card *impl, uint32_t index) pa_log_error("error pcm info: %s", snd_strerror(err)); } if (err >= 0) { + spa_autofree char *name = NULL; pa_assert_se(asprintf(&name, "Mapping pro-output-%d", dev) >= 0); m = pa_alsa_mapping_get(ps, name); + } else { + m = NULL; + } + if (m) { m->description = pa_xstrdup(desc); m->device_strings = pa_split_spaces_strv(devstr); @@ -419,7 +425,6 @@ static int add_pro_profile(pa_card *impl, uint32_t index) n_playback++; } pa_idxset_put(ap->output_mappings, m, NULL); - free(name); } snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); @@ -428,8 +433,13 @@ static int add_pro_profile(pa_card *impl, uint32_t index) pa_log_error("error pcm info: %s", snd_strerror(err)); } if (err >= 0) { + spa_autofree char *name = NULL; pa_assert_se(asprintf(&name, "Mapping pro-input-%d", dev) >= 0); m = pa_alsa_mapping_get(ps, name); + } else { + m = NULL; + } + if (m) { m->description = pa_xstrdup(desc); m->device_strings = pa_split_spaces_strv(devstr); @@ -451,7 +461,6 @@ static int add_pro_profile(pa_card *impl, uint32_t index) n_capture++; } pa_idxset_put(ap->input_mappings, m, NULL); - free(name); } } snd_ctl_close(ctl_hndl); @@ -1190,7 +1199,7 @@ uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *nam static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element, bool ignore_dB) { - const char *mdev; + const char *mdev = NULL; pa_alsa_mapping *mapping = dev->mapping; if (!mapping && !element) @@ -1199,7 +1208,8 @@ static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element, if (!element && mapping && pa_alsa_path_set_is_empty(dev->mixer_path_set)) return; - mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); + if (mapping) + mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); if (mdev) { dev->mixer_handle = pa_alsa_open_mixer_by_name(impl->ucm.mixers, mdev, true); } else { diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 73466f004..d67ac91b5 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -1741,7 +1741,7 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_prof ret = -1; } - } else if (ucm->active_verb) { + } else if (ucm->active_verb && old_profile) { /* Disable modifiers not in new profile. Has to be done before * devices, because _dismod fails if a modifier's supported * devices are disabled. */ diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index c7779759f..f7592e1a6 100644 --- a/spa/plugins/alsa/acp/compat.h +++ b/spa/plugins/alsa/acp/compat.h @@ -327,18 +327,23 @@ static inline size_t pa_snprintf(char *str, size_t size, const char *format, ... return ret; } -#define pa_xstrdup(s) ((s) != NULL ? strdup(s) : NULL) -#define pa_xstrndup(s,n) ((s) != NULL ? strndup(s,n) : NULL) +#define pa_xnullcheck(p) ({ void *_mem_alloc = (p); spa_assert_se(_mem_alloc); _mem_alloc; }) +#define pa_xstrdup(s) ((s) != NULL ? pa_xnullcheck(strdup(s)) : NULL) +#define pa_xstrndup(s,n) ((s) != NULL ? pa_xnullcheck(strndup(s,n)) : NULL) #define pa_xfree free -#define pa_xmalloc malloc -#define pa_xnew0(t,n) calloc(n, sizeof(t)) +#define pa_xmalloc(n) pa_xnullcheck(malloc(n)) +#define pa_xnew0(t,n) pa_xnullcheck(calloc((n), sizeof(t))) #define pa_xnew(t,n) pa_xnew0(t,n) -#define pa_xrealloc realloc -#define pa_xrenew(t,p,n) ((t*) realloc(p, (n)*sizeof(t))) +#define pa_xrenew(t,p,n) ((t*) pa_xnullcheck(realloc(p, (n)*sizeof(t)))) static inline void* pa_xmemdup(const void *p, size_t l) { - return memcpy(malloc(l), p, l); - + if (!p) { + return NULL; + } else { + void *dst = pa_xmalloc(l); + memcpy(dst, p, l); + return dst; + } } #define pa_xnewdup(t,p,n) ((t*) pa_xmemdup((p), (n)*sizeof(t))) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 73df819ba..6d6e8133c 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -968,7 +968,7 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) snd_config_update_free_global(); - if ((str = spa_dict_lookup(info, "device.profile.pro")) != NULL) + if (info && (str = spa_dict_lookup(info, "device.profile.pro")) != NULL) state->is_pro = spa_atob(str); state->multi_rate = true; From baadda3b67955e8c88558c22ee6e79b652c5a3f0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 15:21:29 +0300 Subject: [PATCH 0413/1014] tools: fix some missing free/close --- src/tools/pw-cli.c | 3 +-- src/tools/reserve.c | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index 467a4be63..7b8254537 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -1559,7 +1559,7 @@ static struct global * obj_global_port(struct remote_data *rd, struct global *global, const char *port_direction, const char *port_id) { struct global *global_port_found = NULL; - uint32_t *ports = NULL; + spa_autofree uint32_t *ports = NULL; int port_count; port_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Port, &ports); @@ -1582,7 +1582,6 @@ obj_global_port(struct remote_data *rd, struct global *global, const char *port_ } } - free(ports); return global_port_found; } diff --git a/src/tools/reserve.c b/src/tools/reserve.c index c28cec1a2..9f85c945f 100644 --- a/src/tools/reserve.c +++ b/src/tools/reserve.c @@ -395,6 +395,7 @@ rd_device_new(DBusConnection *connection, const char *device_name, const char *a error_free: free(d->service_name); free(d->object_path); + free(d->application_name); free(d); errno = -res; return NULL; From dc618d37c64c2e388798d3c42b0cc7d03e588027 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 15:24:05 +0300 Subject: [PATCH 0414/1014] modules: fix missing free/close and length checks Fix some missing free and close. Fix not checking length of received netjack data. --- src/modules/module-netjack2/peer.c | 2 ++ src/modules/module-protocol-simple.c | 13 ++++++------- src/modules/module-rtp-sap.c | 6 +++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index b289c9425..3bbb8d47a 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -815,6 +815,8 @@ static int netjack2_recv_midi(struct netjack2_peer *peer, struct nj2_packet_head if ((len = recv(peer->fd, buffer, packet_size, 0)) < 0) return -errno; + if ((size_t)len < sizeof(*header)) + return -EINVAL; active_ports = peer->params.recv_midi_channels; if (active_ports == 0) diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index bbed3b5f7..6fdef15b8 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -665,8 +665,9 @@ static int make_tcp_socket(struct server *server, const char *name, const char * const char *ifaddress) { struct sockaddr_storage addr; - int res, fd, on; + int res, on; socklen_t len = 0; + spa_autoclose int fd = -1; if ((res = pw_net_parse_address_port(name, ifaddress, DEFAULT_PORT, &addr, &len)) < 0) { pw_log_error("%p: can't parse address %s: %s", server, @@ -693,26 +694,24 @@ static int make_tcp_socket(struct server *server, const char *name, const char * if (bind(fd, (struct sockaddr *) &addr, len) < 0) { res = -errno; pw_log_error("%p: bind() failed: %m", server); - goto error_close; + goto error; } if (listen(fd, 5) < 0) { res = -errno; pw_log_error("%p: listen() failed: %m", server); - goto error_close; + goto error; } if (getsockname(fd, (struct sockaddr *)&addr, &len) < 0) { res = -errno; pw_log_error("%p: getsockname() failed: %m", server); - goto error_close; + goto error; } server->type = SERVER_TYPE_TCP; server->addr = addr; - return fd; + return spa_steal_fd(fd); -error_close: - close(fd); error: return res; } diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index dc01cb987..3fa69ef74 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -1478,6 +1478,8 @@ static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info) int count = 0, res = 0; size_t l; + spa_zero(*info); + while (*s) { if ((l = strcspn(s, "\r\n")) < 2) goto too_short; @@ -1523,12 +1525,15 @@ static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info) return 0; too_short: pw_log_warn("SDP: line starting with `%.6s...' too short", s); + clear_sdp_info(info); return -EINVAL; invalid_version: pw_log_warn("SDP: invalid first version line `%*s'", (int)l, s); + clear_sdp_info(info); return -EINVAL; error: pw_log_warn("SDP: error: %s", spa_strerror(res)); + clear_sdp_info(info); return res; } @@ -1570,7 +1575,6 @@ static int parse_sap(struct impl *impl, void *data, size_t len) pw_log_debug("got SAP: %s %s", mime, sdp); - spa_zero(info); if ((res = parse_sdp(impl, sdp, &info)) < 0) return res; From b869305282623d54078b3d9be59b06bc97941d59 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 15 Jun 2025 14:15:22 +0300 Subject: [PATCH 0415/1014] bluez5: fix CVSD decode() "Decoding" in CVSD should just copy bytes, packets may be any size. --- spa/plugins/bluez5/hfp-codec-cvsd.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/hfp-codec-cvsd.c b/spa/plugins/bluez5/hfp-codec-cvsd.c index 2b776b540..d5f30fa2a 100644 --- a/spa/plugins/bluez5/hfp-codec-cvsd.c +++ b/spa/plugins/bluez5/hfp-codec-cvsd.c @@ -163,10 +163,14 @@ static int codec_decode(void *data, void *dst, size_t dst_size, size_t *dst_out) { - struct impl *this = data; - int dummy; + uint32_t avail; - return codec_encode(this, src, src_size, dst, dst_size, dst_out, &dummy); + avail = SPA_MIN(src_size, dst_size); + if (avail) + spa_memcpy(dst, src, avail); + + *dst_out = avail; + return avail; } static void codec_set_log(struct spa_log *global_log) From 3922247356b94e96f84b3c404f1049099338d3cb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 15 Jun 2025 14:35:32 +0300 Subject: [PATCH 0416/1014] bluez5: sco-source: pass read mtu to codec --- spa/plugins/bluez5/sco-source.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index ca3c75047..34568a764 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -566,7 +566,8 @@ static int transport_start(struct impl *this) port->current_format.info.raw.rate * 40 / 1000); /* init codec */ - this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, 0); + this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, + this->transport->read_mtu); if (!this->codec_data) { spa_log_error(this->log, "codec init failed"); res = -EINVAL; From 931b6d9ad8d0bdc72c60878703c3c74fbc73f6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 11 Jun 2025 09:16:29 +0200 Subject: [PATCH 0417/1014] bluez5: hfp-hf: Fix condition for hfp_hf_swap_calls AT+CHLD=2 can be called even if there is no active call, the only condition is to have at least one held call. --- spa/plugins/bluez5/backend-native.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index c670ec892..5f7d6f8e8 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1623,7 +1623,6 @@ static void hfp_hf_swap_calls(void *data, enum spa_bt_telephony_error *err, uint struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; struct spa_bt_telephony_call *call; - bool found_active = false; bool found_held = false; char reply[20]; bool res; @@ -1637,18 +1636,14 @@ static void hfp_hf_swap_calls(void *data, enum spa_bt_telephony_error *err, uint } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { - if (call->state == CALL_STATE_WAITING) { - spa_log_debug(backend->log, "call waiting before swapping"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } else if (call->state == CALL_STATE_ACTIVE) - found_active = true; - else if (call->state == CALL_STATE_HELD) + if (call->state == CALL_STATE_HELD) { found_held = true; + break; + } } - if (!found_active || !found_held) { - spa_log_debug(backend->log, "no active and held calls"); + if (!found_held) { + spa_log_debug(backend->log, "no held calls"); *err = BT_TELEPHONY_ERROR_INVALID_STATE; return; } From 2cb678edb30711848d56e9649434eff988c08f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 11 Jun 2025 09:32:07 +0200 Subject: [PATCH 0418/1014] bluez5: hfp-hf: Remove disconnected calls from call list After AT+CLCC command completion, the calls which has not been listed should be removed. This list the calls returned by AT+CLCC to be able to find the ones which has not been listed but still in the call_list. --- spa/plugins/bluez5/backend-native.c | 84 ++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 5f7d6f8e8..26fe3b033 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -145,7 +145,8 @@ enum hfp_hf_state { hfp_hf_nrec, hfp_hf_clcc, hfp_hf_vgs, - hfp_hf_vgm + hfp_hf_done, + hfp_hf_clcc_update }; enum hsp_hs_state { @@ -175,6 +176,11 @@ struct codec_item { const struct media_codec *codec; }; +struct updated_call { + struct spa_list link; + int id; +}; + struct rfcomm { struct spa_list link; struct spa_source source; @@ -215,6 +221,7 @@ struct rfcomm { char *hf_indicators[MAX_HF_INDICATORS]; struct spa_bt_telephony_ag *telephony_ag; struct spa_list hfp_hf_commands; + struct spa_list updated_call_list; #endif }; @@ -392,6 +399,13 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { + struct updated_call *updated_call; + + spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { + spa_list_remove(&updated_call->link); + free(updated_call); + } + codec_switch_stop_timer(rfcomm); if (rfcomm->telephony_ag) { telephony_ag_destroy(rfcomm->telephony_ag); @@ -457,6 +471,9 @@ static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...) return 0; } + if (rfcomm->hf_state == hfp_hf_done && spa_streq(message, "AT+CLCC")) + rfcomm->hf_state = hfp_hf_clcc_update; + spa_log_debug(backend->log, "RFCOMM >> %s", message); /* @@ -2064,6 +2081,34 @@ static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { .set_microphone_volume = hfp_hf_set_microphone_volume, }; +static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) +{ + struct spa_bt_telephony_call *call, *call_tmp; + struct updated_call *updated_call; + bool found; + + spa_list_for_each_safe(call, call_tmp, &rfcomm->telephony_ag->call_list, link) { + found = false; + spa_list_for_each(updated_call, &rfcomm->updated_call_list, link) { + if (call->id == updated_call->id) { + found = true; + break; + } + } + + if (!found) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + + spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { + spa_list_remove(&updated_call->link); + free(updated_call); + } +} + static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) { struct impl *backend = rfcomm->backend; @@ -2389,6 +2434,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } if (SPA_LIKELY (parsed)) { + struct updated_call *updated_call; + updated_call = calloc(1, sizeof(struct updated_call)); + updated_call->id = idx; + spa_list_append(&rfcomm->updated_call_list, &updated_call->link); + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->id == idx) { bool changed = false; @@ -2430,17 +2480,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hfp_hf_in_progress = false; } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || spa_strstartswith(token, "+CME ERROR:")) { - rfcomm->hfp_hf_cmd_in_progress = false; - if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { - struct rfcomm_cmd *cmd; - cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); - spa_list_remove(&cmd->link); - spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); - rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); - free(cmd->cmd); - free(cmd); - } - if (spa_strstartswith(token, "OK")) { switch(rfcomm->hf_state) { case hfp_hf_brsf: @@ -2539,17 +2578,35 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) /* Report volume on SLC establishment */ SPA_FALLTHROUGH; case hfp_hf_clcc: + if (rfcomm->hf_state == hfp_hf_clcc) { + hfp_hf_remove_disconnected_calls(rfcomm); + } rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); rfcomm->hf_state = hfp_hf_vgs; break; case hfp_hf_vgs: rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); - rfcomm->hf_state = hfp_hf_vgm; + rfcomm->hf_state = hfp_hf_done; + break; + case hfp_hf_clcc_update: + hfp_hf_remove_disconnected_calls(rfcomm); + rfcomm->hf_state = hfp_hf_done; break; default: break; } } + + rfcomm->hfp_hf_cmd_in_progress = false; + if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { + struct rfcomm_cmd *cmd; + cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); + spa_list_remove(&cmd->link); + spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); + rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); + free(cmd->cmd); + free(cmd); + } } return true; @@ -3492,6 +3549,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; spa_list_init(&rfcomm->hfp_hf_commands); + spa_list_init(&rfcomm->updated_call_list); /* By default all indicators are enabled */ rfcomm->cind_enabled_indicators = 0xFFFFFFFF; memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); From 75b4c3379df11e0b9859d31a76bd5e4df7f2f809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Wed, 11 Jun 2025 12:10:25 +0200 Subject: [PATCH 0419/1014] bluez5: hfp-hf: If available use AT+CLCC only to update calls state --- spa/plugins/bluez5/backend-native.c | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 26fe3b033..17288bcbc 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2214,6 +2214,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) { spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5); } else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) { + if (rfcomm->hfp_hf_clcc) { + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + return true; + } + if (value == CIND_CALLSETUP_NONE) { struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { @@ -2268,11 +2273,13 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } } - if (rfcomm->hfp_hf_clcc) - rfcomm_send_cmd(rfcomm, "AT+CLCC"); - else - rfcomm->hfp_hf_in_progress = false; + rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "call")) { + if (rfcomm->hfp_hf_clcc) { + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + return true; + } + if (value == 0) { struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { @@ -2293,11 +2300,13 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } } - if (rfcomm->hfp_hf_clcc) - rfcomm_send_cmd(rfcomm, "AT+CLCC"); - else - rfcomm->hfp_hf_in_progress = false; + rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "callheld")) { + if (rfcomm->hfp_hf_clcc) { + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + return true; + } + if (value == 0) { /* Reject waiting call or no held calls */ struct spa_bt_telephony_call *call, *tcall; bool found_waiting = false; @@ -2348,10 +2357,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } } - if (rfcomm->hfp_hf_clcc) - rfcomm_send_cmd(rfcomm, "AT+CLCC"); - else - rfcomm->hfp_hf_in_progress = false; + rfcomm->hfp_hf_in_progress = false; } } } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) { From 0b2b723a0e0246670c598eb7a86d6b9592d0b798 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 09:14:18 +0200 Subject: [PATCH 0420/1014] filter-graph: add a zeroramp plugin The filter detects unnatural gaps (consisting of 0.0 values) and will ramp-down or ramp-up the volume when entering/leaving those gaps. This makes it filter out the pops and clicks you typically get when pausing and resuming a stream. See #4745 --- spa/plugins/filter-graph/builtin_plugin.c | 98 +++++++++++++++++++++++ src/modules/module-filter-chain.c | 18 +++++ 2 files changed, 116 insertions(+) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 6c60113cf..467b62a17 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -56,6 +56,10 @@ struct builtin { float b0, b1, b2; float a0, a1, a2; float accum; + + int mode; + uint32_t count; + float last; }; static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, @@ -2776,6 +2780,98 @@ static const struct spa_fga_descriptor pipe_desc = { .cleanup = pipe_cleanup, }; +/* zeroramp */ +static struct spa_fga_port zeroramp_ports[] = { + { .index = 0, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Gap (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.000666f, .min = 0.0f, .max = 1.0f + }, + { .index = 3, + .name = "Duration (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.000666f, .min = 0.0f, .max = 1.0f + }, +}; + +static void zeroramp_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[0]; + float *out = impl->port[1]; + uint32_t n, i, c; + uint32_t gap = (uint32_t)(impl->port[2][0] * impl->rate); + uint32_t duration = (uint32_t)(impl->port[3][0] * impl->rate); + + if (out == NULL) + return; + + if (in != NULL) { + for (n = 0; n < SampleCount; n++) { + if (impl->mode == 0) { + /* normal mode, finding gaps */ + out[n] = in[n]; + if (in[n] == 0.0) { + if (++impl->count == gap) { + /* we found gap zeroes, fade out last + * sample and go into zero mode */ + for (c = 1, i = n; c < duration && i > 0; i--, c++) + out[i-1] = impl->last * + (0.5f + 0.5f * cosf(M_PIf + M_PIf * c / duration)); + impl->mode = 1; + } + } else { + /* keep last sample to fade out when needed */ + impl->count = 0; + impl->last = in[n]; + } + } + if (impl->mode == 1) { + /* zero mode */ + if (in[n] != 0.0f) { + /* gap ended, move to fade-in mode */ + impl->mode = 2; + impl->count = 0; + } else { + out[n] = 0.0f; + } + } + if (impl->mode == 2) { + /* fade-in mode */ + out[n] = in[n] * (0.5f + 0.5f * cosf(M_PIf + (M_PIf * ++impl->count / duration))); + if (impl->count == duration) { + /* fade in complete, back to normal mode */ + impl->count = 0; + impl->mode = 0; + } + } + } + } else { + memset(out, 0, SampleCount * sizeof(float)); + } +} + +static const struct spa_fga_descriptor zeroramp_desc = { + .name = "zeroramp", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(zeroramp_ports), + .ports = zeroramp_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = zeroramp_run, + .cleanup = builtin_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -2837,6 +2933,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &debug_desc; case 28: return &pipe_desc; + case 29: + return &zeroramp_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 2356cd4a3..1f64dc174 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -606,6 +606,24 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `command` the command to execute. It should consume samples from stdin and produce * samples on stdout. * + * ### Zeroramp + * + * The `zeroramp` plugin can be used to detect unnatural silence parts in the audio + * stream and ramp the volume down or up when entering or leaving the silent area + * respectively. + * This can be used to avoid loud pops and clicks that occur when the sample values + * suddenly drop to zero or jump from zero to a large value caused by a pause, + * resume or an error of the stream. It only detect areas where the sample values + * are absolute zero values, such as those inserted when pausing a stream. + * + * It has an "In" input port and an "Out" output data ports. + * + * There are also "Gap (s)" and an "Duration (s)" input control ports. "Gap (s)" + * determines how long the silence gap is in seconds (default 0.000666) and + * "Duration (s)" determines how long the fade-in and fade-out should last + * (default 0.000666). + * + * * ## SOFA filters * * There is an optional `sofa` type available (when compiled with `libmysofa`). From 953b0f81ad9d4a293b1b57cd82c9e04cba86be1b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 09:25:54 +0200 Subject: [PATCH 0421/1014] filter-graph: make sure M_PIf is defined --- spa/plugins/filter-graph/builtin_plugin.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 467b62a17..9d293acdb 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -2802,6 +2802,10 @@ static struct spa_fga_port zeroramp_ports[] = { }, }; +#ifndef M_PIf +# define M_PIf 3.14159265358979323846f /* pi */ +#endif + static void zeroramp_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; From 58aab3e16b9343cd35ed991e561623aeed18167b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 11:01:28 +0200 Subject: [PATCH 0422/1014] pw-dump: add -i and -s options -i can be used to configure the indentation (default 2) -s can be used to output simplified SPA JSON --- src/tools/pw-dump.c | 60 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index bf6d5c3a4..b18928b79 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -64,6 +64,9 @@ struct data { #define STATE_MASK 0xffff0000 #define STATE_SIMPLE (1<<16) uint32_t state; + const char *comma_char; + const char *keysep_char; + bool simple_string; unsigned int monitor:1; }; @@ -216,13 +219,25 @@ static void object_destroy(struct object *o) static void put_key(struct data *d, const char *key); +#define REJECT "\"\\'=:,{}[]()#" + +static bool is_simple_string(const char *val) +{ + int i; + for (i = 0; val[i]; i++) { + if (val[i] < 0x20 || strchr(REJECT, val[i]) != NULL) + return false; + } + return true; +} + static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const char *fmt, ...) { va_list va; if (key) put_key(d, key); fprintf(d->out, "%s%s%*s", - d->state & STATE_COMMA ? "," : "", + d->state & STATE_COMMA ? d->comma_char : "", d->state & (STATE_MASK | STATE_KEY) ? " " : (d->state & STATE_FIRST) || raw ? "" : "\n", d->state & (STATE_MASK | STATE_KEY) ? 0 : d->level, ""); va_start(va, fmt); @@ -234,9 +249,13 @@ static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const static void put_key(struct data *d, const char *key) { int size = (strlen(key) + 1) * 4; - char *str = alloca(size); - spa_json_encode_string(str, size, key); - put_fmt(d, NULL, "%s%s%s:", KEY, str, NORMAL); + if (d->simple_string && is_simple_string(key)) { + put_fmt(d, NULL, "%s%s%s%s", KEY, key, NORMAL, d->keysep_char); + } else { + char *str = alloca(size); + spa_json_encode_string(str, size, key); + put_fmt(d, NULL, "%s%s%s%s", KEY, str, NORMAL, d->keysep_char); + } d->state = (d->state & STATE_MASK) + STATE_KEY; } @@ -262,9 +281,13 @@ static void put_encoded_string(struct data *d, const char *key, const char *val) static void put_string(struct data *d, const char *key, const char *val) { int size = (strlen(val) + 1) * 4; - char *str = alloca(size); - spa_json_encode_string(str, size, val); - put_encoded_string(d, key, str); + if (d->simple_string && is_simple_string(val)) { + put_encoded_string(d, key, val); + } else { + char *str = alloca(size); + spa_json_encode_string(str, size, val); + put_encoded_string(d, key, str); + } } static void put_literal(struct data *d, const char *key, const char *val) @@ -1515,7 +1538,9 @@ static void show_help(struct data *data, const char *name, bool error) " -m, --monitor monitor changes\n" " -N, --no-colors disable color output\n" " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" - " -R, --raw force raw output\n", + " -R, --raw force raw output\n" + " -i, --indent indentation amount (default 2)\n" + " -s, --spa SPA JSON output\n", name); } @@ -1533,6 +1558,8 @@ int main(int argc, char *argv[]) { "no-colors", no_argument, NULL, 'N' }, { "color", optional_argument, NULL, 'C' }, { "raw", no_argument, NULL, 'R' }, + { "indent", required_argument, NULL, 'i' }, + { "spa", no_argument, NULL, 's' }, { NULL, 0, NULL, 0} }; int c; @@ -1545,7 +1572,11 @@ int main(int argc, char *argv[]) colors = true; setlinebuf(data.out); - while ((c = getopt_long(argc, argv, "hVr:mNCR", long_options, NULL)) != -1) { + data.comma_char = ","; + data.keysep_char = ":"; + data.indent = INDENT; + + while ((c = getopt_long(argc, argv, "hVr:mNCRi:s", long_options, NULL)) != -1) { switch (c) { case 'h' : show_help(&data, argv[0], false); @@ -1584,12 +1615,21 @@ int main(int argc, char *argv[]) return -1; } break; + case 'i' : + data.indent = atoi(optarg); + break; + case 's' : + data.comma_char = ""; + data.keysep_char = " ="; + data.simple_string = true; + break; default: show_help(&data, argv[0], true); return -1; } } - data.indent = raw ? 0 : INDENT; + if (raw) + data.indent = 0; if (optind < argc) data.pattern = argv[optind++]; From f2905c74edf200389fe627500a9d47ed993cf017 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 12:54:39 +0200 Subject: [PATCH 0423/1014] alsa-udev: support alsa.use-ucm Make a new alsa.use-ucm option that sets api.alsa.use-ucm on the device it creates (when set). There is some documentation floating around (thr arch wiki) with this property. See #4755 --- spa/plugins/alsa/alsa-udev.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c index 9420401f0..7d1ae9a3a 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -94,6 +94,7 @@ struct impl { struct spa_source notify; unsigned int use_acp:1; unsigned int expose_busy:1; + int use_ucm; }; static int impl_udev_open(struct impl *this) @@ -500,7 +501,7 @@ static int emit_added_object_info(struct impl *this, struct card *card) if (num_pcm_devices > 0) { struct spa_device_object_info info; char *cn = NULL, *cln = NULL; - struct spa_dict_item items[25]; + struct spa_dict_item items[26]; unsigned int n_items = 0; card->pcm_device_id = calc_pcm_device_id(card); @@ -521,6 +522,9 @@ static int emit_added_object_info(struct impl *this, struct card *card) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API, "udev"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); + if (this->use_ucm != -1) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_USE_UCM, + this->use_ucm ? "true" : "false"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, path+3); if (snd_card_get_name(card->card_nr, &cn) >= 0) @@ -1105,6 +1109,7 @@ impl_init(const struct spa_handle_factory *factory, alsa_log_topic_init(this->log); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + this->use_ucm = -1; if (this->main_loop == NULL) { spa_log_error(this->log, "a main-loop is needed"); @@ -1131,6 +1136,8 @@ impl_init(const struct spa_handle_factory *factory, this->use_acp = spa_atob(str); else if ((str = spa_dict_lookup(info, "alsa.udev.expose-busy")) != NULL) this->expose_busy = spa_atob(str); + else if ((str = spa_dict_lookup(info, "alsa.use-ucm")) != NULL) + this->use_ucm = spa_atob(str) ? 1 : 0; } return 0; From b57b87abbbb25ae8b7c4635e45553b26a2a87488 Mon Sep 17 00:00:00 2001 From: Harald Sitter Date: Mon, 16 Jun 2025 14:34:51 +0200 Subject: [PATCH 0424/1014] alsa: add Teufel Cage Pro mapping --- spa/plugins/alsa/90-pipewire-alsa.rules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 9ef3d533b..78c6713dd 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -139,6 +139,8 @@ ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0038", ENV{ACP_PROFILE_SET}="usb-gam ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0045", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1532:0520 is for the Razer Kraken Tournament Edition ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0520", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" +# ID 2cc2:0033 is for the Lautsprecher Teufel GmbH CAGE PRO +ATTRS{idVendor}=="2cc2", ATTRS{idProduct}=="0033", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:1250 is for the Arctis 5 From a6199c92a4d8e7ed2be9c602e5857d95f3aed012 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 15:17:07 +0200 Subject: [PATCH 0425/1014] filter-graph: make sure strdupa is defined Fixes #4756 --- spa/plugins/filter-graph/filter-graph.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index fa3cc980e..ed90f48e4 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -351,6 +351,15 @@ static struct node *find_node(struct graph *graph, const char *name) } return NULL; } +#if !defined(strdupa) +# define strdupa(s) \ + ({ \ + const char *__old = (s); \ + size_t __len = strlen(__old) + 1; \ + char *__new = (char *) alloca(__len); \ + (char *) memcpy(__new, __old, __len); \ + }) +#endif /* find a port by name. Valid syntax is: * ":" From b67226fa0c47dfd1c8dbd5abbabdd68ea11470e7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 17:27:10 +0200 Subject: [PATCH 0426/1014] spa-json-dump: add -i and -s options Add -i option to change the indentation Add -s option to generate simplified SPA JSON --- spa/tools/spa-json-dump.c | 151 ++++++++++++++++++++++++++------------ 1 file changed, 103 insertions(+), 48 deletions(-) diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c index afd9569f6..8745cc1da 100644 --- a/spa/tools/spa-json-dump.c +++ b/spa/tools/spa-json-dump.c @@ -11,21 +11,56 @@ #include #include #include +#include #include #include #include -#define REJECT "\"\\'=:,{}[]()#" +#define DEFAULT_INDENT 2 struct data { + const char *filename; FILE *file; + + void *data; + size_t size; + int indent; bool simple_string; const char *comma; const char *key_sep; }; +#define OPTIONS "hi:s" +static const struct option long_options[] = { + { "help", no_argument, NULL, 'h'}, + + { "indent", required_argument, NULL, 'i' }, + { "spa", no_argument, NULL, 's' }, + + { NULL, 0, NULL, 0 } +}; + +static void show_usage(struct data *d, const char *name, bool is_error) +{ + FILE *fp; + + fp = is_error ? stderr : stdout; + + fprintf(fp, "%s [options] [spa-json-file]\n", name); + fprintf(fp, + " -h, --help Show this help\n" + "\n"); + fprintf(fp, + " -i --indent set indent (default %d)\n" + " -s --spa use simplified SPA JSON\n" + "\n", + DEFAULT_INDENT); +} + +#define REJECT "\"\\'=:,{}[]()#" + static bool is_simple_string(const char *val, int len) { int i; @@ -141,59 +176,46 @@ static int dump(struct data *d, int indent, struct spa_json *it, const char *val return 0; } -static int process_json(const char *filename, void *buf, size_t size) +static int process_json(struct data *d) { - struct data d; int len, res; struct spa_json it; const char *value; - spa_zero(d); - if ((len = spa_json_begin(&it, buf, size, &value)) <= 0) { - fprintf(stderr, "not a valid file '%s': %s\n", filename, spa_strerror(len)); + if ((len = spa_json_begin(&it, d->data, d->size, &value)) <= 0) { + fprintf(stderr, "not a valid file '%s': %s\n", d->filename, spa_strerror(len)); return -EINVAL; } if (!spa_json_is_container(value, len)) { - spa_json_init(&it, buf, size); + spa_json_init(&it, d->data, d->size); value = NULL; len = 0; } - d.file = stdout; -#if 1 - d.simple_string = false; - d.comma = ","; - d.key_sep = ":"; - d.indent = 2; -#else - d.simple_string = true; - d.comma = ""; - d.key_sep = " ="; - d.indent = 4; -#endif - res = dump(&d, 0, &it, value, len); + res = dump(d, 0, &it, value, len); if (spa_json_next(&it, &value) < 0) res = -EINVAL; - fprintf(d.file, "\n"); - fflush(d.file); + fprintf(d->file, "\n"); + fflush(d->file); if (res < 0) { struct spa_error_location loc; - if (spa_json_get_error(&it, buf, &loc)) + if (spa_json_get_error(&it, d->data, &loc)) spa_debug_file_error_location(stderr, &loc, "syntax error in file '%s': %s", - filename, loc.reason); + d->filename, loc.reason); else - fprintf(stderr, "error parsing file '%s': %s\n", filename, spa_strerror(res)); + fprintf(stderr, "error parsing file '%s': %s\n", + d->filename, spa_strerror(res)); return -EINVAL; } return 0; } -static int process_stdin(void) +static int process_stdin(struct data *d) { uint8_t *buf = NULL, *p; size_t alloc = 0, size = 0, read_size, res; @@ -217,8 +239,10 @@ static int process_stdin(void) fprintf(stderr, "error: %m\n"); goto error; } + d->data = buf; + d->size = size; - err = process_json("-", buf, size); + err = process_json(d); free(buf); return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE; @@ -230,38 +254,69 @@ error: int main(int argc, char *argv[]) { + int c; + int longopt_index = 0; int fd, res, exit_code = EXIT_FAILURE; - void *data; + struct data d; struct stat sbuf; - if (argc < 1) { - fprintf(stderr, "usage: %s [spa-json-file]\n", argv[0]); - goto error; - } - if (argc == 1) - return process_stdin(); - if ((fd = open(argv[1], O_CLOEXEC | O_RDONLY)) < 0) { - fprintf(stderr, "error opening file '%s': %m\n", argv[1]); - goto error; - } - if (fstat(fd, &sbuf) < 0) { - fprintf(stderr, "error statting file '%s': %m\n", argv[1]); - goto error_close; - } - if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { - fprintf(stderr, "error mmapping file '%s': %m\n", argv[1]); - goto error_close; + spa_zero(d); + d.file = stdout; + + d.filename = "-"; + d.simple_string = false; + d.comma = ","; + d.key_sep = ":"; + d.indent = DEFAULT_INDENT; + + while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { + switch (c) { + case 'h' : + show_usage(&d, argv[0], false); + return 0; + case 'i': + d.indent = atoi(optarg); + break; + case 's': + d.simple_string = true; + d.comma = ""; + d.key_sep = " ="; + break; + default: + show_usage(&d, argv[0], true); + return -1; + } } - res = process_json(argv[1], data, sbuf.st_size); + if (optind < argc) + d.filename = argv[optind++]; + + if (spa_streq(d.filename, "-")) + return process_stdin(&d); + + ((fd = open(d.filename, O_CLOEXEC | O_RDONLY)) < 0) { + fprintf(stderr, "error opening file '%s': %m\n", d.filename); + goto error; + } + if (fstat(fd, &sbuf) < 0) { + fprintf(stderr, "error statting file '%s': %m\n", d.filename); + goto error_close; + } + if ((d.data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { + fprintf(stderr, "error mmapping file '%s': %m\n", d.filename); + goto error_close; + } + d.size = sbuf.st_size; + + res = process_json(&d); if (res < 0) exit_code = EXIT_FAILURE; else exit_code = EXIT_SUCCESS; - munmap(data, sbuf.st_size); + munmap(d.data, sbuf.st_size); error_close: - close(fd); + close(fd); error: return exit_code; } From 7ce9b0daecf562f9a1aa18b0d0aee4ddaed2c5fe Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 17 Jun 2025 18:08:36 +0200 Subject: [PATCH 0427/1014] spa-json-dump: fix compilation --- spa/tools/spa-json-dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c index 8745cc1da..ee3b42da7 100644 --- a/spa/tools/spa-json-dump.c +++ b/spa/tools/spa-json-dump.c @@ -294,7 +294,7 @@ int main(int argc, char *argv[]) if (spa_streq(d.filename, "-")) return process_stdin(&d); - ((fd = open(d.filename, O_CLOEXEC | O_RDONLY)) < 0) { + if ((fd = open(d.filename, O_CLOEXEC | O_RDONLY)) < 0) { fprintf(stderr, "error opening file '%s': %m\n", d.filename); goto error; } From 54923bf5bd09357d773a4b3369c6469c60c9d2b8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 18 Jun 2025 09:48:08 +0200 Subject: [PATCH 0428/1014] alsa: remove UMP negotiation constraint Otherwise we won't be able to negotiate with a port that only wants old style midi. Instead just negotiate the control link, conversion to old style midi will be done in the control mixer for the old client. Fixes #4759 --- spa/plugins/alsa/alsa-seq-bridge.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 68e6c91a8..e08d87e4a 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -529,8 +529,7 @@ impl_node_port_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<have_format = false; } else { struct spa_audio_info info = { 0 }; - uint32_t types; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; @@ -644,13 +641,6 @@ static int port_set_format(void *object, struct seq_port *port, info.media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; - if ((err = spa_pod_parse_object(format, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_CONTROL_types, SPA_POD_Int(&types))) < 0) - return err; - if (types != 1u << SPA_CONTROL_UMP) - return -EINVAL; - port->current_format = info; port->have_format = true; } From b51755bc8b3ae905e72fa1b9f8c7caa6b87a968f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 18 Jun 2025 12:29:39 +0200 Subject: [PATCH 0429/1014] pulse-server: add stream_properties for RTP streams --- src/modules/module-protocol-pulse/modules/module-rtp-recv.c | 5 ++++- src/modules/module-protocol-pulse/modules/module-rtp-send.c | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c index 1777caf61..f81e19047 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c @@ -26,7 +26,8 @@ static const char *const pulse_module_options = "sink= " "sap_address= " - "latency_msec= "; + "latency_msec= " + "stream_properties= "; #define NAME "rtp-recv" @@ -141,6 +142,8 @@ static int module_rtp_recv_prepare(struct module * const module) pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str); if ((str = pw_properties_get(props, "latency_msec")) != NULL) pw_properties_set(stream_props, "sess.latency.msec", str); + if ((str = pw_properties_get(props, "stream_properties")) != NULL) + module_args_add_props(stream_props, str); d->module = module; d->stream_props = stream_props; diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-send.c b/src/modules/module-protocol-pulse/modules/module-rtp-send.c index e84962956..aa4015d12 100644 --- a/src/modules/module-protocol-pulse/modules/module-rtp-send.c +++ b/src/modules/module-protocol-pulse/modules/module-rtp-send.c @@ -36,6 +36,7 @@ static const char *const pulse_module_options = "ttl= " "inhibit_auto_suspend= " "stream_name= " + "stream_properties= " "enable_opus="; #define NAME "rtp-send" @@ -199,6 +200,8 @@ static int module_rtp_send_prepare(struct module * const module) pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str); } } + if ((str = pw_properties_get(props, "stream_properties")) != NULL) + module_args_add_props(stream_props, str); if (module_args_to_audioinfo_keys(module->impl, props, "format", "rate", "channels", "channel_map", &info) < 0) { From 343509abdf37dd2f1e67ed8f3d6de0b719e6b5d8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 18 Jun 2025 15:02:20 +0200 Subject: [PATCH 0430/1014] pw-cli: add type info to variables Add type info to variables. This way we can avoid doing things on variables of the wrong type. Add a list-vars command to list the currently registered variables and their type. Fixes #4746 --- src/tools/pw-cli.c | 121 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 20 deletions(-) diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index 7b8254537..e15918c1f 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -80,6 +80,16 @@ struct global { struct pw_properties *properties; }; +struct var { + int type; +#define TYPE_UNKNOWN 0 +#define TYPE_REMOTE 1 +#define TYPE_PROXY 2 +#define TYPE_MODULE 3 + uint32_t id; + void *object; +}; + struct remote_data { struct spa_list link; struct data *data; @@ -107,6 +117,7 @@ struct proxy_data { const struct class *class; struct spa_hook proxy_listener; struct spa_hook object_listener; + uint32_t id; }; struct command { @@ -116,6 +127,70 @@ struct command { bool (*func) (struct data *data, const char *cmd, char *args, char **error); }; + +static uint32_t add_var(struct data *data, void *object, int type) +{ + struct var *var; + if ((var = calloc(1, sizeof(*var))) == NULL) + return SPA_ID_INVALID; + var->type = type; + var->object = object; + var->id = pw_map_insert_new(&data->vars, var); + return var->id; +} +static void *find_var(struct data *data, uint32_t id, int type) +{ + struct var *var; + var = pw_map_lookup(&data->vars, id); + if (var == NULL || var->type != type) + return NULL; + return var->object; +} + +static void remove_var(struct data *data, uint32_t id, int type) +{ + struct var *var; + var = pw_map_lookup(&data->vars, id); + if (var == NULL || var->type != type) + return; + pw_map_remove(&data->vars, var->id); + free(var); +} + +static int list_var(void *item_data, void *data) +{ + struct var *var = item_data; + switch (var->type) { + case TYPE_MODULE: + printf("%d = @module:%d\n", var->id, pw_global_get_id(pw_impl_module_get_global(var->object))); + break; + case TYPE_PROXY: + printf("%d = @proxy:%d\n", var->id, pw_proxy_get_id(var->object)); + break; + case TYPE_REMOTE: + { + struct remote_data *rd = var->object; + printf("%d = @remote:%p\n", var->id, rd->core); + break; + } + } + return 0; +} + +static void print_var(struct data *data, uint32_t id) +{ + struct var *var; + var = pw_map_lookup(&data->vars, id); + if (var == NULL) + return; + list_var(var, data); +} + +static void list_vars(struct data *data) +{ + pw_map_for_each(&data->vars, list_var, data); +} + static struct spa_dict * global_props(struct global *global); static struct global * obj_global(struct remote_data *rd, uint32_t id); static int children_of(struct remote_data *rd, uint32_t parent_id, @@ -170,6 +245,7 @@ static bool do_not_implemented(struct data *data, const char *cmd, char *args, c #endif static bool do_help(struct data *data, const char *cmd, char *args, char **error); +static bool do_list_vars(struct data *data, const char *cmd, char *args, char **error); static bool do_load_module(struct data *data, const char *cmd, char *args, char **error); static bool do_unload_module(struct data *data, const char *cmd, char *args, char **error); static bool do_list_objects(struct data *data, const char *cmd, char *args, char **error); @@ -194,6 +270,7 @@ static bool do_quit(struct data *data, const char *cmd, char *args, char **error static const struct command command_list[] = { { "help", "h", "Show this help", do_help }, + { "list-vars", "lv", "List all variables", do_list_vars }, { "load-module", "lm", "Load a module. []", do_load_module }, { "unload-module", "um", "Unload a module. ", do_unload_module }, { "connect", "con", "Connect to a remote. []", do_connect }, @@ -238,6 +315,13 @@ static bool do_help(struct data *data, const char *cmd, char *args, char **error return true; } +static bool do_list_vars(struct data *data, const char *cmd, char *args, char **error) +{ + printf("Known variables:\n"); + list_vars(data); + return true; +} + static bool do_load_module(struct data *data, const char *cmd, char *args, char **error) { struct pw_impl_module *module; @@ -257,9 +341,9 @@ static bool do_load_module(struct data *data, const char *cmd, char *args, char return false; } - id = pw_map_insert_new(&data->vars, module); + id = add_var(data, module, TYPE_MODULE); if (data->interactive) - printf("%d = @module:%d\n", id, pw_global_get_id(pw_impl_module_get_global(module))); + print_var(data, id); return true; } @@ -277,12 +361,12 @@ static bool do_unload_module(struct data *data, const char *cmd, char *args, cha return false; } idx = atoi(a[0]); - module = pw_map_lookup(&data->vars, idx); + module = find_var(data, idx, TYPE_MODULE); if (module == NULL) { *error = spa_aprintf("%s: unknown module '%s'", cmd, a[0]); return false; } - pw_map_remove(&data->vars, idx); + remove_var(data, idx, TYPE_MODULE); pw_impl_module_destroy(module); return true; } @@ -496,7 +580,7 @@ static void on_core_destroy(void *_data) spa_hook_remove(&rd->core_listener); spa_hook_remove(&rd->proxy_core_listener); - pw_map_remove(&data->vars, rd->id); + remove_var(data, rd->id, TYPE_REMOTE); pw_map_for_each(&rd->globals, destroy_global, rd); pw_map_clear(&rd->globals); @@ -539,11 +623,11 @@ static bool do_connect(struct data *data, const char *cmd, char *args, char **er rd->core = core; rd->data = data; pw_map_init(&rd->globals, 64, 16); - rd->id = pw_map_insert_new(&data->vars, rd); + rd->id = add_var(data, rd, TYPE_REMOTE); spa_list_append(&data->remotes, &rd->link); if (rd->data->interactive) - printf("%d = @remote:%p\n", rd->id, rd->core); + print_var(data, rd->id); data->current = rd; @@ -572,7 +656,7 @@ static bool do_disconnect(struct data *data, const char *cmd, char *args, char * n = pw_split_ip(args, WHITESPACE, 1, a); if (n >= 1) { idx = atoi(a[0]); - rd = pw_map_lookup(&data->vars, idx); + rd = find_var(data, idx, TYPE_REMOTE); if (rd == NULL) goto no_remote; @@ -614,7 +698,7 @@ static bool do_switch_remote(struct data *data, const char *cmd, char *args, cha if (n == 1) idx = atoi(a[0]); - rd = pw_map_lookup(&data->vars, idx); + rd = find_var(data, idx, TYPE_REMOTE); if (rd == NULL) goto no_remote; @@ -1454,7 +1538,6 @@ static bool do_create_device(struct data *data, const char *cmd, char *args, cha struct remote_data *rd = data->current; char *a[2]; int n; - uint32_t id; struct pw_proxy *proxy; struct pw_properties *props = NULL; struct proxy_data *pd; @@ -1484,9 +1567,9 @@ static bool do_create_device(struct data *data, const char *cmd, char *args, cha pw_proxy_add_object_listener(proxy, &pd->object_listener, &device_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); - id = pw_map_insert_new(&data->vars, proxy); + pd->id = add_var(data, proxy, TYPE_PROXY); if (rd->data->interactive) - printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy)); + print_var(data, pd->id); return true; } @@ -1496,7 +1579,6 @@ static bool do_create_node(struct data *data, const char *cmd, char *args, char struct remote_data *rd = data->current; char *a[2]; int n; - uint32_t id; struct pw_proxy *proxy; struct pw_properties *props = NULL; struct proxy_data *pd; @@ -1526,9 +1608,9 @@ static bool do_create_node(struct data *data, const char *cmd, char *args, char pw_proxy_add_object_listener(proxy, &pd->object_listener, &node_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); - id = pw_map_insert_new(&data->vars, proxy); + pd->id = add_var(data, proxy, TYPE_PROXY); if (rd->data->interactive) - printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy)); + printf("%d = @proxy:%d\n", pd->id, pw_proxy_get_id(proxy)); return true; } @@ -1588,7 +1670,6 @@ obj_global_port(struct remote_data *rd, struct global *global, const char *port_ static void create_link_with_properties(struct data *data, const struct pw_properties *props) { struct remote_data *rd = data->current; - uint32_t id; struct pw_proxy *proxy; struct proxy_data *pd; @@ -1606,9 +1687,9 @@ static void create_link_with_properties(struct data *data, const struct pw_prope pw_proxy_add_object_listener(proxy, &pd->object_listener, &link_events, pd); pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd); - id = pw_map_insert_new(&data->vars, proxy); + pd->id = add_var(data, proxy, TYPE_PROXY); if (rd->data->interactive) - printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy)); + printf("%d = @proxy:%d\n", pd->id, pw_proxy_get_id((struct pw_proxy*)proxy)); } static bool do_create_link(struct data *data, const char *cmd, char *args, char **error) @@ -1712,7 +1793,7 @@ static bool do_export_node(struct data *data, const char *cmd, char *args, char } if (n == 2) { idx = atoi(a[1]); - rd = pw_map_lookup(&data->vars, idx); + rd = find_var(data, idx, TYPE_REMOTE); if (rd == NULL) goto no_remote; } @@ -1729,7 +1810,7 @@ static bool do_export_node(struct data *data, const char *cmd, char *args, char node = pw_global_get_object(global); proxy = pw_core_export(rd->core, PW_TYPE_INTERFACE_Node, NULL, node, 0); - id = pw_map_insert_new(&data->vars, proxy); + id = add_var(data, proxy, TYPE_PROXY); if (rd->data->interactive) printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy)); From 2e18d5f70c5ee1a724cd24bdd218649809a2c801 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 18 Jun 2025 15:13:25 +0200 Subject: [PATCH 0431/1014] doc: update pw-cli man page Fixes #4744 --- doc/dox/programs/pw-cli.1.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/dox/programs/pw-cli.1.md b/doc/dox/programs/pw-cli.1.md index 12ee0fdd5..e5b53ecea 100644 --- a/doc/dox/programs/pw-cli.1.md +++ b/doc/dox/programs/pw-cli.1.md @@ -33,6 +33,10 @@ for many commands. \par quit | q Exit from **pw-cli** +\par list-vars +List all currently known variables and their type. Some commands create +objects that are identified with a variable. + # MODULE MANAGEMENT Modules are loaded and unloaded in the local instance, thus the pw-cli @@ -48,12 +52,12 @@ For most modules it is OK to be loaded more than once. This command returns a module variable that can be used to unload the module. -The locally module is *not* visible in the remote instance. It is not +The local module is *not* visible in the remote instance. It is not possible in PipeWire to load modules in a remote instance. \endparblock \par unload-module *module-var* -Unload a module, specified either by its variable. +Unload a module, specified by its variable. # OBJECT INTROSPECTION From 8047a37b02e2941a2ac113966b8aecc871c110ea Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 18 Jun 2025 15:23:16 +0200 Subject: [PATCH 0432/1014] spa: remove control type from formats We just want to negotiate the control stream, we don't really care about what is in the control stream. --- spa/plugins/audioconvert/audioconvert.c | 8 ++------ spa/plugins/bluez5/midi-node.c | 6 ++---- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 8 ++------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 04069a44a..14a2935f4 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -2615,9 +2615,7 @@ static int port_param_enum_formats(struct impl *impl, struct port *port, uint32_ *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( - (1u<io_position ? @@ -2688,9 +2686,7 @@ static int port_param_format(struct impl *impl, struct port *port, uint32_t id, *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_Int( - (1u<format.info.raw); diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index 0436f12bc..9be26dd59 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -1567,8 +1567,7 @@ next: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<have_format) { /* peer format */ @@ -1477,9 +1475,7 @@ static int port_param_format(struct impl *this, struct port *port, uint32_t id, *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - SPA_FORMAT_CONTROL_types, SPA_POD_Int( - (1u<format); } From cad05236170622874303c82044516ef7c1acfa94 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 16 Jun 2025 16:00:54 +0200 Subject: [PATCH 0433/1014] audioconvert: refactor volume ramping We don't actually have to store the ramp parameters so allocate them on the stack and then use them to generate the sequence. Make it possible to generate a sequence into a custom buffer as well. Make sure we use the right rate (the graph rate) to calculate the number of samples when converting from time to samples. --- spa/plugins/audioconvert/audioconvert.c | 137 ++++++++++++------------ 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 14a2935f4..f5b53785b 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -80,6 +80,9 @@ struct volume_ramp_params { unsigned int volume_ramp_time; unsigned int volume_ramp_step_time; enum spa_audio_volume_ramp_scale scale; + float start; + float end; + uint32_t rate; }; struct props { @@ -92,7 +95,6 @@ struct props { struct volumes channel; struct volumes soft; struct volumes monitor; - struct volume_ramp_params vrp; unsigned int have_soft_volume:1; unsigned int mix_disabled:1; unsigned int resample_disabled:1; @@ -295,6 +297,7 @@ struct impl { struct volume volume; double rate_scale; struct spa_pod_sequence *vol_ramp_sequence; + void *vol_ramp_sequence_data; uint32_t vol_ramp_offset; uint32_t in_offset; @@ -1513,125 +1516,123 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) return changed; } -static int get_ramp_samples(struct impl *this) +static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp) { - struct volume_ramp_params *vrp = &this->props.vrp; int samples = -1; if (vrp->volume_ramp_samples) samples = vrp->volume_ramp_samples; else if (vrp->volume_ramp_time) { - struct dir *d = &this->dir[SPA_DIRECTION_OUTPUT]; - unsigned int sample_rate = d->format.info.raw.rate; - samples = (vrp->volume_ramp_time * sample_rate) / 1000; + samples = (vrp->volume_ramp_time * vrp->rate) / 1000; spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); } if (!samples) samples = -1; - return samples; - } -static int get_ramp_step_samples(struct impl *this) +static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *vrp) { - struct volume_ramp_params *vrp = &this->props.vrp; int samples = -1; if (vrp->volume_ramp_step_samples) samples = vrp->volume_ramp_step_samples; else if (vrp->volume_ramp_step_time) { - struct dir *d = &this->dir[SPA_DIRECTION_OUTPUT]; - int sample_rate = d->format.info.raw.rate; /* convert the step time which is in nano seconds to seconds */ - samples = (vrp->volume_ramp_step_time/1000) * (sample_rate/1000); + samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); } if (!samples) samples = -1; - return samples; - } -static double get_volume_at_scale(struct impl *this, double value) +static float get_volume_at_scale(struct volume_ramp_params *vrp, float value) { - struct volume_ramp_params *vrp = &this->props.vrp; if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR || vrp->scale == SPA_AUDIO_VOLUME_RAMP_INVALID) return value; else if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) return (value * value * value); - return 0.0; } -static struct spa_pod *generate_ramp_up_seq(struct impl *this) +static struct spa_pod *generate_ramp_up_seq(struct impl *this, struct volume_ramp_params *vrp, + void *buffer, size_t size) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f[1]; - struct props *p = &this->props; - double volume_accum = p->prev_volume; - int ramp_samples = get_ramp_samples(this); - int ramp_step_samples = get_ramp_step_samples(this); - double volume_step = ((p->volume - p->prev_volume) / (ramp_samples / ramp_step_samples)); + float start = vrp->start, end = vrp->end, volume_accum = start; + int ramp_samples = get_ramp_samples(this, vrp); + int ramp_step_samples = get_ramp_step_samples(this, vrp); + float volume_step = ((end - start) / (ramp_samples / ramp_step_samples)); uint32_t volume_offs = 0; - spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); + spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_builder_push_sequence(&b.b, &f[0], 0); spa_log_info(this->log, "generating ramp up sequence from %f to %f with a" - " step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); + " step value %f at scale %d", start, end, volume_step, vrp->scale); do { - spa_log_trace(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); + 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(get_volume_at_scale(this, volume_accum))); + SPA_PROP_volume, SPA_POD_Float(vas)); volume_accum += volume_step; volume_offs += ramp_step_samples; - } while (volume_accum < p->volume); + } while (volume_accum < end); return spa_pod_builder_pop(&b.b, &f[0]); } -static struct spa_pod *generate_ramp_down_seq(struct impl *this) +static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp, + void *buffer, size_t size) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f[1]; - int ramp_samples = get_ramp_samples(this); - int ramp_step_samples = get_ramp_step_samples(this); - struct props *p = &this->props; - double volume_accum = p->prev_volume; - double volume_step = ((p->prev_volume - p->volume) / (ramp_samples / ramp_step_samples)); + 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, NULL, 0, 4096); + spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_builder_push_sequence(&b.b, &f[0], 0); spa_log_info(this->log, "generating ramp down sequence from %f to %f with a" - " step value %f at scale %d", p->prev_volume, p->volume, volume_step, p->vrp.scale); + " step value %f at scale %d", start, end, volume_step, vrp->scale); do { - spa_log_trace(this->log, "volume accum %f", get_volume_at_scale(this, volume_accum)); + 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(get_volume_at_scale(this, volume_accum))); + SPA_PROP_volume, SPA_POD_Float(vas)); volume_accum -= volume_step; volume_offs += ramp_step_samples; - } while (volume_accum > p->volume); + } while (volume_accum > end); return spa_pod_builder_pop(&b.b, &f[0]); } -static struct volume_ramp_params *reset_volume_ramp_params(struct impl *this) +static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp, + void *buffer, size_t size) { - if (!this->vol_ramp_sequence) { - struct volume_ramp_params *vrp = &this->props.vrp; - spa_zero(this->props.vrp); - return vrp; - } - return 0; + void *sequence = NULL; + if (vrp->start == vrp->end) + spa_log_error(this->log, "no change in volume, cannot ramp volume"); + else if (vrp->end > vrp->start) + sequence = generate_ramp_up_seq(this, vrp, buffer, size); + else + sequence = generate_ramp_down_seq(this, vrp, buffer, size); + + if (!sequence) + spa_log_error(this->log, "unable to generate sequence"); + + this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence; + this->vol_ramp_sequence_data = (void*)sequence == buffer ? NULL : sequence; + this->vol_ramp_offset = 0; + this->recalc = true; } static int apply_props(struct impl *this, const struct spa_pod *param) @@ -1643,11 +1644,13 @@ static int apply_props(struct impl *this, const struct spa_pod *param) bool have_soft_volume = false; int changed = 0; int vol_ramp_params_changed = 0; - struct volume_ramp_params *vrp = reset_volume_ramp_params(this); + struct volume_ramp_params vrp; uint32_t n; int32_t value; uint32_t id; + spa_zero(vrp); + SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: @@ -1674,7 +1677,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { - vrp->volume_ramp_samples = value; + vrp.volume_ramp_samples = value; spa_log_info(this->log, "%p volume ramp samples %d", this, value); vol_ramp_params_changed++; } @@ -1687,7 +1690,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { - vrp->volume_ramp_step_samples = value; + vrp.volume_ramp_step_samples = value; spa_log_info(this->log, "%p volume ramp step samples is %d", this, value); } @@ -1700,7 +1703,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { - vrp->volume_ramp_time = value; + vrp.volume_ramp_time = value; spa_log_info(this->log, "%p volume ramp time %d", this, value); vol_ramp_params_changed++; } @@ -1713,7 +1716,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { - vrp->volume_ramp_step_time = value; + vrp.volume_ramp_step_time = value; spa_log_info(this->log, "%p volume ramp time %d", this, value); } break; @@ -1725,7 +1728,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (spa_pod_get_id(&prop->value, &id) == 0 && id) { - vrp->scale = id; + vrp.scale = id; spa_log_info(this->log, "%p volume ramp scale %d", this, id); } break; @@ -1799,20 +1802,11 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } if (!p->lock_volumes && vol_ramp_params_changed) { - void *sequence = NULL; - if (p->volume == p->prev_volume) - spa_log_error(this->log, "no change in volume, cannot ramp volume"); - else if (p->volume > p->prev_volume) - sequence = generate_ramp_up_seq(this); - else - sequence = generate_ramp_down_seq(this); - - if (!sequence) - spa_log_error(this->log, "unable to generate sequence"); - - this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence; - this->vol_ramp_offset = 0; - this->recalc = true; + struct dir *dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; + vrp.start = p->prev_volume; + vrp.end = p->volume; + vrp.rate = dir->format.info.raw.rate; + generate_volume_ramp(this, &vrp, NULL, 0); } if (changed) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; @@ -3566,7 +3560,8 @@ static void run_channelmix_stage(struct stage *s, struct stage_context *c) } else if (impl->vol_ramp_sequence) { if (channelmix_process_apply_sequence(impl, impl->vol_ramp_sequence, &impl->vol_ramp_offset, out_datas, in_datas, c->n_samples) == 1) { - free(impl->vol_ramp_sequence); + free(impl->vol_ramp_sequence_data); + impl->vol_ramp_sequence_data = NULL; impl->vol_ramp_sequence = NULL; } } else { @@ -4193,7 +4188,7 @@ static int impl_clear(struct spa_handle *handle) resample_free(&this->resample); if (this->wav_file != NULL) wav_file_close(this->wav_file); - free (this->vol_ramp_sequence); + free (this->vol_ramp_sequence_data); return 0; } From b91864eb37b2b906ff66c1c0fb191790c18e1e03 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 16 Jun 2025 21:20:26 +0300 Subject: [PATCH 0434/1014] bluez5: don't crash on codecs without caps_preference_cmp E.g. LDAC doesn't have that. Add the missing guard that was accidentally dropped in the rewrite. Also explicitly filter out non-A2DP/BAP codecs that can't be used in ensure_media_codec --- spa/plugins/bluez5/bluez5-dbus.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index ddfe7a350..6796bf49c 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4763,6 +4763,9 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct } for (i = 0; codecs[i] != NULL; ++i) { + if (codecs[i]->kind != MEDIA_CODEC_BAP && codecs[i]->kind != MEDIA_CODEC_A2DP) + continue; + if (spa_bt_device_supports_media_codec(device, codecs[i], device->connected_profiles)) { codec = codecs[i]; break; @@ -4827,8 +4830,10 @@ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct } /* Sort in codec preference order */ - codec_switch_cmp_sw = sw; - qsort(sw->paths, num_eps, sizeof(*sw->paths), codec_switch_cmp); + if (codec->caps_preference_cmp) { + codec_switch_cmp_sw = sw; + qsort(sw->paths, num_eps, sizeof(*sw->paths), codec_switch_cmp); + } /* Pick at most one source and one sink endpoint, if corresponding profiles are * set */ From f3ff25c936b413918d0406a28273d6cdd02c1eaf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 20 Jun 2025 15:49:17 +0200 Subject: [PATCH 0435/1014] alsa: don't log unknown events with info Debug is good enough --- spa/plugins/alsa/alsa-seq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 86621a1d2..df4d28766 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -331,7 +331,7 @@ static void alsa_seq_on_sys(struct spa_source *source) state->port_info(state->port_info_data, addr, NULL); break; default: - spa_log_info(state->log, "unhandled event %d: %d:%d", + spa_log_debug(state->log, "unhandled event %d: %d:%d", type, addr->client, addr->port); break; From 2f74789a927531260e82502b019073370c2a07c0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 20 Jun 2025 16:28:26 +0200 Subject: [PATCH 0436/1014] jack: support port_rename callbacks Look at the port name in the port_info properties and emit the port_rename callback when it changes. Fixes #4761 --- pipewire-jack/src/pipewire-jack.c | 123 +++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 37 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 52668e544..588689de1 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -102,6 +102,7 @@ struct notify { #define NOTIFY_TYPE_SHUTDOWN ((7<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_LATENCY ((8<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_TOTAL_LATENCY ((9<<4)|NOTIFY_ACTIVE_FLAG) +#define NOTIFY_TYPE_PORT_RENAME ((10<<4)|NOTIFY_ACTIVE_FLAG) int type; struct object *object; int arg1; @@ -171,6 +172,7 @@ struct object { } port_link; struct { unsigned long flags; + char old_name[REAL_JACK_PORT_NAME_SIZE+1]; char name[REAL_JACK_PORT_NAME_SIZE+1]; char alias1[REAL_JACK_PORT_NAME_SIZE+1]; char alias2[REAL_JACK_PORT_NAME_SIZE+1]; @@ -1083,6 +1085,16 @@ static void on_notify_event(void *data, uint64_t count) notify->arg1, c->portregistration_arg); break; + case NOTIFY_TYPE_PORT_RENAME: + if (o->registered == notify->arg1) + break; + pw_log_debug("%p: port rename %u %s->%s", c, o->serial, + o->port.old_name, o->port.name); + do_callback(c, rename_callback, c->active, + o->serial, + o->port.old_name, o->port.name, + c->rename_arg); + break; case NOTIFY_TYPE_CONNECT: if (o->registered == notify->arg1) break; @@ -1183,6 +1195,9 @@ static int queue_notify(struct client *c, int type, struct object *o, int arg1, emit = c->portregistration_callback != NULL && o != NULL; o->visible = arg1; break; + case NOTIFY_TYPE_PORT_RENAME: + emit = c->rename_callback != NULL && o != NULL; + break; case NOTIFY_TYPE_CONNECT: emit = c->connect_callback != NULL && o != NULL; break; @@ -3657,6 +3672,67 @@ static const struct pw_node_events node_events = { .info = node_info, }; +#define FILTER_NAME " ()[].:*$" +#define FILTER_PORT " ()[].*$" + +static void filter_name(char *str, const char *filter, char filter_char) +{ + char *p; + for (p = str; *p; p++) { + if (strchr(filter, *p) != NULL) + *p = filter_char; + } +} + +static int update_port_name(struct object *o, const char *name) +{ + struct object *ot = o->port.node, *op; + struct client *c = o->client; + char tmp[REAL_JACK_PORT_NAME_SIZE+1]; + char port_name[REAL_JACK_PORT_NAME_SIZE+1]; + + if (o->port.is_monitor && !c->merge_monitor) + snprintf(tmp, sizeof(tmp), "%.*s%s:%s", + (int)(JACK_CLIENT_NAME_SIZE-(sizeof(MONITOR_EXT)-1)), + ot->node.name, MONITOR_EXT, name); + else + snprintf(tmp, sizeof(tmp), "%s:%s", ot->node.name, name); + + if (c->filter_name) + filter_name(tmp, FILTER_PORT, c->filter_char); + + op = find_port_by_name(c, tmp); + if (op != NULL && op != o) + snprintf(port_name, sizeof(port_name), "%.*s-%u", + (int)(sizeof(tmp)-11), tmp, o->serial); + else + snprintf(port_name, sizeof(port_name), "%s", tmp); + + if (spa_streq(port_name, o->port.name)) + return 0; + + strcpy(o->port.old_name, o->port.name); + strcpy(o->port.name, port_name); + return 1; +} + +static void port_info(void *data, const struct pw_port_info *info) +{ + struct object *o = data; + struct client *c = o->client; + + if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) { + const char *str = spa_dict_lookup(info->props, PW_KEY_PORT_NAME); + if (str != NULL) { + if (update_port_name(o, str) > 0) { + pw_log_info("%p: port rename %u %s->%s", c, o->serial, + o->port.old_name, o->port.name); + queue_notify(c, NOTIFY_TYPE_PORT_RENAME, o, 1, NULL); + } + } + } +} + static void port_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) @@ -3679,27 +3755,16 @@ static void port_param(void *data, int seq, static const struct pw_port_events port_events = { PW_VERSION_PORT_EVENTS, + .info = port_info, .param = port_param, }; -#define FILTER_NAME " ()[].:*$" -#define FILTER_PORT " ()[].*$" - -static void filter_name(char *str, const char *filter, char filter_char) -{ - char *p; - for (p = str; *p; p++) { - if (strchr(filter, *p) != NULL) - *p = filter_char; - } -} - static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct client *c = (struct client *) data; - struct object *o, *ot, *op; + struct object *o, *ot; const char *str; bool do_emit = true, do_sync = false; uint32_t serial; @@ -3822,6 +3887,7 @@ static void registry_event_global(void *data, uint32_t id, uint32_t node_id; bool is_monitor = false; char tmp[REAL_JACK_PORT_NAME_SIZE+1]; + const char *name; if ((str = spa_dict_lookup(props, PW_KEY_FORMAT_DSP)) == NULL) str = "other"; @@ -3837,7 +3903,7 @@ static void registry_event_global(void *data, uint32_t id, spa_strstartswith(str, "jack:flags:")) flags = atoi(str+11); - if ((str = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) + if ((name = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) goto exit; if (type_id == TYPE_ID_UMP && c->flag_midi2) @@ -3891,6 +3957,7 @@ static void registry_event_global(void *data, uint32_t id, o->port.node = ot; o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + o->port.type_id = type_id; do_emit = node_is_active(c, ot); @@ -3912,27 +3979,12 @@ static void registry_event_global(void *data, uint32_t id, pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); - - if (is_monitor && !c->merge_monitor) - snprintf(tmp, sizeof(tmp), "%.*s%s:%s", - (int)(JACK_CLIENT_NAME_SIZE-(sizeof(MONITOR_EXT)-1)), - ot->node.name, MONITOR_EXT, str); - else - snprintf(tmp, sizeof(tmp), "%s:%s", ot->node.name, str); - - if (c->filter_name) - filter_name(tmp, FILTER_PORT, c->filter_char); - - op = find_port_by_name(c, tmp); - if (op != NULL) - snprintf(o->port.name, sizeof(o->port.name), "%.*s-%u", - (int)(sizeof(tmp)-11), tmp, serial); - else - snprintf(o->port.name, sizeof(o->port.name), "%s", tmp); - - o->port.type_id = type_id; } + o->port.flags = flags; + o->port.node_id = node_id; + o->port.is_monitor = is_monitor; + if (c->fill_aliases) { if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL) snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", str); @@ -3940,7 +3992,6 @@ static void registry_event_global(void *data, uint32_t id, if ((str = spa_dict_lookup(props, PW_KEY_PORT_ALIAS)) != NULL) snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", str); } - if ((str = spa_dict_lookup(props, PW_KEY_PORT_ID)) != NULL) { o->port.system_id = atoi(str); snprintf(o->port.system, sizeof(o->port.system), "system:%s_%d", @@ -3949,9 +4000,7 @@ static void registry_event_global(void *data, uint32_t id, o->port.system_id+1); } - o->port.flags = flags; - o->port.node_id = node_id; - o->port.is_monitor = is_monitor; + update_port_name(o, name); pw_log_debug("%p: %p add port %d name:%s %d", c, o, id, o->port.name, type_id); From f9b0bf3f95733d93eee30272461c4f36036f6133 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 16:21:06 +0300 Subject: [PATCH 0437/1014] bluez5: limit CVSD block size Don't try to write data in too large blocks. This controls the maximum amount of data to send at once. sco-io will buffer and fragment packets to the right size. Previously in sco-sink, SO_SNDBUF was not set, so there could be a longer queue in the socket. --- spa/plugins/bluez5/hfp-codec-cvsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/hfp-codec-cvsd.c b/spa/plugins/bluez5/hfp-codec-cvsd.c index d5f30fa2a..66b801ecc 100644 --- a/spa/plugins/bluez5/hfp-codec-cvsd.c +++ b/spa/plugins/bluez5/hfp-codec-cvsd.c @@ -88,7 +88,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if (!this) return NULL; - this->block_size = 2 * (mtu/2); + this->block_size = SPA_MIN(2 * (mtu/2), 144u); /* cap to 9 ms */ return this; } From 5b4e9dc33e5f8abec16cc3e0aa70e4d36ec1fd0d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 8 Jun 2025 13:19:05 +0300 Subject: [PATCH 0438/1014] bluez5: replace sco-sink with media-sink Change media-sink to use sco-io for HFP codecs. Move SCO fragmentation to sco-io side. Replace sco-sink with media-sink. sco-sink is mostly copypaste from media-sink, and only differed in the fragmentation detail, which can as well be handled on sco-io side. --- spa/plugins/bluez5/defs.h | 3 +- spa/plugins/bluez5/media-sink.c | 85 +- spa/plugins/bluez5/meson.build | 1 - spa/plugins/bluez5/sco-io.c | 149 ++- spa/plugins/bluez5/sco-sink.c | 1634 ------------------------------- 5 files changed, 141 insertions(+), 1731 deletions(-) delete mode 100644 spa/plugins/bluez5/sco-sink.c diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 03ac283b2..eb7c858a8 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -609,8 +609,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s struct spa_system *data_system, struct spa_log *log); void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io); void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time), void *userdata); -void spa_bt_sco_io_set_sink_cb(struct spa_bt_sco_io *io, int (*sink_cb)(void *userdata), void *userdata); -int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *data, int size); +int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t size); #define SPA_BT_VOLUME_ID_RX 0 #define SPA_BT_VOLUME_ID_TX 1 diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 22dbad068..8cf548744 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -685,6 +685,8 @@ static int get_transport_unsent_size(struct impl *this) if (this->tx_latency.enabled) { res = 0; value = this->tx_latency.unsent; + } else if (this->codec->kind == MEDIA_CODEC_HFP) { + value = 0; } else { res = ioctl(this->flush_source.fd, TIOCOUTQ, &value); if (res < 0) { @@ -705,14 +707,20 @@ static int send_buffer(struct impl *this) int written, unsent; struct timespec ts_pre; - unsent = get_transport_unsent_size(this); - if (unsent >= 0) - this->codec->abr_process(this->codec_data, unsent); + if (this->codec->abr_process) { + unsent = get_transport_unsent_size(this); + if (unsent >= 0) + this->codec->abr_process(this->codec_data, unsent); + } spa_system_clock_gettime(this->data_system, CLOCK_REALTIME, &ts_pre); - written = spa_bt_send(this->flush_source.fd, this->buffer, this->buffer_used, - &this->tx_latency, SPA_TIMESPEC_TO_NSEC(&ts_pre)); + if (this->codec->kind == MEDIA_CODEC_HFP) { + written = spa_bt_sco_io_write(this->transport->sco_io, this->buffer, this->buffer_used); + } else { + written = spa_bt_send(this->flush_source.fd, this->buffer, this->buffer_used, + &this->tx_latency, SPA_TIMESPEC_TO_NSEC(&ts_pre)); + } if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { struct timespec ts; @@ -870,6 +878,7 @@ static int flush_data(struct impl *this, uint64_t now_time) { struct port *port = &this->port; bool is_asha = this->codec->kind == MEDIA_CODEC_ASHA; + bool is_sco = this->codec->kind == MEDIA_CODEC_HFP; uint32_t total_frames; int written; int unsent_buffer; @@ -877,10 +886,12 @@ static int flush_data(struct impl *this, uint64_t now_time) spa_assert(this->transport_started); /* I/O in error state? */ - if (this->transport == NULL || (!this->flush_source.loop && !is_asha)) + if (this->transport == NULL || (!this->flush_source.loop && !is_asha && !is_sco)) return -EIO; if (!this->flush_timer_source.loop && !this->transport->iso_io && !is_asha) return -EIO; + if (!this->transport->sco_io && is_sco) + return -EIO; if (this->transport->iso_io && !this->iso_pending) return 0; @@ -1021,7 +1032,10 @@ again: if (written == -EAGAIN) { spa_log_trace(this->log, "%p: fail flush", this); if (now_time - this->last_error > SPA_NSEC_PER_SEC / 2) { - int res = this->codec->reduce_bitpool(this->codec_data); + int res = 0; + + if (this->codec->reduce_bitpool) + res = this->codec->reduce_bitpool(this->codec_data); spa_log_debug(this->log, "%p: reduce bitpool: %i", this, res); this->last_error = now_time; @@ -1092,7 +1106,10 @@ again: if (now_time - this->last_error > SPA_NSEC_PER_SEC) { if (unsent_buffer == 0) { - int res = this->codec->increase_bitpool(this->codec_data); + int res = 0; + + if (this->codec->increase_bitpool) + res = this->codec->increase_bitpool(this->codec_data); spa_log_debug(this->log, "%p: increase bitpool: %i", this, res); } @@ -1437,6 +1454,7 @@ static int transport_start(struct impl *this) uint8_t *conf; uint32_t flags; bool is_asha; + bool is_sco; if (this->transport_started) return 0; @@ -1452,6 +1470,7 @@ static int transport_start(struct impl *this) conf = this->transport->configuration; size = this->transport->configuration_len; is_asha = this->codec->kind == MEDIA_CODEC_ASHA; + is_sco = this->codec->kind == MEDIA_CODEC_HFP; spa_log_debug(this->log, "Transport configuration:"); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); @@ -1494,7 +1513,7 @@ static int transport_start(struct impl *this) if (this->block_size > sizeof(this->tmp_buffer)) { spa_log_error(this->log, "block-size %d > %zu", this->block_size, sizeof(this->tmp_buffer)); - return -EIO; + goto fail; } spa_log_debug(this->log, "%p: block_size %d", this, this->block_size); @@ -1525,6 +1544,14 @@ static int transport_start(struct impl *this) this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); + spa_zero(this->tx_latency); + + if (is_sco) { + int res; + if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) + goto fail; + } + if (!this->transport->iso_io && !is_asha) { this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; @@ -1533,10 +1560,11 @@ static int transport_start(struct impl *this) this->flush_timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_timer_source); - spa_bt_latency_init(&this->tx_latency, this->transport, LATENCY_PERIOD, this->log); + if (!is_sco) + spa_bt_latency_init(&this->tx_latency, this->transport, LATENCY_PERIOD, this->log); } - if (!is_asha) { + if (!is_asha && !is_sco) { this->flush_source.data = this; this->flush_source.fd = this->transport->fd; this->flush_source.func = media_on_flush_error; @@ -1578,6 +1606,15 @@ static int transport_start(struct impl *this) return 0; + +fail: + if (this->codec_data) { + if (this->own_codec_data) + this->codec->deinit(this->codec_data); + this->own_codec_data = false; + this->codec_data = NULL; + } + return -EIO; } static int do_start(struct impl *this) @@ -1595,7 +1632,8 @@ static int do_start(struct impl *this) this->start_ready = true; - if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) { + bool do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; + if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { this->start_ready = false; return res; } @@ -1748,6 +1786,8 @@ static void emit_node_info(struct impl *this, bool full) { char node_group_buf[256]; char *node_group = NULL; + const char *media_role = NULL; + const char *codec_profile = media_codec_kind_str(this->codec); if (this->transport && (this->transport->profile & SPA_BT_PROFILE_BAP_SINK)) { spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-iso-%s-cig-%d\"]", @@ -1765,7 +1805,10 @@ static void emit_node_info(struct impl *this, bool full) node_group = node_group_buf; } - const char *codec_profile = media_codec_kind_str(this->codec); + if (!this->is_output && this->transport && + (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) + media_role = "Communication"; + struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : @@ -1774,6 +1817,7 @@ static void emit_node_info(struct impl *this, bool full) this->transport->device->name : codec_profile ) }, { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, { "node.group", node_group }, + { SPA_KEY_MEDIA_ROLE, media_role }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) @@ -2035,7 +2079,8 @@ static int port_set_format(struct impl *this, struct port *port, port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { - case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_LE: + case SPA_AUDIO_FORMAT_S16_BE: port->frame_size *= 2; break; case SPA_AUDIO_FORMAT_S24: @@ -2525,6 +2570,8 @@ impl_init(const struct spa_handle_factory *factory, if (this->codec->kind == MEDIA_CODEC_BAP) this->is_output = this->transport->bap_initiator; + else if (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) + this->is_output = false; else this->is_output = true; @@ -2602,3 +2649,13 @@ const struct spa_handle_factory spa_a2dp_sink_factory = { impl_init, impl_enum_interface_info, }; + +/* Retained for backward compatibility: */ +const struct spa_handle_factory spa_sco_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_SCO_SINK, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 97eae0d79..1e3918dd3 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -16,7 +16,6 @@ bluez5_sources = [ 'media-codecs.c', 'media-sink.c', 'media-source.c', - 'sco-sink.c', 'sco-source.c', 'sco-io.c', 'iso-io.c', diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 9cb2dcf4d..d9b3635f1 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -56,7 +56,10 @@ struct spa_bt_sco_io { bool started; uint8_t read_buffer[MAX_MTU]; - uint32_t read_size; + size_t read_size; + + uint8_t write_buffer[MAX_MTU]; + size_t write_size; int fd; uint16_t read_mtu; @@ -71,28 +74,9 @@ struct spa_bt_sco_io { int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time); void *source_userdata; - - int (*sink_cb)(void *userdata); - void *sink_userdata; }; -static void update_source(struct spa_bt_sco_io *io) -{ - int enabled; - int changed = 0; - - enabled = io->sink_cb != NULL; - if (SPA_FLAG_IS_SET(io->source.mask, SPA_IO_OUT) != enabled) { - SPA_FLAG_UPDATE(io->source.mask, SPA_IO_OUT, enabled); - changed = 1; - } - - if (changed) { - spa_loop_update_source(io->data_loop, &io->source); - } -} - static void sco_io_on_ready(struct spa_source *source) { struct spa_bt_sco_io *io = source->data; @@ -116,9 +100,13 @@ static void sco_io_on_ready(struct spa_source *source) goto stop; } - if (res != (int)io->read_size) + if (res != (int)io->read_size) { spa_log_trace(io->log, "%p: packet size:%d", io, res); + /* drop buffer when packet size changes */ + io->write_size = 0; + } + io->read_size = res; if (io->source_cb) { @@ -131,23 +119,9 @@ static void sco_io_on_ready(struct spa_source *source) } read_done: - if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_OUT)) { - if (io->sink_cb) { - int res; - res = io->sink_cb(io->sink_userdata); - if (res) { - io->sink_cb = NULL; - } - } - } - if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_ERR) || SPA_FLAG_IS_SET(source->rmask, SPA_IO_HUP)) { goto stop; } - - /* Poll socket in/out only if necessary */ - update_source(io); - return; stop: @@ -157,47 +131,80 @@ stop: } } +static int write_packets(struct spa_bt_sco_io *io, const uint8_t **buf, size_t *size, size_t packet_size) +{ + while (*size >= packet_size) { + ssize_t written; + + written = send(io->fd, *buf, packet_size, MSG_DONTWAIT | MSG_NOSIGNAL); + if (written < 0) { + if (errno == EINTR) + continue; + return -errno; + } + + *buf += written; + *size -= written; + } + return 0; +} + /* * Write data to socket in correctly sized blocks. - * Returns the number of bytes written, 0 when data cannot be written now or - * there is too little of it to write, and <0 on write error. + * Returns the number of bytes written or buffered, and <0 on write error. */ -int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *buf, int size) +int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t size) { - uint16_t packet_size; - uint8_t *buf_start = buf; + const size_t orig_size = size; + const uint8_t *pos; + size_t packet_size; + int res; if (io->read_size == 0) { /* The proper write packet size is not known yet */ return 0; } - packet_size = SPA_MIN(io->write_mtu, io->read_size); + packet_size = SPA_MIN(SPA_MIN(io->write_mtu, io->read_size), sizeof(io->write_buffer)); - if (size < packet_size) { - return 0; + if (io->write_size >= packet_size) { + /* packet size changed, drop data */ + io->write_size = 0; + } else if (io->write_size) { + /* write fragment */ + size_t need = SPA_MIN(packet_size - io->write_size, size); + + memcpy(io->write_buffer + io->write_size, buf, need); + buf += need; + size -= need; + io->write_size += need; + + if (io->write_size < packet_size) + return orig_size; + + pos = io->write_buffer; + if ((res = write_packets(io, &pos, &io->write_size, packet_size)) < 0) + goto fail; + if (io->write_size) + goto fail; } - do { - int written; + /* write */ + if ((res = write_packets(io, &buf, &size, packet_size)) < 0) + goto fail; - written = send(io->fd, buf, packet_size, MSG_DONTWAIT | MSG_NOSIGNAL); - if (written < 0) { - if (errno == EINTR) { - /* retry if interrupted */ - continue; - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { - /* Don't continue writing */ - break; - } - return -errno; - } + spa_assert(size < packet_size); - buf += written; - size -= written; - } while (size >= packet_size); + /* store fragment */ + io->write_size = size; + if (size) + memcpy(io->write_buffer, buf, size); - return buf - buf_start; + return orig_size; + +fail: + io->write_size = 0; + return res; } @@ -236,7 +243,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s } } - spa_log_debug(io->log, "%p: initial packet size:%d", io, io->read_size); + spa_log_debug(io->log, "%p: initial packet size:%d", io, (int)io->read_size); spa_bt_recvmsg_init(&io->recv, io->fd, io->data_system, io->log); @@ -244,7 +251,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s io->source.data = io; io->source.fd = io->fd; io->source.func = sco_io_on_ready; - io->source.mask = SPA_IO_IN | SPA_IO_OUT | SPA_IO_ERR | SPA_IO_HUP; + io->source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; io->source.rmask = 0; spa_loop_add_source(io->data_loop, &io->source); @@ -285,22 +292,4 @@ void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void { io->source_cb = source_cb; io->source_userdata = userdata; - - if (io->started) { - update_source(io); - } -} - -/* Set sink callback. - * This function should only be called from the data thread. - * Callback is called (in data loop) when socket can be written to. - */ -void spa_bt_sco_io_set_sink_cb(struct spa_bt_sco_io *io, int (*sink_cb)(void *), void *userdata) -{ - io->sink_cb = sink_cb; - io->sink_userdata = userdata; - - if (io->started) { - update_source(io); - } } diff --git a/spa/plugins/bluez5/sco-sink.c b/spa/plugins/bluez5/sco-sink.c deleted file mode 100644 index c3b8dbaa3..000000000 --- a/spa/plugins/bluez5/sco-sink.c +++ /dev/null @@ -1,1634 +0,0 @@ -/* Spa SCO Sink */ -/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "media-codecs.h" - -SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sink.sco"); -#undef SPA_LOG_TOPIC_DEFAULT -#define SPA_LOG_TOPIC_DEFAULT &log_topic - -#define DEFAULT_CLOCK_NAME "clock.system.monotonic" - -struct props { - int64_t latency_offset; - char clock_name[64]; -}; - -#define MAX_BUFFERS 32 - -struct buffer { - uint32_t id; - unsigned int outstanding:1; - struct spa_buffer *buf; - struct spa_meta_header *h; - struct spa_list link; -}; - -struct port { - struct spa_audio_info current_format; - int frame_size; - unsigned int have_format:1; - - uint64_t info_all; - struct spa_port_info info; - struct spa_io_buffers *io; - struct spa_io_rate_match *rate_match; - struct spa_latency_info latency; -#define IDX_EnumFormat 0 -#define IDX_Meta 1 -#define IDX_IO 2 -#define IDX_Format 3 -#define IDX_Buffers 4 -#define IDX_Latency 5 -#define N_PORT_PARAMS 6 - struct spa_param_info params[N_PORT_PARAMS]; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list ready; - - struct buffer *current_buffer; - uint32_t ready_offset; - uint8_t write_buffer[4096]; - uint32_t write_buffer_size; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - /* Support */ - struct spa_log *log; - struct spa_loop *data_loop; - struct spa_system *data_system; - - /* Hooks and callbacks */ - struct spa_hook_list hooks; - struct spa_callbacks callbacks; - - /* Info */ - uint64_t info_all; - struct spa_node_info info; -#define IDX_PropInfo 0 -#define IDX_Props 1 -#define N_NODE_PARAMS 2 - struct spa_param_info params[N_NODE_PARAMS]; - struct props props; - - uint32_t quantum_limit; - - /* Transport */ - struct spa_bt_transport *transport; - struct spa_hook transport_listener; - - /* Port */ - struct port port; - - /* Flags */ - unsigned int started:1; - unsigned int start_ready:1; - unsigned int transport_started:1; - unsigned int following:1; - unsigned int flush_pending:1; - - unsigned int is_internal:1; - - /* Sources */ - struct spa_source source; - struct spa_source flush_timer_source; - - /* Timer */ - int timerfd; - int flush_timerfd; - struct spa_io_clock *clock; - struct spa_io_position *position; - - uint64_t current_time; - uint64_t next_time; - uint64_t process_time; - - uint64_t prev_flush_time; - uint64_t next_flush_time; - - uint8_t buffer[7200]; - size_t buffer_size; - - uint16_t seqnum; - - const struct media_codec *codec; - void *codec_data; -}; - -#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) - -static void reset_props(struct props *props) -{ - props->latency_offset = 0; - strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); -} - -static int impl_node_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_PropInfo: - { - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), - SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, INT64_MIN, INT64_MAX)); - break; - default: - return 0; - } - break; - } - case SPA_PARAM_Props: - { - struct props *p = &this->props; - - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, id, - SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset)); - break; - default: - return 0; - } - break; - } - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int set_timeout(struct impl *this, uint64_t time) -{ - struct itimerspec ts; - ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - return spa_system_timerfd_settime(this->data_system, - this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); -} - -static int set_timers(struct impl *this) -{ - struct timespec now; - - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - this->next_time = SPA_TIMESPEC_TO_NSEC(&now); - - return set_timeout(this, this->following ? 0 : this->next_time); -} - -static int do_reassign_follower(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - set_timers(this); - return 0; -} - -static inline bool is_following(struct impl *this) -{ - return this->position && this->clock && this->position->clock.id != this->clock->id; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - bool following; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_IO_Clock: - this->clock = data; - if (this->clock != NULL) { - spa_scnprintf(this->clock->name, - sizeof(this->clock->name), - "%s", this->props.clock_name); - } - break; - case SPA_IO_Position: - this->position = data; - break; - default: - return -ENOENT; - } - - following = is_following(this); - if (this->started && following != this->following) { - spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); - this->following = following; - spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); - } - return 0; -} - -static void emit_node_info(struct impl *this, bool full); - -static void emit_port_info(struct impl *this, struct port *port, bool full); - -static void set_latency(struct impl *this, bool emit_latency) -{ - struct port *port = &this->port; - int64_t delay; - - if (this->transport == NULL) - return; - - /* - * We start flushing data immediately, so the delay is: - * - * (transport delay) + (packet delay) + (codec internal delay) + (latency offset) - * - * and doesn't depend on the quantum. The codec internal delay is neglected. - * Kernel knows the latency due to socket/controller queue, but doesn't - * tell us, so not included but hopefully in < 20 ms range. - */ - - switch (this->codec->id) { - case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: - case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: - delay = 7500 * SPA_NSEC_PER_USEC; - break; - default: - delay = this->transport->write_mtu / (2 * 8000); - break; - } - - delay += spa_bt_transport_get_delay_nsec(this->transport); - delay += SPA_CLAMP(this->props.latency_offset, -delay, INT64_MAX / 2); - delay = SPA_MAX(delay, 0); - - spa_log_info(this->log, "%p: total latency:%d ms", this, (int)(delay / SPA_NSEC_PER_MSEC)); - - port->latency.min_ns = port->latency.max_ns = delay; - port->latency.min_rate = port->latency.max_rate = 0; - port->latency.min_quantum = port->latency.max_quantum = 0.0f; - - if (emit_latency) { - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - emit_port_info(this, port, false); - } -} - -static int apply_props(struct impl *this, const struct spa_pod *param) -{ - struct props new_props = this->props; - int changed = 0; - - if (param == NULL) { - reset_props(&new_props); - } else { - spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Props, NULL, - SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&new_props.latency_offset)); - } - - changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); - this->props = new_props; - - if (changed) - set_latency(this, true); - - return changed; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_PARAM_Props: - { - if (apply_props(this, param) > 0) { - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - } - break; - } - default: - return -ENOENT; - } - - return 0; -} - -static void enable_flush_timer(struct impl *this, bool enabled) -{ - struct itimerspec ts; - - if (!enabled) - this->next_flush_time = 0; - - ts.it_value.tv_sec = this->next_flush_time / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = this->next_flush_time % SPA_NSEC_PER_SEC; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - spa_system_timerfd_settime(this->data_system, - this->flush_timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); - - this->flush_pending = enabled; -} - -static uint32_t get_queued_frames(struct impl *this) -{ - struct port *port = &this->port; - uint32_t bytes = 0; - struct buffer *b; - - spa_list_for_each(b, &port->ready, link) { - struct spa_data *d = b->buf->datas; - - bytes += d[0].chunk->size; - } - - if (bytes > port->ready_offset) - bytes -= port->ready_offset; - else - bytes = 0; - - return bytes / port->frame_size; -} - -static int encode_data(struct impl *this) -{ - struct port *port = &this->port; - int res; - int need_flush; - size_t written = 0; - size_t avail; - - avail = sizeof(this->buffer) - this->buffer_size; - res = this->codec->start_encode(this->codec_data, - this->buffer + this->buffer_size, avail, - this->seqnum, 0); - if (res < 0) { - spa_log_warn(this->log, "encode failed: %d", res); - goto fail; - } - - this->seqnum++; - this->buffer_size += res; - - avail = sizeof(this->buffer) - this->buffer_size; - res = this->codec->encode(this->codec_data, - port->write_buffer, port->write_buffer_size, - this->buffer + this->buffer_size, avail, - &written, &need_flush); - if (res < 0) { - spa_log_warn(this->log, "encode failed: %d", res); - goto fail; - } - - this->buffer_size += written; - port->write_buffer_size = 0; - - return written; - -fail: - this->buffer_size = 0; - return res; -} - -static int flush_data(struct impl *this) -{ - struct port *port = &this->port; - int written; - - spa_assert(this->transport_started); - - if (this->transport == NULL || this->transport->sco_io == NULL || !this->flush_timer_source.loop) - return -EIO; - - const uint32_t min_in_size = this->codec->get_block_size(this->codec_data); - const uint32_t packet_samples = min_in_size / port->frame_size; - const uint64_t packet_time = (uint64_t)packet_samples * SPA_NSEC_PER_SEC - / port->current_format.info.raw.rate; - - while (!spa_list_is_empty(&port->ready) && port->write_buffer_size < min_in_size) { - struct spa_data *datas; - - /* get buffer */ - if (!port->current_buffer) { - spa_return_val_if_fail(!spa_list_is_empty(&port->ready), -EIO); - port->current_buffer = spa_list_first(&port->ready, struct buffer, link); - port->ready_offset = 0; - } - datas = port->current_buffer->buf->datas; - - /* if buffer has data, copy it into the write buffer */ - if (datas[0].chunk->size - port->ready_offset > 0) { - const uint32_t avail = - SPA_MIN(min_in_size, datas[0].chunk->size - port->ready_offset); - const uint32_t size = - (avail + port->write_buffer_size) > min_in_size ? - min_in_size - port->write_buffer_size : avail; - memcpy(port->write_buffer + port->write_buffer_size, - (uint8_t *)datas[0].data + port->ready_offset, - size); - port->write_buffer_size += size; - port->ready_offset += size; - } else { - struct buffer *b; - - b = port->current_buffer; - port->current_buffer = NULL; - - /* reuse buffer */ - spa_list_remove(&b->link); - b->outstanding = true; - spa_log_trace(this->log, "sco-sink %p: reuse buffer %u", this, b->id); - port->io->buffer_id = b->id; - spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); - } - } - - if (this->flush_pending) { - spa_log_trace(this->log, "%p: wait for flush timer", this); - return 0; - } - - if (port->write_buffer_size < min_in_size) { - /* wait for more data */ - spa_log_trace(this->log, "%p: skip flush", this); - enable_flush_timer(this, false); - return 0; - } - - /* Encode */ - if (encode_data(this) < 0) - goto stop; - - /* Write */ - written = spa_bt_sco_io_write(this->transport->sco_io, this->buffer, this->buffer_size); - if (written < 0) { - spa_log_warn(this->log, "failed to write data: %d (%s)", - written, spa_strerror(written)); - goto stop; - } - - if ((size_t)written == this->buffer_size) { - this->buffer_size = 0; - } else { - spa_memmove(this->buffer, this->buffer + written, this->buffer_size - written); - this->buffer_size -= written; - } - - if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { - struct timespec ts; - uint64_t now; - uint64_t dt; - - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); - now = SPA_TIMESPEC_TO_NSEC(&ts); - dt = now - this->prev_flush_time; - this->prev_flush_time = now; - - spa_log_trace(this->log, - "%p: send wrote:%d dt:%"PRIu64, - this, written, dt); - } - - spa_log_trace(this->log, "write socket data %d", written); - - if (SPA_LIKELY(this->position)) { - uint32_t frames = get_queued_frames(this); - uint64_t duration_ns; - - /* - * Flush at the time position of the next buffered sample. - */ - duration_ns = ((uint64_t)this->position->clock.duration * SPA_NSEC_PER_SEC - / this->position->clock.rate.denom); - this->next_flush_time = this->process_time + duration_ns - - ((uint64_t)frames * SPA_NSEC_PER_SEC - / port->current_format.info.raw.rate); - - /* - * We could delay the output by one packet to avoid waiting - * for the next buffer and so make send intervals more regular. - * However, this appears not needed in practice, and it's better - * to not add latency if not needed. - */ -#if 0 - this->next_flush_time += SPA_MIN(packet_time, - duration_ns * (port->n_buffers - 1)); -#endif - } else { - if (this->next_flush_time == 0) - this->next_flush_time = this->process_time; - this->next_flush_time += packet_time; - } - - enable_flush_timer(this, true); - return 0; - -stop: - enable_flush_timer(this, false); - if (this->flush_timer_source.loop) - spa_loop_remove_source(this->data_loop, &this->flush_timer_source); - return -EIO; -} - -static void sco_on_flush_timeout(struct spa_source *source) -{ - struct impl *this = source->data; - uint64_t exp = 0; - int res; - - spa_log_trace(this->log, "%p: flush on timeout", this); - - if ((res = spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp)) < 0) { - if (res != -EAGAIN) - spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); - return; - } - - if (this->transport == NULL) { - enable_flush_timer(this, false); - return; - } - - while (exp-- > 0) { - this->flush_pending = false; - flush_data(this); - } -} - -static void sco_on_timeout(struct spa_source *source) -{ - struct impl *this = source->data; - struct port *port = &this->port; - uint64_t exp, duration; - uint32_t rate; - struct spa_io_buffers *io = port->io; - uint64_t prev_time, now_time; - int status, res; - - if (this->started) { - if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { - if (res != -EAGAIN) - spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); - return; - } - } - - prev_time = this->current_time; - now_time = this->current_time = this->next_time; - - spa_log_debug(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, - now_time, now_time - prev_time); - - if (SPA_LIKELY(this->position)) { - duration = this->position->clock.target_duration; - rate = this->position->clock.target_rate.denom; - } else { - duration = 1024; - rate = 48000; - } - - this->next_time = now_time + duration * SPA_NSEC_PER_SEC / rate; - - if (SPA_LIKELY(this->clock)) { - this->clock->nsec = now_time; - this->clock->rate = this->clock->target_rate; - this->clock->position += this->clock->duration; - this->clock->duration = duration; - this->clock->rate_diff = 1.0f; - this->clock->next_nsec = this->next_time; - this->clock->delay = 0; - } - - status = this->transport_started ? SPA_STATUS_NEED_DATA : SPA_STATUS_HAVE_DATA; - - spa_log_trace(this->log, "%p: %d -> %d", this, io->status, status); - io->status = status; - io->buffer_id = SPA_ID_INVALID; - spa_node_call_ready(&this->callbacks, status); - - set_timeout(this, this->next_time); -} - -static int transport_start(struct impl *this) -{ - int res; - - /* Don't do anything if the node has already started */ - if (this->transport_started) - return 0; - if (!this->start_ready) - return -EIO; - - /* Make sure the transport is valid */ - spa_return_val_if_fail(this->transport != NULL, -EIO); - - this->following = is_following(this); - - spa_log_debug(this->log, "%p: start transport", this); - - this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, this->transport->write_mtu); - if (!this->codec_data) - return -EINVAL; - - spa_log_info(this->log, "%p: using codec %s, delay:%"PRIi64" ms", this, this->codec->description, - (int64_t)(spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC)); - - /* start socket i/o */ - if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) - goto fail; - - this->flush_timer_source.data = this; - this->flush_timer_source.fd = this->flush_timerfd; - this->flush_timer_source.func = sco_on_flush_timeout; - this->flush_timer_source.mask = SPA_IO_IN; - this->flush_timer_source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->flush_timer_source); - - /* start processing */ - this->flush_pending = false; - - /* Set the started flag */ - this->transport_started = true; - - return 0; - -fail: - if (this->codec_data) { - this->codec->deinit(this->codec_data); - this->codec_data = NULL; - } - return res; -} - -static int do_start(struct impl *this) -{ - bool do_accept; - int res; - - if (this->started) - return 0; - - spa_return_val_if_fail(this->transport, -EIO); - - this->following = is_following(this); - - this->start_ready = true; - - spa_log_debug(this->log, "%p: start following:%d", this, this->following); - - /* Do accept if Gateway; otherwise do connect for Head Unit */ - do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; - - /* acquire the socket fd (false -> connect | true -> accept) */ - if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { - this->start_ready = false; - return res; - } - - /* Add the timeout callback */ - this->source.data = this; - this->source.fd = this->timerfd; - this->source.func = sco_on_timeout; - this->source.mask = SPA_IO_IN; - this->source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->source); - - set_timers(this); - - this->started = true; - - return 0; -} - -/* Drop any buffered data remaining in the port */ -static void drop_port_output(struct impl *this) -{ - struct port *port = &this->port; - - port->write_buffer_size = 0; - port->current_buffer = NULL; - port->ready_offset = 0; - - while (!spa_list_is_empty(&port->ready)) { - struct buffer *b; - b = spa_list_first(&port->ready, struct buffer, link); - - spa_list_remove(&b->link); - b->outstanding = true; - port->io->buffer_id = b->id; - spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); - } -} - -static int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - - if (this->source.loop) - spa_loop_remove_source(this->data_loop, &this->source); - set_timeout(this, 0); - - return 0; -} - -static int do_remove_transport_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - - this->transport_started = false; - - if (this->flush_timer_source.loop) - spa_loop_remove_source(this->data_loop, &this->flush_timer_source); - enable_flush_timer(this, false); - - /* Drop queued data */ - drop_port_output(this); - - return 0; -} - -static void transport_stop(struct impl *this) -{ - if (!this->transport_started) - return; - - spa_log_trace(this->log, "sco-sink %p: transport stop", this); - - spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); - - this->buffer_size = 0; - - if (this->codec_data) { - this->codec->deinit(this->codec_data); - this->codec_data = NULL; - } -} - -static int do_stop(struct impl *this) -{ - int res; - - if (!this->started) - return 0; - - spa_log_debug(this->log, "%p: stop", this); - - this->start_ready = false; - - spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); - - transport_stop(this); - - if (this->transport) - res = spa_bt_transport_release(this->transport); - else - res = 0; - - this->started = false; - - return res; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - struct port *port; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - port = &this->port; - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - if (!port->have_format) - return -EIO; - if (port->n_buffers == 0) - return -EIO; - if ((res = do_start(this)) < 0) - return res; - break; - case SPA_NODE_COMMAND_Pause: - case SPA_NODE_COMMAND_Suspend: - if ((res = do_stop(this)) < 0) - return res; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static void emit_node_info(struct impl *this, bool full) -{ - const struct spa_dict_item hu_node_info_items[] = { - { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : "Audio/Sink" }, - { SPA_KEY_NODE_DRIVER, "true" }, - }; - - const struct spa_dict_item ag_node_info_items[] = { - { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, "Stream/Input/Audio" }, - { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : "HSP/HFP") }, - { SPA_KEY_MEDIA_ROLE, "Communication" }, - }; - bool is_ag = this->transport && - (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); - uint64_t old = full ? this->info.change_mask : 0; - - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - this->info.props = is_ag ? - &SPA_DICT_INIT_ARRAY(ag_node_info_items) : - &SPA_DICT_INIT_ARRAY(hu_node_info_items); - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_INPUT, 0, &port->info); - port->info.change_mask = old; - } -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_node_info(this, true); - emit_port_info(this, &this->port, true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *data) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); - - return 0; -} - -static int impl_node_sync(void *object, int seq) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); - - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - - struct impl *this = object; - struct port *port; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if (result.index > 0) - return 0; - if (this->transport == NULL) - return -EIO; - - if ((res = this->codec->enum_config(this->codec, - 0, NULL, 0, - id, result.index, &b, ¶m)) != 1) - return res; - break; - - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); - break; - - case SPA_PARAM_Buffers: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->quantum_limit * port->frame_size, - 16 * port->frame_size, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); - break; - - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_Latency: - switch (result.index) { - case 0: - param = spa_latency_build(&b, id, &port->latency); - break; - default: - return 0; - } - break; - - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - do_stop(this); - if (port->n_buffers > 0) { - spa_list_init(&port->ready); - port->n_buffers = 0; - } - return 0; -} - -static int port_set_format(struct impl *this, struct port *port, - uint32_t flags, - const struct spa_pod *format) -{ - int err; - - if (format == NULL) { - spa_log_debug(this->log, "clear format"); - clear_buffers(this, port); - port->have_format = false; - } else { - struct spa_audio_info info = { 0 }; - - if (!this->transport) - return -EIO; - - if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return err; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (info.info.raw.rate == 0 || - info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) - return -EINVAL; - - port->frame_size = info.info.raw.channels; - switch (info.info.raw.format) { - case SPA_AUDIO_FORMAT_S16_LE: - case SPA_AUDIO_FORMAT_S16_BE: - port->frame_size *= 2; - break; - case SPA_AUDIO_FORMAT_S24: - port->frame_size *= 3; - break; - case SPA_AUDIO_FORMAT_S24_32: - case SPA_AUDIO_FORMAT_S32: - case SPA_AUDIO_FORMAT_F32: - port->frame_size *= 4; - break; - default: - return -EINVAL; - } - - port->current_format = info; - port->have_format = true; - } - - set_latency(this, false); - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; - port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - } else { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - emit_port_info(this, port, false); - - return 0; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - struct port *port; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); - port = &this->port; - - switch (id) { - case SPA_PARAM_Format: - res = port_set_format(this, port, flags, param); - break; - case SPA_PARAM_Latency: - res = 0; - break; - default: - res = -ENOENT; - break; - } - return res; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - spa_log_debug(this->log, "use buffers %d", n_buffers); - - clear_buffers(this, port); - - if (n_buffers > 0 && !port->have_format) - return -EIO; - if (n_buffers > MAX_BUFFERS) - return -ENOSPC; - - for (i = 0; i < n_buffers; i++) { - struct buffer *b = &port->buffers[i]; - - b->buf = buffers[i]; - b->id = i; - b->outstanding = true; - - b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - if (buffers[i]->datas[0].data == NULL) { - spa_log_error(this->log, "%p: need mapped memory", this); - return -EINVAL; - } - } - port->n_buffers = n_buffers; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - case SPA_IO_RateMatch: - port->rate_match = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - return -ENOTSUP; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *port; - struct spa_io_buffers *io; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - port = &this->port; - if ((io = port->io) == NULL) - return -EIO; - - if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { - io->status = SPA_STATUS_NEED_DATA; - return SPA_STATUS_HAVE_DATA; - } - - if (!this->started || !this->transport_started) { - if (io->status != SPA_STATUS_HAVE_DATA) { - io->status = SPA_STATUS_HAVE_DATA; - io->buffer_id = SPA_ID_INVALID; - } - return SPA_STATUS_HAVE_DATA; - } - - if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { - struct buffer *b = &port->buffers[io->buffer_id]; - - if (!b->outstanding) { - spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); - io->status = -EINVAL; - return -EINVAL; - } - - spa_log_trace(this->log, "%p: queue buffer %u", this, io->buffer_id); - - spa_list_append(&port->ready, &b->link); - b->outstanding = false; - io->buffer_id = SPA_ID_INVALID; - io->status = SPA_STATUS_OK; - } - - if (this->following) { - if (this->position) { - this->current_time = this->position->clock.nsec; - } else { - struct timespec now; - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - this->current_time = SPA_TIMESPEC_TO_NSEC(&now); - } - } - - this->process_time = this->current_time; - - if (!spa_list_is_empty(&port->ready)) { - int res; - spa_log_trace(this->log, "%p: flush on process", this); - if ((res = flush_data(this)) < 0) { - io->status = res; - return SPA_STATUS_STOPPED; - } - } - - return SPA_STATUS_HAVE_DATA; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .sync = impl_node_sync, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static void transport_state_changed(void *data, - enum spa_bt_transport_state old, - enum spa_bt_transport_state state) -{ - struct impl *this = data; - - spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); - - if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) - transport_start(this); - else if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) - transport_stop(this); - - if (state == SPA_BT_TRANSPORT_STATE_ERROR) { - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_node_emit_event(&this->hooks, - spa_pod_builder_add_object(&b, - SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); - } -} - -static int do_transport_destroy(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - this->transport = NULL; - return 0; -} - -static void transport_destroy(void *data) -{ - struct impl *this = data; - spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); -} - -static const struct spa_bt_transport_events transport_events = { - SPA_VERSION_BT_TRANSPORT_EVENTS, - .state_changed = transport_state_changed, - .destroy = transport_destroy, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - struct impl *this = (struct impl *) handle; - - do_stop(this); - if (this->transport) - spa_hook_remove(&this->transport_listener); - spa_system_close(this->data_system, this->timerfd); - spa_system_close(this->data_system, this->flush_timerfd); - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - struct port *port; - const char *str; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - this = (struct impl *) handle; - - this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); - - spa_log_topic_init(this->log, &log_topic); - - if (this->data_loop == NULL) { - spa_log_error(this->log, "a data loop is needed"); - return -EINVAL; - } - if (this->data_system == NULL) { - spa_log_error(this->log, "a data system is needed"); - return -EINVAL; - } - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - spa_hook_list_init(&this->hooks); - - reset_props(&this->props); - - this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PARAMS | - SPA_NODE_CHANGE_MASK_PROPS; - this->info = SPA_NODE_INFO_INIT(); - this->info.max_input_ports = 1; - this->info.max_output_ports = 0; - this->info.flags = SPA_NODE_FLAG_RT; - this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - this->info.params = this->params; - this->info.n_params = N_NODE_PARAMS; - - port = &this->port; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.flags = SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - - port->latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); - port->latency.min_quantum = 1.0f; - port->latency.max_quantum = 1.0f; - - spa_list_init(&port->ready); - - this->quantum_limit = 8192; - - if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) - spa_atou32(str, &this->quantum_limit, 0); - - if (info && (str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) - this->is_internal = spa_atob(str); - - if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) - sscanf(str, "pointer:%p", &this->transport); - - if (this->transport == NULL || this->transport->media_codec == NULL || - this->transport->media_codec->kind != MEDIA_CODEC_HFP) { - spa_log_error(this->log, "a transport with HFP codec is needed"); - return -EINVAL; - } - - this->codec = this->transport->media_codec; - - set_latency(this, false); - - spa_bt_transport_add_listener(this->transport, - &this->transport_listener, &transport_events, this); - - this->timerfd = spa_system_timerfd_create(this->data_system, - CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - - this->flush_timerfd = spa_system_timerfd_create(this->data_system, - CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -static const struct spa_dict_item info_items[] = { - { SPA_KEY_FACTORY_AUTHOR, "Collabora Ltd. " }, - { SPA_KEY_FACTORY_DESCRIPTION, "Play bluetooth audio with hsp/hfp" }, - { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=" }, -}; - -static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); - -const struct spa_handle_factory spa_sco_sink_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_API_BLUEZ5_SCO_SINK, - &info, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; From 665a27f28139d31cc54b91b3e560323829632a17 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 8 Jun 2025 14:46:21 +0300 Subject: [PATCH 0439/1014] bluez5: replace sco-source with media-source Change media-source to use sco-io for HFP codecs. Replace sco-source with media-source. sco-source is mostly copypaste from media-source, only differed in the IO handling. --- spa/plugins/bluez5/media-source.c | 171 +++- spa/plugins/bluez5/meson.build | 1 - spa/plugins/bluez5/sco-source.c | 1582 ----------------------------- 3 files changed, 139 insertions(+), 1615 deletions(-) delete mode 100644 spa/plugins/bluez5/sco-source.c diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 5634fa3c1..6abafe054 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -133,6 +133,7 @@ struct impl { unsigned int following:1; unsigned int matching:1; unsigned int resampling:1; + unsigned int io_error:1; unsigned int is_input:1; unsigned int is_duplex:1; @@ -460,6 +461,8 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, src, src_size, NULL, NULL)) < 0) return processed; + /* TODO: check seqnum and handle PLC */ + src += processed; src_size -= processed; @@ -573,12 +576,65 @@ static void media_on_ready_read(struct spa_source *source) return; stop: + this->io_error = true; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); if (this->transport && this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); } +static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, uint64_t now) +{ + struct impl *this = userdata; + struct port *port = &this->port; + void *buf; + int32_t decoded; + uint32_t avail; + uint64_t dt; + + if (this->transport == NULL) { + spa_log_debug(this->log, "no transport, stop reading"); + goto stop; + } + + if (size_read == 0) + return 0; + + /* decode to buffer */ + buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + spa_log_trace(this->log, "read socket data size:%d, avail:%d", size_read, avail); + decoded = decode_data(this, buffer_read, size_read, buf, avail); + if (decoded < 0) { + spa_log_debug(this->log, "failed to decode data: %d", decoded); + return 0; + } + if (decoded == 0) { + spa_log_trace(this->log, "no decoded socket data"); + return 0; + } + + /* discard when not started */ + if (!this->started) + return 0; + + spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); + + dt = now - this->now; + this->now = now; + + spa_log_trace(this->log, "decoded socket data size:%d frames:%d dt:%d dms", + (int)decoded, (int)decoded/port->frame_size, + (int)(dt / 100000)); + + return 0; + +stop: + this->io_error = true; + if (this->transport && this->transport->sco_io) + spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); + return 1; +} + static int setup_matching(struct impl *this) { struct port *port = &this->port; @@ -715,12 +771,15 @@ static void update_delay_event(void *data, uint64_t count) update_transport_delay(data); } -static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, +static int do_start_sco_iso_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; - spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); + if (this->transport->sco_io) + spa_bt_sco_io_set_source_cb(this->transport->sco_io, media_sco_pull, this); + if (this->transport->iso_io) + spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); return 0; } @@ -767,8 +826,6 @@ static int transport_start(struct impl *this) if (setsockopt(this->fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) spa_log_warn(this->log, "SO_PRIORITY failed: %m"); - spa_bt_recvmsg_init(&this->recv, this->fd, this->data_system, this->log); - reset_buffers(port); spa_bt_decode_buffer_clear(&port->buffer); @@ -777,7 +834,11 @@ static int transport_start(struct impl *this) this->quantum_limit, this->quantum_limit)) < 0) return res; - if (this->is_duplex) { + if (this->codec->kind == MEDIA_CODEC_HFP) { + /* 40 ms max buffer (on top of duration) */ + spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, + port->current_format.info.raw.rate * 40 / 1000); + } else if (this->is_duplex) { /* 80 ms max extra buffer */ spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 80 / 1000); @@ -790,22 +851,39 @@ static int transport_start(struct impl *this) this->sample_count = 0; this->errqueue_count = 0; - this->source.data = this; + this->io_error = false; - this->source.fd = this->fd; - this->source.func = media_on_ready_read; - this->source.mask = SPA_IO_IN; - this->source.rmask = 0; - if ((res = spa_loop_add_source(this->data_loop, &this->source)) < 0) - spa_log_error(this->log, "%p: failed to add poll source: %s", this, - spa_strerror(res)); + if (this->codec->kind != MEDIA_CODEC_HFP) { + spa_bt_recvmsg_init(&this->recv, this->fd, this->data_system, this->log); - if (this->transport->iso_io) - spa_loop_locked(this->data_loop, do_start_iso_io, 0, NULL, 0, this); + this->source.data = this; + + this->source.fd = this->fd; + this->source.func = media_on_ready_read; + this->source.mask = SPA_IO_IN; + this->source.rmask = 0; + if ((res = spa_loop_add_source(this->data_loop, &this->source)) < 0) + spa_log_error(this->log, "%p: failed to add poll source: %s", this, + spa_strerror(res)); + } else { + spa_zero(this->source); + if (spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system) < 0) + goto fail; + } + + if (this->transport->iso_io || this->transport->sco_io) + spa_loop_locked(this->data_loop, do_start_sco_iso_io, 0, NULL, 0, this); this->transport_started = true; return 0; + +fail: + if (this->codec_data) { + this->codec->deinit(this->codec_data); + this->codec_data = NULL; + } + return -EIO; } static int do_start(struct impl *this) @@ -825,7 +903,9 @@ static int do_start(struct impl *this) spa_log_debug(this->log, "%p: transport %p acquire", this, this->transport); - if ((res = spa_bt_transport_acquire(this->transport, false)) < 0) { + + bool do_accept = (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); + if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { this->start_ready = false; return res; } @@ -861,6 +941,8 @@ static int do_remove_source(struct spa_loop *loop, spa_loop_remove_source(this->data_loop, &this->timer_source); if (this->transport && this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); + if (this->transport && this->transport->sco_io) + spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); set_timeout(this, 0); if (this->update_delay_event) { @@ -888,6 +970,8 @@ static int do_remove_transport_source(struct spa_loop *loop, spa_loop_remove_source(this->data_loop, &this->source); if (this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); + if (this->transport->sco_io) + spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); return 0; } @@ -978,6 +1062,7 @@ static void emit_node_info(struct impl *this, bool full) char latency[64]; char rate[64]; char media_name[256]; + const char *media_role = NULL; struct port *port = &this->port; spa_scnprintf( @@ -989,6 +1074,10 @@ static void emit_node_info(struct impl *this, bool full) this->codec->description ); + if (!this->is_input && this->transport && + (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) + media_role = "Communication"; + struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : @@ -997,6 +1086,7 @@ static void emit_node_info(struct impl *this, bool full) { "media.name", media_name }, { "node.rate", this->is_input ? "" : rate }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, + { SPA_KEY_MEDIA_ROLE, media_role }, }; spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); @@ -1251,7 +1341,8 @@ static int port_set_format(struct impl *this, struct port *port, port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { - case SPA_AUDIO_FORMAT_S16: + case SPA_AUDIO_FORMAT_S16_LE: + case SPA_AUDIO_FORMAT_S16_BE: port->frame_size *= 2; break; case SPA_AUDIO_FORMAT_S24: @@ -1562,7 +1653,10 @@ static void process_buffering(struct impl *this) memcpy(datas[0].data, buf, avail); - /* pad with silence */ + /* pad with silence + * + * TODO: should do PLC instead + */ if (avail < data_size) memset(SPA_PTROFF(datas[0].data, avail, void), 0, data_size - avail); @@ -1611,7 +1705,7 @@ static int produce_buffer(struct impl *this) io->buffer_id = SPA_ID_INVALID; } - if (this->transport_started && !this->source.loop) { + if (this->io_error) { io->status = -EIO; return SPA_STATUS_STOPPED; } @@ -1875,19 +1969,8 @@ impl_init(const struct spa_handle_factory *factory, this->quantum_limit = 8192; - if (info != NULL) { - if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) - spa_atou32(str, &this->quantum_limit, 0); - if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT)) != NULL) - sscanf(str, "pointer:%p", &this->transport); - if ((str = spa_dict_lookup(info, "bluez5.media-source-role")) != NULL) - this->is_input = spa_streq(str, "input"); - if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) - this->is_duplex = spa_atob(str); - if ((str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) - this->is_internal = spa_atob(str); - } - + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT)) != NULL) + sscanf(str, "pointer:%p", &this->transport); if (this->transport == NULL) { spa_log_error(this->log, "a transport is needed"); return -EINVAL; @@ -1898,6 +1981,20 @@ impl_init(const struct spa_handle_factory *factory, } this->codec = this->transport->media_codec; + if (this->transport->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + this->is_input = true; + + if (info) { + if ((str = spa_dict_lookup(info, "clock.quantum-limit"))) + spa_atou32(str, &this->quantum_limit, 0); + if ((str = spa_dict_lookup(info, "bluez5.media-source-role")) != NULL) + this->is_input = spa_streq(str, "input"); + if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) + this->is_duplex = spa_atob(str); + if ((str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) + this->is_internal = spa_atob(str); + } + if (this->is_duplex) { if (!this->codec->duplex_codec) { spa_log_error(this->log, "transport codec doesn't support duplex"); @@ -1979,3 +2076,13 @@ const struct spa_handle_factory spa_a2dp_source_factory = { impl_init, impl_enum_interface_info, }; + +/* Retained for backward compatibility: */ +const struct spa_handle_factory spa_sco_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_BLUEZ5_SCO_SOURCE, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 1e3918dd3..bf9c6374b 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -16,7 +16,6 @@ bluez5_sources = [ 'media-codecs.c', 'media-sink.c', 'media-source.c', - 'sco-source.c', 'sco-io.c', 'iso-io.c', 'quirks.c', diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c deleted file mode 100644 index 34568a764..000000000 --- a/spa/plugins/bluez5/sco-source.c +++ /dev/null @@ -1,1582 +0,0 @@ -/* Spa SCO Source */ -/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "defs.h" -#include "media-codecs.h" - -SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.source.sco"); -#undef SPA_LOG_TOPIC_DEFAULT -#define SPA_LOG_TOPIC_DEFAULT &log_topic - -#include "decode-buffer.h" - -#define DEFAULT_CLOCK_NAME "clock.system.monotonic" - -struct props { - char clock_name[64]; -}; - -#define MAX_BUFFERS 32 - -struct buffer { - uint32_t id; - unsigned int outstanding:1; - struct spa_buffer *buf; - struct spa_meta_header *h; - struct spa_list link; -}; - -struct port { - struct spa_audio_info current_format; - int frame_size; - unsigned int have_format:1; - - uint64_t info_all; - struct spa_port_info info; - struct spa_io_buffers *io; - struct spa_io_rate_match *rate_match; - struct spa_latency_info latency; -#define IDX_EnumFormat 0 -#define IDX_Meta 1 -#define IDX_IO 2 -#define IDX_Format 3 -#define IDX_Buffers 4 -#define IDX_Latency 5 -#define N_PORT_PARAMS 6 - struct spa_param_info params[N_PORT_PARAMS]; - - struct buffer buffers[MAX_BUFFERS]; - uint32_t n_buffers; - - struct spa_list free; - struct spa_list ready; - - struct spa_bt_decode_buffer buffer; -}; - -struct impl { - struct spa_handle handle; - struct spa_node node; - - struct spa_log *log; - struct spa_loop *data_loop; - struct spa_system *data_system; - - struct spa_hook_list hooks; - struct spa_callbacks callbacks; - - uint32_t quantum_limit; - - uint64_t info_all; - struct spa_node_info info; -#define IDX_PropInfo 0 -#define IDX_Props 1 -#define IDX_NODE_IO 2 -#define N_NODE_PARAMS 3 - struct spa_param_info params[N_NODE_PARAMS]; - struct props props; - - struct spa_bt_transport *transport; - struct spa_hook transport_listener; - - struct port port; - - unsigned int started:1; - unsigned int start_ready:1; - unsigned int transport_started:1; - unsigned int following:1; - unsigned int matching:1; - unsigned int resampling:1; - unsigned int io_error:1; - - unsigned int is_internal:1; - - struct spa_source timer_source; - int timerfd; - - struct spa_io_clock *clock; - struct spa_io_position *position; - - uint64_t current_time; - uint64_t next_time; - - const struct media_codec *codec; - void *codec_data; - - uint64_t now; -}; - -#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) - -static void reset_props(struct props *props) -{ - strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); -} - -static int impl_node_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct impl *this = object; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_PropInfo: - { - switch (result.index) { - default: - return 0; - } - break; - } - case SPA_PARAM_Props: - { - switch (result.index) { - default: - return 0; - } - break; - } - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int set_timeout(struct impl *this, uint64_t time) -{ - struct itimerspec ts; - ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - return spa_system_timerfd_settime(this->data_system, - this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); -} - -static int set_timers(struct impl *this) -{ - struct timespec now; - - spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); - this->next_time = SPA_TIMESPEC_TO_NSEC(&now); - - return set_timeout(this, this->following ? 0 : this->next_time); -} - -static int do_reassign_follower(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - struct port *port = &this->port; - - set_timers(this); - if (this->transport_started) - spa_bt_decode_buffer_recover(&port->buffer); - return 0; -} - -static inline bool is_following(struct impl *this) -{ - return this->position && this->clock && this->position->clock.id != this->clock->id; -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct impl *this = object; - bool following; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_IO_Clock: - this->clock = data; - if (this->clock != NULL) { - spa_scnprintf(this->clock->name, - sizeof(this->clock->name), - "%s", this->props.clock_name); - } - break; - case SPA_IO_Position: - this->position = data; - break; - default: - return -ENOENT; - } - - following = is_following(this); - if (this->started && following != this->following) { - spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); - this->following = following; - spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); - } - - return 0; -} - -static void emit_node_info(struct impl *this, bool full); - -static int apply_props(struct impl *this, const struct spa_pod *param) -{ - struct props new_props = this->props; - int changed = 0; - - if (param == NULL) { - reset_props(&new_props); - } else { - /* noop */ - } - - changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); - this->props = new_props; - return changed; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch (id) { - case SPA_PARAM_Props: - { - if (apply_props(this, param) > 0) { - this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; - this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; - emit_node_info(this, false); - } - break; - } - default: - return -ENOENT; - } - - return 0; -} - -static void reset_buffers(struct port *port) -{ - uint32_t i; - - spa_list_init(&port->free); - spa_list_init(&port->ready); - - for (i = 0; i < port->n_buffers; i++) { - struct buffer *b = &port->buffers[i]; - spa_list_append(&port->free, &b->link); - b->outstanding = false; - } -} - -static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer_id) -{ - struct buffer *b = &port->buffers[buffer_id]; - - if (b->outstanding) { - spa_log_trace(this->log, "%p: recycle buffer %u", this, buffer_id); - spa_list_append(&port->free, &b->link); - b->outstanding = false; - } -} - -/* Helper function for debugging */ -static SPA_UNUSED void hexdump_to_log(struct impl *this, uint8_t *data, size_t size) -{ - char buf[2048]; - size_t i, col = 0, pos = 0; - buf[0] = '\0'; - for (i = 0; i < size; ++i) { - int res; - res = spa_scnprintf(buf + pos, sizeof(buf) - pos, "%s%02x", - (col == 0) ? "\n\t" : " ", data[i]); - if (res < 0) - break; - pos += res; - col = (col + 1) % 16; - } - spa_log_trace(this->log, "hexdump (%d bytes):%s", (int)size, buf); -} - -static ssize_t decode_data(struct impl *this, uint8_t *src, size_t src_size, uint64_t now) -{ - struct port *port = &this->port; - uint16_t seqnum = 0; - uint32_t timestamp = 0; - size_t total = 0; - size_t written; - int res; - - res = this->codec->start_decode(this->codec_data, src, src_size, &seqnum, ×tamp); - if (res < 0) - return res; - - /* TODO: check seqnum and handle PLC */ - - spa_assert((size_t)res <= src_size); - src = SPA_PTROFF(src, res, void); - src_size -= res; - - do { - void *dst; - uint32_t dst_size; - - dst = spa_bt_decode_buffer_get_write(&port->buffer, &dst_size); - - written = 0; - res = this->codec->decode(this->codec_data, src, src_size, dst, dst_size, &written); - if (res < 0) - return res; - - spa_assert((size_t)res <= src_size); - src = SPA_PTROFF(src, res, void); - src_size -= res; - total += written; - - if (written) - spa_bt_decode_buffer_write_packet(&port->buffer, written, this->now); - } while (src_size && (res || written)); - - return total; -} - -static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read, uint64_t rx_time) -{ - struct impl *this = userdata; - struct port *port = &this->port; - ssize_t decoded; - uint64_t dt; - - /* Drop data when not started */ - if (!this->started) - return 0; - - if (this->transport == NULL) { - spa_log_debug(this->log, "no transport, stop reading"); - goto stop; - } - - /* update the current pts */ - dt = rx_time - this->now; - this->now = rx_time; - - /* handle data read from socket */ -#if 0 - hexdump_to_log(this, read_data, size_read); -#endif - - decoded = decode_data(this, read_data, size_read, this->now); - if (decoded < 0) { - spa_log_debug(this->log, "failed to decode data: %d", (int)decoded); - return 0; - } - - spa_log_trace(this->log, "read socket data size:%d decoded frames:%d dt:%d dms", - size_read, (int)decoded / port->frame_size, - (int)(dt / 100000)); - - return 0; - -stop: - this->io_error = true; - return 1; -} - -static int setup_matching(struct impl *this) -{ - struct port *port = &this->port; - - if (!this->transport_started) - port->buffer.corr = 1.0; - - if (this->position && port->rate_match) { - port->rate_match->rate = 1 / port->buffer.corr; - - this->matching = this->following; - this->resampling = this->matching || - (port->current_format.info.raw.rate != this->position->clock.target_rate.denom); - } else { - this->matching = false; - this->resampling = false; - } - - if (port->rate_match) - SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->matching); - - return 0; -} - -static int produce_buffer(struct impl *this); - -static void sco_on_timeout(struct spa_source *source) -{ - struct impl *this = source->data; - struct port *port = &this->port; - uint64_t exp, duration; - uint32_t rate; - uint64_t prev_time, now_time; - int res; - - if (this->started) { - if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { - if (res != -EAGAIN) - spa_log_warn(this->log, "error reading timerfd: %s", - spa_strerror(res)); - return; - } - } - - prev_time = this->current_time; - now_time = this->current_time = this->next_time; - - spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, - now_time, now_time - prev_time); - - if (SPA_LIKELY(this->position)) { - duration = this->position->clock.target_duration; - rate = this->position->clock.target_rate.denom; - } else { - duration = 1024; - rate = 48000; - } - - setup_matching(this); - - this->next_time = (uint64_t)(now_time + duration * SPA_NSEC_PER_SEC / port->buffer.corr / rate); - - if (SPA_LIKELY(this->clock)) { - this->clock->nsec = now_time; - this->clock->rate = this->clock->target_rate; - this->clock->position += this->clock->duration; - this->clock->duration = duration; - this->clock->rate_diff = port->buffer.corr; - this->clock->next_nsec = this->next_time; - } - - if (port->io) { - int io_status = port->io->status; - int status = produce_buffer(this); - spa_log_trace(this->log, "%p: io:%d->%d status:%d", this, io_status, port->io->status, status); - } - - spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); - - set_timeout(this, this->next_time); -} - -static int do_add_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - - spa_bt_sco_io_set_source_cb(this->transport->sco_io, sco_source_cb, this); - - return 0; -} - -static int transport_start(struct impl *this) -{ - struct port *port = &this->port; - int res; - - /* Don't do anything if the node has already started */ - if (this->transport_started) - return 0; - if (!this->start_ready) - return -EIO; - - spa_log_debug(this->log, "%p: start transport", this); - - /* Make sure the transport is valid */ - spa_return_val_if_fail (this->transport != NULL, -EIO); - - /* Reset the buffers and sample count */ - reset_buffers(port); - - spa_bt_decode_buffer_clear(&port->buffer); - if ((res = spa_bt_decode_buffer_init(&port->buffer, this->log, - port->frame_size, port->current_format.info.raw.rate, - this->quantum_limit, this->quantum_limit)) < 0) - return res; - - /* 40 ms max buffer (on top of duration) */ - spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, - port->current_format.info.raw.rate * 40 / 1000); - - /* init codec */ - this->codec_data = this->codec->init(this->codec, 0, NULL, 0, NULL, NULL, - this->transport->read_mtu); - if (!this->codec_data) { - spa_log_error(this->log, "codec init failed"); - res = -EINVAL; - goto fail; - } - - this->io_error = false; - - /* Start socket i/o */ - if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) - goto fail; - spa_loop_locked(this->data_loop, do_add_source, 0, NULL, 0, this); - - /* Set the started flag */ - this->transport_started = true; - - return 0; - -fail: - if (this->codec_data) { - this->codec->deinit(this->codec_data); - this->codec_data = NULL; - } - return res; -} - -static int do_start(struct impl *this) -{ - bool do_accept; - int res; - - if (this->started) - return 0; - - spa_return_val_if_fail(this->transport, -EIO); - - this->following = is_following(this); - - this->start_ready = true; - - spa_log_debug(this->log, "%p: start following:%d", this, this->following); - - /* Do accept if Gateway; otherwise do connect for Head Unit */ - do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; - - /* acquire the socket fd (false -> connect | true -> accept) */ - if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { - this->start_ready = false; - return res; - } - - /* Start timer */ - this->timer_source.data = this; - this->timer_source.fd = this->timerfd; - this->timer_source.func = sco_on_timeout; - this->timer_source.mask = SPA_IO_IN; - this->timer_source.rmask = 0; - spa_loop_add_source(this->data_loop, &this->timer_source); - - setup_matching(this); - - set_timers(this); - - this->started = true; - - return 0; -} - -static int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - - if (this->timer_source.loop) - spa_loop_remove_source(this->data_loop, &this->timer_source); - set_timeout(this, 0); - - return 0; -} - -static int do_remove_transport_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - - this->transport_started = false; - - if (this->transport && this->transport->sco_io) - spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); - - return 0; -} - -static void transport_stop(struct impl *this) -{ - struct port *port = &this->port; - - if (!this->transport_started) - return; - - spa_log_debug(this->log, "sco-source %p: transport stop", this); - - spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); - - spa_bt_decode_buffer_clear(&port->buffer); - - this->codec->deinit(this->codec_data); - this->codec_data = NULL; -} - -static int do_stop(struct impl *this) -{ - int res; - - if (!this->started) - return 0; - - spa_log_debug(this->log, "%p: stop", this); - - this->start_ready = false; - - spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); - - transport_stop(this); - - if (this->transport) - res = spa_bt_transport_release(this->transport); - else - res = 0; - - this->started = false; - - return res; -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct impl *this = object; - struct port *port; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - port = &this->port; - - switch (SPA_NODE_COMMAND_ID(command)) { - case SPA_NODE_COMMAND_Start: - if (!port->have_format) - return -EIO; - if (port->n_buffers == 0) - return -EIO; - - if ((res = do_start(this)) < 0) - return res; - break; - case SPA_NODE_COMMAND_Pause: - case SPA_NODE_COMMAND_Suspend: - if ((res = do_stop(this)) < 0) - return res; - break; - default: - return -ENOTSUP; - } - return 0; -} - -static void emit_node_info(struct impl *this, bool full) -{ - const struct spa_dict_item hu_node_info_items[] = { - { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : "Audio/Source" }, - { SPA_KEY_NODE_DRIVER, "true" }, - }; - const struct spa_dict_item ag_node_info_items[] = { - { SPA_KEY_DEVICE_API, "bluez5" }, - { SPA_KEY_MEDIA_CLASS, "Stream/Output/Audio" }, - { "media.name", ((this->transport && this->transport->device->name) ? - this->transport->device->name : "HSP/HFP") }, - { SPA_KEY_MEDIA_ROLE, "Communication" }, - }; - bool is_ag = this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); - uint64_t old = full ? this->info.change_mask : 0; - - if (full) - this->info.change_mask = this->info_all; - if (this->info.change_mask) { - this->info.props = is_ag ? - &SPA_DICT_INIT_ARRAY(ag_node_info_items) : - &SPA_DICT_INIT_ARRAY(hu_node_info_items); - spa_node_emit_info(&this->hooks, &this->info); - this->info.change_mask = old; - } -} - -static void emit_port_info(struct impl *this, struct port *port, bool full) -{ - uint64_t old = full ? port->info.change_mask : 0; - if (full) - port->info.change_mask = port->info_all; - if (port->info.change_mask) { - spa_node_emit_port_info(&this->hooks, - SPA_DIRECTION_OUTPUT, 0, &port->info); - port->info.change_mask = old; - } -} - -static int -impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct impl *this = object; - struct spa_hook_list save; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - emit_node_info(this, true); - emit_port_info(this, &this->port, true); - - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *data) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); - - return 0; -} - -static int impl_node_sync(void *object, int seq) -{ - struct impl *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); - - return 0; -} - -static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - return -ENOTSUP; -} - -static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - return -ENOTSUP; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - - struct impl *this = object; - struct port *port; - struct spa_pod *param; - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_result_node_params result; - uint32_t count = 0; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - result.id = id; - result.next = start; - next: - result.index = result.next++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - switch (id) { - case SPA_PARAM_EnumFormat: - if (result.index > 0) - return 0; - if (this->transport == NULL) - return -EIO; - - if ((res = this->codec->enum_config(this->codec, - 0, NULL, 0, - id, result.index, &b, ¶m)) != 1) - return res; - break; - - case SPA_PARAM_Format: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); - break; - - case SPA_PARAM_Buffers: - if (!port->have_format) - return -EIO; - if (result.index > 0) - return 0; - - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamBuffers, id, - SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( - this->quantum_limit * port->frame_size, - 16 * port->frame_size, - INT32_MAX), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); - break; - - case SPA_PARAM_Meta: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamMeta, id, - SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), - SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_IO: - switch (result.index) { - case 0: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); - break; - case 1: - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamIO, id, - SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), - SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); - break; - default: - return 0; - } - break; - - case SPA_PARAM_Latency: - switch (result.index) { - case 0: - param = spa_latency_build(&b, id, &port->latency); - break; - default: - return 0; - } - break; - - default: - return -ENOENT; - } - - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - goto next; - - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - return 0; -} - -static int clear_buffers(struct impl *this, struct port *port) -{ - do_stop(this); - if (port->n_buffers > 0) { - spa_list_init(&port->free); - spa_list_init(&port->ready); - port->n_buffers = 0; - } - return 0; -} - -static int port_set_format(struct impl *this, struct port *port, - uint32_t flags, - const struct spa_pod *format) -{ - int err; - - if (format == NULL) { - spa_log_debug(this->log, "clear format"); - clear_buffers(this, port); - port->have_format = false; - } else { - struct spa_audio_info info = { 0 }; - - if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) - return err; - - if (info.media_type != SPA_MEDIA_TYPE_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - - if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) - return -EINVAL; - - if (info.info.raw.rate == 0 || - info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) - return -EINVAL; - - port->frame_size = info.info.raw.channels; - - switch (info.info.raw.format) { - case SPA_AUDIO_FORMAT_S16_LE: - case SPA_AUDIO_FORMAT_S16_BE: - port->frame_size *= 2; - break; - case SPA_AUDIO_FORMAT_S24: - port->frame_size *= 3; - break; - case SPA_AUDIO_FORMAT_S24_32: - case SPA_AUDIO_FORMAT_S32: - case SPA_AUDIO_FORMAT_F32: - port->frame_size *= 4; - break; - default: - return -EINVAL; - } - - port->current_format = info; - port->have_format = true; - } - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; - if (port->have_format) { - port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; - port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); - port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; - } else { - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - } - emit_port_info(this, port, false); - - return 0; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct impl *this = object; - struct port *port; - int res; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); - port = &this->port; - - switch (id) { - case SPA_PARAM_Format: - res = port_set_format(this, port, flags, param); - break; - case SPA_PARAM_Latency: - res = 0; - break; - default: - res = -ENOENT; - break; - } - return res; -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, uint32_t n_buffers) -{ - struct impl *this = object; - struct port *port; - uint32_t i; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - spa_log_debug(this->log, "use buffers %d", n_buffers); - - clear_buffers(this, port); - - if (n_buffers > 0 && !port->have_format) - return -EIO; - if (n_buffers > MAX_BUFFERS) - return -ENOSPC; - - for (i = 0; i < n_buffers; i++) { - struct buffer *b = &port->buffers[i]; - struct spa_data *d = buffers[i]->datas; - - b->buf = buffers[i]; - b->id = i; - - b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - if (d[0].data == NULL) { - spa_log_error(this->log, "%p: need mapped memory", this); - return -EINVAL; - } - spa_list_append(&port->free, &b->link); - b->outstanding = false; - } - port->n_buffers = n_buffers; - - return 0; -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - void *data, size_t size) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - port = &this->port; - - switch (id) { - case SPA_IO_Buffers: - port->io = data; - break; - case SPA_IO_RateMatch: - port->rate_match = data; - break; - default: - return -ENOENT; - } - return 0; -} - -static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct impl *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_return_val_if_fail(port_id == 0, -EINVAL); - port = &this->port; - - if (port->n_buffers == 0) - return -EIO; - - if (buffer_id >= port->n_buffers) - return -EINVAL; - - recycle_buffer(this, port, buffer_id); - - return 0; -} - -static uint32_t get_samples(struct impl *this, uint32_t *result_duration) -{ - struct port *port = &this->port; - uint32_t samples, rate_denom; - uint64_t duration; - - if (SPA_LIKELY(this->position)) { - duration = this->position->clock.duration; - rate_denom = this->position->clock.rate.denom; - } else { - duration = 1024; - rate_denom = port->current_format.info.raw.rate; - } - - *result_duration = duration * port->current_format.info.raw.rate / rate_denom; - - if (SPA_LIKELY(port->rate_match) && this->resampling) - samples = port->rate_match->size; - else - samples = *result_duration; - - return samples; -} - -#define WARN_ONCE(cond, ...) \ - if (SPA_UNLIKELY(cond)) { static bool __once; if (!__once) { __once = true; spa_log_warn(__VA_ARGS__); } } - -static void process_buffering(struct impl *this) -{ - struct port *port = &this->port; - uint32_t duration; - const uint32_t samples = get_samples(this, &duration); - void *buf; - uint32_t avail; - - spa_bt_decode_buffer_process(&port->buffer, samples, duration, - this->position ? this->position->clock.rate_diff : 1.0, - this->position ? this->position->clock.next_nsec : 0); - - setup_matching(this); - - buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); - - /* copy data to buffers */ - if (!spa_list_is_empty(&port->free)) { - struct buffer *buffer; - struct spa_data *datas; - uint32_t data_size; - - buffer = spa_list_first(&port->free, struct buffer, link); - datas = buffer->buf->datas; - - data_size = samples * port->frame_size; - - WARN_ONCE(datas[0].maxsize < data_size && !this->following, - this->log, "source buffer too small (%u < %u)", - datas[0].maxsize, data_size); - - data_size = SPA_MIN(data_size, SPA_ROUND_DOWN(datas[0].maxsize, port->frame_size)); - - avail = SPA_MIN(avail, data_size); - - spa_bt_decode_buffer_read(&port->buffer, avail); - - spa_list_remove(&buffer->link); - - spa_log_trace(this->log, "dequeue %d", buffer->id); - - datas[0].chunk->offset = 0; - datas[0].chunk->size = data_size; - datas[0].chunk->stride = port->frame_size; - - memcpy(datas[0].data, buf, avail); - - /* pad with silence */ - if (avail < data_size) - memset(SPA_PTROFF(datas[0].data, avail, void), 0, data_size - avail); - - /* ready buffer if full */ - spa_log_trace(this->log, "queue %d frames:%d", buffer->id, (int)samples); - spa_list_append(&port->ready, &buffer->link); - } -} - -static int produce_buffer(struct impl *this) -{ - struct buffer *buffer; - struct port *port = &this->port; - struct spa_io_buffers *io = port->io; - - if (io == NULL) - return -EIO; - - /* Return if we already have a buffer */ - if (io->status == SPA_STATUS_HAVE_DATA && - (this->following || port->rate_match == NULL)) - return SPA_STATUS_HAVE_DATA; - - /* Recycle */ - if (io->buffer_id < port->n_buffers) { - recycle_buffer(this, port, io->buffer_id); - io->buffer_id = SPA_ID_INVALID; - } - - if (this->io_error) { - io->status = -EIO; - return SPA_STATUS_STOPPED; - } - - /* Handle buffering */ - if (this->transport_started) - process_buffering(this); - - /* Return if there are no buffers ready to be processed */ - if (spa_list_is_empty(&port->ready)) - return SPA_STATUS_OK; - - /* Get the new buffer from the ready list */ - buffer = spa_list_first(&port->ready, struct buffer, link); - spa_list_remove(&buffer->link); - buffer->outstanding = true; - - /* Set the new buffer in IO */ - io->buffer_id = buffer->id; - io->status = SPA_STATUS_HAVE_DATA; - - /* Notify we have a buffer ready to be processed */ - return SPA_STATUS_HAVE_DATA; -} - -static int impl_node_process(void *object) -{ - struct impl *this = object; - struct port *port; - struct spa_io_buffers *io; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - port = &this->port; - if ((io = port->io) == NULL) - return -EIO; - - if (!this->started || !this->transport_started) - return SPA_STATUS_OK; - - spa_log_trace(this->log, "%p status:%d", this, io->status); - - /* Return if we already have a buffer */ - if (io->status == SPA_STATUS_HAVE_DATA) - return SPA_STATUS_HAVE_DATA; - - /* Recycle */ - if (io->buffer_id < port->n_buffers) { - recycle_buffer(this, port, io->buffer_id); - io->buffer_id = SPA_ID_INVALID; - } - - /* Follower produces buffers here, driver in timeout */ - if (this->following) - return produce_buffer(this); - else - return SPA_STATUS_OK; -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .sync = impl_node_sync, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static void transport_state_changed(void *data, - enum spa_bt_transport_state old, - enum spa_bt_transport_state state) -{ - struct impl *this = data; - - spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); - - if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) - transport_start(this); - else if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) - transport_stop(this); - - if (state == SPA_BT_TRANSPORT_STATE_ERROR) { - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_node_emit_event(&this->hooks, - spa_pod_builder_add_object(&b, - SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); - } -} - -static int do_transport_destroy(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *this = user_data; - this->transport = NULL; - return 0; -} - -static void transport_destroy(void *data) -{ - struct impl *this = data; - spa_log_debug(this->log, "transport %p destroy", this->transport); - spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); -} - -static const struct spa_bt_transport_events transport_events = { - SPA_VERSION_BT_TRANSPORT_EVENTS, - .state_changed = transport_state_changed, - .destroy = transport_destroy, -}; - -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) -{ - struct impl *this; - - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); - - this = (struct impl *) handle; - - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) - *interface = &this->node; - else - return -ENOENT; - - return 0; -} - -static int impl_clear(struct spa_handle *handle) -{ - struct impl *this = (struct impl *) handle; - - do_stop(this); - if (this->transport) - spa_hook_remove(&this->transport_listener); - spa_system_close(this->data_system, this->timerfd); - spa_bt_decode_buffer_clear(&this->port.buffer); - return 0; -} - -static size_t -impl_get_size(const struct spa_handle_factory *factory, - const struct spa_dict *params) -{ - return sizeof(struct impl); -} - -static int -impl_init(const struct spa_handle_factory *factory, - struct spa_handle *handle, - const struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - struct impl *this; - struct port *port; - const char *str; - - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); - - handle->get_interface = impl_get_interface; - handle->clear = impl_clear; - - this = (struct impl *) handle; - - this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); - - spa_log_topic_init(this->log, &log_topic); - - if (this->data_loop == NULL) { - spa_log_error(this->log, "a data loop is needed"); - return -EINVAL; - } - if (this->data_system == NULL) { - spa_log_error(this->log, "a data system is needed"); - return -EINVAL; - } - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - spa_hook_list_init(&this->hooks); - - reset_props(&this->props); - - /* set the node info */ - this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | - SPA_NODE_CHANGE_MASK_PROPS | - SPA_NODE_CHANGE_MASK_PARAMS; - this->info = SPA_NODE_INFO_INIT(); - this->info.flags = SPA_NODE_FLAG_RT; - this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); - this->params[IDX_NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - this->info.params = this->params; - this->info.n_params = N_NODE_PARAMS; - - /* set the port info */ - port = &this->port; - port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | - SPA_PORT_CHANGE_MASK_PARAMS; - port->info = SPA_PORT_INFO_INIT(); - port->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS; - port->info.flags = SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL; - port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); - port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); - port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); - port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); - port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); - port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); - port->info.params = port->params; - port->info.n_params = N_PORT_PARAMS; - - port->latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - port->latency.min_quantum = 1.0f; - port->latency.max_quantum = 1.0f; - - /* Init the buffer lists */ - spa_list_init(&port->ready); - spa_list_init(&port->free); - - this->quantum_limit = 8192; - if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) - spa_atou32(str, &this->quantum_limit, 0); - - if (info && (str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) - this->is_internal = spa_atob(str); - - if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) - sscanf(str, "pointer:%p", &this->transport); - - if (this->transport == NULL || this->transport->media_codec == NULL || - this->transport->media_codec->kind != MEDIA_CODEC_HFP) { - spa_log_error(this->log, "a transport with HFP codec is needed"); - return -EINVAL; - } - - this->codec = this->transport->media_codec; - - spa_bt_transport_add_listener(this->transport, - &this->transport_listener, &transport_events, this); - - this->timerfd = spa_system_timerfd_create(this->data_system, - CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - - return 0; -} - -static const struct spa_interface_info impl_interfaces[] = { - {SPA_TYPE_INTERFACE_Node,}, -}; - -static int -impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, uint32_t *index) -{ - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); - - switch (*index) { - case 0: - *info = &impl_interfaces[*index]; - break; - default: - return 0; - } - (*index)++; - return 1; -} - -static const struct spa_dict_item info_items[] = { - { SPA_KEY_FACTORY_AUTHOR, "Collabora Ltd. " }, - { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with hsp/hfp" }, - { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=" }, -}; - -static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); - -const struct spa_handle_factory spa_sco_source_factory = { - SPA_VERSION_HANDLE_FACTORY, - SPA_NAME_API_BLUEZ5_SCO_SOURCE, - &info, - impl_get_size, - impl_init, - impl_enum_interface_info, -}; From 7fd05e7eaad80a1975b44a36c9d77edd9809f95f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 21 Jun 2025 16:13:57 +0300 Subject: [PATCH 0440/1014] bluez5: drop old SCO fragment data when sink starts Any pending SCO fragment data should be cleared when sink starts, so that we don't send out any old data. --- spa/plugins/bluez5/defs.h | 1 + spa/plugins/bluez5/media-sink.c | 1 + spa/plugins/bluez5/sco-io.c | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index eb7c858a8..45754c19a 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -610,6 +610,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io); void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time), void *userdata); int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t size); +void spa_bt_sco_io_write_start(struct spa_bt_sco_io *io); #define SPA_BT_VOLUME_ID_RX 0 #define SPA_BT_VOLUME_ID_TX 1 diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 8cf548744..7b0bafeb6 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1550,6 +1550,7 @@ static int transport_start(struct impl *this) int res; if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) goto fail; + spa_bt_sco_io_write_start(this->transport->sco_io); } if (!this->transport->iso_io && !is_asha) { diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index d9b3635f1..94e2e08b8 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -207,6 +207,11 @@ fail: return res; } +void spa_bt_sco_io_write_start(struct spa_bt_sco_io *io) +{ + /* drop fragment */ + io->write_size = 0; +} struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_system *data_system, struct spa_log *log) From fd2db174c1e866e70833ec358ba6cef668441eb5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 20 Jun 2025 16:31:16 +0200 Subject: [PATCH 0441/1014] pipewire: update global properties When the properties of an object change, update the properties on the global as well. There is no way to notify clients of a changed global but they are supposed to listen to the object specific events for that. The global properties are meant to be a snapshot at the time of enumerating the registry and can change later. --- src/pipewire/impl-client.c | 20 +++++++++-------- src/pipewire/impl-device.c | 34 +++++++++++++++------------- src/pipewire/impl-node.c | 46 ++++++++++++++++++++------------------ src/pipewire/impl-port.c | 45 ++++++++++++++++++++----------------- 4 files changed, 77 insertions(+), 68 deletions(-) diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index d46e07939..06234e2b7 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -27,6 +27,13 @@ struct impl { unsigned int registered:1; }; +static const char * const global_keys[] = { + PW_KEY_ACCESS, + PW_KEY_CLIENT_ACCESS, + PW_KEY_APP_NAME, + NULL +}; + #define pw_client_resource(r,m,v,...) pw_resource_call(r,struct pw_client_events,m,v,__VA_ARGS__) #define pw_client_resource_info(r,...) pw_client_resource(r,info,0,__VA_ARGS__) #define pw_client_resource_permissions(r,...) pw_client_resource(r,permissions,0,__VA_ARGS__) @@ -220,9 +227,11 @@ static int update_properties(struct pw_impl_client *client, const struct spa_dic pw_impl_client_emit_info_changed(client, &client->info); - if (client->global) + if (client->global) { + pw_global_update_keys(client->global, client->info.props, global_keys); spa_list_for_each(resource, &client->global->resource_list, link) pw_client_resource_info(resource, &client->info); + } client->info.change_mask = 0; @@ -238,13 +247,6 @@ static void update_busy(struct pw_impl_client *client) static int finish_register(struct pw_impl_client *client) { - static const char * const keys[] = { - PW_KEY_ACCESS, - PW_KEY_CLIENT_ACCESS, - PW_KEY_APP_NAME, - NULL - }; - struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); struct pw_impl_client *current; @@ -260,7 +262,7 @@ static int finish_register(struct pw_impl_client *client) update_busy(client); - pw_global_update_keys(client->global, client->info.props, keys); + pw_global_update_keys(client->global, client->info.props, global_keys); pw_global_register(client->global); #ifdef OLD_MEDIA_SESSION_WORKAROUND diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 71e2e266b..73af60cdf 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -27,6 +27,19 @@ struct impl { unsigned int cache_params:1; }; +static const char * const global_keys[] = { + PW_KEY_OBJECT_PATH, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_DEVICE_API, + PW_KEY_DEVICE_DESCRIPTION, + PW_KEY_DEVICE_NAME, + PW_KEY_DEVICE_NICK, + PW_KEY_MEDIA_CLASS, + NULL +}; + #define pw_device_resource(r,m,v,...) pw_resource_call(r,struct pw_device_events,m,v,__VA_ARGS__) #define pw_device_resource_info(r,...) pw_device_resource(r,info,0,__VA_ARGS__) #define pw_device_resource_param(r,...) pw_device_resource(r,param,0,__VA_ARGS__) @@ -581,19 +594,6 @@ SPA_EXPORT int pw_impl_device_register(struct pw_impl_device *device, struct pw_properties *properties) { - static const char * const keys[] = { - PW_KEY_OBJECT_PATH, - PW_KEY_MODULE_ID, - PW_KEY_FACTORY_ID, - PW_KEY_CLIENT_ID, - PW_KEY_DEVICE_API, - PW_KEY_DEVICE_DESCRIPTION, - PW_KEY_DEVICE_NAME, - PW_KEY_DEVICE_NICK, - PW_KEY_MEDIA_CLASS, - NULL - }; - struct pw_context *context = device->context; struct object_data *od; @@ -619,7 +619,7 @@ int pw_impl_device_register(struct pw_impl_device *device, pw_global_get_serial(device->global)); device->info.props = &device->properties->dict; - pw_global_update_keys(device->global, device->info.props, keys); + pw_global_update_keys(device->global, device->info.props, global_keys); pw_impl_device_emit_initialized(device); @@ -668,10 +668,12 @@ static void emit_info_changed(struct pw_impl_device *device) pw_impl_device_emit_info_changed(device, &device->info); - if (device->global) + if (device->global) { + if (device->info.change_mask & PW_DEVICE_CHANGE_MASK_PROPS) + pw_global_update_keys(device->global, device->info.props, global_keys); spa_list_for_each(resource, &device->global->resource_list, link) pw_device_resource_info(resource, &device->info); - + } device->info.change_mask = 0; } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index b30b99f8b..822c86b21 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -55,6 +55,27 @@ struct impl { char *sync_group; }; +static const char * const global_keys[] = { + PW_KEY_OBJECT_PATH, + PW_KEY_MODULE_ID, + PW_KEY_FACTORY_ID, + PW_KEY_CLIENT_ID, + PW_KEY_CLIENT_API, + PW_KEY_DEVICE_ID, + PW_KEY_PRIORITY_SESSION, + PW_KEY_PRIORITY_DRIVER, + PW_KEY_APP_NAME, + PW_KEY_NODE_DESCRIPTION, + PW_KEY_NODE_NAME, + PW_KEY_NODE_NICK, + PW_KEY_NODE_SESSION, + PW_KEY_MEDIA_CLASS, + PW_KEY_MEDIA_TYPE, + PW_KEY_MEDIA_CATEGORY, + PW_KEY_MEDIA_ROLE, + NULL +}; + #define pw_node_resource(r,m,v,...) pw_resource_call(r,struct pw_node_events,m,v,__VA_ARGS__) #define pw_node_resource_info(r,...) pw_node_resource(r,info,0,__VA_ARGS__) #define pw_node_resource_param(r,...) pw_node_resource(r,param,0,__VA_ARGS__) @@ -357,6 +378,8 @@ static void emit_info_changed(struct pw_impl_node *node, bool flags_changed) if (node->global && node->info.change_mask != 0) { struct pw_resource *resource; + if (node->info.change_mask & PW_NODE_CHANGE_MASK_PROPS) + pw_global_update_keys(node->global, node->info.props, global_keys); spa_list_for_each(resource, &node->global->resource_list, link) pw_node_resource_info(resource, &node->info); } @@ -929,27 +952,6 @@ SPA_EXPORT int pw_impl_node_register(struct pw_impl_node *this, struct pw_properties *properties) { - static const char * const keys[] = { - PW_KEY_OBJECT_PATH, - PW_KEY_MODULE_ID, - PW_KEY_FACTORY_ID, - PW_KEY_CLIENT_ID, - PW_KEY_CLIENT_API, - PW_KEY_DEVICE_ID, - PW_KEY_PRIORITY_SESSION, - PW_KEY_PRIORITY_DRIVER, - PW_KEY_APP_NAME, - PW_KEY_NODE_DESCRIPTION, - PW_KEY_NODE_NAME, - PW_KEY_NODE_NICK, - PW_KEY_NODE_SESSION, - PW_KEY_MEDIA_CLASS, - PW_KEY_MEDIA_TYPE, - PW_KEY_MEDIA_CATEGORY, - PW_KEY_MEDIA_ROLE, - NULL - }; - struct pw_context *context = this->context; struct pw_impl_port *port; @@ -985,7 +987,7 @@ int pw_impl_node_register(struct pw_impl_node *this, pw_global_get_serial(this->global)); this->info.props = &this->properties->dict; - pw_global_update_keys(this->global, &this->properties->dict, keys); + pw_global_update_keys(this->global, &this->properties->dict, global_keys); pw_impl_node_initialized(this); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 0bcff9f4c..f5fb70997 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -42,6 +42,25 @@ struct impl { unsigned int cache_params:1; }; +static const char * const global_keys[] = { + PW_KEY_OBJECT_PATH, + PW_KEY_FORMAT_DSP, + PW_KEY_NODE_ID, + PW_KEY_AUDIO_CHANNEL, + PW_KEY_PORT_ID, + PW_KEY_PORT_NAME, + PW_KEY_PORT_DIRECTION, + PW_KEY_PORT_MONITOR, + PW_KEY_PORT_PHYSICAL, + PW_KEY_PORT_TERMINAL, + PW_KEY_PORT_CONTROL, + PW_KEY_PORT_ALIAS, + PW_KEY_PORT_EXTRA, + PW_KEY_PORT_IGNORE_LATENCY, + PW_KEY_PORT_GROUP, + NULL +}; + #define pw_port_resource(r,m,v,...) pw_resource_call(r,struct pw_port_events,m,v,__VA_ARGS__) #define pw_port_resource_info(r,...) pw_port_resource(r,info,0,__VA_ARGS__) #define pw_port_resource_param(r,...) pw_port_resource(r,param,0,__VA_ARGS__) @@ -70,9 +89,12 @@ static void emit_info_changed(struct pw_impl_port *port) if (port->node) pw_impl_node_emit_port_info_changed(port->node, port, &port->info); - if (port->global) + if (port->global) { + if (port->info.change_mask & PW_PORT_CHANGE_MASK_PROPS) + pw_global_update_keys(port->global, port->info.props, global_keys); spa_list_for_each(resource, &port->global->resource_list, link) pw_port_resource_info(resource, &port->info); + } port->info.change_mask = 0; } @@ -1115,25 +1137,6 @@ static const struct pw_global_events global_events = { int pw_impl_port_register(struct pw_impl_port *port, struct pw_properties *properties) { - static const char * const keys[] = { - PW_KEY_OBJECT_PATH, - PW_KEY_FORMAT_DSP, - PW_KEY_NODE_ID, - PW_KEY_AUDIO_CHANNEL, - PW_KEY_PORT_ID, - PW_KEY_PORT_NAME, - PW_KEY_PORT_DIRECTION, - PW_KEY_PORT_MONITOR, - PW_KEY_PORT_PHYSICAL, - PW_KEY_PORT_TERMINAL, - PW_KEY_PORT_CONTROL, - PW_KEY_PORT_ALIAS, - PW_KEY_PORT_EXTRA, - PW_KEY_PORT_IGNORE_LATENCY, - PW_KEY_PORT_GROUP, - NULL - }; - struct pw_impl_node *node = port->node; if (node == NULL || node->global == NULL) @@ -1158,7 +1161,7 @@ int pw_impl_port_register(struct pw_impl_port *port, pw_global_get_serial(port->global)); port->info.props = &port->properties->dict; - pw_global_update_keys(port->global, &port->properties->dict, keys); + pw_global_update_keys(port->global, &port->properties->dict, global_keys); pw_impl_port_emit_initialized(port); From 220b0376837edb58eb972fd63f79ca5792dad1b0 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 6 Jun 2025 11:02:40 +0530 Subject: [PATCH 0442/1014] pulse-server: Implement stream suspended callback --- .../module-protocol-pulse/pulse-server.c | 9 +++++++ src/modules/module-protocol-pulse/stream.c | 25 +++++++++++++++++++ src/modules/module-protocol-pulse/stream.h | 1 + 3 files changed, 35 insertions(+) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 12ffed1c1..e8cf29d79 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1127,6 +1127,15 @@ static void stream_state_changed(void *data, enum pw_stream_state old, break; } + /* Don't emit suspended if we are creating a corked stream, as that will have a quick + * RUNNING/SUSPENDED transition for initial negotiation */ + if (stream->create_tag == SPA_ID_INVALID || !stream->corked) { + if (old == PW_STREAM_STATE_PAUSED && state == PW_STREAM_STATE_STREAMING) + stream_send_suspended(stream, false); + if (old == PW_STREAM_STATE_STREAMING && state == PW_STREAM_STATE_PAUSED) + stream_send_suspended(stream, true); + } + if (destroy_stream) { pw_work_queue_add(impl->work_queue, stream, 0, do_destroy_stream, NULL); diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 9426428d2..7d0ac0cf4 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -314,6 +314,31 @@ int stream_send_started(struct stream *stream) return client_queue_message(client, reply); } +int stream_send_suspended(struct stream *stream, bool suspended) +{ + struct client *client = stream->client; + struct impl *impl = client->impl; + struct message *reply; + uint32_t command; + + pw_log_debug("client %p [%s]: stream %p SUSPENDED channel:%u", + client, client->name, stream, stream->channel); + + command = stream->direction == PW_DIRECTION_OUTPUT ? + COMMAND_PLAYBACK_STREAM_SUSPENDED : + COMMAND_RECORD_STREAM_SUSPENDED; + + reply = message_alloc(impl, -1, 0); + message_put(reply, + TAG_U32, command, + TAG_U32, -1, + TAG_U32, stream->channel, + TAG_BOOLEAN, suspended, + TAG_INVALID); + + return client_queue_message(client, reply); +} + int stream_send_request(struct stream *stream) { struct client *client = stream->client; diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index 64ecea680..3d017f391 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -114,6 +114,7 @@ int stream_send_underflow(struct stream *stream, int64_t offset); int stream_send_overflow(struct stream *stream); int stream_send_killed(struct stream *stream); int stream_send_started(struct stream *stream); +int stream_send_suspended(struct stream *stream, bool suspended); int stream_send_request(struct stream *stream); int stream_update_minreq(struct stream *stream, uint32_t minreq); int stream_send_moved(struct stream *stream, uint32_t peer_index, const char *peer_name); From 4b236f827476f53b3761741f2e3a7946737554b8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 23 Jun 2025 11:34:43 +0200 Subject: [PATCH 0443/1014] stream: fix a typo --- src/pipewire/stream.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index 8e8ba0950..ea884337b 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -296,7 +296,7 @@ struct pw_stream_control { * stream. * * pw_time.ticks gives a monotonic increasing counter of the time in the graph - * driver. I can be used to generate a timetime to schedule samples as well + * driver. I can be used to generate a timeline to schedule samples as well * as detect discontinuities in the timeline caused by xruns. * * pw_time.delay is expressed as pw_time.rate, the time domain of the graph. This From 43441a4d697bd7cb32a7f8bf57dad27c5844c222 Mon Sep 17 00:00:00 2001 From: Elliot Chen Date: Sat, 21 Jun 2025 17:35:48 +0900 Subject: [PATCH 0444/1014] pipewiresrc: fix sending last buffer failure if waiting operation exits in advance --- src/gst/gstpipewiresrc.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 0e9cab853..686ceb5de 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1512,6 +1512,8 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) GstBuffer *buf; gboolean update_time = FALSE, timeout = FALSE; GstCaps *caps = NULL; + struct timespec abstime = { 0, }; + bool have_abstime = false; pwsrc = GST_PIPEWIRE_SRC (psrc); @@ -1555,13 +1557,11 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) update_time = TRUE; GST_LOG_OBJECT (pwsrc, "EOS, send last buffer"); break; - } else if (timeout) { - if (pwsrc->last_buffer != NULL) { - update_time = TRUE; - buf = gst_buffer_ref(pwsrc->last_buffer); - GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer"); - break; - } + } else if (timeout && pwsrc->last_buffer != NULL) { + update_time = TRUE; + buf = gst_buffer_ref(pwsrc->last_buffer); + GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer"); + break; } else { buf = dequeue_buffer (pwsrc); GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf); @@ -1573,9 +1573,13 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) } timeout = FALSE; if (pwsrc->keepalive_time > 0) { - struct timespec abstime; - pw_thread_loop_get_time(pwsrc->stream->core->loop, &abstime, - pwsrc->keepalive_time * SPA_NSEC_PER_MSEC); + if (!have_abstime) { + /* Record the time we want to timeout at once, for this loop -- the loop might get unrelated signal()s, + * and we don't want the keepalive time to get reset by that */ + pw_thread_loop_get_time(pwsrc->stream->core->loop, &abstime, + pwsrc->keepalive_time * SPA_NSEC_PER_MSEC); + have_abstime = TRUE; + } if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) == -ETIMEDOUT) timeout = TRUE; } else { From b2edefbc4ca8a707df1e3d16e0fe028c4f25d0f8 Mon Sep 17 00:00:00 2001 From: yay me <1228371-yayme@users.noreply.gitlab.freedesktop.org> Date: Mon, 23 Jun 2025 10:59:45 +0000 Subject: [PATCH 0445/1014] po: Add Arabic translation --- po/LINGUAS | 1 + po/ar.po | 797 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 798 insertions(+) create mode 100644 po/ar.po diff --git a/po/LINGUAS b/po/LINGUAS index a6b999f41..8ebdbea2e 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1,4 +1,5 @@ af +ar as be bg diff --git a/po/ar.po b/po/ar.po new file mode 100644 index 000000000..348a55f3f --- /dev/null +++ b/po/ar.po @@ -0,0 +1,797 @@ +# Arabic translation of PipeWire +# This file is distributed under the same license as the pipewire package. +# +# SPDX-FileCopyrightText: 2025 r +msgid "" +msgstr "" +"Project-Id-Version: pipewire\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/" +"new\n" +"POT-Creation-Date: 2024-02-25 03:43+0300\n" +"PO-Revision-Date: 2025-06-22 13:09+0200\n" +"Last-Translator: r \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && " +"n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 25.04.0\n" + +#: src/daemon/pipewire.c:26 +#, c-format +msgid "" +"%s [options]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -c, --config Load config (Default %s)\n" +msgstr "" +"%s [خيارات]\n" +" -h, --help إظهار هذه المساعدة\n" +" --version إظهار الإصدار\n" +" -c, --config تحميل التضبيط (الافتراضي %s)\n" + +#: src/daemon/pipewire.desktop.in:4 +msgid "PipeWire Media System" +msgstr "نظام الوسائط بايب‌واير" + +#: src/daemon/pipewire.desktop.in:5 +msgid "Start the PipeWire Media System" +msgstr "ابدأ نظام الوسائط بايب‌واير" + +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 +#, c-format +msgid "Tunnel to %s%s%s" +msgstr "نفّق إلى %s%s%s" + +#: src/modules/module-fallback-sink.c:40 +msgid "Dummy Output" +msgstr "إخراج وهمي" + +#: src/modules/module-pulse-tunnel.c:774 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "نفّق ل %s@%s" + +#: src/modules/module-zeroconf-discover.c:315 +msgid "Unknown device" +msgstr "جهاز غير معروف" + +#: src/modules/module-zeroconf-discover.c:327 +#, c-format +msgid "%s on %s@%s" +msgstr "%s على %s@%s" + +#: src/modules/module-zeroconf-discover.c:331 +#, c-format +msgid "%s on %s" +msgstr "%s على %s" + +#: src/tools/pw-cat.c:991 +#, c-format +msgid "" +"%s [options] [|-]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -v, --verbose Enable verbose operations\n" +"\n" +msgstr "" +"%s [خيارات] [|-]\n" +" -h, --help إظهار هذه المساعدة\n" +" --version إظهار الإصدار\n" +" -v, --verbose تمكين العمليات المطولة\n" +"\n" + +#: src/tools/pw-cat.c:998 +#, c-format +msgid "" +" -R, --remote Remote daemon name\n" +" --media-type Set media type (default %s)\n" +" --media-category Set media category (default %s)\n" +" --media-role Set media role (default %s)\n" +" --target Set node target serial or name " +"(default %s)\n" +" 0 means don't link\n" +" --latency Set node latency (default %s)\n" +" Xunit (unit = s, ms, us, ns)\n" +" or direct samples (256)\n" +" the rate is the one of the source " +"file\n" +" -P --properties Set node properties\n" +"\n" +msgstr "" +"-R, --remote اسم العملية الخفية البعيدة\n" +" --media-type تعيين نوع الوسائط (الافتراضي %s)\n" +" --media-category تعيين فئة الوسائط (الافتراضي %s)\n" +" --media-role تعيين دور الوسائط (الافتراضي %s)\n" +" --target تعيين المسلسل أو اسم هدف العُقدة " +"\n (الافتراضي %s)" +" 0 يعني عدم الربط\n" +" --latency تعيين زمن انتقال العُقدة (الافتراضي %s" +")\n" +" وحدة X (الوحدة = s, ms, us, ns)\n" +" أو عينات مباشرة (256)\n" +" المعدل هو معدل ملف المصدر\n" +" -P --properties تعيين خصائص العُقدة\n" +"\n" + +#: src/tools/pw-cat.c:1016 +#, c-format +msgid "" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" +" --channel-map Channel map\n" +" one of: \"stereo\", " +"\"surround-51\",... or\n" +" comma separated list of channel " +"names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) " +"(default %s)\n" +" --volume Stream volume 0-1.0 (default %.3f)\n" +" -q --quality Resampler quality (0 - 15) (default " +"%d)\n" +"\n" +msgstr "" +"--rate معدل العينة (مطلوب للتسجيل) (الافتراضي %u)\n" +" --channels عدد القنوات (مطلوب للتسجيل) (الافتراضي" +" %u)\n" +" --channel-map خريطة القنوات\n" +" أحد الخيارات: \"مِجساميّ\", \"محيط" +"ي-51\",... أو\n" +" قائمة بأسماء القنوات مفصولة بفاصلة" +": مثال \"FL,FR\"\n" +" --format تنسيق العينة %s (مطلوب للتسجيل) (الافت" +"راضي %s)\n" +" --volume حجم البث 0-1.0 (الافتراضي %.3f)\n" +" -q --quality جودة إعادة التشكيل (0 - 15) (الافتراضي" +" %d)\n" +"\n" + +#: src/tools/pw-cat.c:1033 +msgid "" +" -p, --playback Playback mode\n" +" -r, --record Recording mode\n" +" -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" +"\n" +msgstr "" +"-p, --playback وضع التشغيل\n" +" -r, --record وضع التسجيل\n" +" -m, --midi وضع Midi\n" +" -d, --dsd وضع DSD\n" +" -o, --encoded الوضع المرمّز\n" +"\n" + +#: src/tools/pw-cli.c:2252 +#, c-format +msgid "" +"%s [options] [command]\n" +" -h, --help Show this help\n" +" --version Show version\n" +" -d, --daemon Start as daemon (Default false)\n" +" -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" +"\n" +msgstr "" +"%s [خيارات] [أمر]\n" +" -h, --help إظهار هذه المساعدة\n" +" --version إظهار الإصدار\n" +" -d, --daemon بدء كخفي (افتراضي خطأ)\n" +" -r, --remote اسم الخفي البعيد\n" +" -m, --monitor مراقبة النشاط\n" +"\n" + +#: spa/plugins/alsa/acp/acp.c:327 +msgid "Pro Audio" +msgstr "صوت احترافي" + +#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1701 +msgid "Off" +msgstr "معطل" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 +msgid "Input" +msgstr "الإدخال" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 +msgid "Docking Station Input" +msgstr "إدخال محطة الإرساء" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 +msgid "Docking Station Microphone" +msgstr "ميكروفون محطة الإرساء" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 +msgid "Docking Station Line In" +msgstr "مدخل خطي محط الارساء" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 +msgid "Line In" +msgstr "مدخل خطي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1989 +msgid "Microphone" +msgstr "ميكروفون" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 +msgid "Front Microphone" +msgstr "ميكروفون أمامي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 +msgid "Rear Microphone" +msgstr "ميكروفون خلفي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 +msgid "External Microphone" +msgstr "ميكروفون خارجي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 +msgid "Internal Microphone" +msgstr "ميكروفون داخلي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 +msgid "Radio" +msgstr "راديو" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 +msgid "Video" +msgstr "مرئيّ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 +msgid "Automatic Gain Control" +msgstr "التحكم التلقائي في الكسب" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 +msgid "No Automatic Gain Control" +msgstr "دون التحكم التلقائي في الكسب" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 +msgid "Boost" +msgstr "تعزيز" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 +msgid "No Boost" +msgstr "دون تعزيز" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 +msgid "Amplifier" +msgstr "" +"" +"مُضَخِّم" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 +msgid "No Amplifier" +msgstr "دون مُضَخِّم" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 +msgid "Bass Boost" +msgstr "تعزيز القرار" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 +msgid "No Bass Boost" +msgstr "دون تعزيز القرار" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1995 +msgid "Speaker" +msgstr "مكبر صوت" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 +msgid "Headphones" +msgstr "سماعات الأذن" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +msgid "Analog Input" +msgstr "إدخال التناظري" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 +msgid "Dock Microphone" +msgstr "ميكروفون محطة الارساء" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 +msgid "Headset Microphone" +msgstr "ميكروفون سماعة الرأس" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 +msgid "Analog Output" +msgstr "إخراج تناظري" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 +msgid "Headphones 2" +msgstr "سماعات الأذن 2" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 +msgid "Headphones Mono Output" +msgstr "سماعات الأذن أحادية الإخراج" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 +msgid "Line Out" +msgstr "مخرج خطي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 +msgid "Analog Mono Output" +msgstr "إخراج أحادي تناظري" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 +msgid "Speakers" +msgstr "مكبرات الصوت" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 +msgid "HDMI / DisplayPort" +msgstr "HDMI / ديسبلاي بورت" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 +msgid "Digital Output (S/PDIF)" +msgstr "إخراج الرقمي (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 +msgid "Digital Input (S/PDIF)" +msgstr "إدخال الرقمي (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 +msgid "Multichannel Input" +msgstr "إدخال متعدد القنوات" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 +msgid "Multichannel Output" +msgstr "إخراج متعدد القنوات" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 +msgid "Game Output" +msgstr "إخراج اللعبة" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 +msgid "Chat Output" +msgstr "إخراج المحادثة" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 +msgid "Chat Input" +msgstr "إدخال المحادثة" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 +msgid "Virtual Surround 7.1" +msgstr "محيطي تخيلي 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4456 +msgid "Analog Mono" +msgstr "تناظري أحادي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4457 +msgid "Analog Mono (Left)" +msgstr "تناظري أحادي (يسار)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 +msgid "Analog Mono (Right)" +msgstr "تناظري أحادي (يمين)" + +#. Note: Not translated to "Analog Stereo Input", because the source +#. * name gets "Input" appended to it automatically, so adding "Input" +#. * here would lead to the source name to become "Analog Stereo Input +#. * Input". The same logic applies to analog-stereo-output, +#. * multichannel-input and multichannel-output. +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 +#: spa/plugins/alsa/acp/alsa-mixer.c:4467 +#: spa/plugins/alsa/acp/alsa-mixer.c:4468 +msgid "Analog Stereo" +msgstr "مِجْساميّ تناظري" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 +msgid "Mono" +msgstr "أحادي" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +msgid "Stereo" +msgstr "مِجْساميّ" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +#: spa/plugins/bluez5/bluez5-device.c:1977 +msgid "Headset" +msgstr "سماعة الرأس" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 +msgid "Speakerphone" +msgstr "مكبر صوت الجوّال" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +msgid "Multichannel" +msgstr "متعدد القنوات" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +msgid "Analog Surround 2.1" +msgstr "تناظري محيطي 2.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +msgid "Analog Surround 3.0" +msgstr "تناظري محيطي 3.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +msgid "Analog Surround 3.1" +msgstr "تناظري محيطي 3.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +msgid "Analog Surround 4.0" +msgstr "تناظري محيطي 4.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 +msgid "Analog Surround 4.1" +msgstr "تناظري محيطي 4.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 +msgid "Analog Surround 5.0" +msgstr "تناظري محيطي 5.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 +msgid "Analog Surround 5.1" +msgstr "تناظري محيطي 5.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 +msgid "Analog Surround 6.0" +msgstr "تناظري محيطي 6.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 +msgid "Analog Surround 6.1" +msgstr "تناظري محيطي 6.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +msgid "Analog Surround 7.0" +msgstr "تناظري محيطي 7.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +msgid "Analog Surround 7.1" +msgstr "تناظري محيطي 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +msgid "Digital Stereo (IEC958)" +msgstr "مِجْساميّ رقمي (IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +msgid "Digital Surround 4.0 (IEC958/AC3)" +msgstr "محيطي رقمي 4.0 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +msgid "Digital Surround 5.1 (IEC958/AC3)" +msgstr "محيطي رقمي 5.1 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +msgid "Digital Surround 5.1 (IEC958/DTS)" +msgstr "محيطي رقمي 5.1 (IEC958/DTS)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +msgid "Digital Stereo (HDMI)" +msgstr "مِجْساميّ رقمي (HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +msgid "Digital Surround 5.1 (HDMI)" +msgstr "محيطي رقمي 5.1 (HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +msgid "Chat" +msgstr "محادثة" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +msgid "Game" +msgstr "لعبة" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4625 +msgid "Analog Mono Duplex" +msgstr "تناظري أحادي مزدوج" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4626 +msgid "Analog Stereo Duplex" +msgstr "مِجْساميّ تناظري مزدوج" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +msgid "Digital Stereo Duplex (IEC958)" +msgstr "مِجْساميّ رقمي مزدوج (IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 +msgid "Multichannel Duplex" +msgstr "مزدوج متعدد القنوات" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 +msgid "Stereo Duplex" +msgstr "مِجساميّ مزدوج" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 +msgid "Mono Chat + 7.1 Surround" +msgstr "محادثة أحادية + محيطي 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4733 +#, c-format +msgid "%s Output" +msgstr "إخراج %s" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4741 +#, c-format +msgid "%s Input" +msgstr "إدخال %s" + +#: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 +#, c-format +msgid "" +"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " +"ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايت " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[1] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[2] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[3] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[4] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[5] "" +"أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1286 +#, c-format +msgid "" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايت " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[1] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[2] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[3] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[4] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[5] "" +"أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " +"(%s%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1333 +#, c-format +msgid "" +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " +"%lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr "" +"أرجعت الدالة snd_pcm_avail_delay() قيمًا غريبة: التأخير %lu أقل من avail " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1376 +#, c-format +msgid "" +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgid_plural "" +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " +"(%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." +msgstr[0] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايت " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[1] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[2] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[3] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[4] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." +msgstr[5] "" +"أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " +"(%lu مللي ثانية).\n" +"من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" +"كلة " +"لمطوري ALSA." + +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(غير صالح)" + +#: spa/plugins/alsa/acp/compat.c:193 +msgid "Built-in Audio" +msgstr "صوت مدمج" + +#: spa/plugins/alsa/acp/compat.c:198 +msgid "Modem" +msgstr "مودم" + +#: spa/plugins/bluez5/bluez5-device.c:1712 +msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" +msgstr "بوابة الصوت (مصدر A2DP و HSP/HFP AG)" + +#: spa/plugins/bluez5/bluez5-device.c:1760 +#, c-format +msgid "High Fidelity Playback (A2DP Sink, codec %s)" +msgstr "تشغيل عالي الدقة (A2DP Sink, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1763 +#, c-format +msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgstr "مزدوج عالي الدقة (مصدر A2DP/Sink, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1771 +msgid "High Fidelity Playback (A2DP Sink)" +msgstr "تشغيل عالي الدقة (A2DP Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1773 +msgid "High Fidelity Duplex (A2DP Source/Sink)" +msgstr "مزدوج عالي الدقة (مصدر A2DP/Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1823 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "تشغيل عالي الدقة (BAP Sink, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1828 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "إدخال عالي الدقة (مصدر BAP, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1832 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "مزدوج عالي الدقة (مصدر BAP/Sink, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1841 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "تشغيل عالي الدقة (BAP Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1845 +msgid "High Fidelity Input (BAP Source)" +msgstr "إدخال عالي الدقة (مصدر BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1848 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "مزدوج عالي الدقة (مصدر BAP/Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:1897 +#, c-format +msgid "Headset Head Unit (HSP/HFP, codec %s)" +msgstr "وحدة رأس سماعة الرأس (HSP/HFP, codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1978 +#: spa/plugins/bluez5/bluez5-device.c:1983 +#: spa/plugins/bluez5/bluez5-device.c:1990 +#: spa/plugins/bluez5/bluez5-device.c:1996 +#: spa/plugins/bluez5/bluez5-device.c:2002 +#: spa/plugins/bluez5/bluez5-device.c:2008 +#: spa/plugins/bluez5/bluez5-device.c:2014 +#: spa/plugins/bluez5/bluez5-device.c:2020 +#: spa/plugins/bluez5/bluez5-device.c:2026 +msgid "Handsfree" +msgstr "دون استخدام اليدين" + +#: spa/plugins/bluez5/bluez5-device.c:1984 +msgid "Handsfree (HFP)" +msgstr "دون استخدام اليدين (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:2001 +msgid "Headphone" +msgstr "سماعة الأذن" + +#: spa/plugins/bluez5/bluez5-device.c:2007 +msgid "Portable" +msgstr "محمول" + +#: spa/plugins/bluez5/bluez5-device.c:2013 +msgid "Car" +msgstr "سيارة" + +#: spa/plugins/bluez5/bluez5-device.c:2019 +msgid "HiFi" +msgstr "HiFi" + +#: spa/plugins/bluez5/bluez5-device.c:2025 +msgid "Phone" +msgstr "جوّال" + +#: spa/plugins/bluez5/bluez5-device.c:2032 +msgid "Bluetooth" +msgstr "بلوتوث" + +#: spa/plugins/bluez5/bluez5-device.c:2033 +msgid "Bluetooth (HFP)" +msgstr "بلوتوث (HFP)" From 5c8e1ab7b619c7e2bfb3c2d6cd66b81f3403112d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 13:43:17 +0200 Subject: [PATCH 0446/1014] jack: only update port name for other ports Don't try to check for renames of our own port, we cause that ourselves. Also use the name of the port to find our ports. --- pipewire-jack/src/pipewire-jack.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 588689de1..511f44606 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -3939,7 +3939,7 @@ static void registry_event_global(void *data, uint32_t id, o = NULL; if (node_id == c->node_id) { - snprintf(tmp, sizeof(tmp), "%s:%s", c->name, str); + snprintf(tmp, sizeof(tmp), "%s:%s", c->name, name); o = find_port_by_name(c, tmp); if (o != NULL) pw_log_info("%p: %s found our port %p", c, tmp, o); @@ -4000,7 +4000,8 @@ static void registry_event_global(void *data, uint32_t id, o->port.system_id+1); } - update_port_name(o, name); + if (node_id != c->node_id) + update_port_name(o, name); pw_log_debug("%p: %p add port %d name:%s %d", c, o, id, o->port.name, type_id); From fa52a596f4bd7299b56b628a6bc43758aa1ebe8c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 13:46:08 +0200 Subject: [PATCH 0447/1014] audioconvert: undef the right function --- spa/plugins/audioconvert/peaks-ops.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h index 1629e8edf..0d2b661e8 100644 --- a/spa/plugins/audioconvert/peaks-ops.h +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -49,4 +49,5 @@ DEFINE_MIN_MAX_FUNCTION(sse); DEFINE_ABS_MAX_FUNCTION(sse); #endif -#undef DEFINE_FUNCTION +#undef DEFINE_MIN_MAX_FUNCTION +#undef DEFINE_ABS_MAX_FUNCTION From d093402d976fe8ec761e50e32a70673f1defb1e7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 13:46:30 +0200 Subject: [PATCH 0448/1014] filter-graph: compare with float to avoid conversion --- spa/plugins/filter-graph/builtin_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index 9d293acdb..fad14f879 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -2823,7 +2823,7 @@ static void zeroramp_run(void * Instance, unsigned long SampleCount) if (impl->mode == 0) { /* normal mode, finding gaps */ out[n] = in[n]; - if (in[n] == 0.0) { + if (in[n] == 0.0f) { if (++impl->count == gap) { /* we found gap zeroes, fade out last * sample and go into zero mode */ From bcb9ff20fd56936b84584b541766a93b8ceec1d1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 18:12:42 +0200 Subject: [PATCH 0449/1014] audioconvert: mark output as not empty when draining When we are draining, we use an empty input buffer but then we push out the remaining samples out of filters and we can't assume they are empty. --- spa/plugins/audioconvert/audioconvert.c | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index f5b53785b..445587f51 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -3805,6 +3805,7 @@ static int impl_node_process(void *object) if (io->status & SPA_STATUS_DRAINED) { spa_log_debug(this->log, "%p: port %d drained", this, port->id); in_avail = flush_in = draining = true; + in_empty = false; } else { spa_log_trace_fp(this->log, "%p: empty input port %d %p %d %d %d", this, port->id, io, io->status, io->buffer_id, From bd7ce5c7fa85cb982bb138aaabdfcacc9e7386e2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 18:21:28 +0200 Subject: [PATCH 0450/1014] pulse-server: only react to state changes when not corked When we are starting or corked we don't emit suspend/resume caused by state changes. --- src/modules/module-protocol-pulse/pulse-server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index e8cf29d79..5f5cd558f 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1129,7 +1129,7 @@ static void stream_state_changed(void *data, enum pw_stream_state old, /* Don't emit suspended if we are creating a corked stream, as that will have a quick * RUNNING/SUSPENDED transition for initial negotiation */ - if (stream->create_tag == SPA_ID_INVALID || !stream->corked) { + if (stream->create_tag == SPA_ID_INVALID && !stream->corked) { if (old == PW_STREAM_STATE_PAUSED && state == PW_STREAM_STATE_STREAMING) stream_send_suspended(stream, false); if (old == PW_STREAM_STATE_STREAMING && state == PW_STREAM_STATE_PAUSED) From 8a09bacdf66b16aefa9cd2c63551277d94e7af8f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 24 Jun 2025 20:12:51 +0200 Subject: [PATCH 0451/1014] audioconvert: map buffers with right prot --- spa/plugins/audioconvert/audioconvert.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 445587f51..49b7b0917 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -3201,9 +3201,13 @@ impl_node_port_use_buffers(void *object, for (j = 0; j < n_datas; j++) { void *data = d[j].data; if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { + int prot = 0; + if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_READABLE)) + prot |= PROT_READ; + if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_WRITABLE)) + prot |= PROT_WRITE; data = mmap(NULL, d[j].maxsize, - PROT_READ, MAP_SHARED, d[j].fd, - d[j].mapoffset); + prot, MAP_SHARED, d[j].fd, d[j].mapoffset); if (data == MAP_FAILED) { spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", this, j, i, d[j].type, data); From dd6c9de604be7974163f9f8a777936003f131567 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 25 Jun 2025 10:34:50 +0200 Subject: [PATCH 0452/1014] tests: set the flags on buffers correctly --- spa/plugins/audioconvert/test-audioconvert.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c index bc6e2a5ef..0546e629a 100644 --- a/spa/plugins/audioconvert/test-audioconvert.c +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -598,7 +598,7 @@ static int run_convert(struct context *ctx, struct data *in_data, for (j = 0; j < in_data->planes; j++, k++) { b->datas[j].type = SPA_DATA_MemPtr; - b->datas[j].flags = 0; + b->datas[j].flags = SPA_DATA_FLAG_READABLE; b->datas[j].fd = -1; b->datas[j].mapoffset = 0; b->datas[j].maxsize = in_data->size; @@ -629,7 +629,7 @@ static int run_convert(struct context *ctx, struct data *in_data, for (j = 0; j < out_data->planes; j++) { b->datas[j].type = SPA_DATA_MemPtr; - b->datas[j].flags = 0; + b->datas[j].flags = SPA_DATA_FLAG_READWRITE; b->datas[j].fd = -1; b->datas[j].mapoffset = 0; b->datas[j].maxsize = out_data->size; From a9cece3c2e0846459163fb433edd91f0a64fd178 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 25 Jun 2025 10:37:56 +0200 Subject: [PATCH 0453/1014] audioconvert: remove unused field --- spa/plugins/audioconvert/audioconvert.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 49b7b0917..3c2682d8c 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -305,7 +305,6 @@ struct impl { unsigned int started:1; unsigned int setup:1; unsigned int resample_peaks:1; - unsigned int is_passthrough:1; unsigned int ramp_volume:1; unsigned int drained:1; unsigned int rate_adjust:1; @@ -3225,9 +3224,6 @@ impl_node_port_use_buffers(void *object, spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } - if (direction == SPA_DIRECTION_OUTPUT && - !SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_DYNAMIC)) - this->is_passthrough = false; b->datas[j] = data; From c045767252df4b89681abbd354c657e5574691db Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 25 Jun 2025 13:07:16 +0200 Subject: [PATCH 0454/1014] stream: improve drain Make sure we safely stop draining the stream by using the loop lock. Always stop draining when we change the state of the stream. The idea is that you either wait for the drain signal or cancel the pending drain early with a new set_active() call. --- src/pipewire/stream.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 6b367ec75..44c1d3a27 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -2370,6 +2370,18 @@ const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, return NULL; } +static int +do_stop_drain(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct stream *impl = user_data; + pw_log_trace_fp("%p", impl); + if (impl->drained && impl->io != NULL) + impl->io->status = SPA_STATUS_NEED_DATA; + impl->draining = impl->drained = false; + return 0; +} + SPA_EXPORT int pw_stream_set_active(struct pw_stream *stream, bool active) { @@ -2383,12 +2395,8 @@ int pw_stream_set_active(struct pw_stream *stream, bool active) return -EIO; pw_impl_node_set_active(stream->node, active); + pw_loop_locked(impl->data_loop, do_stop_drain, 1, NULL, 0, impl); - if (!active || impl->drained) { - if (impl->drained && impl->io != NULL) - impl->io->status = SPA_STATUS_NEED_DATA; - impl->drained = impl->draining = false; - } return 0; } From 1541ce3368f3a2e733fb479a002edb0a2f853d8d Mon Sep 17 00:00:00 2001 From: Harald Sitter Date: Tue, 24 Jun 2025 16:34:01 +0200 Subject: [PATCH 0455/1014] Revert "alsa: add Teufel Cage Pro mapping" This reverts commit b57b87abbbb25ae8b7c4635e45553b26a2a87488. It turns out this device is subtily different and doesn't work with the current profile configurations. A UCM profile was added to alsa-ucm-conf instead. --- spa/plugins/alsa/90-pipewire-alsa.rules | 2 -- 1 file changed, 2 deletions(-) diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 78c6713dd..9ef3d533b 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -139,8 +139,6 @@ ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0038", ENV{ACP_PROFILE_SET}="usb-gam ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0045", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1532:0520 is for the Razer Kraken Tournament Edition ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0520", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" -# ID 2cc2:0033 is for the Lautsprecher Teufel GmbH CAGE PRO -ATTRS{idVendor}=="2cc2", ATTRS{idProduct}=="0033", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:1250 is for the Arctis 5 From 17c755714d9be6b3a3ff2553c1a5a5570c4c6fe2 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 26 Nov 2024 16:58:57 +0100 Subject: [PATCH 0456/1014] v4l2: allow negotiation with modifier This assumes that the modifier is always 'linear'. That is not quite correct for all V4L2 formats. But PipeWire only uses input devices and other modifiers are very unlikely. This makes it possible to use DMABUFs with the GStreamer pipewiresrc. --- spa/plugins/v4l2/v4l2-source.c | 8 ++++++++ spa/plugins/v4l2/v4l2-utils.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index b06dda994..6b6dcff0d 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -90,6 +90,7 @@ struct port { struct v4l2_frmivalenum frmival; bool have_format; + bool have_modifier; struct spa_video_info current_format; struct spa_v4l2_device dev; @@ -219,6 +220,12 @@ static int port_get_format(struct port *port, case SPA_MEDIA_SUBTYPE_raw: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), + 0); + if (port->have_modifier) + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_modifier, SPA_POD_Long(0), + 0); + spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), 0); @@ -706,6 +713,7 @@ static int port_set_format(struct impl *this, struct port *port, spa_log_error(this->log, "can't parse video raw"); return -EINVAL; } + port->have_modifier = info.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 58758f758..7e972dc6e 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -513,11 +513,12 @@ spa_v4l2_enum_format(struct impl *this, int seq, struct spa_pod_frame f[2]; struct spa_result_node_params result; uint32_t count = 0; + bool with_modifier; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; - spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 8192); spa_pod_builder_get_state(&b.b, &state); result.id = SPA_PARAM_EnumFormat; @@ -537,6 +538,7 @@ spa_v4l2_enum_format(struct impl *this, int seq, if ((res = spa_format_parse(filter, &filter_media_type, &filter_media_subtype)) < 0) return res; } + with_modifier = !filter || spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_modifier); if (false) { next_fmtdesc: @@ -729,6 +731,10 @@ do_frmsize_filter: if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_id(&b.b, info->format); + if (with_modifier) { + spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(&b.b, 0L); + } } spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_size, 0); @@ -899,6 +905,27 @@ do_frminterval_filter: spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + if (++count == num) + goto enum_end; + + if (with_modifier && info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { + struct spa_pod_object *op = (struct spa_pod_object *) result.param; + const struct spa_pod_prop *p; + bool drop_next = false; + + spa_pod_builder_push_object(&b.b, &f[0], op->body.type, op->body.id); + + SPA_POD_OBJECT_FOREACH(op, p) { + if (p->key != SPA_FORMAT_VIDEO_modifier) + spa_pod_builder_raw_padded(&b.b, p, SPA_POD_PROP_SIZE(p)); + } + + result.index = result.next++; + result.param = spa_pod_builder_pop(&b.b, &f[0]); + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + } + if (++count != num) goto next; From 9a6f8d31dc36202f32c474c15d38cacda702e706 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 26 Jun 2025 14:23:36 +0200 Subject: [PATCH 0457/1014] loop: unlock the lock when blocking on invoke When we are the owners of the loop lock and we are not in the loop thread itself, release all locks so that the loop can start processing our invoke items and we get a chance to make progress. After that re-acquire the locks. This can happen when you change some of the core loop_locked() calls to blocking _invoke functions that are called with the loop locked. We have all core blocking invoke functions removed now so this is not actually going to be used but just in case an application tries to blocking invoke while locking the loop, this will now at least do something else than deadlock. --- spa/plugins/support/loop.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 2f4559651..f65454b67 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -477,11 +477,27 @@ again: if (block && queue->ack_fd != -1) { uint64_t count = 1; + int i, recurse = 0; + + if (pthread_mutex_trylock(&impl->lock) == 0) { + /* we are holding the lock, unlock recurse times */ + recurse = impl->recurse; + while (impl->recurse > 0) { + impl->recurse--; + pthread_mutex_unlock(&impl->lock); + } + pthread_mutex_unlock(&impl->lock); + } if ((res = spa_system_eventfd_read(impl->system, queue->ack_fd, &count)) < 0) spa_log_warn(impl->log, "%p: failed to read event fd:%d: %s", queue, queue->ack_fd, spa_strerror(res)); + for (i = 0; i < recurse; i++) { + pthread_mutex_lock(&impl->lock); + impl->recurse++; + } + res = item->res; } else { From 4c5e3f90152c0ae13b45ea2d1cc465069c583276 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 4 Mar 2025 10:48:12 +0100 Subject: [PATCH 0458/1014] spa: video/color: drop 'since' comments These are GStreamer versions that make no sense here. --- spa/include/spa/param/video/color.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/param/video/color.h b/spa/include/spa/param/video/color.h index 9a65bf04a..96a37d5ca 100644 --- a/spa/include/spa/param/video/color.h +++ b/spa/include/spa/param/video/color.h @@ -36,7 +36,7 @@ enum spa_video_color_matrix { SPA_VIDEO_COLOR_MATRIX_BT709, /**< ITU BT.709 color matrix */ SPA_VIDEO_COLOR_MATRIX_BT601, /**< ITU BT.601 color matrix */ SPA_VIDEO_COLOR_MATRIX_SMPTE240M, /**< SMTPE 240M color matrix */ - SPA_VIDEO_COLOR_MATRIX_BT2020, /**< ITU-R BT.2020 color matrix. since 1.6. */ + SPA_VIDEO_COLOR_MATRIX_BT2020, /**< ITU-R BT.2020 color matrix */ }; /** @@ -57,8 +57,8 @@ enum spa_video_transfer_function { SPA_VIDEO_TRANSFER_LOG316, /**< Logarithmic transfer characteristic 316.22777:1 range */ SPA_VIDEO_TRANSFER_BT2020_12, /**< Gamma 2.2 curve with a linear segment in the lower * range. Used for BT.2020 with 12 bits per - * component. \since 1.6. */ - SPA_VIDEO_TRANSFER_ADOBERGB, /**< Gamma 2.19921875. \since 1.8 */ + * component */ + SPA_VIDEO_TRANSFER_ADOBERGB, /**< Gamma 2.19921875 */ }; /** @@ -73,8 +73,8 @@ enum spa_video_color_primaries { SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, /**< SMPTE170M primaries */ SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, /**< SMPTE240M primaries */ SPA_VIDEO_COLOR_PRIMARIES_FILM, /**< Generic film */ - SPA_VIDEO_COLOR_PRIMARIES_BT2020, /**< BT2020 primaries. \since 1.6. */ - SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, /**< Adobe RGB primaries. \since 1.8 */ + SPA_VIDEO_COLOR_PRIMARIES_BT2020, /**< BT2020 primaries */ + SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, /**< Adobe RGB primaries */ }; /** From 20cdc9155fba92d4e8e214c97bd9f838b25e5bb1 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 4 Mar 2025 10:51:14 +0100 Subject: [PATCH 0459/1014] spa: video/color: add some more transfer functions and color primaries --- spa/include/spa/param/video/color.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spa/include/spa/param/video/color.h b/spa/include/spa/param/video/color.h index 96a37d5ca..68b69a5b3 100644 --- a/spa/include/spa/param/video/color.h +++ b/spa/include/spa/param/video/color.h @@ -59,6 +59,16 @@ enum spa_video_transfer_function { * range. Used for BT.2020 with 12 bits per * component */ SPA_VIDEO_TRANSFER_ADOBERGB, /**< Gamma 2.19921875 */ + SPA_VIDEO_TRANSFER_BT2020_10, /**< Rec. ITU-R BT.2020-2 with 10 bits per component. + * (functionally the same as the values + * SPA_VIDEO_TRANSFER_BT709 and SPA_VIDEO_TRANSFER_BT601) */ + SPA_VIDEO_TRANSFER_SMPTE2084, /**< SMPTE ST 2084 for 10, 12, 14, and 16-bit systems. + * Known as perceptual quantization (PQ) */ + SPA_VIDEO_TRANSFER_ARIB_STD_B67,/**< Association of Radio Industries and Businesses (ARIB) + * STD-B67 and Rec. ITU-R BT.2100-1 hybrid loggamma (HLG) system */ + SPA_VIDEO_TRANSFER_BT601, /**< also known as SMPTE170M / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC + * Functionally the same as the values + * SPA_VIDEO_TRANSFER_BT709, and SPA_VIDEO_TRANSFER_BT2020_10 */ }; /** @@ -75,6 +85,10 @@ enum spa_video_color_primaries { SPA_VIDEO_COLOR_PRIMARIES_FILM, /**< Generic film */ SPA_VIDEO_COLOR_PRIMARIES_BT2020, /**< BT2020 primaries */ SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, /**< Adobe RGB primaries */ + SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, /**< SMPTE ST 428 primaries (CIE 1931 XYZ) */ + SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, /**< SMPTE RP 431 primaries (ST 431-2 (2011) / DCI P3) */ + SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, /**< SMPTE EG 432 primaries (ST 432-1 (2010) / P3 D65) */ + SPA_VIDEO_COLOR_PRIMARIES_EBU3213, /**< EBU 3213 primaries (JEDEC P22 phosphors) */ }; /** From 6294cbeb68aff761ac7ec95d344ef9544689f408 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 4 Mar 2025 10:52:32 +0100 Subject: [PATCH 0460/1014] spa: add helper to determine if a video format is RGB --- spa/include/spa/param/video/raw-utils.h | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/spa/include/spa/param/video/raw-utils.h b/spa/include/spa/param/video/raw-utils.h index 1e462036a..067cf32e5 100644 --- a/spa/include/spa/param/video/raw-utils.h +++ b/spa/include/spa/param/video/raw-utils.h @@ -117,6 +117,56 @@ spa_format_video_raw_build(struct spa_pod_builder *builder, uint32_t id, return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } +static inline bool +spa_format_video_is_rgb(enum spa_video_format format) +{ + switch (format) { + case SPA_VIDEO_FORMAT_RGBx: + case SPA_VIDEO_FORMAT_BGRx: + case SPA_VIDEO_FORMAT_xRGB: + case SPA_VIDEO_FORMAT_xBGR: + case SPA_VIDEO_FORMAT_RGBA: + case SPA_VIDEO_FORMAT_BGRA: + case SPA_VIDEO_FORMAT_ARGB: + case SPA_VIDEO_FORMAT_ABGR: + case SPA_VIDEO_FORMAT_RGB: + case SPA_VIDEO_FORMAT_BGR: + case SPA_VIDEO_FORMAT_GRAY8: + case SPA_VIDEO_FORMAT_GRAY16_BE: + case SPA_VIDEO_FORMAT_GRAY16_LE: + case SPA_VIDEO_FORMAT_RGB16: + case SPA_VIDEO_FORMAT_BGR16: + case SPA_VIDEO_FORMAT_RGB15: + case SPA_VIDEO_FORMAT_BGR15: + case SPA_VIDEO_FORMAT_RGB8P: + case SPA_VIDEO_FORMAT_ARGB64: + case SPA_VIDEO_FORMAT_r210: + case SPA_VIDEO_FORMAT_GBR: + case SPA_VIDEO_FORMAT_GBR_10BE: + case SPA_VIDEO_FORMAT_GBR_10LE: + case SPA_VIDEO_FORMAT_GBRA: + case SPA_VIDEO_FORMAT_GBRA_10BE: + case SPA_VIDEO_FORMAT_GBRA_10LE: + case SPA_VIDEO_FORMAT_GBR_12BE: + case SPA_VIDEO_FORMAT_GBR_12LE: + case SPA_VIDEO_FORMAT_GBRA_12BE: + case SPA_VIDEO_FORMAT_GBRA_12LE: + case SPA_VIDEO_FORMAT_RGBA_F16: + case SPA_VIDEO_FORMAT_RGBA_F32: + case SPA_VIDEO_FORMAT_xRGB_210LE: + case SPA_VIDEO_FORMAT_xBGR_210LE: + case SPA_VIDEO_FORMAT_RGBx_102LE: + case SPA_VIDEO_FORMAT_BGRx_102LE: + case SPA_VIDEO_FORMAT_ARGB_210LE: + case SPA_VIDEO_FORMAT_ABGR_210LE: + case SPA_VIDEO_FORMAT_RGBA_102LE: + case SPA_VIDEO_FORMAT_BGRA_102LE: + return true; + default: + return false; + } +} + /** * \} */ From 41b831d0f859bdba2d59f6853b638d90a16c5b1b Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 4 Mar 2025 10:53:36 +0100 Subject: [PATCH 0461/1014] spa: v4l2: add colorimetry support --- spa/plugins/v4l2/v4l2-utils.c | 191 +++++++++++++++++++++++++++++++++- 1 file changed, 190 insertions(+), 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 7e972dc6e..7053fdf69 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -2,6 +2,7 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -494,6 +495,162 @@ filter_framerate(struct v4l2_frmivalenum *frmival, return true; } +struct spa_video_colorimetry v4l2_colorimetry_map[] = { + { /* V4L2_COLORSPACE_DEFAULT */ + .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, + }, + { /* V4L2_COLORSPACE_SMPTE170M */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_BT601, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, + }, + { /* V4L2_COLORSPACE_SMPTE240M */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_SMPTE240M, + .transfer = SPA_VIDEO_TRANSFER_SMPTE240M, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, + }, + { /* V4L2_COLORSPACE_REC709 */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT709, + .transfer = SPA_VIDEO_TRANSFER_BT709, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, + }, + { /* V4L2_COLORSPACE_BT878 (deprecated) */ + .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, + }, + { /* V4L2_COLORSPACE_470_SYSTEM_M */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_BT709, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT470M, + }, + { /* V4L2_COLORSPACE_470_SYSTEM_BG */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_BT709, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT470BG, + }, + { /* V4L2_COLORSPACE_JPEG */ + .range = SPA_VIDEO_COLOR_RANGE_0_255, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_SRGB, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, + }, + { /* V4L2_COLORSPACE_SRGB */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_SRGB, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, + }, + { /* V4L2_COLORSPACE_OPRGB */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, + .transfer = SPA_VIDEO_TRANSFER_ADOBERGB, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, + }, + { /* V4L2_COLORSPACE_BT2020 */ + .range = SPA_VIDEO_COLOR_RANGE_16_235, + .matrix = SPA_VIDEO_COLOR_MATRIX_BT2020, + .transfer = SPA_VIDEO_TRANSFER_BT2020_12, + .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020, + }, + { /* V4L2_COLORSPACE_RAW */ + .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, + } +}; + +enum spa_video_color_range v4l2_color_range_map[] = { + SPA_VIDEO_COLOR_RANGE_UNKNOWN, + SPA_VIDEO_COLOR_RANGE_0_255, + SPA_VIDEO_COLOR_RANGE_16_235 +}; + +enum spa_video_color_matrix v4l2_color_matrix_map[] = { + /* V4L2_YCBCR_ENC_DEFAULT */ + SPA_VIDEO_COLOR_MATRIX_UNKNOWN, + /* V4L2_YCBCR_ENC_601 */ + SPA_VIDEO_COLOR_MATRIX_BT601, + /* V4L2_YCBCR_ENC_709 */ + SPA_VIDEO_COLOR_MATRIX_BT709, + /* V4L2_YCBCR_ENC_XV601 */ + SPA_VIDEO_COLOR_MATRIX_BT601, + /* V4L2_YCBCR_ENC_XV709 */ + SPA_VIDEO_COLOR_MATRIX_BT709, + /* V4L2_YCBCR_ENC_SYCC */ + SPA_VIDEO_COLOR_MATRIX_BT601, + /* V4L2_YCBCR_ENC_BT2020 */ + SPA_VIDEO_COLOR_MATRIX_BT2020, + /* V4L2_YCBCR_ENC_BT2020_CONST_LUM */ + SPA_VIDEO_COLOR_MATRIX_BT2020, + /* V4L2_YCBCR_ENC_SMPTE240M */ + SPA_VIDEO_COLOR_MATRIX_SMPTE240M +}; + +enum spa_video_transfer_function v4l2_transfer_function_map[] = { + /* V4L2_XFER_FUNC_DEFAULT */ + SPA_VIDEO_TRANSFER_UNKNOWN, + /* V4L2_XFER_FUNC_709 */ + SPA_VIDEO_TRANSFER_BT709, + /* V4L2_XFER_FUNC_SRGB */ + SPA_VIDEO_TRANSFER_SRGB, + /* V4L2_XFER_FUNC_OPRGB */ + SPA_VIDEO_TRANSFER_ADOBERGB, + /* V4L2_XFER_FUNC_SMPTE240M */ + SPA_VIDEO_TRANSFER_SMPTE240M, + /* V4L2_XFER_FUNC_NONE */ + SPA_VIDEO_TRANSFER_GAMMA10, + /* V4L2_XFER_FUNC_DCI_P3 */ + SPA_VIDEO_TRANSFER_UNKNOWN, + /* V4L2_XFER_FUNC_SMPTE2084 */ + SPA_VIDEO_TRANSFER_SMPTE2084 +}; + +static bool +parse_colorimetry(struct impl *this, const struct v4l2_pix_format *pix, bool is_rgb, + struct spa_video_colorimetry *colorimetry) +{ + struct spa_video_colorimetry c = { 0 }; + + if (pix->colorspace < V4L2_COLORSPACE_RAW) + c = v4l2_colorimetry_map[pix->colorspace]; + + if (c.range == SPA_VIDEO_COLOR_RANGE_UNKNOWN) + return false; + + switch (pix->quantization) { + case V4L2_QUANTIZATION_FULL_RANGE: + case V4L2_QUANTIZATION_LIM_RANGE: + c.range = v4l2_color_range_map[pix->quantization]; + break; + case V4L2_QUANTIZATION_DEFAULT: + if (is_rgb) + c.range = SPA_VIDEO_COLOR_RANGE_0_255; + break; + default: + spa_log_warn(this->log, "Unknown enum v4l2_quantization value %d", + pix->quantization); + c.range = SPA_VIDEO_COLOR_RANGE_UNKNOWN; + break; + } + + if (pix->ycbcr_enc >= V4L2_YCBCR_ENC_SMPTE240M) + spa_log_warn(this->log, "Unknown enum v4l2_ycbcr_encoding value %d", + pix->ycbcr_enc); + else if (pix->ycbcr_enc > 0) + c.matrix = v4l2_color_matrix_map[pix->ycbcr_enc]; + + if (pix->xfer_func >= V4L2_XFER_FUNC_SMPTE2084) + spa_log_warn(this->log, "Unknown enum v4l2_xfer_func value %d", + pix->xfer_func); + else if (pix->xfer_func > 0) + c.transfer = v4l2_transfer_function_map[pix->xfer_func]; + + *colorimetry = c; + return true; +} + #define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f static int @@ -512,7 +669,8 @@ spa_v4l2_enum_format(struct impl *this, int seq, struct spa_pod_builder_state state; struct spa_pod_frame f[2]; struct spa_result_node_params result; - uint32_t count = 0; + struct v4l2_format fmt; + uint32_t count = 0, try_width = 0, try_height = 0; bool with_modifier; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) @@ -742,6 +900,8 @@ do_frmsize_filter: spa_pod_builder_rectangle(&b.b, port->frmsize.discrete.width, port->frmsize.discrete.height); + try_width = port->frmsize.discrete.width; + try_height = port->frmsize.discrete.height; } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS || port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { spa_pod_builder_push_choice(&b.b, &f[1], SPA_CHOICE_None, 0); @@ -766,6 +926,35 @@ do_frmsize_filter: port->frmsize.stepwise.max_height); } spa_pod_builder_pop(&b.b, &f[1]); + try_width = port->frmsize.stepwise.min_width; + try_height = port->frmsize.stepwise.min_height; + } + + spa_zero(fmt); + fmt.type = port->fmtdesc.type; + fmt.fmt.pix.pixelformat = info->fourcc; + fmt.fmt.pix.field = V4L2_FIELD_ANY; + fmt.fmt.pix.width = try_width; + fmt.fmt.pix.height = try_height; + + if ((res = xioctl(dev->fd, VIDIOC_TRY_FMT, &fmt)) < 0) { + spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT %08x: %m", + this->props.device, info->fourcc); + } else { + struct spa_video_colorimetry colorimetry; + bool is_rgb = spa_format_video_is_rgb(info->format); + + if (parse_colorimetry(this, &fmt.fmt.pix, is_rgb, &colorimetry)) { + spa_pod_builder_add(&b.b, + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_Id(colorimetry.range), + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_Id(colorimetry.matrix), + SPA_FORMAT_VIDEO_transferFunction, + SPA_POD_Id(colorimetry.transfer), + SPA_FORMAT_VIDEO_colorPrimaries, + SPA_POD_Id(colorimetry.primaries), 0); + } } spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_framerate, 0); From 7795e06563dbb18112a4f63524f1f27f5a88c86e Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 4 Mar 2025 10:56:18 +0100 Subject: [PATCH 0462/1014] gst: add colorimetry support --- src/gst/gstpipewireformat.c | 107 ++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 59765c360..d7deb4e40 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -132,6 +132,58 @@ static const uint32_t video_format_map[] = { SPA_VIDEO_FORMAT_Y444_12LE, }; +static const uint32_t color_range_map[] = { + SPA_VIDEO_COLOR_RANGE_UNKNOWN, + SPA_VIDEO_COLOR_RANGE_0_255, + SPA_VIDEO_COLOR_RANGE_16_235, +}; + +static const uint32_t color_matrix_map[] = { + SPA_VIDEO_COLOR_MATRIX_UNKNOWN, + SPA_VIDEO_COLOR_MATRIX_RGB, + SPA_VIDEO_COLOR_MATRIX_FCC, + SPA_VIDEO_COLOR_MATRIX_BT709, + SPA_VIDEO_COLOR_MATRIX_BT601, + SPA_VIDEO_COLOR_MATRIX_SMPTE240M, + SPA_VIDEO_COLOR_MATRIX_BT2020, +}; + +static const uint32_t transfer_function_map[] = { + SPA_VIDEO_TRANSFER_UNKNOWN, + SPA_VIDEO_TRANSFER_GAMMA10, + SPA_VIDEO_TRANSFER_GAMMA18, + SPA_VIDEO_TRANSFER_GAMMA20, + SPA_VIDEO_TRANSFER_GAMMA22, + SPA_VIDEO_TRANSFER_BT709, + SPA_VIDEO_TRANSFER_SMPTE240M, + SPA_VIDEO_TRANSFER_SRGB, + SPA_VIDEO_TRANSFER_GAMMA28, + SPA_VIDEO_TRANSFER_LOG100, + SPA_VIDEO_TRANSFER_LOG316, + SPA_VIDEO_TRANSFER_BT2020_12, + SPA_VIDEO_TRANSFER_ADOBERGB, + SPA_VIDEO_TRANSFER_BT2020_10, + SPA_VIDEO_TRANSFER_SMPTE2084, + SPA_VIDEO_TRANSFER_ARIB_STD_B67, + SPA_VIDEO_TRANSFER_BT601, +}; + +static const uint32_t color_primaries_map[] = { + SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN, + SPA_VIDEO_COLOR_PRIMARIES_BT709, + SPA_VIDEO_COLOR_PRIMARIES_BT470M, + SPA_VIDEO_COLOR_PRIMARIES_BT470BG, + SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, + SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, + SPA_VIDEO_COLOR_PRIMARIES_FILM, + SPA_VIDEO_COLOR_PRIMARIES_BT2020, + SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, + SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, + SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, + SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, + SPA_VIDEO_COLOR_PRIMARIES_EBU3213, +}; + static const uint32_t interlace_mode_map[] = { SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE, SPA_VIDEO_INTERLACE_MODE_INTERLEAVED, @@ -849,6 +901,46 @@ static const char *audio_id_to_string(uint32_t id) return gst_audio_format_to_string(idx); } +static GstVideoColorRange color_range_to_gst(uint32_t id) +{ + int idx; + if ((idx = find_index(color_range_map, SPA_N_ELEMENTS(color_range_map), id)) == -1) + return GST_VIDEO_COLOR_RANGE_UNKNOWN; + return idx; +} + +static GstVideoColorMatrix color_matrix_to_gst(uint32_t id) +{ + int idx; + if ((idx = find_index(color_matrix_map, SPA_N_ELEMENTS(color_matrix_map), id)) == -1) + return GST_VIDEO_COLOR_MATRIX_UNKNOWN; + return idx; +} + +static GstVideoTransferFunction transfer_function_to_gst(uint32_t id) +{ + int idx; + if ((idx = find_index(transfer_function_map, SPA_N_ELEMENTS(transfer_function_map), id)) == -1) + return GST_VIDEO_TRANSFER_UNKNOWN; + return idx; +} + +static GstVideoColorPrimaries color_primaries_to_gst(uint32_t id) +{ + int idx; + if ((idx = find_index(color_primaries_map, SPA_N_ELEMENTS(color_primaries_map), id)) == -1) + return GST_VIDEO_COLOR_PRIMARIES_UNKNOWN; + return idx; +} + +static void colorimetry_to_gst_colorimetry(struct spa_video_colorimetry *colorimetry, GstVideoColorimetry *gst_colorimetry) +{ + gst_colorimetry->range = color_range_to_gst(colorimetry->range); + gst_colorimetry->matrix = color_matrix_to_gst(colorimetry->matrix); + gst_colorimetry->transfer = transfer_function_to_gst(colorimetry->transfer); + gst_colorimetry->primaries = color_primaries_to_gst(colorimetry->primaries); +} + static void handle_id_prop (const struct spa_pod_prop *prop, const char *key, id_to_string_func func, GstCaps *res) { @@ -1165,6 +1257,7 @@ gst_caps_from_format (const struct spa_pod *format) { GstCaps *res = NULL; uint32_t media_type, media_subtype; + struct spa_video_colorimetry colorimetry = { 0 }; const struct spa_pod_prop *prop = NULL; const struct spa_pod_object *obj = (const struct spa_pod_object *) format; @@ -1217,6 +1310,20 @@ gst_caps_from_format (const struct spa_pod *format) if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_maxFramerate))) { handle_fraction_prop (prop, "max-framerate", res); } + if (spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&colorimetry.range), + SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&colorimetry.matrix), + SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&colorimetry.transfer), + SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&colorimetry.primaries)) > 0) { + GstVideoColorimetry gst_colorimetry; + char *color; + colorimetry_to_gst_colorimetry(&colorimetry, &gst_colorimetry); + color = gst_video_colorimetry_to_string(&gst_colorimetry); + gst_caps_set_simple(res, "colorimetry", G_TYPE_STRING, color, NULL); + g_free(color); + + } } else if (media_type == SPA_MEDIA_TYPE_audio) { if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { res = gst_caps_new_simple ("audio/x-raw", From a77c1cbd0b490739e283be8d8241e183412d375b Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Tue, 24 Jun 2025 13:00:19 +0200 Subject: [PATCH 0463/1014] gst: pipewireformat: enforce DMA_DRM when possible with memory:DMABuf, DMA_DRM should be used as 'format'. So only add formats to 'format' if there is no equivalent drm format. --- src/gst/gstpipewireformat.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index d7deb4e40..46d9a228e 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -1023,21 +1023,25 @@ handle_dmabuf_prop (const struct spa_pod_prop *prop, for (i = 0; i < n_fmts; i++) { for (j = 0; j < n_mods; j++) { + gboolean as_drm = FALSE; const char *fmt_str; - if ((mods[j] == DRM_FORMAT_MOD_LINEAR || - mods[j] == DRM_FORMAT_MOD_INVALID) && - (fmt_str = video_id_to_string(id[i]))) - g_ptr_array_add(fmt_array, g_strdup_printf ("%s", fmt_str)); - #ifdef HAVE_GSTREAMER_DMA_DRM if (mods[j] != DRM_FORMAT_MOD_INVALID) { char *drm_str; - if ((drm_str = video_id_to_dma_drm_fourcc(id[i], mods[j]))) + if ((drm_str = video_id_to_dma_drm_fourcc(id[i], mods[j]))) { g_ptr_array_add(drm_fmt_array, drm_str); + as_drm = TRUE; + } } #endif + + if (!as_drm && + (mods[j] == DRM_FORMAT_MOD_LINEAR || + mods[j] == DRM_FORMAT_MOD_INVALID) && + (fmt_str = video_id_to_string(id[i]))) + g_ptr_array_add(fmt_array, g_strdup_printf ("%s", fmt_str)); } } From 64b7a8990e2d6f37572d0b86c0d0847a283ba99e Mon Sep 17 00:00:00 2001 From: Sam James Date: Sat, 28 Jun 2025 02:59:03 +0100 Subject: [PATCH 0464/1014] meson: add fftw option Packagers need to have a way to control whether a dependency is used even if it's installed/available. --- meson.build | 2 +- meson_options.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2331ac826..0ba5c57a8 100644 --- a/meson.build +++ b/meson.build @@ -321,7 +321,7 @@ cdata.set('HAVE_DBUS', dbus_dep.found()) sdl_dep = dependency('sdl2', required : get_option('sdl2')) summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies') drm_dep = dependency('libdrm', required : false) -fftw_dep = dependency('fftw3f', required : false) +fftw_dep = dependency('fftw3f', required : get_option('fftw')) summary({'fftw3f (filter-chain convolver)': fftw_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_FFTW', fftw_dep.found()) diff --git a/meson_options.txt b/meson_options.txt index 74ce3fa94..cd2c839f7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -383,3 +383,7 @@ option('ebur128', description: 'Enable code that depends on ebur128', type: 'feature', value: 'auto') +option('fftw', + description: 'Enable code that depends on fftw', + type: 'feature', + value: 'auto') From 69eaa6d0984054868ceb3f9d3bbcf6c9c5185dea Mon Sep 17 00:00:00 2001 From: Sam James Date: Sat, 28 Jun 2025 07:56:10 +0100 Subject: [PATCH 0465/1014] ci: add fftw --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e2a549469..75427c6fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-05-10.0' + FDO_DISTRIBUTION_TAG: '2025-06-28.0' FDO_DISTRIBUTION_VERSION: '41' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -86,6 +86,8 @@ include: openal-soft readline-devel pandoc + fftw-libs-single + fftw-devel # Uncommenting the following two lines and disabling the meson entry above # will re-enable use of Meson via pip but please consider using a newer distro # image first or making the build system compatible instead! This is because From e9a0ac13463da251db0d26c3f4dc8214202757fc Mon Sep 17 00:00:00 2001 From: Sam James Date: Sat, 28 Jun 2025 02:54:34 +0100 Subject: [PATCH 0466/1014] spa: allow disabling deps via -Debur128/-Dudev With .enabled(), Meson doesn't have some magic that packagers rely on to explicitly disable finding a dependency if an option is off. Drop the unnecessary .enabled() accordingly. --- spa/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/meson.build b/spa/meson.build index 48a0000a1..637aeb8d4 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -129,7 +129,7 @@ if get_option('spa-plugins').allowed() cdata.set('HAVE_ALSA_COMPRESS_OFFLOAD', compress_offload_option.allowed()) # common dependencies - libudev_dep = dependency('libudev', required: get_option('udev').enabled()) + libudev_dep = dependency('libudev', required: get_option('udev')) cdata.set('HAVE_LIBUDEV', libudev_dep.found()) summary({'Udev': libudev_dep.found()}, bool_yn: true, section: 'Backend') @@ -139,7 +139,7 @@ if get_option('spa-plugins').allowed() lilv_lib = dependency('lilv-0', required: get_option('lv2')) summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true, section: 'filter-graph') - ebur128_lib = dependency('libebur128', required: get_option('ebur128').enabled()) + ebur128_lib = dependency('libebur128', required: get_option('ebur128')) summary({'EBUR128': ebur128_lib.found()}, bool_yn: true, section: 'filter-graph') summary({'ffmpeg': avfilter_dep.found()}, bool_yn: true, section: 'filter-graph') From f93b3b23a3500ec3a2db91ff684ac54190f0f826 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 30 Jun 2025 12:44:15 +0200 Subject: [PATCH 0467/1014] loop: fix use after free case Because we can now destroy sources (and free the source structure) by simply holding the lock, there is a window where we might access the freed source. When we in iterate release the lock and go into the epoll, another thread might acquire the lock and delete the fd from epoll. This might happen right after epoll detected activity on the fd. When iterate manages to acquire the lock again, it will process to dispatch the active fd and deref the ep.data pointer, which is now pointing to freed memory. Fix this by incrementing a removed_count whenever we remove a source. Check the counter if it was the same as before the epoll otherwise we can't assume all sources are alive still. Return in that case as if there were no fds to poll. The caller should reenter the iterate at some point and we will return all the fds with activity, minus the one that got destroyed. We need to give control to the caller because part of the removal could be to stop the loop iteration all together. --- spa/plugins/support/loop.c | 48 ++++++++++++-------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index f65454b67..fd53ef16d 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -80,7 +80,6 @@ struct impl { struct spa_system *system; struct spa_list source_list; - struct spa_list destroy_list; struct spa_list free_list; struct spa_hook_list hooks_list; @@ -107,8 +106,7 @@ struct impl { uint32_t count; uint32_t flush_count; - - unsigned int polling:1; + uint32_t remove_count; }; struct queue { @@ -191,6 +189,7 @@ static int remove_from_poll(struct impl *impl, struct spa_source *source) { spa_assert(source->loop == &impl->loop); + impl->remove_count++; return spa_system_pollfd_del(impl->system, impl->poll_fd, source->fd); } @@ -626,7 +625,6 @@ static void loop_leave(void *object) if (--impl->enter_count == 0) { impl->thread = 0; flush_all_queues(impl); - impl->polling = false; } pthread_mutex_unlock(&impl->lock); } @@ -724,16 +722,6 @@ static int loop_accept(void *object) return -pthread_cond_signal(&impl->accept_cond); } -static inline void process_destroy(struct impl *impl) -{ - struct source_impl *s; - spa_list_consume(s, &impl->destroy_list, link) { - spa_list_remove(&s->link); - detach_source(&s->source); - spa_list_append(&s->impl->free_list, &s->link); - } -} - struct cancellation_handler_data { struct spa_poll_event *ep; int ep_count; @@ -757,8 +745,9 @@ static int loop_iterate_cancel(void *object, int timeout) struct impl *impl = object; struct spa_poll_event ep[MAX_EP], *e; int i, nfds; + uint32_t remove_count; - impl->polling = true; + remove_count = impl->remove_count; spa_loop_control_hook_before(&impl->hooks_list); pthread_mutex_unlock(&impl->lock); @@ -766,7 +755,8 @@ static int loop_iterate_cancel(void *object, int timeout) pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); - impl->polling = false; + if (remove_count != impl->remove_count) + nfds = 0; struct cancellation_handler_data cdata = { ep, nfds }; pthread_cleanup_push(cancellation_handler, &cdata); @@ -787,9 +777,6 @@ static int loop_iterate_cancel(void *object, int timeout) s->priv = &ep[i]; } - if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) - process_destroy(impl); - for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) @@ -806,8 +793,9 @@ static int loop_iterate(void *object, int timeout) struct impl *impl = object; struct spa_poll_event ep[MAX_EP], *e; int i, nfds; + uint32_t remove_count; - impl->polling = true; + remove_count = impl->remove_count; spa_loop_control_hook_before(&impl->hooks_list); pthread_mutex_unlock(&impl->lock); @@ -815,7 +803,8 @@ static int loop_iterate(void *object, int timeout) pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); - impl->polling = false; + if (remove_count != impl->remove_count) + return 0; /* first we set all the rmasks, then call the callbacks. The reason is that * some callback might also want to look at other sources it manages and @@ -831,9 +820,6 @@ static int loop_iterate(void *object, int timeout) s->priv = &ep[i]; } - if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) - process_destroy(impl); - for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) @@ -1220,11 +1206,8 @@ static void loop_destroy_source(void *object, struct spa_source *source) } spa_list_remove(&s->link); - if (!s->impl->polling) { - detach_source(source); - spa_list_insert(&s->impl->free_list, &s->link); - } else - spa_list_insert(&s->impl->destroy_list, &s->link); + detach_source(source); + spa_list_insert(&s->impl->free_list, &s->link); } static const struct spa_loop_methods impl_loop = { @@ -1315,9 +1298,9 @@ static int impl_clear(struct spa_handle *handle) spa_log_debug(impl->log, "%p: clear", impl); - if (impl->enter_count != 0 || impl->polling) - spa_log_warn(impl->log, "%p: loop is entered %d times polling:%d", - impl, impl->enter_count, impl->polling); + if (impl->enter_count != 0) + spa_log_warn(impl->log, "%p: loop is entered %d times", + impl, impl->enter_count); spa_list_consume(source, &impl->source_list, link) loop_destroy_source(impl, &source->source); @@ -1431,7 +1414,6 @@ impl_init(const struct spa_handle_factory *factory, impl->poll_fd = res; spa_list_init(&impl->source_list); - spa_list_init(&impl->destroy_list); spa_list_init(&impl->free_list); spa_hook_list_init(&impl->hooks_list); From 3ff0c270ddc749933eabf7b06653e1a015dcc607 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 30 Jun 2025 15:48:20 +0200 Subject: [PATCH 0468/1014] echo-cancel: send capture/source latency correctly Input latency received on the source (output stream) should be propagated on the input stream (capture). --- src/modules/module-echo-cancel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 4f08927ac..2ff10ed95 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -624,9 +624,9 @@ static void input_param_latency_changed(struct impl *impl, const struct spa_pod params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); if (latency.direction == SPA_DIRECTION_INPUT) - pw_stream_update_params(impl->source, params, 1); - else pw_stream_update_params(impl->capture, params, 1); + else + pw_stream_update_params(impl->source, params, 1); } static struct spa_pod* get_props_param(struct impl* impl, struct spa_pod_builder* b) From 081700172823d90473c2482879813afc2905d3fc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Jul 2025 18:06:39 +0200 Subject: [PATCH 0469/1014] audioconvert: add clear function Sets all samples to 0 in the target format. --- spa/plugins/audioconvert/fmt-ops-c.c | 50 ++++++++++++++++++++++ spa/plugins/audioconvert/fmt-ops.c | 62 ++++++++++++++++++++++++++++ spa/plugins/audioconvert/fmt-ops.h | 27 ++++++++++++ 3 files changed, 139 insertions(+) diff --git a/spa/plugins/audioconvert/fmt-ops-c.c b/spa/plugins/audioconvert/fmt-ops-c.c index e92b5bf31..800951ce0 100644 --- a/spa/plugins/audioconvert/fmt-ops-c.c +++ b/spa/plugins/audioconvert/fmt-ops-c.c @@ -411,3 +411,53 @@ MAKE_INTERLEAVE(24, 24, uint24_t, (uint24_t)); MAKE_INTERLEAVE(32, 32, uint32_t, (uint32_t)); MAKE_INTERLEAVE(32, 32s, uint32_t, bswap_32); MAKE_INTERLEAVE(64, 64, uint64_t, (uint64_t)); + +#define MAKE_CLEAR(size) \ +void conv_clear_ ##size## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], uint32_t n_samples) \ +{ \ + uint32_t i, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) \ + memset(dst[i], 0, n_samples * (size>>3)); \ +} \ +void conv_clear_ ##size## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], uint32_t n_samples) \ +{ \ + memset(dst[0], 0, n_samples * conv->n_channels * (size>>3)); \ +} + +MAKE_CLEAR(8); +MAKE_CLEAR(16); +MAKE_CLEAR(24); +MAKE_CLEAR(32); +MAKE_CLEAR(64); + +#define MAKE_CLEAR_VAL(size,dtype,val) \ +void conv_clear_ ##size## d_c(struct convert *conv, \ + void * SPA_RESTRICT dst[], uint32_t n_samples) \ +{ \ + uint32_t i, j, n_channels = conv->n_channels; \ + for (i = 0; i < n_channels; i++) { \ + dtype *d = dst[i]; \ + for (j = 0; j < n_samples; j++) \ + d[j] = val; \ + } \ +} \ +void conv_clear_ ##size## _c(struct convert *conv, \ + void * SPA_RESTRICT dst[], uint32_t n_samples) \ +{ \ + uint32_t j; \ + dtype *d = dst[0]; \ + n_samples *= conv->n_channels; \ + for (j = 0; j < n_samples; j++) \ + d[j] = val; \ +} + +MAKE_CLEAR_VAL(alaw, uint8_t, 0x55); +MAKE_CLEAR_VAL(ulaw, uint8_t, 0xff); +MAKE_CLEAR_VAL(u8, uint8_t, 0x80); +MAKE_CLEAR_VAL(u16, uint16_t, 0x8000); +MAKE_CLEAR_VAL(u24, uint24_t, U32_TO_U24(0x800000)); +MAKE_CLEAR_VAL(u24_32, uint32_t, 0x800000); +MAKE_CLEAR_VAL(u32, uint32_t, 0x80000000); + diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 4df31323a..3296c220b 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -376,6 +376,64 @@ static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt return NULL; } + +typedef void (*clear_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], + uint32_t n_samples); + +struct clear_info { + uint32_t fmt; + + clear_func_t clear; + const char *name; + + uint32_t cpu_flags; +}; + +#define MAKE(fmt,func,...) \ + { SPA_AUDIO_FORMAT_ ##fmt, func, #func , __VA_ARGS__ } + +static struct clear_info clear_table[] = +{ + MAKE(U8, conv_clear_u8_c), + MAKE(U8P, conv_clear_u8d_c), + MAKE(S8, conv_clear_8_c), + MAKE(S8P, conv_clear_8d_c), + MAKE(U16, conv_clear_u16_c), + MAKE(S16, conv_clear_16_c), + MAKE(S16_OE, conv_clear_16_c), + MAKE(S16P, conv_clear_16d_c), + MAKE(U24, conv_clear_u24_c), + MAKE(S24, conv_clear_24_c), + MAKE(S24_OE, conv_clear_24_c), + MAKE(S24P, conv_clear_24d_c), + MAKE(U24_32, conv_clear_u24_32_c), + MAKE(S24_32, conv_clear_32_c), + MAKE(S24_32_OE, conv_clear_32_c), + MAKE(U32, conv_clear_u32_c), + MAKE(S32, conv_clear_32_c), + MAKE(S32_OE, conv_clear_32_c), + MAKE(S32P, conv_clear_32d_c), + MAKE(F32, conv_clear_32_c), + MAKE(F32_OE, conv_clear_32_c), + MAKE(F32P, conv_clear_32d_c), + MAKE(F64, conv_clear_64_c), + MAKE(F64_OE, conv_clear_64_c), + MAKE(F64P, conv_clear_64d_c), + MAKE(ALAW, conv_clear_alaw_c), + MAKE(ULAW, conv_clear_ulaw_c), +}; +#undef MAKE + +static const struct clear_info *find_clear_info(uint32_t fmt, uint32_t cpu_flags) +{ + SPA_FOR_EACH_ELEMENT_VAR(clear_table, c) { + if (c->fmt == fmt && + MATCH_CPU_FLAGS(c->cpu_flags, cpu_flags)) + return c; + } + return NULL; +} + typedef void (*noise_func_t) (struct convert *conv, float * noise, uint32_t n_samples); struct noise_info { @@ -492,6 +550,7 @@ int convert_init(struct convert *conv) const struct conv_info *info; const struct dither_info *dinfo; const struct noise_info *ninfo; + const struct clear_info *cinfo; uint32_t i, conv_flags, data_size[3]; /* we generate int32 bits of random values. With this scale @@ -549,6 +608,8 @@ int convert_init(struct convert *conv) if (ninfo == NULL) return -ENOTSUP; + cinfo = find_clear_info(conv->dst_fmt, conv->cpu_flags); + conv->noise_size = NOISE_SIZE; data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); @@ -571,6 +632,7 @@ int convert_init(struct convert *conv) conv->cpu_flags = info->cpu_flags; conv->update_noise = ninfo->noise; conv->process = info->process; + conv->clear = cinfo ? cinfo->clear : NULL; conv->free = impl_convert_free; conv->func_name = info->name; diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index 7aed0bc65..9f4655c22 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -241,6 +241,7 @@ struct convert { 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[], uint32_t n_samples); + void (*clear) (struct convert *conv, void * SPA_RESTRICT dst[], uint32_t n_samples); void (*free) (struct convert *conv); void *data; @@ -278,6 +279,7 @@ static inline uint32_t dither_method_from_label(const char *label) #define convert_update_noise(conv,...) (conv)->update_noise(conv, __VA_ARGS__) #define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) +#define convert_clear(conv,...) (conv)->clear(conv, __VA_ARGS__) #define convert_free(conv) (conv)->free(conv) #define DEFINE_NOISE_FUNCTION(name,arch) \ @@ -490,3 +492,28 @@ DEFINE_FUNCTION(f32d_to_s16, avx2); #endif #undef DEFINE_FUNCTION + +#define DEFINE_CLEAR_FUNCTION(name,arch) \ +void conv_clear_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ + uint32_t n_samples) + +DEFINE_CLEAR_FUNCTION(alaw, c); +DEFINE_CLEAR_FUNCTION(ulaw, c); +DEFINE_CLEAR_FUNCTION(8, c); +DEFINE_CLEAR_FUNCTION(8d, c); +DEFINE_CLEAR_FUNCTION(16, c); +DEFINE_CLEAR_FUNCTION(16d, c); +DEFINE_CLEAR_FUNCTION(24, c); +DEFINE_CLEAR_FUNCTION(24d, c); +DEFINE_CLEAR_FUNCTION(32, c); +DEFINE_CLEAR_FUNCTION(32d, c); +DEFINE_CLEAR_FUNCTION(64, c); +DEFINE_CLEAR_FUNCTION(64d, c); +DEFINE_CLEAR_FUNCTION(u8, c); +DEFINE_CLEAR_FUNCTION(u8d, c); +DEFINE_CLEAR_FUNCTION(u16, c); +DEFINE_CLEAR_FUNCTION(u24, c); +DEFINE_CLEAR_FUNCTION(u24_32, c); +DEFINE_CLEAR_FUNCTION(u32, c); + +#undef DEFINE_CLEAR_FUNCTION From 653e1578a12e5efc587d8dc6751651874a941d20 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 1 Jul 2025 18:07:17 +0200 Subject: [PATCH 0470/1014] audioconvert: use faster clear when dealing with empty buffers When we are converting an empty buffer, use the more efficient clear function. --- spa/plugins/audioconvert/audioconvert.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 3c2682d8c..e7414a82b 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -215,6 +215,7 @@ struct stage_context { uint32_t dst_idx; uint32_t final_idx; struct port *ctrlport; + bool empty; }; struct stage { @@ -3499,7 +3500,10 @@ static void run_src_convert_stage(struct stage *s, struct stage_context *c) } else { dst = c->datas[s->out_idx]; } - convert_process(&dir->conv, dst, (const void**)c->datas[s->in_idx], c->n_samples); + if (c->empty && dir->conv.clear) + convert_clear(&dir->conv, dst, c->n_samples); + else + convert_process(&dir->conv, dst, (const void**)c->datas[s->in_idx], c->n_samples); } static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) { @@ -3622,7 +3626,10 @@ static void run_dst_convert_stage(struct stage *s, struct stage_context *c) } else { src = c->datas[s->in_idx]; } - convert_process(&dir->conv, c->datas[s->out_idx], (const void **)src, c->n_samples); + if (c->empty && dir->conv.clear) + convert_clear(&dir->conv, c->datas[s->out_idx], c->n_samples); + else + convert_process(&dir->conv, c->datas[s->out_idx], (const void **)src, c->n_samples); } static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) { @@ -3751,7 +3758,7 @@ static int impl_node_process(void *object) struct dir *dir; int res = 0, suppressed; bool in_avail = false, flush_in = false, flush_out = false; - bool draining = false, in_empty = this->out_offset == 0; + bool draining = false, in_empty = this->out_offset == 0, out_empty; struct spa_io_buffers *io; const struct spa_pod_sequence *ctrl = NULL; uint64_t current_time; @@ -4027,6 +4034,7 @@ static int impl_node_process(void *object) ctx.n_samples = n_samples; ctx.n_out = n_out; ctx.ctrlport = ctrlport; + ctx.empty = in_empty; if (SPA_UNLIKELY(this->recalc)) { ctx.src_idx = CTX_DATA_SRC; @@ -4041,6 +4049,7 @@ static int impl_node_process(void *object) } this->in_offset += ctx.in_samples; this->out_offset += ctx.n_samples; + out_empty = ctx.empty; spa_log_trace_fp(this->log, "%d/%d %d/%d %d->%d", this->in_offset, max_in, this->out_offset, max_out, n_samples, n_out); @@ -4082,7 +4091,7 @@ static int impl_node_process(void *object) bd = &buf->buf->datas[j]; bd->chunk->size = this->out_offset * port->stride; bd->chunk->stride = port->stride; - SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, in_empty); + SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, out_empty); spa_log_trace_fp(this->log, "out: offs:%d stride:%d size:%d", this->out_offset, port->stride, bd->chunk->size); } From a8b9ce2050dfb3f91c19061562d0a161117c3ab8 Mon Sep 17 00:00:00 2001 From: Julian Bouzas Date: Wed, 2 Jul 2025 08:09:54 -0400 Subject: [PATCH 0471/1014] alsa: add option to disable pro-audio profiles Some devices might have nonfunctional 'Pro Audio' sound. This patch adds a new 'api.acp.disable-pro-audio' option to disable pro-audio profile entirely. --- spa/plugins/alsa/acp/acp.c | 5 ++++- spa/plugins/alsa/acp/card.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 5b3de930b..ecb017a9f 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -522,7 +522,8 @@ static void add_profiles(pa_card *impl) ap->profile.flags = ACP_PROFILE_OFF; pa_hashmap_put(impl->profiles, ap->name, ap); - add_pro_profile(impl, impl->card.index); + if (!impl->disable_pro_audio) + add_pro_profile(impl, impl->card.index); PA_HASHMAP_FOREACH(ap, impl->profile_set->profiles, state) { pa_alsa_mapping *m; @@ -1860,6 +1861,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->pro_channels = atoi(s); if ((s = acp_dict_lookup(props, "api.alsa.split-enable")) != NULL) impl->ucm.split_enable = spa_atob(s); + if ((s = acp_dict_lookup(props, "api.acp.disable-pro-audio")) != NULL) + impl->disable_pro_audio = spa_atob(s); } #if SND_LIB_VERSION < 0x10207 diff --git a/spa/plugins/alsa/acp/card.h b/spa/plugins/alsa/acp/card.h index 814e3c6ef..0ef1a1ce1 100644 --- a/spa/plugins/alsa/acp/card.h +++ b/spa/plugins/alsa/acp/card.h @@ -48,6 +48,7 @@ struct pa_card { bool auto_profile; bool auto_port; bool ignore_dB; + bool disable_pro_audio; uint32_t rate; uint32_t pro_channels; From 70aaec0ac49f2894a9093a442e2a8c684c184a95 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Jul 2025 10:06:41 -0400 Subject: [PATCH 0472/1014] spa: v4l2: Drop unused variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes warning: [186/359] Compiling C object spa/plugins/v4l2/libspa-v4l2.so.p/v4l2-source.c.o In file included from ../spa/plugins/v4l2/v4l2-source.c:164: ../spa/plugins/v4l2/v4l2-utils.c: In function ‘spa_v4l2_enum_format’: ../spa/plugins/v4l2/v4l2-utils.c:1103:22: warning: unused variable ‘drop_next’ [-Wunused-variable] 1103 | bool drop_next = false; | ^~~~~~~~~ --- spa/plugins/v4l2/v4l2-utils.c | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 7053fdf69..2c8a88443 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1100,7 +1100,6 @@ do_frminterval_filter: if (with_modifier && info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { struct spa_pod_object *op = (struct spa_pod_object *) result.param; const struct spa_pod_prop *p; - bool drop_next = false; spa_pod_builder_push_object(&b.b, &f[0], op->body.type, op->body.id); From 47ee9ef10a1bf4eab4dd2a84c7a3215465d8c9b1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 2 Jul 2025 11:12:40 +0200 Subject: [PATCH 0473/1014] module-rtp: set the EMPTY flag on empty buffers And make sure other flags are reset. --- src/modules/module-rtp/audio.c | 7 +++++-- src/modules/module-rtp/midi.c | 3 ++- src/modules/module-rtp/opus.c | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index e89712a32..65a05fee1 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -8,7 +8,7 @@ static void rtp_audio_process_playback(void *data) struct pw_buffer *buf; struct spa_data *d; uint32_t wanted, timestamp, target_buffer, stride, maxsize; - int32_t avail; + int32_t avail, flags = 0; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_info("Out of stream buffers: %m"); @@ -35,6 +35,8 @@ static void rtp_audio_process_playback(void *data) if (avail < (int32_t)wanted) { enum spa_log_level level; memset(d[0].data, 0, wanted * stride); + flags |= SPA_CHUNK_FLAG_EMPTY; + if (impl->have_sync) { impl->have_sync = false; level = SPA_LOG_LEVEL_INFO; @@ -95,9 +97,10 @@ static void rtp_audio_process_playback(void *data) timestamp += wanted; spa_ringbuffer_read_update(&impl->ring, timestamp); } + d[0].chunk->offset = 0; d[0].chunk->size = wanted * stride; d[0].chunk->stride = stride; - d[0].chunk->offset = 0; + d[0].chunk->flags = flags; buf->size = wanted; pw_stream_queue_buffer(impl->stream, buf); diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index 1b7f9ad65..b03dbd116 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -83,9 +83,10 @@ complete: pw_log_warn("overflow buffer %u %u", b.state.offset, maxsize); b.state.offset = 0; } + d[0].chunk->offset = 0; d[0].chunk->size = b.state.offset; d[0].chunk->stride = 1; - d[0].chunk->offset = 0; + d[0].chunk->flags = 0; done: pw_stream_queue_buffer(impl->stream, buf); } diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index bc0150386..70e2b0fd4 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -87,9 +87,10 @@ static void rtp_opus_process_playback(void *data) timestamp += wanted; spa_ringbuffer_read_update(&impl->ring, timestamp); } + d[0].chunk->offset = 0; d[0].chunk->size = wanted * stride; d[0].chunk->stride = stride; - d[0].chunk->offset = 0; + d[0].chunk->flags = 0; buf->size = wanted; pw_stream_queue_buffer(impl->stream, buf); From 616db9809e6c4e3af938df6b63d9cd23f529d555 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 2 Jul 2025 18:31:17 +0200 Subject: [PATCH 0474/1014] module-rtp: add some rate limit to send/recv errors --- src/modules/module-rtp-sink.c | 20 ++++++++++++++++++-- src/modules/module-rtp-source.c | 21 +++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index 7abd4d531..fb16d7793 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -175,6 +176,8 @@ struct impl { struct pw_properties *stream_props; struct rtp_stream *stream; + struct spa_ratelimit rate_limit; + unsigned int do_disconnect:1; char *ifname; @@ -279,6 +282,13 @@ static void stream_destroy(void *d) impl->stream = NULL; } +static inline uint64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen) { struct impl *impl = data; @@ -293,8 +303,11 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen) msg.msg_flags = 0; n = sendmsg(impl->rtp_fd, &msg, MSG_NOSIGNAL); - if (n < 0) - pw_log_warn("sendmsg() failed: %m"); + if (n < 0) { + int suppressed; + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0) + pw_log_warn("(%d suppressed) sendmsg() failed: %m", suppressed); + } } static void stream_state_changed(void *data, bool started, const char *error) @@ -516,6 +529,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } impl->stream_props = stream_props; + impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + impl->rate_limit.burst = 1; + impl->module = module; impl->context = context; impl->loop = pw_context_get_main_loop(context); diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index c942e32fe..185d82954 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -159,6 +160,8 @@ struct impl { struct spa_hook core_proxy_listener; unsigned int do_disconnect:1; + struct spa_ratelimit rate_limit; + char *ifname; bool always_process; uint32_t cleanup_interval; @@ -185,6 +188,13 @@ struct impl { bool waiting; }; +static inline uint64_t get_time_ns(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); +} + static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { @@ -211,6 +221,7 @@ on_rtp_io(void *data, int fd, uint32_t mask) { struct impl *impl = data; ssize_t len; + int suppressed; if (mask & SPA_IO_IN) { if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0) @@ -232,10 +243,13 @@ on_rtp_io(void *data, int fd, uint32_t mask) return; receive_error: - pw_log_warn("recv error: %m"); + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0) + pw_log_warn("(%d suppressed) recv() error: %m", suppressed); return; short_packet: - pw_log_warn("short packet of len %zd received", len); + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0) + pw_log_warn("(%d suppressed) short packet of len %zd received", + suppressed, len); return; } @@ -666,6 +680,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->main_loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); + impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + impl->rate_limit.burst = 1; + if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) sess_name = pw_get_host_name(); From ef5d9ff0287f21dd7ac5386248d56cfb517efd1c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 12:38:05 +0200 Subject: [PATCH 0475/1014] filter-graph: add a simple noise gate --- spa/plugins/filter-graph/builtin_plugin.c | 135 ++++++++++++++++++++++ src/modules/module-filter-chain.c | 13 +++ 2 files changed, 148 insertions(+) diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c index fad14f879..0d39bbe70 100644 --- a/spa/plugins/filter-graph/builtin_plugin.c +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -60,6 +60,9 @@ struct builtin { int mode; uint32_t count; float last; + + float gate; + float hold; }; static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, @@ -2876,6 +2879,136 @@ static const struct spa_fga_descriptor zeroramp_desc = { .cleanup = builtin_cleanup, }; + +/* noisegate */ +static struct spa_fga_port noisegate_ports[] = { + { .index = 0, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Open Threshold", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.004f, .min = 0.0f, .max = 1.0f + }, + { .index = 3, + .name = "Close Threshold", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.003f, .min = 0.0f, .max = 1.0f + }, + { .index = 4, + .name = "Attack (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.005f, .min = 0.0f, .max = 1.0f + }, + { .index = 5, + .name = "Hold (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.050f, .min = 0.0f, .max = 1.0f + }, + { .index = 6, + .name = "Release (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.010f, .min = 0.0f, .max = 1.0f + }, +}; + +static void noisegate_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[0]; + float *out = impl->port[1]; + unsigned long n; + float o_thres = impl->port[2][0]; + float c_thres = impl->port[3][0]; + float gate, hold, o_rate, c_rate, level; + int mode; + + if (out == NULL) + return; + + if (in == NULL) { + memset(out, 0, SampleCount * sizeof(float)); + return; + } + + o_rate = 1.0f / (impl->port[4][0] * impl->rate); + c_rate = 1.0f / (impl->port[6][0] * impl->rate); + gate = impl->gate; + hold = impl->hold; + mode = impl->mode; + level = impl->last; + + for (n = 0; n < SampleCount; n++) { + float lev = fabsf(in[n]); + + if (lev > level) + level = lev; + else + level = lev * 0.05f + level * 0.95f; + + switch (mode) { + case 0: + /* closed */ + if (level >= o_thres) + mode = 1; + break; + case 1: + /* opening */ + gate += o_rate; + if (gate >= 1.0f) { + gate = 1.0f; + mode = 2; + hold = impl->port[5][0] * impl->rate; + } + break; + case 2: + /* hold */ + hold -= 1.0f; + if (hold <= 0.0f) + mode = 3; + break; + case 3: + /* open */ + if (level < c_thres) + mode = 4; + break; + case 4: + /* closing */ + gate -= c_rate; + if (level >= o_thres) + mode = 1; + else if (gate <= 0.0f) { + gate = 0.0f; + mode = 0; + } + break; + } + out[n] = in[n] * gate; + } + impl->gate = gate; + impl->hold = hold; + impl->mode = mode; + impl->last = level; +} + +static const struct spa_fga_descriptor noisegate_desc = { + .name = "noisegate", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(noisegate_ports), + .ports = noisegate_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = noisegate_run, + .cleanup = builtin_cleanup, +}; + static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -2939,6 +3072,8 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &pipe_desc; case 29: return &zeroramp_desc; + case 30: + return &noisegate_desc; } return NULL; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 1f64dc174..b00130964 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -623,6 +623,19 @@ extern struct spa_handle_factory spa_filter_graph_factory; * "Duration (s)" determines how long the fade-in and fade-out should last * (default 0.000666). * + * ### Noisegate + * + * The `noisegate` plugin can be used to remove low volume noise. + * + * It has an "In" input port and an "Out" output data ports. Normally the input + * data is passed directly to the output. + * + * If the volume drops below "Close threshold", the noisegate will ramp down the + * volume to zero for a duration of "Release (s)" seconds. When the volume is above + * "Open threshold", the noisegate will ramp up the volume to 1 for a duration + * of "Attack (s)" seconds. The noise gate stays open for at least "Hold (s)" + * seconds before it can close again. + * * * ## SOFA filters * From 019b53ace8761174c9fd38aedca81696ce671eb2 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 3 Jul 2025 10:21:57 -0400 Subject: [PATCH 0476/1014] spa: alsa: Try to get driver rate before setting up matching In some cases, it is possible that the follower shares a clock with the driver, but the driver rate is not known when the follower is assigned to the driver. If this happens, then state->driver_rate is 0, and when setting the format, we might think that we need to resample (because follower rate != driver rate). This can cause us to incorrectly halve the period size for the node. This was introduced in commit 0b67c10a9cbe62d2f32a49226976611fd1553cdc, which forces reevaluation of matching status on driver change. To avoid this, let us also probe for the driver rate when updating the matching status, so we can make the update more accurate. --- spa/plugins/alsa/alsa-pcm.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 6d6e8133c..769e7443e 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2603,7 +2603,7 @@ static inline int do_start(struct state *state) return 0; } -static inline int check_position_config(struct state *state, bool starting); +static inline int check_position_config(struct state *state, bool starting, bool check_driver_only); static void update_sources(struct state *state, bool active); static int alsa_recover(struct state *state) @@ -2671,7 +2671,7 @@ recover: spa_list_for_each(follower, &driver->rt.followers, rt.driver_link) { if (follower != driver && follower->linked) { do_drop(follower); - check_position_config(follower, false); + check_position_config(follower, false, false); } } do_prepare(driver); @@ -2907,6 +2907,8 @@ static int setup_matching(struct state *state) spa_log_debug(state->log, "driver clock:'%s' our clock:'%s'", state->position->clock.name, state->clock_name); + check_position_config(state, false, true); + if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; @@ -2931,7 +2933,7 @@ static void update_sources(struct state *state, bool active) } } -static inline int check_position_config(struct state *state, bool starting) +static inline int check_position_config(struct state *state, bool starting, bool check_driver_only) { uint64_t target_duration; struct spa_fraction target_rate; @@ -2965,13 +2967,16 @@ static inline int check_position_config(struct state *state, bool starting) state->driver_duration = target_duration; state->driver_rate = target_rate; - state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); - state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); - state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); - state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; - state->resample = !state->pitch_elem && - (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); - state->alsa_sync = true; + + if (!check_driver_only) { + state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); + state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); + state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); + state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; + state->resample = !state->pitch_elem && + (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); + state->alsa_sync = true; + } } return 0; } @@ -2982,7 +2987,7 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) snd_pcm_uframes_t avail, delay, target; bool following = state->following; - if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) + if (SPA_UNLIKELY((res = check_position_config(state, false, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { @@ -3249,7 +3254,7 @@ static int alsa_read_sync(struct state *state, uint64_t current_time) if (SPA_UNLIKELY(!state->alsa_started)) return 0; - if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) + if (SPA_UNLIKELY((res = check_position_config(state, false, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { @@ -3659,7 +3664,7 @@ int spa_alsa_prepare(struct state *state) if (state->prepared) return 0; - if (check_position_config(state, true) < 0) { + if (check_position_config(state, true, false) < 0) { spa_log_error(state->log, "%s: invalid position config", state->name); return -EIO; } From a0beb30ba860dbd97931671083864d3e85e16740 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Thu, 3 Jul 2025 16:51:21 +0200 Subject: [PATCH 0477/1014] echo-cancel: drop if playback is not streaming capture and sink streams may start before playback stream so process() may fail to dequeue a playback buffer. In that case advance the read pointers to avoid building up latency in the ringbuffers. --- src/modules/module-echo-cancel.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 2ff10ed95..174c8173c 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -309,11 +309,6 @@ static void process(struct impl *impl) uint32_t i, size; uint32_t rindex, pindex, oindex, pdindex, avail; - if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { - pw_log_debug("out of playback buffers: %m"); - goto done; - } - size = impl->aec_blocksize; /* First read a block from the playback and capture ring buffers */ @@ -338,6 +333,16 @@ static void process(struct impl *impl) spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { + pw_log_debug("out of playback buffers: %m"); + + /* playback stream may not yet be in streaming state, drop play + * data to avoid introducing additional playback latency */ + spa_ringbuffer_read_update(&impl->play_ring, pindex + size); + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + goto done; + } + for (i = 0; i < impl->play_info.channels; i++) { /* echo from sink */ play[i] = &play_buf[i][0]; From b75ed93e51f6c04652e211998d74732975c16c42 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 13:35:07 +0200 Subject: [PATCH 0478/1014] pod: improve spa_pod_from_data() spa_pod_from_data() is now safe against integer overflow. --- spa/include/spa/pod/iter.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 19ed9823a..f77f47e70 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -129,10 +129,14 @@ SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_p SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) { void *pod; - if (size < sizeof(struct spa_pod) || offset + size > maxsize) + if (offset < 0 || offset > (int64_t)UINT32_MAX) + return NULL; + if (size < sizeof(struct spa_pod) || + size > maxsize || + maxsize - size < (uint32_t)offset) return NULL; pod = SPA_PTROFF(data, offset, void); - if (SPA_POD_SIZE(pod) > size) + if (SPA_POD_BODY_SIZE(pod) > size - sizeof(struct spa_pod)) return NULL; return pod; } From aa2289a25bf1d8130e22e3bc2569de946fdaca2b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 14:53:07 +0200 Subject: [PATCH 0479/1014] pod: check pod alignment Make a SPA_POD_ALIGN = 8 and make sure all pods are aligned to it. Use the new constant to pad and check alignment. Make some new macros to check for the pod type, alignment and minimal size. --- spa/include/spa/pod/builder.h | 2 +- spa/include/spa/pod/iter.h | 54 +++++++++++++++++------------------ spa/include/spa/pod/parser.h | 8 +++--- spa/include/spa/pod/pod.h | 4 ++- test/test-spa-pod.c | 22 ++++++++++++++ 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index f4b68fd63..f3277d40a 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -167,7 +167,7 @@ SPA_API_POD_BUILDER void spa_pod_builder_remove(struct spa_pod_builder *builder, SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) { uint64_t zeroes = 0; - size = SPA_ROUND_UP_N(size, 8) - size; + size = SPA_ROUND_UP_N(size, SPA_POD_ALIGN) - size; return size ? spa_pod_builder_raw(builder, &zeroes, size) : 0; } diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index f77f47e70..87c5b17a3 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -44,7 +44,7 @@ SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const vo SPA_API_POD_ITER void *spa_pod_next(const void *iter) { - return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_SIZE(iter), 8), void); + return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_SIZE(iter), SPA_POD_ALIGN), void); } SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_first(const struct spa_pod_object_body *body) @@ -63,7 +63,7 @@ SPA_API_POD_ITER bool spa_pod_prop_is_inside(const struct spa_pod_object_body *b SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) { - return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(iter), 8), struct spa_pod_prop); + return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(iter), SPA_POD_ALIGN), struct spa_pod_prop); } SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_first(const struct spa_pod_sequence_body *body) @@ -82,7 +82,7 @@ SPA_API_POD_ITER bool spa_pod_control_is_inside(const struct spa_pod_sequence_bo SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) { - return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(iter), 8), struct spa_pod_control); + return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(iter), SPA_POD_ALIGN), struct spa_pod_control); } #define SPA_POD_ARRAY_BODY_FOREACH(body, _size, iter) \ @@ -136,19 +136,25 @@ SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offse maxsize - size < (uint32_t)offset) return NULL; pod = SPA_PTROFF(data, offset, void); + if (!SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) + return NULL; if (SPA_POD_BODY_SIZE(pod) > size - sizeof(struct spa_pod)) return NULL; return pod; } +#define SPA_POD_CHECK_0(pod,_type) ((pod)->type == (_type) && SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) +#define SPA_POD_CHECK(pod,_type,_size) \ + (SPA_POD_CHECK_0(pod,_type) && (pod)->size >= (_size)) + SPA_API_POD_ITER int spa_pod_is_none(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_None); + return SPA_POD_CHECK_0(pod, SPA_TYPE_None); } SPA_API_POD_ITER int spa_pod_is_bool(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Bool && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) @@ -161,7 +167,7 @@ SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) SPA_API_POD_ITER int spa_pod_is_id(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Id && SPA_POD_BODY_SIZE(pod) >= sizeof(uint32_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); } SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) @@ -174,7 +180,7 @@ SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) SPA_API_POD_ITER int spa_pod_is_int(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Int && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); } SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) @@ -187,7 +193,7 @@ SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) SPA_API_POD_ITER int spa_pod_is_long(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Long && SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); } SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) @@ -200,7 +206,7 @@ SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) SPA_API_POD_ITER int spa_pod_is_float(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Float && SPA_POD_BODY_SIZE(pod) >= sizeof(float)); + return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); } SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) @@ -213,7 +219,7 @@ SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) SPA_API_POD_ITER int spa_pod_is_double(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Double && SPA_POD_BODY_SIZE(pod) >= sizeof(double)); + return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); } SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) @@ -227,9 +233,8 @@ SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value SPA_API_POD_ITER int spa_pod_is_string(const struct spa_pod *pod) { const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); - return (SPA_POD_TYPE(pod) == SPA_TYPE_String && - SPA_POD_BODY_SIZE(pod) > 0 && - s[SPA_POD_BODY_SIZE(pod)-1] == '\0'); + return SPA_POD_CHECK(pod, SPA_TYPE_String, 1) && + s[pod->size-1] == '\0'; } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) @@ -252,7 +257,7 @@ SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxle SPA_API_POD_ITER int spa_pod_is_bytes(const struct spa_pod *pod) { - return SPA_POD_TYPE(pod) == SPA_TYPE_Bytes; + return SPA_POD_CHECK_0(pod, SPA_TYPE_Bytes); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) @@ -266,8 +271,7 @@ SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **v SPA_API_POD_ITER int spa_pod_is_pointer(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Pointer && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_pointer_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); } SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) @@ -281,8 +285,7 @@ SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *ty SPA_API_POD_ITER int spa_pod_is_fd(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Fd && - SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); } SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) @@ -295,8 +298,7 @@ SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) SPA_API_POD_ITER int spa_pod_is_rectangle(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Rectangle && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_rectangle)); + return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); } SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) @@ -309,8 +311,7 @@ SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa SPA_API_POD_ITER int spa_pod_is_fraction(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Fraction && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_fraction)); + return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); } SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) @@ -322,14 +323,12 @@ SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_ SPA_API_POD_ITER int spa_pod_is_bitmap(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Bitmap && - SPA_POD_BODY_SIZE(pod) >= sizeof(uint8_t)); + return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); } SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Array && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_array_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) @@ -353,8 +352,7 @@ SPA_API_POD_ITER uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Choice && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_choice_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); } SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 922992e92..a3a5527bc 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -79,8 +79,8 @@ spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t si * Check that the pointer is aligned and that the size (rounded * to the next multiple of 8) is in bounds. */ - if (SPA_IS_ALIGNED(pod, __alignof__(struct spa_pod)) && - long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), 8) <= size) + if (SPA_IS_ALIGNED(pod, SPA_POD_ALIGN) && + long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), SPA_POD_ALIGN) <= size) return (struct spa_pod *)pod; } return NULL; @@ -110,7 +110,7 @@ SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_current(struct spa_pod_parser SPA_API_POD_PARSER void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod) { - parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), 8); + parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), SPA_POD_ALIGN); } SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) @@ -125,7 +125,7 @@ SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { parser->state.frame = frame->parent; - parser->state.offset = frame->offset + SPA_ROUND_UP_N(SPA_POD_SIZE(&frame->pod), 8); + parser->state.offset = frame->offset + SPA_ROUND_UP_N(SPA_POD_SIZE(&frame->pod), SPA_POD_ALIGN); return 0; } diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 398236e79..854a65abc 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -17,6 +17,8 @@ extern "C" { * \{ */ +#define SPA_POD_ALIGN 8 + #define SPA_POD_BODY_SIZE(pod) (((struct spa_pod*)(pod))->size) #define SPA_POD_TYPE(pod) (((struct spa_pod*)(pod))->type) #define SPA_POD_SIZE(pod) ((uint64_t)sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) @@ -30,7 +32,7 @@ extern "C" { struct spa_pod { uint32_t size; /* size of the body */ uint32_t type; /* a basic id of enum spa_type */ -}; +} SPA_ALIGNED(SPA_POD_ALIGN); #define SPA_POD_VALUE(type,pod) (((type*)(pod))->value) diff --git a/test/test-spa-pod.c b/test/test-spa-pod.c index e84b5a18e..f499e818b 100644 --- a/test/test-spa-pod.c +++ b/test/test-spa-pod.c @@ -524,6 +524,8 @@ PWTEST(pod_build) spa_assert_se(memcmp(&val.F, &SPA_FRACTION(25,1), sizeof(struct spa_fraction)) == 0); spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod)); + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size >= sizeof(struct spa_pod_array_body)); spa_assert_se(spa_pod_is_array(pod)); spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(pod) == SPA_TYPE_Int); spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(pod) == sizeof(int32_t)); @@ -542,6 +544,8 @@ PWTEST(pod_build) } spa_assert_se((pod = spa_pod_next(pod)) != NULL && spa_pod_is_inside(head, len, pod)); + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size >= sizeof(struct spa_pod_array_body)); spa_assert_se(spa_pod_is_array(pod)); spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(pod) == SPA_TYPE_Long); spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(pod) == sizeof(int64_t)); @@ -691,6 +695,10 @@ PWTEST(pod_empty) array = spa_pod_builder_pop(&b, &f); spa_assert_se(array != NULL); spa_debug_mem(0, array, 16); + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size == sizeof(struct spa_pod_array_body)); + spa_assert_se(SPA_POD_ARRAY_VALUE_TYPE(array) == SPA_TYPE_Id); + spa_assert_se(SPA_POD_ARRAY_VALUE_SIZE(array) == 4); spa_assert_se(spa_pod_is_array(array)); a2 = spa_pod_get_array(array, &n_vals); spa_assert_se(a2 != NULL); @@ -700,6 +708,8 @@ PWTEST(pod_empty) spa_assert_se(spa_pod_builder_push_array(&b, &f) == 0); array = spa_pod_builder_pop(&b, &f); spa_assert_se(array != NULL); + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size >= sizeof(struct spa_pod_array_body)); spa_assert_se(spa_pod_is_array(array)); a2 = spa_pod_get_array(array, &n_vals); spa_assert_se(a2 != NULL); @@ -710,6 +720,8 @@ PWTEST(pod_empty) spa_assert_se(spa_pod_builder_none(&b) == 0); array = spa_pod_builder_pop(&b, &f); spa_assert_se(array != NULL); + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size >= sizeof(struct spa_pod_array_body)); spa_assert_se(spa_pod_is_array(array)); a2 = spa_pod_get_array(array, &n_vals); spa_assert_se(a2 != NULL); @@ -718,6 +730,8 @@ PWTEST(pod_empty) spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_assert_se(spa_pod_builder_array(&b, 4, SPA_TYPE_Id, 0, NULL) == 0); array = (struct spa_pod*)buffer; + spa_assert_se(array->type == SPA_TYPE_Array); + spa_assert_se(array->size >= sizeof(struct spa_pod_array_body)); spa_assert_se(spa_pod_is_array(array)); a2 = spa_pod_get_array(array, &n_vals); spa_assert_se(a2 != NULL); @@ -730,6 +744,10 @@ PWTEST(pod_empty) choice = spa_pod_builder_pop(&b, &f); spa_assert_se(choice != NULL); spa_debug_mem(0, choice, 32); + spa_assert_se(choice->type == SPA_TYPE_Choice); + spa_assert_se(choice->size == sizeof(struct spa_pod_choice_body)); + spa_assert_se(SPA_POD_CHOICE_TYPE(choice) == SPA_CHOICE_None); + spa_assert_se(SPA_POD_CHOICE_CHILD(choice)->size == 4); spa_assert_se(spa_pod_is_choice(choice)); ch2 = spa_pod_get_values(choice, &n_vals, &ch); spa_assert_se(ch2 != NULL); @@ -739,6 +757,8 @@ PWTEST(pod_empty) spa_assert_se(spa_pod_builder_push_choice(&b, &f, 0, 0) == 0); choice = spa_pod_builder_pop(&b, &f); spa_assert_se(choice != NULL); + spa_assert_se(SPA_POD_CHOICE_TYPE(choice) == SPA_CHOICE_None); + spa_assert_se(SPA_POD_CHOICE_CHILD(choice)->size == 0); spa_assert_se(spa_pod_is_choice(choice)); ch2 = spa_pod_get_values(choice, &n_vals, &ch); spa_assert_se(ch2 != NULL); @@ -749,6 +769,8 @@ PWTEST(pod_empty) spa_assert_se(spa_pod_builder_none(&b) == 0); choice = spa_pod_builder_pop(&b, &f); spa_assert_se(choice != NULL); + spa_assert_se(SPA_POD_CHOICE_TYPE(choice) == SPA_CHOICE_None); + spa_assert_se(SPA_POD_CHOICE_CHILD(choice)->size == 0); spa_assert_se(spa_pod_is_choice(choice)); ch2 = spa_pod_get_values(choice, &n_vals, &ch); spa_assert_se(ch2 != NULL); From e4fcbef89a37a521552ac95a35af1bed0e131ec4 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 7 Jun 2025 15:21:37 -0400 Subject: [PATCH 0480/1014] pod: Improve type-safety in SPA POD code Use direct field access when the type is known, instead of a macro that includes a cast. --- spa/include/spa/debug/pod.h | 4 +--- spa/include/spa/pod/compare.h | 14 +++++++------- spa/include/spa/pod/filter.h | 10 +++++----- spa/include/spa/pod/pod.h | 8 ++++---- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index fe425676d..1dd1a86b9 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -186,9 +186,7 @@ SPA_API_DEBUG_POD int spa_debugc_pod(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *pod) { return spa_debugc_pod_value(ctx, indent, info ? info : SPA_TYPE_ROOT, - SPA_POD_TYPE(pod), - SPA_POD_BODY(pod), - SPA_POD_BODY_SIZE(pod)); + pod->type, SPA_POD_BODY(pod), pod->size); } SPA_API_DEBUG_POD int diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 12c65fd44..96de89d24 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -94,10 +94,10 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, if (n_vals1 != n_vals2) return -EINVAL; - if (SPA_POD_TYPE(pod1) != SPA_POD_TYPE(pod2)) + if (pod1->type != pod2->type) return -EINVAL; - switch (SPA_POD_TYPE(pod1)) { + switch (pod1->type) { case SPA_TYPE_Struct: { const struct spa_pod *p1, *p2; @@ -145,17 +145,17 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, } case SPA_TYPE_Array: { - if (SPA_POD_BODY_SIZE(pod1) != SPA_POD_BODY_SIZE(pod2)) + if (pod1->size != pod2->size) return -EINVAL; - res = memcmp(SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), SPA_POD_BODY_SIZE(pod2)); + res = memcmp(SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), pod2->size); break; } default: - if (SPA_POD_BODY_SIZE(pod1) != SPA_POD_BODY_SIZE(pod2)) + if (pod1->size != pod2->size) return -EINVAL; - res = spa_pod_compare_value(SPA_POD_TYPE(pod1), + res = spa_pod_compare_value(pod1->type, SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), - SPA_POD_BODY_SIZE(pod1)); + pod1->size); break; } return res; diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 5f94683fd..699bddc27 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -262,14 +262,14 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, uint32_t filter_offset = 0; struct spa_pod_frame f; - switch (SPA_POD_TYPE(pp)) { + switch (pp->type) { case SPA_TYPE_Object: if (pf != NULL) { struct spa_pod_object *op = (struct spa_pod_object *) pp; struct spa_pod_object *of = (struct spa_pod_object *) pf; const struct spa_pod_prop *p1, *p2; - if (SPA_POD_TYPE(pf) != SPA_POD_TYPE(pp)) + if (pf->type != pp->type) return -EINVAL; spa_pod_builder_push_object(b, &f, op->body.type, op->body.id); @@ -307,7 +307,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, case SPA_TYPE_Struct: if (pf != NULL) { - if (SPA_POD_TYPE(pf) != SPA_POD_TYPE(pp)) + if (pf->type != pp->type) return -EINVAL; filter_offset = sizeof(struct spa_pod_struct); @@ -326,9 +326,9 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, default: if (pf != NULL) { - if (SPA_POD_SIZE(pp) != SPA_POD_SIZE(pf)) + if (pp->size != pf->size) return -EINVAL; - if (memcmp(pp, pf, SPA_POD_SIZE(pp)) != 0) + if (memcmp(pp, pf, pp->size) != 0) return -EINVAL; do_advance = true; } diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 854a65abc..f2b5c766e 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -96,8 +96,8 @@ struct spa_pod_bitmap { }; #define SPA_POD_ARRAY_CHILD(arr) (&((struct spa_pod_array*)(arr))->body.child) -#define SPA_POD_ARRAY_VALUE_TYPE(arr) (SPA_POD_TYPE(SPA_POD_ARRAY_CHILD(arr))) -#define SPA_POD_ARRAY_VALUE_SIZE(arr) (SPA_POD_BODY_SIZE(SPA_POD_ARRAY_CHILD(arr))) +#define SPA_POD_ARRAY_VALUE_TYPE(arr) (SPA_POD_ARRAY_CHILD(arr)->type) +#define SPA_POD_ARRAY_VALUE_SIZE(arr) (SPA_POD_ARRAY_CHILD(arr)->size) #define SPA_POD_ARRAY_N_VALUES(arr) (SPA_POD_ARRAY_VALUE_SIZE(arr) ? ((SPA_POD_BODY_SIZE(arr) - sizeof(struct spa_pod_array_body)) / SPA_POD_ARRAY_VALUE_SIZE(arr)) : 0) #define SPA_POD_ARRAY_VALUES(arr) SPA_POD_CONTENTS(struct spa_pod_array, arr) @@ -114,8 +114,8 @@ struct spa_pod_array { #define SPA_POD_CHOICE_CHILD(choice) (&((struct spa_pod_choice*)(choice))->body.child) #define SPA_POD_CHOICE_TYPE(choice) (((struct spa_pod_choice*)(choice))->body.type) #define SPA_POD_CHOICE_FLAGS(choice) (((struct spa_pod_choice*)(choice))->body.flags) -#define SPA_POD_CHOICE_VALUE_TYPE(choice) (SPA_POD_TYPE(SPA_POD_CHOICE_CHILD(choice))) -#define SPA_POD_CHOICE_VALUE_SIZE(choice) (SPA_POD_BODY_SIZE(SPA_POD_CHOICE_CHILD(choice))) +#define SPA_POD_CHOICE_VALUE_TYPE(choice) (SPA_POD_CHOICE_CHILD(choice)->type) +#define SPA_POD_CHOICE_VALUE_SIZE(choice) (SPA_POD_CHOICE_CHILD(choice)->size) #define SPA_POD_CHOICE_N_VALUES(choice) (SPA_POD_CHOICE_VALUE_SIZE(choice) ? ((SPA_POD_BODY_SIZE(choice) - sizeof(struct spa_pod_choice_body)) / SPA_POD_CHOICE_VALUE_SIZE(choice)) : 0) #define SPA_POD_CHOICE_VALUES(choice) (SPA_POD_CONTENTS(struct spa_pod_choice, choice)) From 201147493686b6c5e49cd4c9e321a982d02754f7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 15:55:13 +0200 Subject: [PATCH 0481/1014] pod: improve type checks some more --- spa/include/spa/pod/iter.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 87c5b17a3..96145a9f7 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -357,10 +357,11 @@ SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) { - if (pod->type == SPA_TYPE_Choice) { + if (spa_pod_is_choice(pod)) { *n_vals = SPA_POD_CHOICE_N_VALUES(pod); - if ((*choice = SPA_POD_CHOICE_TYPE(pod)) == SPA_CHOICE_None) - *n_vals = SPA_MIN(1u, SPA_POD_CHOICE_N_VALUES(pod)); + *choice = SPA_POD_CHOICE_TYPE(pod); + if (*choice == SPA_CHOICE_None) + *n_vals = SPA_MIN(1u, *n_vals); return (struct spa_pod*)SPA_POD_CHOICE_CHILD(pod); } else { *n_vals = 1; @@ -371,13 +372,12 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, u SPA_API_POD_ITER int spa_pod_is_struct(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Struct); + return SPA_POD_CHECK_0(pod, SPA_TYPE_Struct); } SPA_API_POD_ITER int spa_pod_is_object(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Object && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_object_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); } SPA_API_POD_ITER bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type) @@ -392,8 +392,7 @@ SPA_API_POD_ITER bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t i SPA_API_POD_ITER int spa_pod_is_sequence(const struct spa_pod *pod) { - return (SPA_POD_TYPE(pod) == SPA_TYPE_Sequence && - SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_sequence_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); } SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod, From 8554c9d02a6b6c10d63d4054003a4030653e8084 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 18:27:37 +0200 Subject: [PATCH 0482/1014] pod: enforce max pod size Set a max pod size and add some more over and underflow checks --- spa/include/spa/pod/builder.h | 16 ++++++++++----- spa/include/spa/pod/iter.h | 38 ++++++++++++++++++++++------------- spa/include/spa/pod/pod.h | 1 + 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index f3277d40a..abfaa8aaa 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -65,6 +65,11 @@ spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builde *state = builder->state; } +SPA_API_POD_BUILDER bool spa_pod_builder_corrupted(const struct spa_pod_builder *builder) +{ + return builder->state.offset > builder->size; +} + SPA_API_POD_BUILDER void spa_pod_builder_set_callbacks(struct spa_pod_builder *builder, const struct spa_pod_builder_callbacks *callbacks, void *data) @@ -79,7 +84,7 @@ spa_pod_builder_reset(struct spa_pod_builder *builder, struct spa_pod_builder_st uint32_t size = builder->state.offset - state->offset; builder->state = *state; for (f = builder->state.frame; f ; f = f->parent) - f->pod.size -= size; + f->pod.size -= SPA_MIN(size, f->pod.size); } SPA_API_POD_BUILDER void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size) @@ -91,9 +96,10 @@ SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) { uint32_t size = builder->size; - if (offset + 8 <= size) { + if (offset + UINT64_C(8) <= size) { struct spa_pod *pod = SPA_PTROFF(builder->data, offset, struct spa_pod); - if (offset + SPA_POD_SIZE(pod) <= size) + if (offset + (uint64_t)SPA_POD_SIZE(pod) <= size && + SPA_POD_IS_VALID(pod)) return pod; } return NULL; @@ -159,9 +165,9 @@ SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, con SPA_API_POD_BUILDER void spa_pod_builder_remove(struct spa_pod_builder *builder, uint32_t size) { struct spa_pod_frame *f; - builder->state.offset -= size; + builder->state.offset -= SPA_MIN(size, builder->state.offset); for (f = builder->state.frame; f ; f = f->parent) - f->pod.size -= size; + f->pod.size -= SPA_MIN(size, f->pod.size); } SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 96145a9f7..872c5e593 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -34,12 +34,25 @@ struct spa_pod_frame { uint32_t flags; }; +#define SPA_POD_IS_VALID(pod) \ + (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE && \ + SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) + +#define SPA_POD_CHECK_TYPE(pod,_type) \ + (SPA_POD_IS_VALID(pod) && \ + (pod)->type == (_type)) + +#define SPA_POD_CHECK(pod,_type,_size) \ + (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) + + SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; return spa_ptr_type_inside(pod, size, iter, struct spa_pod, &remaining) && - remaining >= SPA_POD_BODY_SIZE(iter); + SPA_POD_IS_VALID((struct spa_pod*)iter) && + remaining >= SPA_POD_BODY_SIZE(iter); } SPA_API_POD_ITER void *spa_pod_next(const void *iter) @@ -58,7 +71,7 @@ SPA_API_POD_ITER bool spa_pod_prop_is_inside(const struct spa_pod_object_body *b size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_prop, &remaining) && - remaining >= iter->value.size; + SPA_POD_IS_VALID(&iter->value) && remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) @@ -77,7 +90,7 @@ SPA_API_POD_ITER bool spa_pod_control_is_inside(const struct spa_pod_sequence_bo size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_control, &remaining) && - remaining >= iter->value.size; + SPA_POD_IS_VALID(&iter->value) && remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) @@ -136,20 +149,15 @@ SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offse maxsize - size < (uint32_t)offset) return NULL; pod = SPA_PTROFF(data, offset, void); - if (!SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) + if (!SPA_POD_IS_VALID(pod)) return NULL; if (SPA_POD_BODY_SIZE(pod) > size - sizeof(struct spa_pod)) return NULL; return pod; } - -#define SPA_POD_CHECK_0(pod,_type) ((pod)->type == (_type) && SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) -#define SPA_POD_CHECK(pod,_type,_size) \ - (SPA_POD_CHECK_0(pod,_type) && (pod)->size >= (_size)) - SPA_API_POD_ITER int spa_pod_is_none(const struct spa_pod *pod) { - return SPA_POD_CHECK_0(pod, SPA_TYPE_None); + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_None); } SPA_API_POD_ITER int spa_pod_is_bool(const struct spa_pod *pod) @@ -257,7 +265,7 @@ SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxle SPA_API_POD_ITER int spa_pod_is_bytes(const struct spa_pod *pod) { - return SPA_POD_CHECK_0(pod, SPA_TYPE_Bytes); + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) @@ -328,7 +336,8 @@ SPA_API_POD_ITER int spa_pod_is_bitmap(const struct spa_pod *pod) SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) { - return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)) && + SPA_POD_IS_VALID(SPA_POD_ARRAY_CHILD(pod)); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) @@ -352,7 +361,8 @@ SPA_API_POD_ITER uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) { - return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); + return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)) && + SPA_POD_IS_VALID(SPA_POD_CHOICE_CHILD(pod)); } SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) @@ -372,7 +382,7 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, u SPA_API_POD_ITER int spa_pod_is_struct(const struct spa_pod *pod) { - return SPA_POD_CHECK_0(pod, SPA_TYPE_Struct); + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Struct); } SPA_API_POD_ITER int spa_pod_is_object(const struct spa_pod *pod) diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index f2b5c766e..63c2d4ee6 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -18,6 +18,7 @@ extern "C" { */ #define SPA_POD_ALIGN 8 +#define SPA_POD_MAX_SIZE (1u<<20) #define SPA_POD_BODY_SIZE(pod) (((struct spa_pod*)(pod))->size) #define SPA_POD_TYPE(pod) (((struct spa_pod*)(pod))->type) From a0f5c4153f50c9f7b6efb9d3edeb9faf27c74c08 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 3 Jul 2025 19:31:48 +0200 Subject: [PATCH 0483/1014] builder: avoid oveflow --- spa/include/spa/pod/builder.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index abfaa8aaa..2e3f5982a 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -135,8 +135,12 @@ SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, con struct spa_pod_frame *f; uint32_t offset = builder->state.offset; size_t data_offset = -1; + uint64_t total_size = offset + (uint64_t) size; + + if (total_size > builder->size) { + if (total_size > UINT32_MAX) + return -ENOSPC; - if (offset + size > builder->size) { /* data could be inside the data we will realloc */ if (spa_ptrinside(builder->data, builder->size, data, size, NULL)) data_offset = SPA_PTRDIFF(data, builder->data); @@ -145,7 +149,7 @@ SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, con if (offset <= builder->size) spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res, - overflow, 0, offset + size); + overflow, 0, total_size); } if (res == 0 && data) { if (data_offset != (size_t) -1) @@ -154,7 +158,7 @@ SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, con memcpy(SPA_PTROFF(builder->data, offset, void), data, size); } - builder->state.offset += size; + builder->state.offset = total_size; for (f = builder->state.frame; f ; f = f->parent) f->pod.size += size; From 5f4b4b02f95172ec8a9cd8e4cf899498042e3f8c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 7 Jul 2025 12:07:05 +0200 Subject: [PATCH 0484/1014] pod: remove the ALIGNED from the pod struct We don't really want to do this here otherwise structs that include a pod will be padded so that an array of those structs will be aligned. This makes a test case fail where we have a struct with a choice_body followed by 3 uint32_t enum values. The size with and without the aligned attribute is different. --- spa/include/spa/pod/pod.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 63c2d4ee6..4f87427da 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -33,7 +33,7 @@ extern "C" { struct spa_pod { uint32_t size; /* size of the body */ uint32_t type; /* a basic id of enum spa_type */ -} SPA_ALIGNED(SPA_POD_ALIGN); +}; #define SPA_POD_VALUE(type,pod) (((type*)(pod))->value) From deb7dddbef77965cff58a958c194e226425e451a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 7 Jul 2025 12:09:55 +0200 Subject: [PATCH 0485/1014] test: format float values with . Depending on the locale, the decimal separator can be , or . We need it to be . in all cases or else the checks for the expected value might fail. --- test/test-spa-json.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/test-spa-json.c b/test/test-spa-json.c index b07acc00d..66ef2eeac 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -882,8 +882,15 @@ static int validate_strict_json(struct spa_json *it, int depth, FILE *f) fprintf(f, "%d", v); } else if (spa_json_is_float(value, len)) { float v; - if (spa_json_parse_float(value, len, &v) > 0) - fprintf(f, "%G", v); + char float_str[64]; + if (spa_json_parse_float(value, len, &v) > 0) { + int i, l; + l = spa_scnprintf(float_str, sizeof(float_str), "%G", v); + for (i = 0; i < l; i++) + if (float_str[i] == ',') + float_str[i] = '.'; + fprintf(f, "%s", float_str); + } } else { /* bare value: error here, as we want to test * int/float/etc parsing */ From 8aa836d588f9b998bd5d825aacac1dde44b805bc Mon Sep 17 00:00:00 2001 From: Julien Massot Date: Fri, 4 Jul 2025 14:51:15 +0200 Subject: [PATCH 0486/1014] alsa-pcm: add support for api.alsa.dll-bandwidth-max In USB Audio Class 2 (UAC2) setups, pitch control is handled by feedback endpoints. The host adjusts its data rate accordingly. When pitch control is active (pitch_elem), applying the default delay-locked loop (DLL) bandwidth can lead to instability and oscillations around the target rate. This patch adds a new parameter, api.alsa.dll-bandwidth-max, to configure the maximum DLL bandwidth. It introduces a new field in the ALSA state to store this value. By default, it uses SPA_DLL_BW_MAX, but when pitch control is in use, setting it to a lower value (e.g. 0.02) helps ensure better stability, based on empirical testing. --- doc/dox/config/pipewire-props.7.md | 6 ++++++ spa/plugins/alsa/alsa-pcm.c | 8 ++++++-- spa/plugins/alsa/alsa-pcm.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 8d1cbd70c..e7ee8f848 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -778,6 +778,12 @@ Setting this to 0 makes htimestamp never get disabled. Disable timer-based scheduling, and use IRQ for scheduling instead. The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware. +@PAR@ node-prop api.alsa.dll-bandwidth-max # double +Sets the maximum bandwidth of the DLL (delay-locked loop) filter used to smooth out rate adjustments. +The default value may be too responsive in some scenarios. +For example, with UAC2 pitch control, the host reacts more slowly compared to local resampling, +so using a lower bandwidth helps avoid oscillations or instability. + @PAR@ node-prop api.alsa.auto-link = false # boolean Link follower PCM devices to the driver PCM device when using IRQ-based scheduling. The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware. diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 769e7443e..3e5e67b38 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -20,6 +20,7 @@ static struct spa_list cards = SPA_LIST_INIT(&cards); static struct spa_list states = SPA_LIST_INIT(&states); +#define SPA_ALSA_DLL_BW_MIN 0.001 static struct card *find_card(uint32_t index) { @@ -198,6 +199,9 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) state->disable_batch = spa_atob(s); } else if (spa_streq(k, "api.alsa.disable-tsched")) { state->disable_tsched = spa_atob(s); + } else if (spa_streq(k, "api.alsa.dll-bandwidth-max")) { + state->dll_bw_max = SPA_CLAMPD(spa_strtod(s, NULL), + SPA_ALSA_DLL_BW_MIN, SPA_DLL_BW_MAX); } else if (spa_streq(k, "api.alsa.use-chmap")) { state->props.use_chmap = spa_atob(s); } else if (spa_streq(k, "api.alsa.multi-rate")) { @@ -2799,7 +2803,7 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram int32_t diff; if (SPA_UNLIKELY(state->dll.bw == 0.0)) { - spa_dll_set_bw(&state->dll, SPA_DLL_BW_MAX, state->threshold, state->rate); + spa_dll_set_bw(&state->dll, state->dll_bw_max, state->threshold, state->rate); state->next_time = current_time; state->base_time = current_time; } @@ -2862,7 +2866,7 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram err, state->max_error, state->max_resync, state->err_avg, state->err_var, bw); spa_dll_set_bw(&state->dll, - SPA_CLAMPD(bw, 0.001, SPA_DLL_BW_MAX), + SPA_CLAMPD(bw, SPA_ALSA_DLL_BW_MIN, state->dll_bw_max), state->threshold, state->rate); } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 6fb4a3403..0d23fa2f6 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -244,6 +244,7 @@ struct state { uint64_t underrun; struct spa_dll dll; + double dll_bw_max; double max_error; double max_resync; double err_avg, err_var, err_wdw; From 289b33281fb089620a6a9edb200e880493176fd1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 7 Jul 2025 19:40:24 +0200 Subject: [PATCH 0487/1014] pod: check size before getting pod contents Before accessing the pod contents, check if the size is at least what we expect it to be or else we might read out of bounds. --- spa/include/spa/pod/compare.h | 18 ++++++++++++++++++ spa/include/spa/pod/filter.h | 12 +++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 96de89d24..1c043927e 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -39,23 +39,39 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con case SPA_TYPE_None: return 0; case SPA_TYPE_Bool: + if (size < sizeof(int32_t)) + return -EINVAL; return SPA_CMP(!!*(int32_t *)r1, !!*(int32_t *)r2); case SPA_TYPE_Id: + if (size < sizeof(uint32_t)) + return -EINVAL; return SPA_CMP(*(uint32_t *)r1, *(uint32_t *)r2); case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + return -EINVAL; return SPA_CMP(*(int32_t *)r1, *(int32_t *)r2); case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + return -EINVAL; return SPA_CMP(*(int64_t *)r1, *(int64_t *)r2); case SPA_TYPE_Float: + if (size < sizeof(float)) + return -EINVAL; return SPA_CMP(*(float *)r1, *(float *)r2); case SPA_TYPE_Double: + if (size < sizeof(double)) + return -EINVAL; return SPA_CMP(*(double *)r1, *(double *)r2); case SPA_TYPE_String: + if (size < sizeof(char)) + return -EINVAL; return strcmp((char *)r1, (char *)r2); case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; + if (size < sizeof(struct spa_rectangle)) + return -EINVAL; if (rec1->width == rec2->width && rec1->height == rec2->height) return 0; else if (rec1->width < rec2->width || rec1->height < rec2->height) @@ -68,6 +84,8 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con const struct spa_fraction *f1 = (struct spa_fraction *) r1, *f2 = (struct spa_fraction *) r2; uint64_t n1, n2; + if (size < sizeof(struct spa_fraction)) + return -EINVAL; n1 = ((uint64_t) f1->num) * f2->denom; n2 = ((uint64_t) f2->num) * f1->denom; return SPA_CMP(n1, n2); diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 699bddc27..6c92e910d 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -35,12 +35,15 @@ extern "C" { */ SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, - uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) + uint32_t type, const void *r1, const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_Int: { - int32_t val = (*(int32_t *) r1) & (*(int32_t *) r2); + int32_t val; + if (size < sizeof(int32_t)) + return -EINVAL; + val = (*(int32_t *) r1) & (*(int32_t *) r2); if (val == 0) return 0; spa_pod_builder_int(b, val); @@ -48,7 +51,10 @@ SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, } case SPA_TYPE_Long: { - int64_t val = (*(int64_t *) r1) & (*(int64_t *) r2); + int64_t val; + if (size < sizeof(int64_t)) + return -EINVAL; + val = (*(int64_t *) r1) & (*(int64_t *) r2); if (val == 0) return 0; spa_pod_builder_long(b, val); From ce2f9eebb42a8f48d5b35fcc1bf53fedd2e8627e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 8 Jul 2025 10:06:51 +0200 Subject: [PATCH 0488/1014] pod: avoid checking size or alignment For the embedded children, they will always be aligned. We can also avoid the max size checks for children because this is already checked for the parent and with the remaining size check. For arrays and choice we simply don't get any elements in the array when the sizes are too large. --- spa/include/spa/pod/iter.h | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 872c5e593..567dd4a81 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -45,14 +45,12 @@ struct spa_pod_frame { #define SPA_POD_CHECK(pod,_type,_size) \ (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) - SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; return spa_ptr_type_inside(pod, size, iter, struct spa_pod, &remaining) && - SPA_POD_IS_VALID((struct spa_pod*)iter) && - remaining >= SPA_POD_BODY_SIZE(iter); + SPA_IS_ALIGNED(iter, SPA_POD_ALIGN) && remaining >= SPA_POD_BODY_SIZE(iter); } SPA_API_POD_ITER void *spa_pod_next(const void *iter) @@ -71,7 +69,7 @@ SPA_API_POD_ITER bool spa_pod_prop_is_inside(const struct spa_pod_object_body *b size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_prop, &remaining) && - SPA_POD_IS_VALID(&iter->value) && remaining >= iter->value.size; + remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) @@ -90,7 +88,7 @@ SPA_API_POD_ITER bool spa_pod_control_is_inside(const struct spa_pod_sequence_bo size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_control, &remaining) && - SPA_POD_IS_VALID(&iter->value) && remaining >= iter->value.size; + remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) @@ -138,7 +136,6 @@ SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_p #define SPA_POD_SEQUENCE_FOREACH(seq, iter) \ SPA_POD_SEQUENCE_BODY_FOREACH(&(seq)->body, SPA_POD_BODY_SIZE(seq), iter) - SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) { void *pod; @@ -336,8 +333,7 @@ SPA_API_POD_ITER int spa_pod_is_bitmap(const struct spa_pod *pod) SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) { - return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)) && - SPA_POD_IS_VALID(SPA_POD_ARRAY_CHILD(pod)); + return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) @@ -361,11 +357,11 @@ SPA_API_POD_ITER uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) { - return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)) && - SPA_POD_IS_VALID(SPA_POD_CHOICE_CHILD(pod)); + return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); } -SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) +SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, + uint32_t *n_vals, uint32_t *choice) { if (spa_pod_is_choice(pod)) { *n_vals = SPA_POD_CHOICE_N_VALUES(pod); From d3eb06ab74baad44c35d3ebedefa66c2aa4f56f1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 8 Jul 2025 10:11:11 +0200 Subject: [PATCH 0489/1014] pod: improve array copy function Make a new function to also returnt he child size and type. Make a new function that accepts the array item size. Check that the array item size and destination item size match before memcpy the array contents. This avoids overflowing the target array with a malformed array pod. --- spa/include/spa/pod/iter.h | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 567dd4a81..0dcf514f8 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -336,25 +336,36 @@ SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } -SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) +SPA_API_POD_ITER void *spa_pod_get_array_full(const struct spa_pod *pod, uint32_t *n_values, + uint32_t *val_size, uint32_t *val_type) { spa_return_val_if_fail(spa_pod_is_array(pod), NULL); *n_values = SPA_POD_ARRAY_N_VALUES(pod); + *val_size = SPA_POD_ARRAY_VALUE_SIZE(pod); + *val_type = SPA_POD_ARRAY_VALUE_TYPE(pod); return SPA_POD_ARRAY_VALUES(pod); } - -SPA_API_POD_ITER uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t type, - void *values, uint32_t max_values) +SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) { - uint32_t n_values; - void *v = spa_pod_get_array(pod, &n_values); - if (v == NULL || max_values == 0 || SPA_POD_ARRAY_VALUE_TYPE(pod) != type) + uint32_t size, type; + return spa_pod_get_array_full(pod, n_values, &size, &type); +} + +SPA_API_POD_ITER uint32_t spa_pod_copy_array_full(const struct spa_pod *pod, uint32_t type, + uint32_t size, void *values, uint32_t max_values) +{ + uint32_t n_values, val_size, val_type; + void *v = spa_pod_get_array_full(pod, &n_values, &val_size, &val_type); + if (v == NULL || max_values == 0 || val_type != type || val_size != size) return 0; n_values = SPA_MIN(n_values, max_values); - memcpy(values, v, SPA_POD_ARRAY_VALUE_SIZE(pod) * n_values); + memcpy(values, v, val_size * n_values); return n_values; } +#define spa_pod_copy_array(pod,type,values,max_values) \ + spa_pod_copy_array_full(pod,type,sizeof(values[0]),values,max_values) + SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); From 2581575bd1802f8b08dff38bd7fc169366914d36 Mon Sep 17 00:00:00 2001 From: Albert Sjolund Date: Tue, 8 Jul 2025 13:07:44 +0200 Subject: [PATCH 0490/1014] gst: fix leak in sink_update_params buffer_pool_get_config returns a copy of the config structure, but it is never freed in this function. Add a gst_structure_free to fix the leak --- src/gst/gstpipewiresink.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index c7ac851ae..58a6cd8db 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -363,6 +363,8 @@ gst_pipewire_sink_update_params (GstPipeWireSink *sink) pw_thread_loop_lock (sink->stream->core->loop); pw_stream_update_params (sink->stream->pwstream, port_params, n_params); pw_thread_loop_unlock (sink->stream->core->loop); + + gst_structure_free (config); } static void From 80d44e8f39fcea57913eb347f34ddff19e0fc1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Thu, 3 Jul 2025 10:01:51 +0200 Subject: [PATCH 0491/1014] bluez5: backend-native: Fix incorrect dial number management When dialing an incorrect phone number some phones (e.g. iOS 18.5) replies with OK but never send +CIEV updates, so there's no way to know that the dial is not in progress and the call object should be removed. This change waits for +CIEV event to create the call object. --- spa/plugins/bluez5/backend-native.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 17288bcbc..7aeefdc9f 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -222,6 +222,7 @@ struct rfcomm { struct spa_bt_telephony_ag *telephony_ag; struct spa_list hfp_hf_commands; struct spa_list updated_call_list; + char *dialing_number; #endif }; @@ -1623,9 +1624,10 @@ static void hfp_hf_dial(void *data, const char *number, enum spa_bt_telephony_er rfcomm_send_cmd(rfcomm, "ATD%s;", number); res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); if (res && spa_strstartswith(reply, "OK")) { - struct spa_bt_telephony_call *call; - call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, number); - *err = call ? BT_TELEPHONY_ERROR_NONE : BT_TELEPHONY_ERROR_FAILED; + if (rfcomm->dialing_number) + free(rfcomm->dialing_number); + rfcomm->dialing_number = strdup(number); + *err = BT_TELEPHONY_ERROR_NONE; } else { spa_log_info(backend->log, "Failed to dial: \"%s\"", number); if (res) @@ -2260,8 +2262,12 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (!found && !rfcomm->hfp_hf_clcc) { spa_log_info(backend->log, "Dialing call"); - if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, NULL) == NULL) + if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, rfcomm->dialing_number) == NULL) spa_log_warn(backend->log, "failed to create dialing call"); + if (rfcomm->dialing_number) { + free(rfcomm->dialing_number); + rfcomm->dialing_number = NULL; + } } } else if (value == CIND_CALLSETUP_ALERTING) { struct spa_bt_telephony_call *call; @@ -2478,6 +2484,11 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_log_warn(backend->log, "failed to create call"); else if (call->id != idx) spa_log_warn(backend->log, "wrong call index: %d, expected: %d", call->id, idx); + + if (spa_streq(number, rfcomm->dialing_number)) { + free(rfcomm->dialing_number); + rfcomm->dialing_number = NULL; + } } } else { spa_log_warn(backend->log, "malformed +CLCC command received from AG"); From 9debb4b814f2901a35ad5013dbe90ba63625a74b Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sun, 6 Jul 2025 21:27:33 +0200 Subject: [PATCH 0492/1014] gst: pipewireformat: Validate fourcc before converting to string gst_video_dma_drm_fourcc_to_string() asserts when called with DRM_FORMAT_INVALID. --- src/gst/gstpipewireformat.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 46d9a228e..0640acc64 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -881,6 +881,8 @@ static char *video_id_to_dma_drm_fourcc(uint32_t id, uint64_t mod) if ((idx = find_index(video_format_map, SPA_N_ELEMENTS(video_format_map), id)) == -1) return NULL; fourcc = gst_video_dma_drm_fourcc_from_format(idx); + if (fourcc == DRM_FORMAT_INVALID) + return NULL; return gst_video_dma_drm_fourcc_to_string(fourcc, mod); } #endif From 0efd0258a7e48b9bd0f6c47526e8bdb039cac483 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 8 Jul 2025 16:16:47 +0200 Subject: [PATCH 0493/1014] filter-graph: rename the plugin files Move the plugin prefix to the front like everywhere else and this also makes it easier to spot the plugins when listing the directory. --- spa/plugins/filter-graph/meson.build | 12 ++++++------ .../{builtin_plugin.c => plugin_builtin.c} | 0 .../{ebur128_plugin.c => plugin_ebur128.c} | 0 .../{ffmpeg_plugin.c => plugin_ffmpeg.c} | 0 .../{ladspa_plugin.c => plugin_ladspa.c} | 0 .../filter-graph/{lv2_plugin.c => plugin_lv2.c} | 0 .../filter-graph/{sofa_plugin.c => plugin_sofa.c} | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename spa/plugins/filter-graph/{builtin_plugin.c => plugin_builtin.c} (100%) rename spa/plugins/filter-graph/{ebur128_plugin.c => plugin_ebur128.c} (100%) rename spa/plugins/filter-graph/{ffmpeg_plugin.c => plugin_ffmpeg.c} (100%) rename spa/plugins/filter-graph/{ladspa_plugin.c => plugin_ladspa.c} (100%) rename spa/plugins/filter-graph/{lv2_plugin.c => plugin_lv2.c} (100%) rename spa/plugins/filter-graph/{sofa_plugin.c => plugin_sofa.c} (100%) diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build index 1968d8a48..715fa7cfa 100644 --- a/spa/plugins/filter-graph/meson.build +++ b/spa/plugins/filter-graph/meson.build @@ -67,7 +67,7 @@ filter_graph_dependencies = [ ] spa_filter_graph_plugin_builtin = shared_library('spa-filter-graph-plugin-builtin', - [ 'builtin_plugin.c', + [ 'plugin_builtin.c', 'convolver.c' ], include_directories : [configinc], install : true, @@ -77,7 +77,7 @@ spa_filter_graph_plugin_builtin = shared_library('spa-filter-graph-plugin-builti ) spa_filter_graph_plugin_ladspa = shared_library('spa-filter-graph-plugin-ladspa', - [ 'ladspa_plugin.c' ], + [ 'plugin_ladspa.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', @@ -86,7 +86,7 @@ spa_filter_graph_plugin_ladspa = shared_library('spa-filter-graph-plugin-ladspa' if libmysofa_dep.found() spa_filter_graph_plugin_sofa = shared_library('spa-filter-graph-plugin-sofa', - [ 'sofa_plugin.c', + [ 'plugin_sofa.c', 'convolver.c' ], include_directories : [configinc], install : true, @@ -97,7 +97,7 @@ endif if lilv_lib.found() spa_filter_graph_plugin_lv2 = shared_library('spa-filter-graph-plugin-lv2', - [ 'lv2_plugin.c' ], + [ 'plugin_lv2.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', @@ -107,7 +107,7 @@ endif if ebur128_lib.found() spa_filter_graph_plugin_ebur128 = shared_library('spa-filter-graph-plugin-ebur128', - [ 'ebur128_plugin.c' ], + [ 'plugin_ebur128.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', @@ -117,7 +117,7 @@ endif if avfilter_dep.found() spa_filter_graph_plugin_ffmpeg = shared_library('spa-filter-graph-plugin-ffmpeg', - [ 'ffmpeg_plugin.c' ], + [ 'plugin_ffmpeg.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/plugin_builtin.c similarity index 100% rename from spa/plugins/filter-graph/builtin_plugin.c rename to spa/plugins/filter-graph/plugin_builtin.c diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/plugin_ebur128.c similarity index 100% rename from spa/plugins/filter-graph/ebur128_plugin.c rename to spa/plugins/filter-graph/plugin_ebur128.c diff --git a/spa/plugins/filter-graph/ffmpeg_plugin.c b/spa/plugins/filter-graph/plugin_ffmpeg.c similarity index 100% rename from spa/plugins/filter-graph/ffmpeg_plugin.c rename to spa/plugins/filter-graph/plugin_ffmpeg.c diff --git a/spa/plugins/filter-graph/ladspa_plugin.c b/spa/plugins/filter-graph/plugin_ladspa.c similarity index 100% rename from spa/plugins/filter-graph/ladspa_plugin.c rename to spa/plugins/filter-graph/plugin_ladspa.c diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/plugin_lv2.c similarity index 100% rename from spa/plugins/filter-graph/lv2_plugin.c rename to spa/plugins/filter-graph/plugin_lv2.c diff --git a/spa/plugins/filter-graph/sofa_plugin.c b/spa/plugins/filter-graph/plugin_sofa.c similarity index 100% rename from spa/plugins/filter-graph/sofa_plugin.c rename to spa/plugins/filter-graph/plugin_sofa.c From 85ff73d69031282e4ecc0367cf9ae3d0c3b1a921 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Wed, 9 Jul 2025 10:17:23 +0200 Subject: [PATCH 0494/1014] echo-cancel: reset buffers when deactivating Reset buffers when deactivating to avoid having old data in the ringbuffers, which also adds latency when activated again. Clear sink_ready and capture_ready when resetting buffers to avoid calling process() before there is new data to process. --- src/modules/module-echo-cancel.c | 51 ++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 174c8173c..113a89cea 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -458,6 +458,31 @@ done: impl->capture_ready = false; } +static void reset_buffers(struct impl *impl) +{ + uint32_t index, i; + + spa_ringbuffer_init(&impl->rec_ring); + spa_ringbuffer_init(&impl->play_ring); + spa_ringbuffer_init(&impl->play_delayed_ring); + spa_ringbuffer_init(&impl->out_ring); + + for (i = 0; i < impl->rec_info.channels; i++) + memset(impl->rec_buffer[i], 0, impl->rec_ringsize); + for (i = 0; i < impl->play_info.channels; i++) + memset(impl->play_buffer[i], 0, impl->play_ringsize); + for (i = 0; i < impl->out_info.channels; i++) + memset(impl->out_buffer[i], 0, impl->out_ringsize); + + spa_ringbuffer_get_write_index(&impl->play_ring, &index); + spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); + spa_ringbuffer_get_read_index(&impl->play_ring, &index); + spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); + + impl->sink_ready = false; + impl->capture_ready = false; +} + static void capture_destroy(void *d) { struct impl *impl = d; @@ -542,6 +567,8 @@ static void capture_state_changed(void *data, enum pw_stream_state old, if (old == PW_STREAM_STATE_STREAMING) { if (pw_stream_get_state(impl->sink, NULL) != PW_STREAM_STATE_STREAMING) { + reset_buffers(impl); + pw_log_debug("%p: deactivate %s", impl, impl->aec->name); res = spa_audio_aec_deactivate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { @@ -593,28 +620,6 @@ static void source_state_changed(void *data, enum pw_stream_state old, } } -static void reset_buffers(struct impl *impl) -{ - uint32_t index, i; - - spa_ringbuffer_init(&impl->rec_ring); - spa_ringbuffer_init(&impl->play_ring); - spa_ringbuffer_init(&impl->play_delayed_ring); - spa_ringbuffer_init(&impl->out_ring); - - for (i = 0; i < impl->rec_info.channels; i++) - memset(impl->rec_buffer[i], 0, impl->rec_ringsize); - for (i = 0; i < impl->play_info.channels; i++) - memset(impl->play_buffer[i], 0, impl->play_ringsize); - for (i = 0; i < impl->out_info.channels; i++) - memset(impl->out_buffer[i], 0, impl->out_ringsize); - - spa_ringbuffer_get_write_index(&impl->play_ring, &index); - spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); - spa_ringbuffer_get_read_index(&impl->play_ring, &index); - spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); -} - static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; @@ -797,6 +802,8 @@ static void sink_state_changed(void *data, enum pw_stream_state old, impl->current_delay = 0; if (pw_stream_get_state(impl->capture, NULL) != PW_STREAM_STATE_STREAMING) { + reset_buffers(impl); + pw_log_debug("%p: deactivate %s", impl, impl->aec->name); res = spa_audio_aec_deactivate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { From 241147968ee44da5dc24c19992366ca982e519e8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 9 Jul 2025 14:11:07 +0200 Subject: [PATCH 0495/1014] doc: add FOSDEM talk --- doc/dox/index.dox | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/dox/index.dox b/doc/dox/index.dox index 6688ca161..a90e19cd8 100644 --- a/doc/dox/index.dox +++ b/doc/dox/index.dox @@ -48,5 +48,6 @@ See \ref page_api. - [Bluetooth, PipeWire and Whatsapp calls](https://gjhenrique.com/pipewire.html) - [Intoduction to PipeWire](https://bootlin.com/blog/an-introduction-to-pipewire/) - [A custom PipeWire node](https://bootlin.com/blog/a-custom-pipewire-node/) +- [FOSDEM 2025 talk and slides](https://fosdem.org/2025/schedule/event/fosdem-2025-4749-pipewire-state-of-the-union/) */ From e8dbd328d8d15937d79a84f515abc06950843630 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 9 Jul 2025 14:13:56 +0200 Subject: [PATCH 0496/1014] doc: fix a typo --- src/modules/module-filter-chain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index b00130964..262ad0c51 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -801,7 +801,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * The `filtergraph` plugin will automatically add format converters when the input * port channel-layout, format or graph sample-rates don't match. * - * Note that the FFmpeg filtergraph is not Real-time safe because it might does + * Note that the FFmpeg filtergraph is not Real-time safe because it might do * allocations from the processing thread. It is advised to run the filter-chain * streams in async mode (`node.async = true`) to avoid interrupting the other * RT threads. From 8f429ac04b1ed97f59541b78d6e7c9799a959a0e Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Jul 2025 08:52:55 -0400 Subject: [PATCH 0497/1014] spa: aec: webrtc: Actually enable echo cancellation for 2.0 --- spa/plugins/aec/aec-webrtc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 74255aae4..e20991d5b 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -191,6 +191,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args, config.voice_detection.enabled = voice_detection; #elif defined(HAVE_WEBRTC2) webrtc::AudioProcessing::Config config; + config.echo_canceller.enabled = true; config.pipeline.multi_channel_capture = rec_info->channels > 1; config.pipeline.multi_channel_render = play_info->channels > 1; // FIXME: Example code enables both gain controllers, but that seems sus From a328e0ae282930563a2532a358c3c6b7502cd335 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Jul 2025 07:55:09 -0400 Subject: [PATCH 0498/1014] spa: audioconvert: Avoid reading past filter-graph param name end Ensure we have at least a `.` after `audioconvert.filter-graph`, so we don't try to read past the end if it does not exist. Also document in the param name that an index is expected. --- spa/plugins/audioconvert/audioconvert.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e7414a82b..743feb765 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -818,7 +818,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, case 29: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph"), + SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.N"), SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"), SPA_PROP_INFO_type, SPA_POD_String(""), SPA_PROP_INFO_params, SPA_POD_Bool(true)); @@ -1448,8 +1448,8 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char * } else if (spa_streq(k, "channelmix.lock-volumes")) this->props.lock_volumes = spa_atob(s); - else if (spa_strstartswith(k, "audioconvert.filter-graph")) { - int order = atoi(k+ strlen("audioconvert.filter-graph.")); + else if (spa_strstartswith(k, "audioconvert.filter-graph.")) { + int order = atoi(k + strlen("audioconvert.filter-graph.")); if ((res = load_filter_graph(this, s, order)) < 0) { spa_log_warn(this->log, "Can't load filter-graph %d: %s", order, spa_strerror(res)); From 477674740e007590c5c3511f743dfab9b06134a7 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Jul 2025 12:48:24 -0400 Subject: [PATCH 0499/1014] spa: aec: webrtc: Drop outdated comment The Intelligibility enhancer was removed, so the FIXMEs are irrelevant. --- spa/plugins/aec/aec-webrtc.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index e20991d5b..6ad3a92e0 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -124,10 +124,6 @@ static int webrtc_init2(void *object, const struct spa_dict *args, // result in very poor performance, disable by default bool gain_control = webrtc_get_spa_bool(args, "webrtc.gain_control", false); - // FIXME: Intelligibility enhancer is not currently supported - // This filter will modify playback buffer (when calling ProcessReverseStream), but now - // playback buffer modifications are discarded. - #if defined(HAVE_WEBRTC) webrtc::Config config; config.Set(new webrtc::ExtendedFilter(extended_filter)); @@ -309,9 +305,6 @@ static int webrtc_run(void *object, const float *rec[], const float *play[], flo for (size_t j = 0; j < impl->out_info.channels; j++) impl->out_buffer[j] = out[j] + out_config.num_frames() * i; - /* FIXME: ProcessReverseStream may change the playback buffer, in which - * case we should use that, if we ever expose the intelligibility - * enhancer */ if ((res = impl->apm->ProcessReverseStream(impl->play_buffer.get(), play_config, play_config, impl->play_buffer.get())) != webrtc::AudioProcessing::kNoError) { From eec1ac20b767900a5436cb791108b12b75dcdd18 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Jul 2025 12:51:29 -0400 Subject: [PATCH 0500/1014] spa: aec: webrtc: Expose echo canceller mobile_mode Significantly better CPU performance in lieu of canceller quality. Not implemented for 0.x series, as there's a lot more to enable there (such as routing modes), and I am hoping to drop support for those versions before too long. --- spa/plugins/aec/aec-webrtc.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 6ad3a92e0..7b8cafcde 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -119,6 +119,9 @@ static int webrtc_init2(void *object, const struct spa_dict *args, #elif defined(HAVE_WEBRTC1) bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); bool transient_suppression = webrtc_get_spa_bool(args, "webrtc.transient_suppression", true); + bool mobile_mode = webrtc_get_spa_bool(args, "webrtc.mobile_mode", false); +#elif defined(HAVE_WEBRTC2) + bool mobile_mode = webrtc_get_spa_bool(args, "webrtc.mobile_mode", false); #endif // Note: AGC seems to mess up with Agnostic Delay Detection, especially with speech, // result in very poor performance, disable by default @@ -171,6 +174,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args, #elif defined(HAVE_WEBRTC1) webrtc::AudioProcessing::Config config; config.echo_canceller.enabled = true; + config.echo_canceller.mobile_mode = mobile_mode; config.pipeline.multi_channel_capture = rec_info->channels > 1; config.pipeline.multi_channel_render = play_info->channels > 1; // FIXME: Example code enables both gain controllers, but that seems sus @@ -188,6 +192,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args, #elif defined(HAVE_WEBRTC2) webrtc::AudioProcessing::Config config; config.echo_canceller.enabled = true; + config.echo_canceller.mobile_mode = mobile_mode; config.pipeline.multi_channel_capture = rec_info->channels > 1; config.pipeline.multi_channel_render = play_info->channels > 1; // FIXME: Example code enables both gain controllers, but that seems sus From 5ca74996f0733b0b462926278536cc184f07438c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 10 Jul 2025 13:40:37 +0200 Subject: [PATCH 0501/1014] filter-graph: allow setting a fixed rate It's possible to make the filter streams resample to a specific rate before running the graph. --- src/modules/module-filter-chain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 262ad0c51..263767068 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1718,6 +1718,7 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, + SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_POSITION, NULL); } From b4f97c62c696a387a5c1ff34c13d5c082ccb7321 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 10 Jul 2025 13:42:38 +0200 Subject: [PATCH 0502/1014] filter-graph: support more complicated labels If we see a container as the label, copy the whole container and use that as the label. The label is used to construct a plugin description. --- spa/plugins/filter-graph/filter-graph.c | 55 ++++++++++++------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index ed90f48e4..4bcf889b7 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -67,7 +67,7 @@ struct descriptor { struct spa_list link; int ref; struct plugin *plugin; - char label[256]; + char *label; const struct spa_fga_descriptor *desc; @@ -911,6 +911,7 @@ static void descriptor_unref(struct descriptor *desc) if (desc->desc) spa_fga_descriptor_free(desc->desc); plugin_unref(desc->plugin); + free(desc->label); free(desc->input); free(desc->output); free(desc->control); @@ -954,12 +955,12 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, spa_list_init(&desc->link); if ((d = spa_fga_plugin_make_desc(pl->plugin, label)) == NULL) { - spa_log_error(impl->log, "cannot find label %s", label); + spa_log_error(impl->log, "cannot create label %s", label); res = -ENOENT; goto exit; } desc->desc = d; - snprintf(desc->label, sizeof(desc->label), "%s", label); + desc->label = strdup(label); n_input = n_output = n_control = n_notify = 0; for (p = 0; p < d->n_ports; p++) { @@ -1035,38 +1036,36 @@ exit: * ... * } */ -static int parse_config(struct node *node, struct spa_json *config) +static char *copy_value(struct impl *impl, struct spa_json *value) { - const char *val, *s = config->cur; - struct impl *impl = node->graph->impl; - int res = 0, len; + const char *val, *s = value->cur; + int len; struct spa_error_location loc; + char *result = NULL; - if ((len = spa_json_next(config, &val)) <= 0) { - res = -EINVAL; + if ((len = spa_json_next(value, &val)) <= 0) { + errno = EINVAL; goto done; } if (spa_json_is_null(val, len)) goto done; if (spa_json_is_container(val, len)) { - len = spa_json_container_len(config, val, len); + len = spa_json_container_len(value, val, len); if (len == 0) { - res = -EINVAL; + errno = EINVAL; goto done; } } - if ((node->config = malloc(len+1)) == NULL) { - res = -errno; + if ((result = malloc(len+1)) == NULL) goto done; - } - spa_json_parse_stringn(val, len, node->config, len+1); + spa_json_parse_stringn(val, len, result, len+1); done: - if (spa_json_get_error(config, s, &loc)) + if (spa_json_get_error(value, s, &loc)) spa_debug_log_error_location(impl->log, SPA_LOG_LEVEL_WARN, &loc, "error: %s", loc.reason); - return res; + return result; } /** @@ -1296,7 +1295,7 @@ static int parse_volume(struct graph *graph, struct spa_json *json, enum spa_dir static int load_node(struct graph *graph, struct spa_json *json) { struct impl *impl = graph->impl; - struct spa_json control, config; + struct spa_json control, it; struct descriptor *desc; struct node *node; const char *val; @@ -1304,11 +1303,11 @@ static int load_node(struct graph *graph, struct spa_json *json) char type[256] = ""; char name[256] = ""; char plugin[256] = ""; - char label[256] = ""; + spa_autofree char *label = NULL; + spa_autofree char *config = NULL; bool have_control = false; - bool have_config = false; uint32_t i; - int res, len; + int len; while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { if (spa_streq("type", key)) { @@ -1327,8 +1326,9 @@ static int load_node(struct graph *graph, struct spa_json *json) return -EINVAL; } } else if (spa_streq("label", key)) { - if (spa_json_parse_stringn(val, len, label, sizeof(label)) <= 0) { - spa_log_error(impl->log, "label expects a string"); + it = SPA_JSON_START(json, val); + if ((label = copy_value(impl, &it)) == NULL) { + spa_log_warn(impl->log, "error parsing label: %s", spa_strerror(-errno)); return -EINVAL; } } else if (spa_streq("control", key)) { @@ -1339,8 +1339,9 @@ static int load_node(struct graph *graph, struct spa_json *json) spa_json_enter(json, &control); have_control = true; } else if (spa_streq("config", key)) { - config = SPA_JSON_START(json, val); - have_config = true; + it = SPA_JSON_START(json, val); + if ((config = copy_value(impl, &it)) == NULL) + spa_log_warn(impl->log, "error parsing config: %s", spa_strerror(-errno)); } else { spa_log_warn(impl->log, "unexpected node key '%s'", key); } @@ -1365,6 +1366,7 @@ static int load_node(struct graph *graph, struct spa_json *json) node->desc = desc; snprintf(node->name, sizeof(node->name), "%s", name); node->latency_index = SPA_IDX_INVALID; + node->config = spa_steal_ptr(config); node->input_port = calloc(desc->n_input, sizeof(struct port)); node->output_port = calloc(desc->n_output, sizeof(struct port)); @@ -1410,9 +1412,6 @@ static int load_node(struct graph *graph, struct spa_json *json) node->latency_index = i; spa_list_init(&port->link_list); } - if (have_config) - if ((res = parse_config(node, &config)) < 0) - spa_log_warn(impl->log, "error parsing config: %s", spa_strerror(res)); if (have_control) parse_control(node, &control); From 4a92ec35a6ba8b26f672c01b525c97ab0a3e1889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 9 Jul 2025 18:16:08 +0200 Subject: [PATCH 0503/1014] spa: debug: pod: use the appropriate format specifiers Use the appropriate format specifiers wrt. the signedness of the type, as well as use the `PRI*N` macros for the `[u]intN_t` types. --- spa/include/spa/debug/pod.h | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index 1dd1a86b9..bec620b48 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -5,6 +5,8 @@ #ifndef SPA_DEBUG_POD_H #define SPA_DEBUG_POD_H +#include + #include #include #include @@ -37,11 +39,11 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa spa_debugc(ctx, "%*s" "Bool %s", indent, "", (*(int32_t *) body) ? "true" : "false"); break; case SPA_TYPE_Id: - spa_debugc(ctx, "%*s" "Id %-8d (%s)", indent, "", *(int32_t *) body, + spa_debugc(ctx, "%*s" "Id %-8" PRId32 " (%s)", indent, "", *(int32_t *) body, spa_debug_type_find_name(info, *(int32_t *) body)); break; case SPA_TYPE_Int: - spa_debugc(ctx, "%*s" "Int %d", indent, "", *(int32_t *) body); + spa_debugc(ctx, "%*s" "Int %" PRId32, indent, "", *(int32_t *) body); break; case SPA_TYPE_Long: spa_debugc(ctx, "%*s" "Long %" PRIi64 "", indent, "", *(int64_t *) body); @@ -86,7 +88,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa void *p; const struct spa_type_info *ti = spa_debug_type_find(SPA_TYPE_ROOT, b->child.type); - spa_debugc(ctx, "%*s" "Array: child.size %d, child.type %s", indent, "", + spa_debugc(ctx, "%*s" "Array: child.size %" PRIu32 ", child.type %s", indent, "", b->child.size, ti ? ti->name : "unknown"); info = info && info->values ? info->values : info; @@ -100,7 +102,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa void *p; const struct spa_type_info *ti = spa_debug_type_find(spa_type_choice, b->type); - spa_debugc(ctx, "%*s" "Choice: type %s, flags %08x %d %d", indent, "", + spa_debugc(ctx, "%*s" "Choice: type %s, flags %08" PRIx32 " %" PRIu32 " %" PRIu32, indent, "", ti ? ti->name : "unknown", b->flags, size, b->child.size); SPA_POD_CHOICE_BODY_FOREACH(b, size, p) @@ -110,7 +112,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa case SPA_TYPE_Struct: { struct spa_pod *b = (struct spa_pod *)body, *p; - spa_debugc(ctx, "%*s" "Struct: size %d", indent, "", size); + spa_debugc(ctx, "%*s" "Struct: size %" PRIu32, indent, "", size); SPA_POD_FOREACH(b, size, p) spa_debugc_pod_value(ctx, indent + 2, info, p->type, SPA_POD_BODY(p), p->size); break; @@ -125,16 +127,16 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa ii = ti ? spa_debug_type_find(ti->values, 0) : NULL; ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL; - spa_debugc(ctx, "%*s" "Object: size %d, type %s (%d), id %s (%d)", indent, "", size, - ti ? ti->name : "unknown", b->type, ii ? ii->name : "unknown", b->id); + spa_debugc(ctx, "%*s" "Object: size %" PRIu32 ", type %s (%" PRIu32 "), id %s (%" PRIu32 ")", + indent, "", size, ti ? ti->name : "unknown", b->type, ii ? ii->name : "unknown", b->id); info = ti ? ti->values : info; SPA_POD_OBJECT_BODY_FOREACH(b, size, p) { ii = spa_debug_type_find(info, p->key); - spa_debugc(ctx, "%*s" "Prop: key %s (%d), flags %08x", indent+2, "", - ii ? ii->name : "unknown", p->key, p->flags); + spa_debugc(ctx, "%*s" "Prop: key %s (%" PRIu32 "), flags %08" PRIx32, + indent+2, "", ii ? ii->name : "unknown", p->key, p->flags); spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, p->value.type, @@ -151,13 +153,13 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa ti = spa_debug_type_find(info, b->unit); - spa_debugc(ctx, "%*s" "Sequence: size %d, unit %s", indent, "", size, + spa_debugc(ctx, "%*s" "Sequence: size %" PRIu32 ", unit %s", indent, "", size, ti ? ti->name : "unknown"); SPA_POD_SEQUENCE_BODY_FOREACH(b, size, c) { ii = spa_debug_type_find(spa_type_control, c->type); - spa_debugc(ctx, "%*s" "Control: offset %d, type %s", indent+2, "", + spa_debugc(ctx, "%*s" "Control: offset %" PRIu32 ", type %s", indent+2, "", c->offset, ii ? ii->name : "unknown"); spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, @@ -176,7 +178,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa spa_debugc_mem(ctx, indent + 2, body, size); break; default: - spa_debugc(ctx, "%*s" "unhandled POD type %d", indent, "", type); + spa_debugc(ctx, "%*s" "unhandled POD type %" PRIu32, indent, "", type); break; } return 0; From 5cf84fa3fe0d0125c1bb20764096288d98199e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 9 Jul 2025 18:19:25 +0200 Subject: [PATCH 0504/1014] spa: debug: pod: print custom properties with more detail Previously, custom object properties were printed as "unknown", and the offset (wrt. `SPA_PROP_START_CUSTOM`) was not displayed. A custom property is distinct from an "unknown" one. Being able to quickly differentiate the two is useful. Furthermore, knowing the custom property id (i.e. the actual numeric id minus `SPA_PROP_START_CUSTOM`) is also very helpful. To address the above, print a custom property (i.e. anything with an id at least `SPA_PROP_START_CUSTOM`) as follows: Spa:Pod:Object:Param:Props:Custom:123 where the last component is the custom property id. --- spa/include/spa/debug/pod.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index bec620b48..21771691a 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -133,10 +133,21 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa info = ti ? ti->values : info; SPA_POD_OBJECT_BODY_FOREACH(b, size, p) { + static const char custom_prefix[] = SPA_TYPE_INFO_PROPS_BASE "Custom:"; + char custom_name[sizeof(custom_prefix) + 16]; + const char *name = "unknown"; + ii = spa_debug_type_find(info, p->key); + if (ii) { + name = ii->name; + } else if (p->key >= SPA_PROP_START_CUSTOM) { + snprintf(custom_name, sizeof(custom_name), + "%s%" PRIu32, custom_prefix, p->key - SPA_PROP_START_CUSTOM); + name = custom_name; + } spa_debugc(ctx, "%*s" "Prop: key %s (%" PRIu32 "), flags %08" PRIx32, - indent+2, "", ii ? ii->name : "unknown", p->key, p->flags); + indent+2, "", name, p->key, p->flags); spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, p->value.type, From b3bf5be1f6445ece6d558dcb65fbfe8ee276fa97 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 7 Jun 2025 15:21:37 -0400 Subject: [PATCH 0505/1014] *: Avoid macros that use casts where possible Use direct field access when the type is known, instead of a macro that includes a cast. These were missed in e4fcbef89a37a521552ac95a35af1bed0e131ec4. --- spa/include/spa/debug/format.h | 2 +- spa/include/spa/pod/iter.h | 2 +- spa/include/spa/pod/parser.h | 2 +- spa/plugins/audioconvert/audioadapter.c | 2 +- spa/plugins/v4l2/v4l2-utils.c | 2 +- spa/plugins/videoconvert/videoadapter.c | 2 +- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 2 +- .../module-protocol-native/v0/protocol-native.c | 13 +++++++------ src/modules/module-protocol-pulse/collect.c | 4 ++-- src/pipewire/stream.c | 2 +- src/tools/pw-dump.c | 6 ++---- 11 files changed, 19 insertions(+), 20 deletions(-) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 74c8b62bb..5fe0eac3e 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -128,7 +128,7 @@ SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int in if (info == NULL) info = spa_type_format; - if (format == NULL || SPA_POD_TYPE(format) != SPA_TYPE_Object) + if (format == NULL || format->type != SPA_TYPE_Object) return -EINVAL; if (spa_format_parse(format, &mtype, &mstype) < 0) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 0dcf514f8..826ac0d86 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -270,7 +270,7 @@ SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **v if (!spa_pod_is_bytes(pod)) return -EINVAL; *value = (const void *)SPA_POD_CONTENTS(struct spa_pod_bytes, pod); - *len = SPA_POD_BODY_SIZE(pod); + *len = pod->size; return 0; } diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index a3a5527bc..9c128f26a 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -281,7 +281,7 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch if (pod == NULL) return false; - if (SPA_POD_TYPE(pod) == SPA_TYPE_Choice) { + if (pod->type == SPA_TYPE_Choice) { if (!spa_pod_is_choice(pod)) return false; if (type == 'V') diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index e9ff63009..b904923ac 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -918,7 +918,7 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * struct spa_pod_builder_state state; int res = 0; - if (o2 == NULL || SPA_POD_TYPE(o1) != SPA_POD_TYPE(o2)) + if (o2 == NULL || o1->pod.type != o2->pod.type) return (struct spa_pod*)o1; spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 2c8a88443..96a5d73ed 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1585,7 +1585,7 @@ spa_v4l2_set_control(struct impl *this, uint32_t id, if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; - switch (SPA_POD_TYPE(&prop->value)) { + switch (prop->value.type) { case SPA_TYPE_Bool: { bool val; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 84a24cd2d..ccf9365f3 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -969,7 +969,7 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * struct spa_pod_builder_state state; int res = 0; - if (o2 == NULL || SPA_POD_TYPE(o1) != SPA_POD_TYPE(o2)) + if (o2 == NULL || o1->pod.type != o2->pod.type) return (struct spa_pod*)o1; spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 9ab8fddeb..3a0f3903c 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1286,7 +1286,7 @@ static int diff_prop(struct impl *impl, struct spa_pod_prop *prop, void *vals, *v, *best = NULL; int res = INT_MAX; - if (SPA_POD_TYPE(val) != type) + if (val->type != type) return -EINVAL; size = SPA_POD_BODY_SIZE(val); diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c index 862d9eda1..04ad349be 100644 --- a/src/modules/module-protocol-native/v0/protocol-native.c +++ b/src/modules/module-protocol-native/v0/protocol-native.c @@ -646,10 +646,11 @@ struct spa_pod * pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, if (pod == NULL) return NULL; - if ((res = remap_from_v2(SPA_POD_TYPE(pod), - SPA_POD_BODY(pod), - SPA_POD_BODY_SIZE(pod), - client, &b)) < 0) { + if ((res = remap_from_v2(pod->type, + SPA_POD_BODY(pod), + pod->size, + client, + &b)) < 0) { errno = -res; return NULL; } @@ -669,9 +670,9 @@ int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct sp } if ((res = remap_to_v2(client, pw_type_info(), - SPA_POD_TYPE(pod), + pod->type, SPA_POD_BODY(pod), - SPA_POD_BODY_SIZE(pod), + pod->size, b)) < 0) { return -res; } diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index 189008019..720577216 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -524,8 +524,8 @@ uint32_t collect_transport_codec_info(struct pw_manager_object *card, if (iid != SPA_PROP_bluetoothAudioCodec) continue; - if (SPA_POD_CHOICE_TYPE(type) != SPA_CHOICE_Enum || - SPA_POD_TYPE(SPA_POD_CHOICE_CHILD(type)) != SPA_TYPE_Int) + if (type->body.type != SPA_CHOICE_Enum || + type->body.child.type != SPA_TYPE_Int) continue; /* diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 44c1d3a27..d24a446c1 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1278,7 +1278,7 @@ static int node_event_param(void *object, int seq, return -EINVAL; } - c->type = SPA_POD_TYPE(pod); + c->type = pod->type; if (spa_pod_is_float(pod)) vals = SPA_POD_BODY(pod); else if (spa_pod_is_double(pod)) { diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index b18928b79..e04cf74e1 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -511,10 +511,8 @@ static void put_pod(struct data *d, const char *key, const struct spa_pod *pod) if (pod == NULL) { put_value(d, key, NULL); } else { - put_pod_value(d, key, SPA_TYPE_ROOT, - SPA_POD_TYPE(pod), - SPA_POD_BODY(pod), - SPA_POD_BODY_SIZE(pod)); + put_pod_value(d, key, SPA_TYPE_ROOT, pod->type, + SPA_POD_BODY(pod), pod->size); } } From 32b8e5f500feabe0e0a74db139fe546d0698fc83 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 8 Jul 2025 19:42:16 -0400 Subject: [PATCH 0506/1014] src: use correct format string for uint32_t provides the correct and portable format string to be used. --- src/pipewire/filter.c | 2 +- src/pipewire/stream.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 101cdd603..a92819710 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -206,7 +206,7 @@ static void fix_datatype(struct spa_pod *param) if (spa_pod_get_int(&vals[0], (int32_t*)&dataType) < 0) return; - pw_log_debug("dataType: %u", dataType); + pw_log_debug("dataType: %" PRIu32, dataType); if (dataType & (1u << SPA_DATA_MemPtr)) { SPA_POD_VALUE(struct spa_pod_int, &vals[0]) = dataType | (1< %u", dataType, + pw_log_debug("Change dataType: %" PRIu32 " -> %" PRIu32, dataType, SPA_POD_VALUE(struct spa_pod_int, &vals[0])); } } From 3c3ead27846fb9550127a2aa80489dd7b9e0b0ff Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 8 Jul 2025 19:42:05 -0400 Subject: [PATCH 0507/1014] gst: whitespace fix No functional change. --- src/gst/gstpipewireformat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 0640acc64..20b93065a 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -1305,7 +1305,7 @@ gst_caps_from_format (const struct spa_pod *format) "alignment", G_TYPE_STRING, "au", NULL); } else { - return NULL; + return NULL; } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_size))) { handle_rect_prop (prop, "width", "height", res); From 9a669382830dd6c74e690ecbcf494d5c0a7516a3 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 8 Jul 2025 19:41:12 -0400 Subject: [PATCH 0508/1014] pulse: don't ignore return value If a function can fail don't pretend it succeeded. --- src/modules/module-protocol-pulse/format.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 3143e830b..34a835ef8 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -767,9 +767,7 @@ static int format_info_iec958_from_param(struct format_info *info, struct spa_po if ((info->props = pw_properties_new(NULL, NULL)) == NULL) return -errno; - add_int(info, "format.rate", param, SPA_FORMAT_AUDIO_rate); - - return 0; + return add_int(info, "format.rate", param, SPA_FORMAT_AUDIO_rate); } int format_info_from_param(struct format_info *info, struct spa_pod *param, uint32_t index) From adb3a55703447f85e1d27f522213761b2ef6beea Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 9 Jul 2025 14:25:32 -0400 Subject: [PATCH 0509/1014] protocl-native: v0: Fix integer overflow to buffer overflow Too many dict items could cause an integer overflow leading to a stack-based buffer overflow. --- src/modules/module-protocol-native/connection.h | 4 ++++ src/modules/module-protocol-native/protocol-native.c | 4 ---- src/modules/module-protocol-native/v0/protocol-native.c | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/modules/module-protocol-native/connection.h b/src/modules/module-protocol-native/connection.h index 0d3b4fba2..bc9bb530d 100644 --- a/src/modules/module-protocol-native/connection.h +++ b/src/modules/module-protocol-native/connection.h @@ -10,6 +10,10 @@ #include +#define MAX_DICT 1024 +#define MAX_PARAM_INFO 128 +#define MAX_PERMISSIONS 4096 + #ifdef __cplusplus extern "C" { #endif diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c index 03f4c8a7e..3a7a1a179 100644 --- a/src/modules/module-protocol-native/protocol-native.c +++ b/src/modules/module-protocol-native/protocol-native.c @@ -15,10 +15,6 @@ #include "connection.h" -#define MAX_DICT 1024 -#define MAX_PARAM_INFO 128 -#define MAX_PERMISSIONS 4096 - PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c index 04ad349be..c50d01d55 100644 --- a/src/modules/module-protocol-native/v0/protocol-native.c +++ b/src/modules/module-protocol-native/v0/protocol-native.c @@ -173,6 +173,8 @@ static int core_demarshal_client_update(void *object, const struct pw_protocol_n "i", &props.n_items, NULL) < 0) return -EINVAL; + if (props.n_items > MAX_DICT) + return -ENOSPC; props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); for (i = 0; i < props.n_items; i++) { if (spa_pod_parser_get(&prs, @@ -219,6 +221,8 @@ static int core_demarshal_permissions(void *object, const struct pw_protocol_nat spa_pod_parser_get(&prs, "i", &props.n_items, NULL) < 0) return -EINVAL; + if (props.n_items > MAX_DICT) + return -ENOSPC; props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); n_permissions = 0; @@ -698,6 +702,8 @@ static int core_demarshal_create_object(void *object, const struct pw_protocol_n "i", &props.n_items, NULL) < 0) return -EINVAL; + if (props.n_items > MAX_DICT) + return -ENOSPC; props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); for (i = 0; i < props.n_items; i++) { if (spa_pod_parser_get(&prs, @@ -764,6 +770,8 @@ static int core_demarshal_update_types_server(void *object, const struct pw_prot if (first_id == 0) compat_v2->send_types = true; + if (n_types > MAX_DICT) + return -ENOSPC; types = alloca(n_types * sizeof(char *)); for (i = 0; i < n_types; i++) { if (spa_pod_parser_get(&prs, "s", &types[i], NULL) < 0) From c54fdb76f8c1d14a2105f5489b5681af1c5345a5 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 4 Jul 2025 13:53:23 -0400 Subject: [PATCH 0510/1014] protocol-native: check for NULL strings SPA_POD_String allows NULL strings, so check for them. --- src/modules/module-client-device/protocol-native.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/module-client-device/protocol-native.c b/src/modules/module-client-device/protocol-native.c index 59d16e31f..cbcca0c9a 100644 --- a/src/modules/module-client-device/protocol-native.c +++ b/src/modules/module-client-device/protocol-native.c @@ -33,6 +33,8 @@ static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *i SPA_POD_String(&item->value), NULL)) < 0) return res; + if (item->key == NULL || item->value == NULL) + return -EINVAL; if (spa_strstartswith(item->value, "pointer:")) item->value = ""; return 0; From 7e202a384472b5477deba1861281c484054ae945 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Wed, 9 Jul 2025 21:46:20 +0200 Subject: [PATCH 0511/1014] spa: libcamera: add colorimetry support Libcamera equivalent to 41b831d0f ("spa: v4l2: add colorimetry support") --- spa/plugins/libcamera/libcamera-utils.cpp | 72 +++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 97634a5d8..a2d69243a 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -279,6 +279,62 @@ static int score_size(Size &a, Size &b) return x * x + y * y; } +static void +parse_colorimetry(const ColorSpace& colorspace, + struct spa_video_colorimetry *colorimetry) +{ + switch (colorspace.range) { + case ColorSpace::Range::Full: + colorimetry->range = SPA_VIDEO_COLOR_RANGE_0_255; + break; + case ColorSpace::Range::Limited: + colorimetry->range = SPA_VIDEO_COLOR_RANGE_16_235; + break; + } + + switch (colorspace.ycbcrEncoding) { + case ColorSpace::YcbcrEncoding::None: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_RGB; + break; + case ColorSpace::YcbcrEncoding::Rec601: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT601; + break; + case ColorSpace::YcbcrEncoding::Rec709: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT709; + break; + case ColorSpace::YcbcrEncoding::Rec2020: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; + break; + } + + switch (colorspace.transferFunction) { + case ColorSpace::TransferFunction::Linear: + colorimetry->transfer = SPA_VIDEO_TRANSFER_UNKNOWN; + break; + case ColorSpace::TransferFunction::Srgb: + colorimetry->transfer = SPA_VIDEO_TRANSFER_SRGB; + break; + case ColorSpace::TransferFunction::Rec709: + colorimetry->transfer = SPA_VIDEO_TRANSFER_BT709; + break; + } + + switch (colorspace.primaries) { + case ColorSpace::Primaries::Raw: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; + break; + case ColorSpace::Primaries::Smpte170m: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; + break; + case ColorSpace::Primaries::Rec709: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; + break; + case ColorSpace::Primaries::Rec2020: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; + break; + } +} + static int spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) @@ -289,6 +345,7 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[2]; struct spa_result_node_params result; + struct spa_video_colorimetry colorimetry = {}; struct spa_pod *fmt; uint32_t i, count = 0, num_sizes; PixelFormat format; @@ -300,6 +357,9 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, const StreamConfiguration& streamConfig = impl->config->at(0); const StreamFormats &formats = streamConfig.formats(); + if (streamConfig.colorSpace) + parse_colorimetry(*streamConfig.colorSpace, &colorimetry); + result.id = SPA_PARAM_EnumFormat; result.next = start; @@ -394,6 +454,18 @@ next_fmt: spa_pod_builder_rectangle(&b, frameSize.width, frameSize.height); } + if (streamConfig.colorSpace) { + spa_pod_builder_add(&b, + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_Id(colorimetry.range), + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_Id(colorimetry.matrix), + SPA_FORMAT_VIDEO_transferFunction, + SPA_POD_Id(colorimetry.transfer), + SPA_FORMAT_VIDEO_colorPrimaries, + SPA_POD_Id(colorimetry.primaries), 0); + } + fmt = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) From ae7a893ce9b41c788217439b742698d65d289f4d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 10 Jul 2025 15:21:16 +0300 Subject: [PATCH 0512/1014] bluez5: aac: fix for A2DP v1.4 using rfa bits for more channels A2DP v1.4 uses the rfa bits for adding 5.1 and 7.1 configurations. Clear those bits properly when sending configuration, in case remote device sets them. --- spa/plugins/bluez5/a2dp-codec-caps.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-caps.h b/spa/plugins/bluez5/a2dp-codec-caps.h index d775ce101..b58e33624 100644 --- a/spa/plugins/bluez5/a2dp-codec-caps.h +++ b/spa/plugins/bluez5/a2dp-codec-caps.h @@ -116,8 +116,10 @@ #define AAC_SAMPLING_FREQ_88200 0x0002 #define AAC_SAMPLING_FREQ_96000 0x0001 -#define AAC_CHANNELS_1 0x02 -#define AAC_CHANNELS_2 0x01 +#define AAC_CHANNELS_1 0x08 +#define AAC_CHANNELS_2 0x04 +#define AAC_CHANNELS_5_1 0x02 +#define AAC_CHANNELS_7_1 0x01 #define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ (a).bitrate2 << 8 | (a).bitrate3) @@ -341,8 +343,7 @@ typedef struct { typedef struct { uint8_t object_type; uint8_t frequency1; - uint8_t rfa:2; - uint8_t channels:2; + uint8_t channels:4; uint8_t frequency2:4; uint8_t bitrate1:7; uint8_t vbr:1; @@ -403,8 +404,7 @@ typedef struct { uint8_t object_type; uint8_t frequency1; uint8_t frequency2:4; - uint8_t channels:2; - uint8_t rfa:2; + uint8_t channels:4; uint8_t vbr:1; uint8_t bitrate1:7; uint8_t bitrate2; From 20a4aa8cf954e35e17bd75938f6b8d76fceb5171 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 10 Jul 2025 16:22:25 +0200 Subject: [PATCH 0513/1014] modules: remove v0 protocol support --- src/modules/meson.build | 4 - src/modules/module-client-node.c | 6 +- .../module-client-node/v0/client-node.c | 1428 ----------------- .../module-client-node/v0/client-node.h | 91 -- .../module-client-node/v0/ext-client-node.h | 394 ----- .../module-client-node/v0/protocol-native.c | 514 ------ src/modules/module-client-node/v0/transport.c | 241 --- src/modules/module-client-node/v0/transport.h | 39 - src/modules/module-protocol-native.c | 10 - src/modules/module-protocol-native/defs.h | 6 - .../module-protocol-native/v0/interfaces.h | 514 ------ .../v0/protocol-native.c | 1361 ---------------- .../module-protocol-native/v0/typemap.h | 292 ---- src/pipewire/private.h | 3 - 14 files changed, 2 insertions(+), 4901 deletions(-) delete mode 100644 src/modules/module-client-node/v0/client-node.c delete mode 100644 src/modules/module-client-node/v0/client-node.h delete mode 100644 src/modules/module-client-node/v0/ext-client-node.h delete mode 100644 src/modules/module-client-node/v0/protocol-native.c delete mode 100644 src/modules/module-client-node/v0/transport.c delete mode 100644 src/modules/module-client-node/v0/transport.h delete mode 100644 src/modules/module-protocol-native/v0/interfaces.h delete mode 100644 src/modules/module-protocol-native/v0/protocol-native.c delete mode 100644 src/modules/module-protocol-native/v0/typemap.h diff --git a/src/modules/meson.build b/src/modules/meson.build index be9912dd6..e0bf6178b 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -289,7 +289,6 @@ pipewire_module_protocol_native = shared_library('pipewire-module-protocol-nativ 'module-protocol-native/local-socket.c', 'module-protocol-native/portal-screencast.c', 'module-protocol-native/protocol-native.c', - 'module-protocol-native/v0/protocol-native.c', 'module-protocol-native/protocol-footer.c', 'module-protocol-native/security-context.c', 'module-protocol-native/connection.c' ], @@ -476,9 +475,6 @@ pipewire_module_client_node = shared_library('pipewire-module-client-node', 'module-client-node/remote-node.c', 'module-client-node/client-node.c', 'module-client-node/protocol-native.c', - 'module-client-node/v0/client-node.c', - 'module-client-node/v0/transport.c', - 'module-client-node/v0/protocol-native.c', 'spa/spa-node.c', ], include_directories : [configinc], link_with : pipewire_module_protocol_native, diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c index ea082b33d..9ef2561f0 100644 --- a/src/modules/module-client-node.c +++ b/src/modules/module-client-node.c @@ -14,7 +14,6 @@ #include #define PW_API_CLIENT_NODE_IMPL SPA_EXPORT -#include "module-client-node/v0/client-node.h" #include "module-client-node/client-node.h" /** \page page_module_client_node Client Node @@ -108,7 +107,6 @@ struct pw_proxy *pw_core_spa_node_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size); struct pw_protocol *pw_protocol_native_ext_client_node_init(struct pw_context *context); -struct pw_protocol *pw_protocol_native_ext_client_node0_init(struct pw_context *context); struct factory_data { struct pw_impl_factory *factory; @@ -146,7 +144,8 @@ static void *create_object(void *_data, } if (version == 0) { - result = pw_impl_client_node0_new(node_resource, properties); + result = NULL; + errno = ENOTSUP; } else { result = pw_impl_client_node_new(node_resource, properties, true); } @@ -265,7 +264,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) goto error_remove; pw_protocol_native_ext_client_node_init(context); - pw_protocol_native_ext_client_node0_init(context); pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); diff --git a/src/modules/module-client-node/v0/client-node.c b/src/modules/module-client-node/v0/client-node.c deleted file mode 100644 index c5321afa5..000000000 --- a/src/modules/module-client-node/v0/client-node.c +++ /dev/null @@ -1,1428 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2015 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define PW_ENABLE_DEPRECATED - -#include "pipewire/pipewire.h" - -#include "pipewire/context.h" -#include "modules/spa/spa-node.h" -#include "client-node.h" -#include "transport.h" - -PW_LOG_TOPIC_EXTERN(mod_topic); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -/** \cond */ - -#define MAX_INPUTS 64 -#define MAX_OUTPUTS 64 - -#define MAX_BUFFERS 64 - -#define CHECK_IN_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_INPUTS) -#define CHECK_OUT_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_OUTPUTS) -#define CHECK_PORT_ID(this,d,p) (CHECK_IN_PORT_ID(this,d,p) || CHECK_OUT_PORT_ID(this,d,p)) -#define CHECK_FREE_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && !(this)->in_ports[p].valid) -#define CHECK_FREE_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && !(this)->out_ports[p].valid) -#define CHECK_FREE_PORT(this,d,p) (CHECK_FREE_IN_PORT (this,d,p) || CHECK_FREE_OUT_PORT (this,d,p)) -#define CHECK_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && (this)->in_ports[p].valid) -#define CHECK_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && (this)->out_ports[p].valid) -#define CHECK_PORT(this,d,p) (CHECK_IN_PORT (this,d,p) || CHECK_OUT_PORT (this,d,p)) - -#define GET_IN_PORT(this,p) (&this->in_ports[p]) -#define GET_OUT_PORT(this,p) (&this->out_ports[p]) -#define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) - -#define CHECK_PORT_BUFFER(this,b,p) (b < p->n_buffers) - -extern uint32_t pw_protocol_native0_type_from_v2(struct pw_impl_client *client, uint32_t type); -extern uint32_t pw_protocol_native0_name_to_v2(struct pw_impl_client *client, const char *name); - -struct mem { - uint32_t id; - int ref; - int fd; - uint32_t type; - uint32_t flags; -}; - -struct buffer { - struct spa_buffer *outbuf; - struct spa_buffer buffer; - struct spa_meta metas[4]; - struct spa_data datas[4]; - bool outstanding; - uint32_t memid; -}; - -struct port { - uint32_t id; - enum spa_direction direction; - - bool valid; - struct spa_port_info info; - struct pw_properties *properties; - - bool have_format; - uint32_t n_params; - struct spa_pod **params; - struct spa_io_buffers *io; - - uint32_t n_buffers; - struct buffer buffers[MAX_BUFFERS]; -}; - -struct node { - struct spa_node node; - - struct impl *impl; - - struct spa_log *log; - struct spa_loop *data_loop; - struct spa_system *data_system; - - struct spa_hook_list hooks; - struct spa_callbacks callbacks; - - struct spa_io_position *position; - - struct pw_resource *resource; - - struct spa_source data_source; - int writefd; - - struct spa_node_info info; - - uint32_t n_inputs; - uint32_t n_outputs; - struct port in_ports[MAX_INPUTS]; - struct port out_ports[MAX_OUTPUTS]; - - uint32_t n_params; - struct spa_pod **params; - - uint32_t seq; - uint32_t init_pending; -}; - -struct impl { - struct pw_impl_client_node0 this; - - bool client_reuse; - - struct pw_context *context; - struct pw_mempool *context_pool; - - struct node node; - - struct pw_client_node0_transport *transport; - - struct spa_hook node_listener; - struct spa_hook resource_listener; - struct spa_hook object_listener; - - struct pw_array mems; - - int fds[2]; - int other_fds[2]; - - uint32_t input_ready; - bool out_pending; -}; - -/** \endcond */ - -static struct mem *ensure_mem(struct impl *impl, int fd, uint32_t type, uint32_t flags) -{ - struct mem *m, *f = NULL; - - pw_array_for_each(m, &impl->mems) { - if (m->ref <= 0) - f = m; - else if (m->fd == fd) - goto found; - } - - if (f == NULL) { - m = pw_array_add(&impl->mems, sizeof(struct mem)); - m->id = pw_array_get_len(&impl->mems, struct mem) - 1; - m->ref = 0; - } - else { - m = f; - } - m->fd = fd; - m->type = type; - m->flags = flags; - - pw_client_node0_resource_add_mem(impl->node.resource, - m->id, - type, - m->fd, - m->flags); - found: - m->ref++; - return m; -} - - -static int clear_buffers(struct node *this, struct port *port) -{ - uint32_t i, j; - struct impl *impl = this->impl; - - for (i = 0; i < port->n_buffers; i++) { - struct buffer *b = &port->buffers[i]; - struct mem *m; - - spa_log_debug(this->log, "node %p: clear buffer %d", this, i); - - for (j = 0; j < b->buffer.n_datas; j++) { - struct spa_data *d = &b->datas[j]; - - if (d->type == SPA_DATA_DmaBuf || - d->type == SPA_DATA_MemFd) { - uint32_t id; - - id = SPA_PTR_TO_UINT32(b->buffer.datas[j].data); - m = pw_array_get_unchecked(&impl->mems, id, struct mem); - m->ref--; - } - } - m = pw_array_get_unchecked(&impl->mems, b->memid, struct mem); - m->ref--; - } - port->n_buffers = 0; - return 0; -} - -static void emit_port_info(struct node *this, struct port *port) -{ - spa_node_emit_port_info(&this->hooks, - port->direction, port->id, &port->info); -} - -static int impl_node_add_listener(void *object, - struct spa_hook *listener, - const struct spa_node_events *events, - void *data) -{ - struct node *this = object; - struct spa_hook_list save; - uint32_t i; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - spa_hook_list_isolate(&this->hooks, &save, listener, events, data); - - for (i = 0; i < MAX_INPUTS; i++) { - if (this->in_ports[i].valid) - emit_port_info(this, &this->in_ports[i]); - } - for (i = 0; i < MAX_OUTPUTS; i++) { - if (this->out_ports[i].valid) - emit_port_info(this, &this->out_ports[i]); - } - spa_hook_list_join(&this->hooks, &save); - - return 0; -} - -static int impl_node_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct node *this = object; - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - struct spa_result_node_params result; - uint32_t count = 0; - bool found = false; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - - result.id = id; - result.next = 0; - - while (true) { - struct spa_pod *param; - - result.index = result.next++; - if (result.index >= this->n_params) - break; - - param = this->params[result.index]; - - if (param == NULL || !spa_pod_is_object_id(param, id)) - continue; - - found = true; - - if (result.index < start) - continue; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result.param, param, filter) != 0) - continue; - - pw_log_debug("%p: %d param %u", this, seq, result.index); - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count == num) - break; - } - return found ? 0 : -ENOENT; -} - -static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - if (this->resource == NULL) - return -EIO; - - pw_client_node0_resource_set_param(this->resource, this->seq, id, flags, param); - - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) -{ - struct node *this = object; - int res = 0; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - switch(id) { - case SPA_IO_Position: - this->position = data; - break; - default: - res = -ENOTSUP; - break; - } - return res; -} - -static inline void do_flush(struct node *this) -{ - if (spa_system_eventfd_write(this->data_system, this->writefd, 1) < 0) - spa_log_warn(this->log, "node %p: error flushing : %s", this, strerror(errno)); - -} - -static int send_clock_update(struct node *this) -{ - struct pw_impl_client *client = pw_resource_get_client(this->resource); - uint32_t type = pw_protocol_native0_name_to_v2(client, SPA_TYPE_INFO_NODE_COMMAND_BASE "ClockUpdate"); - struct timespec ts; - int64_t now; - - clock_gettime(CLOCK_MONOTONIC, &ts); - now = SPA_TIMESPEC_TO_NSEC(&ts); - pw_log_trace("%p: now %"PRIi64, this, now); - - struct spa_command_node0_clock_update cu = - SPA_COMMAND_NODE0_CLOCK_UPDATE_INIT(type, - SPA_COMMAND_NODE0_CLOCK_UPDATE_TIME | - SPA_COMMAND_NODE0_CLOCK_UPDATE_SCALE | - SPA_COMMAND_NODE0_CLOCK_UPDATE_STATE | - SPA_COMMAND_NODE0_CLOCK_UPDATE_LATENCY, /* change_mask */ - SPA_USEC_PER_SEC, /* rate */ - now / SPA_NSEC_PER_USEC, /* ticks */ - now, /* monotonic_time */ - 0, /* offset */ - (1 << 16) | 1, /* scale */ - SPA_CLOCK0_STATE_RUNNING, /* state */ - SPA_COMMAND_NODE0_CLOCK_UPDATE_FLAG_LIVE, /* flags */ - 0); /* latency */ - - pw_client_node0_resource_command(this->resource, this->seq, (const struct spa_command*)&cu); - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int impl_node_send_command(void *object, const struct spa_command *command) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); - - if (this->resource == NULL) - return -EIO; - - if (SPA_NODE_COMMAND_ID(command) == SPA_NODE_COMMAND_Start) { - send_clock_update(this); - } - - pw_client_node0_resource_command(this->resource, this->seq, command); - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int -impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *data) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); - - return 0; -} - -static int -impl_node_sync(void *object, int seq) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - - pw_log_debug("%p: sync %p", this, this->resource); - - if (this->resource == NULL) - return -EIO; - - this->init_pending = SPA_RESULT_RETURN_ASYNC(this->seq++); - - return this->init_pending; -} - - -extern struct spa_pod *pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, const struct spa_pod *pod); -extern int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod, - struct spa_pod_builder *b); - -static void -do_update_port(struct node *this, - enum spa_direction direction, - uint32_t port_id, - uint32_t change_mask, - uint32_t n_params, - const struct spa_pod **params, - const struct spa_port_info *info) -{ - struct port *port; - - port = GET_PORT(this, direction, port_id); - - if (!port->valid) { - spa_log_debug(this->log, "node %p: adding port %d, direction %d", - this, port_id, direction); - port->id = port_id; - port->direction = direction; - port->have_format = false; - port->valid = true; - - if (direction == SPA_DIRECTION_INPUT) - this->n_inputs++; - else - this->n_outputs++; - } - - if (change_mask & PW_CLIENT_NODE0_PORT_UPDATE_PARAMS) { - uint32_t i; - - port->have_format = false; - - spa_log_debug(this->log, "node %p: port %u update %d params", this, port_id, n_params); - for (i = 0; i < port->n_params; i++) - free(port->params[i]); - port->n_params = n_params; - if (port->n_params == 0) { - free(port->params); - port->params = NULL; - } else { - void *p; - p = pw_reallocarray(port->params, port->n_params, sizeof(struct spa_pod *)); - if (p == NULL) { - pw_log_error("%p: port %u can't realloc: %m", this, port_id); - free(port->params); - port->n_params = 0; - } - port->params = p; - } - for (i = 0; i < port->n_params; i++) { - port->params[i] = params[i] ? - pw_protocol_native0_pod_from_v2(pw_resource_get_client(this->resource), params[i]) : NULL; - - if (port->params[i] && spa_pod_is_object_id(port->params[i], SPA_PARAM_Format)) - port->have_format = true; - } - } - - if (change_mask & PW_CLIENT_NODE0_PORT_UPDATE_INFO) { - pw_properties_free(port->properties); - port->properties = NULL; - port->info.props = NULL; - port->info.n_params = 0; - port->info.params = NULL; - - if (info) { - port->info = *info; - if (info->props) { - port->properties = pw_properties_new_dict(info->props); - port->info.props = &port->properties->dict; - } - } - spa_node_emit_port_info(&this->hooks, direction, port_id, info); - } -} - -static void -clear_port(struct node *this, - struct port *port, enum spa_direction direction, uint32_t port_id) -{ - do_update_port(this, - direction, - port_id, - PW_CLIENT_NODE0_PORT_UPDATE_PARAMS | - PW_CLIENT_NODE0_PORT_UPDATE_INFO, 0, NULL, NULL); - clear_buffers(this, port); -} - -static void do_uninit_port(struct node *this, enum spa_direction direction, uint32_t port_id) -{ - struct port *port; - - spa_log_debug(this->log, "node %p: removing port %d", this, port_id); - - if (direction == SPA_DIRECTION_INPUT) { - port = GET_IN_PORT(this, port_id); - this->n_inputs--; - } else { - port = GET_OUT_PORT(this, port_id); - this->n_outputs--; - } - clear_port(this, port, direction, port_id); - port->valid = false; - spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); -} - -static int -impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, - const struct spa_dict *props) -{ - struct node *this = object; - struct port *port; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_FREE_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - clear_port(this, port, direction, port_id); - - return 0; -} - -static int -impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - do_uninit_port(this, direction, port_id); - - return 0; -} - -static int -impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) -{ - struct node *this = object; - struct port *port; - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - struct spa_result_node_params result; - uint32_t count = 0; - bool found = false; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(num != 0, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - port = GET_PORT(this, direction, port_id); - - pw_log_debug("%p: %d port %d.%d %u %u %u", this, seq, - direction, port_id, id, start, num); - - result.id = id; - result.next = 0; - - while (true) { - struct spa_pod *param; - - result.index = result.next++; - if (result.index >= port->n_params) - break; - - param = port->params[result.index]; - - if (param == NULL || !spa_pod_is_object_id(param, id)) - continue; - - found = true; - - if (result.index < start) - continue; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if (spa_pod_filter(&b, &result.param, param, filter) < 0) - continue; - - pw_log_debug("%p: %d param %u", this, seq, result.index); - spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count == num) - break; - } - return found ? 0 : -ENOENT; -} - -static int -impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct node *this = object; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - if (this->resource == NULL) - return -EIO; - - pw_client_node0_resource_port_set_param(this->resource, - this->seq, - direction, port_id, - id, flags, - param); - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int -impl_node_port_set_io(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - void *data, size_t size) -{ - struct node *this = object; - struct impl *impl; - struct pw_memblock *mem; - struct mem *m; - uint32_t memid, mem_offset, mem_size; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - impl = this->impl; - - spa_log_debug(this->log, "node %p: port %d.%d set io %d %p", this, - direction, port_id, id, data); - - if (id == SPA_IO_Buffers) { - struct port *port = GET_PORT(this, direction, port_id); - port->io = data; - } - - if (this->resource == NULL) - return -EIO; - - - if (data) { - if ((mem = pw_mempool_find_ptr(impl->context_pool, data)) == NULL) - return -EINVAL; - - mem_offset = SPA_PTRDIFF(data, mem->map->ptr); - mem_size = mem->size; - if (mem_size - mem_offset < size) - return -EINVAL; - - mem_offset += mem->map->offset; - m = ensure_mem(impl, mem->fd, SPA_DATA_MemFd, mem->flags); - memid = m->id; - } - else { - memid = SPA_ID_INVALID; - mem_offset = mem_size = 0; - } - - pw_client_node0_resource_port_set_io(this->resource, - this->seq, - direction, port_id, - id, - memid, - mem_offset, mem_size); - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int -impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - struct node *this = object; - struct impl *impl; - struct port *port; - uint32_t i, j; - struct pw_client_node0_buffer *mb; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); - - impl = this->impl; - spa_log_debug(this->log, "node %p: use buffers %p %u", this, buffers, n_buffers); - - port = GET_PORT(this, direction, port_id); - - if (!port->have_format) - return -EIO; - - clear_buffers(this, port); - - if (n_buffers > 0) { - mb = alloca(n_buffers * sizeof(struct pw_client_node0_buffer)); - } else { - mb = NULL; - } - - port->n_buffers = n_buffers; - - if (this->resource == NULL) - return -EIO; - - for (i = 0; i < n_buffers; i++) { - struct buffer *b = &port->buffers[i]; - struct pw_memblock *mem; - struct mem *m; - size_t data_size; - void *baseptr; - - b->outbuf = buffers[i]; - memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer)); - b->buffer.datas = b->datas; - b->buffer.metas = b->metas; - - if (buffers[i]->n_metas > 0) - baseptr = buffers[i]->metas[0].data; - else if (buffers[i]->n_datas > 0) - baseptr = buffers[i]->datas[0].chunk; - else - return -EINVAL; - - if ((mem = pw_mempool_find_ptr(impl->context_pool, baseptr)) == NULL) - return -EINVAL; - - data_size = 0; - for (j = 0; j < buffers[i]->n_metas; j++) { - data_size += buffers[i]->metas[j].size; - } - for (j = 0; j < buffers[i]->n_datas; j++) { - struct spa_data *d = buffers[i]->datas; - data_size += sizeof(struct spa_chunk); - if (d->type == SPA_DATA_MemPtr) - data_size += d->maxsize; - } - - m = ensure_mem(impl, mem->fd, SPA_DATA_MemFd, mem->flags); - b->memid = m->id; - - mb[i].buffer = &b->buffer; - mb[i].mem_id = b->memid; - mb[i].offset = SPA_PTRDIFF(baseptr, SPA_PTROFF(mem->map->ptr, mem->map->offset, void)); - mb[i].size = data_size; - - for (j = 0; j < buffers[i]->n_metas; j++) - memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta)); - b->buffer.n_metas = j; - - for (j = 0; j < buffers[i]->n_datas; j++) { - struct spa_data *d = &buffers[i]->datas[j]; - - memcpy(&b->buffer.datas[j], d, sizeof(struct spa_data)); - - if (d->type == SPA_DATA_DmaBuf || - d->type == SPA_DATA_MemFd) { - m = ensure_mem(impl, d->fd, d->type, d->flags); - b->buffer.datas[j].data = SPA_UINT32_TO_PTR(m->id); - } else if (d->type == SPA_DATA_MemPtr) { - b->buffer.datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr)); - } else { - b->buffer.datas[j].type = SPA_ID_INVALID; - b->buffer.datas[j].data = 0; - spa_log_error(this->log, "invalid memory type %d", d->type); - } - } - } - - pw_client_node0_resource_port_use_buffers(this->resource, - this->seq, - direction, port_id, - n_buffers, mb); - - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int -impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) -{ - struct node *this = object; - struct impl *impl; - - spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_OUT_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); - - impl = this->impl; - - spa_log_trace(this->log, "reuse buffer %d", buffer_id); - - pw_client_node0_transport_add_message(impl->transport, (struct pw_client_node0_message *) - &PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER_INIT(port_id, buffer_id)); - do_flush(this); - - return 0; -} - -static int impl_node_process_input(struct spa_node *node) -{ - struct node *this = SPA_CONTAINER_OF(node, struct node, node); - struct impl *impl = this->impl; -// bool client_reuse = impl->client_reuse; - uint32_t i; - int res; - - if (impl->input_ready == 0) { - /* the client is not ready to receive our buffers, recycle them */ - pw_log_trace("node not ready, recycle buffers"); - for (i = 0; i < MAX_INPUTS; i++) { - struct port *p = &this->in_ports[i]; - struct spa_io_buffers *io = p->io; - - if (!p->valid || io == NULL) - continue; - - io->status = SPA_STATUS_NEED_DATA; - } - res = SPA_STATUS_NEED_DATA; - } - else { - for (i = 0; i < MAX_INPUTS; i++) { - struct port *p = &this->in_ports[i]; - struct spa_io_buffers *io = p->io; - - if (!p->valid || io == NULL) - continue; - - pw_log_trace("set io status to %d %d", io->status, io->buffer_id); - impl->transport->inputs[p->id] = *io; - - /* explicitly recycle buffers when the client is not going to do it */ -// if (!client_reuse && (pp = p->peer)) -// spa_node_port_reuse_buffer(pp->node->implementation, -// pp->port_id, io->buffer_id); - } - pw_client_node0_transport_add_message(impl->transport, - &PW_CLIENT_NODE0_MESSAGE_INIT(PW_CLIENT_NODE0_MESSAGE_PROCESS_INPUT)); - do_flush(this); - - impl->input_ready--; - res = SPA_STATUS_OK; - } - return res; -} - -#if 0 -/** this is used for clients providing data to pipewire and currently - * not supported in the compat layer */ -static int impl_node_process_output(struct spa_node *node) -{ - struct node *this; - struct impl *impl; - uint32_t i; - - this = SPA_CONTAINER_OF(node, struct node, node); - impl = this->impl; - - if (impl->out_pending) - goto done; - - impl->out_pending = true; - - for (i = 0; i < MAX_OUTPUTS; i++) { - struct port *p = &this->out_ports[i]; - struct spa_io_buffers *io = p->io; - - if (!p->valid || io == NULL) - continue; - - impl->transport->outputs[p->id] = *io; - - pw_log_trace("%d %d -> %d %d", io->status, io->buffer_id, - impl->transport->outputs[p->id].status, - impl->transport->outputs[p->id].buffer_id); - } - - done: - pw_client_node0_transport_add_message(impl->transport, - &PW_CLIENT_NODE0_MESSAGE_INIT(PW_CLIENT_NODE0_MESSAGE_PROCESS_OUTPUT)); - do_flush(this); - - return SPA_STATUS_OK; -} -#endif - -static int impl_node_process(void *object) -{ - struct node *this = object; - struct impl *impl = this->impl; - struct pw_impl_node *n = impl->this.node; - return impl_node_process_input(pw_impl_node_get_implementation(n)); -} - -static int handle_node_message(struct node *this, struct pw_client_node0_message *message) -{ - struct impl *impl = SPA_CONTAINER_OF(this, struct impl, node); - uint32_t i; - - switch (PW_CLIENT_NODE0_MESSAGE_TYPE(message)) { - case PW_CLIENT_NODE0_MESSAGE_HAVE_OUTPUT: - for (i = 0; i < MAX_OUTPUTS; i++) { - struct port *p = &this->out_ports[i]; - struct spa_io_buffers *io = p->io; - if (!p->valid || io == NULL) - continue; - *io = impl->transport->outputs[p->id]; - pw_log_trace("have output %d %d", io->status, io->buffer_id); - } - impl->out_pending = false; - spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); - break; - - case PW_CLIENT_NODE0_MESSAGE_NEED_INPUT: - for (i = 0; i < MAX_INPUTS; i++) { - struct port *p = &this->in_ports[i]; - struct spa_io_buffers *io = p->io; - if (!p->valid || io == NULL) - continue; - pw_log_trace("need input %d %d", i, p->id); - *io = impl->transport->inputs[p->id]; - pw_log_trace("need input %d %d", io->status, io->buffer_id); - } - impl->input_ready++; - spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); - break; - - case PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER: - if (impl->client_reuse) { - struct pw_client_node0_message_port_reuse_buffer *p = - (struct pw_client_node0_message_port_reuse_buffer *) message; - spa_node_call_reuse_buffer(&this->callbacks, p->body.port_id.value, - p->body.buffer_id.value); - } - break; - - default: - pw_log_warn("unhandled message %d", PW_CLIENT_NODE0_MESSAGE_TYPE(message)); - return -ENOTSUP; - } - return 0; -} - -static void setup_transport(struct impl *impl) -{ - struct node *this = &impl->node; - uint32_t max_inputs = 0, max_outputs = 0, n_inputs = 0, n_outputs = 0; - struct spa_dict_item items[1]; - - n_inputs = this->n_inputs; - max_inputs = this->info.max_input_ports == 0 ? this->n_inputs : this->info.max_input_ports; - n_outputs = this->n_outputs; - max_outputs = this->info.max_output_ports == 0 ? this->n_outputs : this->info.max_output_ports; - - impl->transport = pw_client_node0_transport_new(impl->context, max_inputs, max_outputs); - impl->transport->area->n_input_ports = n_inputs; - impl->transport->area->n_output_ports = n_outputs; - - if (n_inputs > 0) { - items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Stream/Input/Video"); - } else { - items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Stream/Output/Video"); - } - pw_impl_node_update_properties(impl->this.node, &SPA_DICT_INIT(items, 1)); -} - -static void -client_node0_done(void *data, int seq, int res) -{ - struct impl *impl = data; - struct node *this = &impl->node; - - if (seq == 0 && res == 0 && impl->transport == NULL) - setup_transport(impl); - - pw_log_debug("seq:%d res:%d pending:%d", seq, res, this->init_pending); - spa_node_emit_result(&this->hooks, seq, res, 0, NULL); - - if (this->init_pending != SPA_ID_INVALID) { - spa_node_emit_result(&this->hooks, this->init_pending, res, 0, NULL); - this->init_pending = SPA_ID_INVALID; - } -} - -static void -client_node0_update(void *data, - uint32_t change_mask, - uint32_t max_input_ports, - uint32_t max_output_ports, - uint32_t n_params, - const struct spa_pod **params) -{ - struct impl *impl = data; - struct node *this = &impl->node; - - if (change_mask & PW_CLIENT_NODE0_UPDATE_MAX_INPUTS) - this->info.max_input_ports = max_input_ports; - if (change_mask & PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS) - this->info.max_output_ports = max_output_ports; - if (change_mask & PW_CLIENT_NODE0_UPDATE_PARAMS) { - uint32_t i; - spa_log_debug(this->log, "node %p: update %d params", this, n_params); - - for (i = 0; i < this->n_params; i++) - free(this->params[i]); - this->n_params = n_params; - if (this->n_params == 0) { - free(this->params); - this->params = NULL; - } else { - void *p; - p = pw_reallocarray(this->params, this->n_params, sizeof(struct spa_pod *)); - if (p == NULL) { - pw_log_error("%p: can't realloc: %m", this); - free(this->params); - this->n_params = 0; - } - this->params = p; - } - for (i = 0; i < this->n_params; i++) - this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL; - } - if (change_mask & (PW_CLIENT_NODE0_UPDATE_MAX_INPUTS | PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS)) { - spa_node_emit_info(&this->hooks, &this->info); - } - - spa_log_debug(this->log, "node %p: got node update max_in %u, max_out %u", this, - this->info.max_input_ports, this->info.max_output_ports); -} - -static void -client_node0_port_update(void *data, - enum spa_direction direction, - uint32_t port_id, - uint32_t change_mask, - uint32_t n_params, - const struct spa_pod **params, - const struct spa_port_info *info) -{ - struct impl *impl = data; - struct node *this = &impl->node; - bool remove; - - spa_log_debug(this->log, "node %p: got port update", this); - if (!CHECK_PORT_ID(this, direction, port_id)) - return; - - remove = (change_mask == 0); - - if (remove) { - do_uninit_port(this, direction, port_id); - } else { - do_update_port(this, - direction, - port_id, - change_mask, - n_params, params, info); - } -} - -static void client_node0_set_active(void *data, bool active) -{ - struct impl *impl = data; - pw_impl_node_set_active(impl->this.node, active); -} - -static void client_node0_event(void *data, struct spa_event *event) -{ - struct impl *impl = data; - struct node *this = &impl->node; - - switch (SPA_EVENT_TYPE(event)) { - case SPA_NODE0_EVENT_RequestClockUpdate: - send_clock_update(this); - break; - default: - spa_node_emit_event(&this->hooks, event); - } -} - -static const struct pw_client_node0_methods client_node0_methods = { - PW_VERSION_CLIENT_NODE0_METHODS, - .done = client_node0_done, - .update = client_node0_update, - .port_update = client_node0_port_update, - .set_active = client_node0_set_active, - .event = client_node0_event, -}; - -static void node_on_data_fd_events(struct spa_source *source) -{ - struct node *this = source->data; - struct impl *impl = this->impl; - - if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) { - spa_log_warn(this->log, "node %p: got error", this); - return; - } - - if (source->rmask & SPA_IO_IN) { - struct pw_client_node0_message message; - uint64_t cmd; - - if (spa_system_eventfd_read(this->data_system, this->data_source.fd, &cmd) < 0) - spa_log_warn(this->log, "node %p: error reading message: %s", - this, strerror(errno)); - - while (pw_client_node0_transport_next_message(impl->transport, &message) == 1) { - struct pw_client_node0_message *msg = alloca(SPA_POD_SIZE(&message)); - pw_client_node0_transport_parse_message(impl->transport, msg); - handle_node_message(this, msg); - } - } -} - -static const struct spa_node_methods impl_node = { - SPA_VERSION_NODE_METHODS, - .add_listener = impl_node_add_listener, - .set_callbacks = impl_node_set_callbacks, - .sync = impl_node_sync, - .enum_params = impl_node_enum_params, - .set_param = impl_node_set_param, - .set_io = impl_node_set_io, - .send_command = impl_node_send_command, - .add_port = impl_node_add_port, - .remove_port = impl_node_remove_port, - .port_enum_params = impl_node_port_enum_params, - .port_set_param = impl_node_port_set_param, - .port_use_buffers = impl_node_port_use_buffers, - .port_set_io = impl_node_port_set_io, - .port_reuse_buffer = impl_node_port_reuse_buffer, - .process = impl_node_process, -}; - -static int -node_init(struct node *this, - struct spa_dict *info, - const struct spa_support *support, - uint32_t n_support) -{ - this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); - this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); - - if (this->data_loop == NULL) { - spa_log_error(this->log, "a data-loop is needed"); - return -EINVAL; - } - - this->node.iface = SPA_INTERFACE_INIT( - SPA_TYPE_INTERFACE_Node, - SPA_VERSION_NODE, - &impl_node, this); - spa_hook_list_init(&this->hooks); - - this->data_source.func = node_on_data_fd_events; - this->data_source.data = this; - this->data_source.fd = -1; - this->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; - this->data_source.rmask = 0; - - this->seq = 1; - this->init_pending = SPA_ID_INVALID; - - return SPA_RESULT_RETURN_ASYNC(this->seq++); -} - -static int node_clear(struct node *this) -{ - uint32_t i; - - for (i = 0; i < MAX_INPUTS; i++) { - if (this->in_ports[i].valid) - clear_port(this, &this->in_ports[i], SPA_DIRECTION_INPUT, i); - } - for (i = 0; i < MAX_OUTPUTS; i++) { - if (this->out_ports[i].valid) - clear_port(this, &this->out_ports[i], SPA_DIRECTION_OUTPUT, i); - } - - return 0; -} - -static int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct spa_source *source = user_data; - spa_loop_remove_source(loop, source); - return 0; -} - -static void client_node0_resource_destroy(void *data) -{ - struct impl *impl = data; - struct pw_impl_client_node0 *this = &impl->this; - struct node *node = &impl->node; - - pw_log_debug("client-node %p: destroy", impl); - - impl->node.resource = this->resource = NULL; - spa_hook_remove(&impl->resource_listener); - spa_hook_remove(&impl->object_listener); - - if (node->data_source.fd != -1) { - spa_loop_locked(node->data_loop, - do_remove_source, - SPA_ID_INVALID, - NULL, - 0, - &node->data_source); - } - if (this->node) - pw_impl_node_destroy(this->node); -} - -static void node_initialized(void *data) -{ - struct impl *impl = data; - struct pw_impl_client_node0 *this = &impl->this; - struct pw_impl_node *node = this->node; - struct spa_system *data_system = impl->node.data_system; - - if (this->resource == NULL) - return; - - impl->fds[0] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - impl->fds[1] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - impl->node.data_source.fd = impl->fds[0]; - impl->node.writefd = impl->fds[1]; - impl->other_fds[0] = impl->fds[1]; - impl->other_fds[1] = impl->fds[0]; - - spa_loop_add_source(impl->node.data_loop, &impl->node.data_source); - pw_log_debug("client-node %p: transport fd %d %d", node, impl->fds[0], impl->fds[1]); - - pw_client_node0_resource_transport(this->resource, - pw_global_get_id(pw_impl_node_get_global(node)), - impl->other_fds[0], - impl->other_fds[1], - impl->transport); -} - -static void node_free(void *data) -{ - struct impl *impl = data; - struct pw_impl_client_node0 *this = &impl->this; - struct spa_system *data_system = impl->node.data_system; - - this->node = NULL; - - pw_log_debug("client-node %p: free", &impl->this); - node_clear(&impl->node); - - if (impl->transport) - pw_client_node0_transport_destroy(impl->transport); - - spa_hook_remove(&impl->node_listener); - - if (this->resource) - pw_resource_destroy(this->resource); - - pw_array_clear(&impl->mems); - - if (impl->fds[0] != -1) - spa_system_close(data_system, impl->fds[0]); - if (impl->fds[1] != -1) - spa_system_close(data_system, impl->fds[1]); - free(impl); -} - -static const struct pw_impl_node_events node_events = { - PW_VERSION_IMPL_NODE_EVENTS, - .free = node_free, - .initialized = node_initialized, -}; - -static const struct pw_resource_events resource_events = { - PW_VERSION_RESOURCE_EVENTS, - .destroy = client_node0_resource_destroy, -}; - -static void convert_properties(struct pw_properties *properties) -{ - static const struct { - const char *from, *to; - } props[] = { - { "pipewire.autoconnect", PW_KEY_NODE_AUTOCONNECT, }, - /* XXX deprecated */ - { "pipewire.target.node", PW_KEY_NODE_TARGET, } - }; - - const char *str; - - SPA_FOR_EACH_ELEMENT_VAR(props, p) { - if ((str = pw_properties_get(properties, p->from)) != NULL) { - pw_properties_set(properties, p->to, str); - pw_properties_set(properties, p->from, NULL); - } - } -} - -/** Create a new client node - * \param client an owner \ref pw_client - * \param id an id - * \param name a name - * \param properties extra properties - * \return a newly allocated client node - * - * Create a new \ref pw_impl_node. - * - * \memberof pw_impl_client_node - */ -struct pw_impl_client_node0 *pw_impl_client_node0_new(struct pw_resource *resource, - struct pw_properties *properties) -{ - struct impl *impl; - struct pw_impl_client_node0 *this; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct pw_context *context = pw_impl_client_get_context(client); - const struct spa_support *support; - uint32_t n_support; - const char *name; - int res; - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return NULL; - - this = &impl->this; - - if (properties == NULL) - properties = pw_properties_new(NULL, NULL); - if (properties == NULL) { - res = -errno; - goto error_exit_free; - } - convert_properties(properties); - - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_global_get_id(pw_impl_client_get_global(client))); - - impl->context = context; - impl->context_pool = pw_context_get_mempool(context); - impl->fds[0] = impl->fds[1] = -1; - pw_log_debug("client-node %p: new", impl); - - support = pw_context_get_support(impl->context, &n_support); - - node_init(&impl->node, NULL, support, n_support); - impl->node.impl = impl; - - pw_array_init(&impl->mems, 64); - - if ((name = pw_properties_get(properties, "node.name")) == NULL) - name = "client-node"; - pw_properties_set(properties, PW_KEY_MEDIA_TYPE, "Video"); - - impl->node.resource = resource; - this->resource = resource; - this->node = pw_spa_node_new(context, - PW_SPA_NODE_FLAG_ASYNC, - &impl->node.node, - NULL, - properties, 0); - if (this->node == NULL) { - res = -errno; - goto error_no_node; - } - - impl->client_reuse = pw_properties_get_bool(properties, "pipewire.client.reuse", false); - - pw_resource_add_listener(this->resource, - &impl->resource_listener, - &resource_events, - impl); - pw_resource_add_object_listener(this->resource, - &impl->object_listener, - &client_node0_methods, - impl); - - - pw_impl_node_add_listener(this->node, &impl->node_listener, &node_events, impl); - - return this; - -error_no_node: - pw_resource_destroy(this->resource); - node_clear(&impl->node); -error_exit_free: - free(impl); - errno = -res; - return NULL; -} - -/** Destroy a client node - * \param node the client node to destroy - * \memberof pw_impl_client_node - */ -void pw_impl_client_node0_destroy(struct pw_impl_client_node0 *node) -{ - pw_resource_destroy(node->resource); -} diff --git a/src/modules/module-client-node/v0/client-node.h b/src/modules/module-client-node/v0/client-node.h deleted file mode 100644 index 5c274060b..000000000 --- a/src/modules/module-client-node/v0/client-node.h +++ /dev/null @@ -1,91 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2015 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PIPEWIRE_CLIENT_NODE0_H -#define PIPEWIRE_CLIENT_NODE0_H - -#include - -#include "ext-client-node.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** The state of the clock */ -enum spa_clock0_state { - SPA_CLOCK0_STATE_STOPPED, /*< the clock is stopped */ - SPA_CLOCK0_STATE_PAUSED, /*< the clock is paused */ - SPA_CLOCK0_STATE_RUNNING, /*< the clock is running */ -}; - -struct spa_command_node0_clock_update_body { - struct spa_pod_object_body body; -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_TIME (1 << 0) -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_SCALE (1 << 1) -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_STATE (1 << 2) -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_LATENCY (1 << 3) - struct spa_pod_int change_mask SPA_ALIGNED(8); - struct spa_pod_int rate SPA_ALIGNED(8); - struct spa_pod_long ticks SPA_ALIGNED(8); - struct spa_pod_long monotonic_time SPA_ALIGNED(8); - struct spa_pod_long offset SPA_ALIGNED(8); - struct spa_pod_int scale SPA_ALIGNED(8); - struct spa_pod_int state SPA_ALIGNED(8); -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_FLAG_LIVE (1 << 0) - struct spa_pod_int flags SPA_ALIGNED(8); - struct spa_pod_long latency SPA_ALIGNED(8); -}; - -struct spa_command_node0_clock_update { - struct spa_pod pod; - struct spa_command_node0_clock_update_body body; -}; - -enum spa_node0_event { - SPA_NODE0_EVENT_START = SPA_TYPE_VENDOR_PipeWire, - SPA_NODE0_EVENT_RequestClockUpdate, -}; - -enum spa_node0_command { - SPA_NODE0_COMMAND_START = SPA_TYPE_VENDOR_PipeWire, - SPA_NODE0_COMMAND_ClockUpdate, -}; - -#define SPA_COMMAND_NODE0_CLOCK_UPDATE_INIT(type,change_mask,rate,ticks,monotonic_time,offset,scale,state,flags,latency) \ - SPA_COMMAND_INIT_FULL(struct spa_command_node0_clock_update, \ - sizeof(struct spa_command_node0_clock_update_body), 0, type, \ - SPA_POD_INIT_Int(change_mask), \ - SPA_POD_INIT_Int(rate), \ - SPA_POD_INIT_Long(ticks), \ - SPA_POD_INIT_Long(monotonic_time), \ - SPA_POD_INIT_Long(offset), \ - SPA_POD_INIT_Int(scale), \ - SPA_POD_INIT_Int(state), \ - SPA_POD_INIT_Int(flags), \ - SPA_POD_INIT_Long(latency)) - - -/** \class pw_impl_client_node0 - * - * PipeWire client node interface - */ -struct pw_impl_client_node0 { - struct pw_impl_node *node; - - struct pw_resource *resource; -}; - -struct pw_impl_client_node0 * -pw_impl_client_node0_new(struct pw_resource *resource, - struct pw_properties *properties); - -void -pw_impl_client_node0_destroy(struct pw_impl_client_node0 *node); - -#ifdef __cplusplus -} -#endif - -#endif /* PIPEWIRE_CLIENT_NODE0_H */ diff --git a/src/modules/module-client-node/v0/ext-client-node.h b/src/modules/module-client-node/v0/ext-client-node.h deleted file mode 100644 index ccc57e204..000000000 --- a/src/modules/module-client-node/v0/ext-client-node.h +++ /dev/null @@ -1,394 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2016 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef __PIPEWIRE_EXT_CLIENT_NODE0_H__ -#define __PIPEWIRE_EXT_CLIENT_NODE0_H__ - -#include -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode" - -#define PW_VERSION_CLIENT_NODE0 0 - -struct pw_client_node0_message; - -/** Shared structure between client and server \memberof pw_client_node */ -struct pw_client_node0_area { - uint32_t max_input_ports; /**< max input ports of the node */ - uint32_t n_input_ports; /**< number of input ports of the node */ - uint32_t max_output_ports; /**< max output ports of the node */ - uint32_t n_output_ports; /**< number of output ports of the node */ -}; - -/** \class pw_client_node0_transport - * - * \brief Transport object - * - * The transport object contains shared data and ringbuffers to exchange - * events and data between the server and the client in a low-latency and - * lockfree way. - */ -struct pw_client_node0_transport { - struct pw_client_node0_area *area; /**< the transport area */ - struct spa_io_buffers *inputs; /**< array of buffer input io */ - struct spa_io_buffers *outputs; /**< array of buffer output io */ - void *input_data; /**< input memory for ringbuffer */ - struct spa_ringbuffer *input_buffer; /**< ringbuffer for input memory */ - void *output_data; /**< output memory for ringbuffer */ - struct spa_ringbuffer *output_buffer; /**< ringbuffer for output memory */ - - /** Destroy a transport - * \param trans a transport to destroy - * \memberof pw_client_node0_transport - */ - void (*destroy) (struct pw_client_node0_transport *trans); - - /** Add a message to the transport - * \param trans the transport to send the message on - * \param message the message to add - * \return 0 on success, < 0 on error - * - * Write \a message to the shared ringbuffer. - */ - int (*add_message) (struct pw_client_node0_transport *trans, struct pw_client_node0_message *message); - - /** Get next message from a transport - * \param trans the transport to get the message of - * \param[out] message the message to read - * \return < 0 on error, 1 when a message is available, - * 0 when no more messages are available. - * - * Get the skeleton next message from \a trans into \a message. This function will - * only read the head and object body of the message. - * - * After the complete size of the message has been calculated, you should call - * \ref parse_message() to read the complete message contents. - */ - int (*next_message) (struct pw_client_node0_transport *trans, struct pw_client_node0_message *message); - - /** Parse the complete message on transport - * \param trans the transport to read from - * \param[out] message memory that can hold the complete message - * \return 0 on success, < 0 on error - * - * Use this function after \ref next_message(). - */ - int (*parse_message) (struct pw_client_node0_transport *trans, void *message); -}; - -#define pw_client_node0_transport_destroy(t) ((t)->destroy((t))) -#define pw_client_node0_transport_add_message(t,m) ((t)->add_message((t), (m))) -#define pw_client_node0_transport_next_message(t,m) ((t)->next_message((t), (m))) -#define pw_client_node0_transport_parse_message(t,m) ((t)->parse_message((t), (m))) - -enum pw_client_node0_message_type { - PW_CLIENT_NODE0_MESSAGE_HAVE_OUTPUT, /*< signal that the node has output */ - PW_CLIENT_NODE0_MESSAGE_NEED_INPUT, /*< signal that the node needs input */ - PW_CLIENT_NODE0_MESSAGE_PROCESS_INPUT, /*< instruct the node to process input */ - PW_CLIENT_NODE0_MESSAGE_PROCESS_OUTPUT, /*< instruct the node output is processed */ - PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER, /*< reuse a buffer */ -}; - -struct pw_client_node0_message_body { - struct spa_pod_int type SPA_ALIGNED(8); /*< one of enum pw_client_node0_message_type */ -}; - -struct pw_client_node0_message { - struct spa_pod_struct pod; - struct pw_client_node0_message_body body; -}; - -struct pw_client_node0_message_port_reuse_buffer_body { - struct spa_pod_int type SPA_ALIGNED(8); /*< PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER */ - struct spa_pod_int port_id SPA_ALIGNED(8); /*< port id */ - struct spa_pod_int buffer_id SPA_ALIGNED(8); /*< buffer id to reuse */ -}; - -struct pw_client_node0_message_port_reuse_buffer { - struct spa_pod_struct pod; - struct pw_client_node0_message_port_reuse_buffer_body body; -}; - -#define PW_CLIENT_NODE0_MESSAGE_TYPE(message) (((struct pw_client_node0_message*)(message))->body.type.value) - -#define PW_CLIENT_NODE0_MESSAGE_INIT(message) ((struct pw_client_node0_message) \ - { { { sizeof(struct pw_client_node0_message_body), SPA_TYPE_Struct } }, \ - { SPA_POD_INIT_Int(message) } }) - -#define PW_CLIENT_NODE0_MESSAGE_INIT_FULL(type,size,message,...) (type) \ - { { { size, SPA_TYPE_Struct } }, \ - { SPA_POD_INIT_Int(message), ##__VA_ARGS__ } } \ - -#define PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER_INIT(port_id,buffer_id) \ - PW_CLIENT_NODE0_MESSAGE_INIT_FULL(struct pw_client_node0_message_port_reuse_buffer, \ - sizeof(struct pw_client_node0_message_port_reuse_buffer_body), \ - PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER, \ - SPA_POD_INIT_Int(port_id), \ - SPA_POD_INIT_Int(buffer_id)) - -/** information about a buffer */ -struct pw_client_node0_buffer { - uint32_t mem_id; /**< the memory id for the metadata */ - uint32_t offset; /**< offset in memory */ - uint32_t size; /**< size in memory */ - struct spa_buffer *buffer; /**< buffer describing metadata and buffer memory */ -}; - -#define PW_CLIENT_NODE0_METHOD_DONE 0 -#define PW_CLIENT_NODE0_METHOD_UPDATE 1 -#define PW_CLIENT_NODE0_METHOD_PORT_UPDATE 2 -#define PW_CLIENT_NODE0_METHOD_SET_ACTIVE 3 -#define PW_CLIENT_NODE0_METHOD_EVENT 4 -#define PW_CLIENT_NODE0_METHOD_DESTROY 5 -#define PW_CLIENT_NODE0_METHOD_NUM 6 - -/** \ref pw_client_node methods */ -struct pw_client_node0_methods { -#define PW_VERSION_CLIENT_NODE0_METHODS 0 - uint32_t version; - - /** Complete an async operation */ - void (*done) (void *object, int seq, int res); - - /** - * Update the node ports and properties - * - * Update the maximum number of ports and the params of the - * client node. - * \param change_mask bitfield with changed parameters - * \param max_input_ports new max input ports - * \param max_output_ports new max output ports - * \param params new params - */ - void (*update) (void *object, -#define PW_CLIENT_NODE0_UPDATE_MAX_INPUTS (1 << 0) -#define PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS (1 << 1) -#define PW_CLIENT_NODE0_UPDATE_PARAMS (1 << 2) - uint32_t change_mask, - uint32_t max_input_ports, - uint32_t max_output_ports, - uint32_t n_params, - const struct spa_pod **params); - - /** - * Update a node port - * - * Update the information of one port of a node. - * \param direction the direction of the port - * \param port_id the port id to update - * \param change_mask a bitfield of changed items - * \param n_params number of port parameters - * \param params array of port parameters - * \param info port information - */ - void (*port_update) (void *object, - enum spa_direction direction, - uint32_t port_id, -#define PW_CLIENT_NODE0_PORT_UPDATE_PARAMS (1 << 0) -#define PW_CLIENT_NODE0_PORT_UPDATE_INFO (1 << 1) - uint32_t change_mask, - uint32_t n_params, - const struct spa_pod **params, - const struct spa_port_info *info); - /** - * Activate or deactivate the node - */ - void (*set_active) (void *object, bool active); - /** - * Send an event to the node - * \param event the event to send - */ - void (*event) (void *object, struct spa_event *event); - /** - * Destroy the client_node - */ - void (*destroy) (void *object); -}; - -#define PW_CLIENT_NODE0_EVENT_ADD_MEM 0 -#define PW_CLIENT_NODE0_EVENT_TRANSPORT 1 -#define PW_CLIENT_NODE0_EVENT_SET_PARAM 2 -#define PW_CLIENT_NODE0_EVENT_EVENT 3 -#define PW_CLIENT_NODE0_EVENT_COMMAND 4 -#define PW_CLIENT_NODE0_EVENT_ADD_PORT 5 -#define PW_CLIENT_NODE0_EVENT_REMOVE_PORT 6 -#define PW_CLIENT_NODE0_EVENT_PORT_SET_PARAM 7 -#define PW_CLIENT_NODE0_EVENT_PORT_USE_BUFFERS 8 -#define PW_CLIENT_NODE0_EVENT_PORT_COMMAND 9 -#define PW_CLIENT_NODE0_EVENT_PORT_SET_IO 10 -#define PW_CLIENT_NODE0_EVENT_NUM 11 - -/** \ref pw_client_node events */ -struct pw_client_node0_events { -#define PW_VERSION_CLIENT_NODE0_EVENTS 0 - uint32_t version; - /** - * Memory was added to a node - * - * \param mem_id the id of the memory - * \param type the memory type - * \param memfd the fd of the memory - * \param flags flags for the \a memfd - */ - void (*add_mem) (void *data, - uint32_t mem_id, - uint32_t type, - int memfd, - uint32_t flags); - /** - * Notify of a new transport area - * - * The transport area is used to exchange real-time commands between - * the client and the server. - * - * \param node_id the node id created for this client node - * \param readfd fd for signal data can be read - * \param writefd fd for signal data can be written - * \param transport the shared transport area - */ - void (*transport) (void *data, - uint32_t node_id, - int readfd, - int writefd, - struct pw_client_node0_transport *transport); - /** - * Notify of a property change - * - * When the server configures the properties on the node - * this event is sent - * - * \param seq a sequence number - * \param id the id of the parameter - * \param flags parameter flags - * \param param the param to set - */ - void (*set_param) (void *data, uint32_t seq, - uint32_t id, uint32_t flags, - const struct spa_pod *param); - /** - * Receive an event from the client node - * \param event the received event */ - void (*event) (void *data, const struct spa_event *event); - /** - * Notify of a new node command - * - * \param seq a sequence number - * \param command the command - */ - void (*command) (void *data, uint32_t seq, const struct spa_command *command); - /** - * A new port was added to the node - * - * The server can at any time add a port to the node when there - * are free ports available. - * - * \param seq a sequence number - * \param direction the direction of the port - * \param port_id the new port id - */ - void (*add_port) (void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id); - /** - * A port was removed from the node - * - * \param seq a sequence number - * \param direction a port direction - * \param port_id the remove port id - */ - void (*remove_port) (void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id); - /** - * A parameter was configured on the port - * - * \param seq a sequence number - * \param direction a port direction - * \param port_id the port id - * \param id the id of the parameter - * \param flags flags used when setting the param - * \param param the new param - */ - void (*port_set_param) (void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param); - /** - * Notify the port of buffers - * - * \param seq a sequence number - * \param direction a port direction - * \param port_id the port id - * \param n_buffer the number of buffers - * \param buffers and array of buffer descriptions - */ - void (*port_use_buffers) (void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t n_buffers, - struct pw_client_node0_buffer *buffers); - /** - * Notify of a new port command - * - * \param direction a port direction - * \param port_id the port id - * \param command the command - */ - void (*port_command) (void *data, - enum spa_direction direction, - uint32_t port_id, - const struct spa_command *command); - - /** - * Configure the io area with \a id of \a port_id. - * - * \param seq a sequence number - * \param direction the direction of the port - * \param port_id the port id - * \param id the id of the io area to set - * \param mem_id the id of the memory to use - * \param offset offset of io area in memory - * \param size size of the io area - */ - void (*port_set_io) (void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - uint32_t mem_id, - uint32_t offset, - uint32_t size); -}; -#define pw_client_node0_resource(r,m,v,...) pw_resource_call(r, struct pw_client_node0_events, m, v, ##__VA_ARGS__) - -#define pw_client_node0_resource_add_mem(r,...) pw_client_node0_resource(r,add_mem,0,__VA_ARGS__) -#define pw_client_node0_resource_transport(r,...) pw_client_node0_resource(r,transport,0,__VA_ARGS__) -#define pw_client_node0_resource_set_param(r,...) pw_client_node0_resource(r,set_param,0,__VA_ARGS__) -#define pw_client_node0_resource_event(r,...) pw_client_node0_resource(r,event,0,__VA_ARGS__) -#define pw_client_node0_resource_command(r,...) pw_client_node0_resource(r,command,0,__VA_ARGS__) -#define pw_client_node0_resource_add_port(r,...) pw_client_node0_resource(r,add_port,0,__VA_ARGS__) -#define pw_client_node0_resource_remove_port(r,...) pw_client_node0_resource(r,remove_port,0,__VA_ARGS__) -#define pw_client_node0_resource_port_set_param(r,...) pw_client_node0_resource(r,port_set_param,0,__VA_ARGS__) -#define pw_client_node0_resource_port_use_buffers(r,...) pw_client_node0_resource(r,port_use_buffers,0,__VA_ARGS__) -#define pw_client_node0_resource_port_command(r,...) pw_client_node0_resource(r,port_command,0,__VA_ARGS__) -#define pw_client_node0_resource_port_set_io(r,...) pw_client_node0_resource(r,port_set_io,0,__VA_ARGS__) - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __PIPEWIRE_EXT_CLIENT_NODE0_H__ */ diff --git a/src/modules/module-client-node/v0/protocol-native.c b/src/modules/module-client-node/v0/protocol-native.c deleted file mode 100644 index a577c6f4a..000000000 --- a/src/modules/module-client-node/v0/protocol-native.c +++ /dev/null @@ -1,514 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2017 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include - -#include -#include -#include - -#include "pipewire/impl.h" - -#include "pipewire/extensions/protocol-native.h" - -#include "ext-client-node.h" - -#include "transport.h" - -#define PW_PROTOCOL_NATIVE_FLAG_REMAP (1<<0) - -extern uint32_t pw_protocol_native0_find_type(struct pw_impl_client *client, const char *type); -extern int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod, - struct spa_pod_builder *b); -extern struct spa_pod * pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, - const struct spa_pod *pod); -extern uint32_t pw_protocol_native0_type_to_v2(struct pw_impl_client *client, - const struct spa_type_info *info, uint32_t type); - -static void -client_node_marshal_add_mem(void *data, - uint32_t mem_id, - uint32_t type, - int memfd, uint32_t flags) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - const char *typename; - - switch (type) { - case SPA_DATA_MemFd: - typename = "Spa:Enum:DataType:Fd:MemFd"; - break; - case SPA_DATA_DmaBuf: - typename = "Spa:Enum:DataType:Fd:DmaBuf"; - break; - default: - case SPA_DATA_MemPtr: - return; - - } - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_ADD_MEM, NULL); - - spa_pod_builder_add_struct(b, - "i", mem_id, - "I", pw_protocol_native0_find_type(client, typename), - "i", pw_protocol_native_add_resource_fd(resource, memfd), - "i", flags); - - pw_protocol_native_end_resource(resource, b); -} - -static void client_node_marshal_transport(void *data, uint32_t node_id, int readfd, int writefd, - struct pw_client_node0_transport *transport) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct pw_client_node0_transport_info info; - - pw_client_node0_transport_get_info(transport, &info); - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_TRANSPORT, NULL); - - spa_pod_builder_add_struct(b, - "i", node_id, - "i", pw_protocol_native_add_resource_fd(resource, readfd), - "i", pw_protocol_native_add_resource_fd(resource, writefd), - "i", pw_protocol_native_add_resource_fd(resource, info.memfd), - "i", info.offset, - "i", info.size); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_set_param(void *data, uint32_t seq, uint32_t id, uint32_t flags, - const struct spa_pod *param) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_SET_PARAM, NULL); - - spa_pod_builder_add_struct(b, - "i", seq, - "I", id, - "i", flags, - "P", param); - - pw_protocol_native_end_resource(resource, b); -} - -static void client_node_marshal_event_event(void *data, const struct spa_event *event) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_EVENT, NULL); - - spa_pod_builder_add_struct(b, "P", event); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_command(void *data, uint32_t seq, const struct spa_command *command) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_COMMAND, NULL); - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, "i", seq, NULL); - if (SPA_COMMAND_TYPE(command) == 0) - spa_pod_builder_add(b, "P", command, NULL); - else - pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)command, b); - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_add_port(void *data, - uint32_t seq, enum spa_direction direction, uint32_t port_id) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_ADD_PORT, NULL); - - spa_pod_builder_add_struct(b, - "i", seq, - "i", direction, - "i", port_id); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_remove_port(void *data, - uint32_t seq, enum spa_direction direction, uint32_t port_id) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_REMOVE_PORT, NULL); - - spa_pod_builder_add_struct(b, - "i", seq, - "i", direction, - "i", port_id); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_port_set_param(void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - uint32_t flags, - const struct spa_pod *param) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - const char *typename; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_SET_PARAM, NULL); - - switch (id) { - case SPA_PARAM_Props: - typename = "Spa:Enum:ParamId:Props"; - break; - case SPA_PARAM_Format: - typename = "Spa:Enum:ParamId:Format"; - break; - default: - return; - } - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", seq, - "i", direction, - "i", port_id, - "I", pw_protocol_native0_find_type(client, typename), - "i", flags, NULL); - pw_protocol_native0_pod_to_v2(client, param, b); - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_port_use_buffers(void *data, - uint32_t seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t n_buffers, struct pw_client_node0_buffer *buffers) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, j; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_USE_BUFFERS, NULL); - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", seq, - "i", direction, - "i", port_id, - "i", n_buffers, NULL); - - for (i = 0; i < n_buffers; i++) { - struct spa_buffer *buf = buffers[i].buffer; - - spa_pod_builder_add(b, - "i", buffers[i].mem_id, - "i", buffers[i].offset, - "i", buffers[i].size, - "i", i, - "i", buf->n_metas, NULL); - - for (j = 0; j < buf->n_metas; j++) { - struct spa_meta *m = &buf->metas[j]; - spa_pod_builder_add(b, - "I", pw_protocol_native0_type_to_v2(client, spa_type_meta_type, m->type), - "i", m->size, NULL); - } - spa_pod_builder_add(b, "i", buf->n_datas, NULL); - for (j = 0; j < buf->n_datas; j++) { - struct spa_data *d = &buf->datas[j]; - spa_pod_builder_add(b, - "I", pw_protocol_native0_type_to_v2(client, spa_type_data_type, d->type), - "i", SPA_PTR_TO_UINT32(d->data), - "i", d->flags, - "i", d->mapoffset, - "i", d->maxsize, NULL); - } - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_port_command(void *data, - uint32_t direction, - uint32_t port_id, - const struct spa_command *command) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_COMMAND, NULL); - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", direction, - "i", port_id, NULL); - pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)command, b); - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void -client_node_marshal_port_set_io(void *data, - uint32_t seq, - uint32_t direction, - uint32_t port_id, - uint32_t id, - uint32_t memid, - uint32_t offset, - uint32_t size) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_SET_IO, NULL); - - spa_pod_builder_add_struct(b, - "i", seq, - "i", direction, - "i", port_id, - "I", id, - "i", memid, - "i", offset, - "i", size); - - pw_protocol_native_end_resource(resource, b); -} - - -static int client_node_demarshal_done(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - uint32_t seq, res; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "i", &seq, - "i", &res) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_client_node0_methods, done, 0, seq, res); -} - -static int client_node_demarshal_update(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - struct spa_pod_frame f; - uint32_t change_mask, max_input_ports, max_output_ports, n_params; - const struct spa_pod **params; - uint32_t i; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f) < 0 || - spa_pod_parser_get(&prs, - "i", &change_mask, - "i", &max_input_ports, - "i", &max_output_ports, - "i", &n_params, NULL) < 0) - return -EINVAL; - - params = alloca(n_params * sizeof(struct spa_pod *)); - for (i = 0; i < n_params; i++) - if (spa_pod_parser_get(&prs, "O", ¶ms[i], NULL) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_client_node0_methods, update, 0, change_mask, - max_input_ports, - max_output_ports, - n_params, - params); -} - -static int client_node_demarshal_port_update(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - struct spa_pod_frame f[2]; - uint32_t i, direction, port_id, change_mask, n_params; - const struct spa_pod **params = NULL; - struct spa_port_info info = { 0 }, *infop = NULL; - struct spa_dict props; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || - spa_pod_parser_get(&prs, - "i", &direction, - "i", &port_id, - "i", &change_mask, - "i", &n_params, NULL) < 0) - return -EINVAL; - - params = alloca(n_params * sizeof(struct spa_pod *)); - for (i = 0; i < n_params; i++) - if (spa_pod_parser_get(&prs, "O", ¶ms[i], NULL) < 0) - return -EINVAL; - - - if (spa_pod_parser_push_struct(&prs, &f[1]) >= 0) { - infop = &info; - - if (spa_pod_parser_get(&prs, - "i", &info.flags, - "i", &info.rate, - "i", &props.n_items, NULL) < 0) - return -EINVAL; - - if (props.n_items > 0) { - info.props = &props; - - props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); - for (i = 0; i < props.n_items; i++) { - if (spa_pod_parser_get(&prs, - "s", &props.items[i].key, - "s", &props.items[i].value, - NULL) < 0) - return -EINVAL; - } - } - } - - return pw_resource_notify(resource, struct pw_client_node0_methods, port_update, 0, direction, - port_id, - change_mask, - n_params, - params, infop); -} - -static int client_node_demarshal_set_active(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - int active; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "b", &active) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_client_node0_methods, set_active, 0, active); -} - -static int client_node_demarshal_event_method(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - struct spa_event *event; - int res; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "O", &event) < 0) - return -EINVAL; - - event = (struct spa_event*)pw_protocol_native0_pod_from_v2(client, (struct spa_pod *)event); - - res = pw_resource_notify(resource, struct pw_client_node0_methods, event, 0, event); - free(event); - - return res; -} - -static int client_node_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - int res; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, NULL) < 0) - return -EINVAL; - - res = pw_resource_notify(resource, struct pw_client_node0_methods, destroy, 0); - pw_resource_destroy(resource); - return res; -} - -static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_method_demarshal[] = { - { &client_node_demarshal_done, 0, 0 }, - { &client_node_demarshal_update, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP }, - { &client_node_demarshal_port_update, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP }, - { &client_node_demarshal_set_active, 0, 0 }, - { &client_node_demarshal_event_method, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP }, - { &client_node_demarshal_destroy, 0, 0 }, -}; - -static const struct pw_client_node0_events pw_protocol_native_client_node_event_marshal = { - PW_VERSION_CLIENT_NODE0_EVENTS, - &client_node_marshal_add_mem, - &client_node_marshal_transport, - &client_node_marshal_set_param, - &client_node_marshal_event_event, - &client_node_marshal_command, - &client_node_marshal_add_port, - &client_node_marshal_remove_port, - &client_node_marshal_port_set_param, - &client_node_marshal_port_use_buffers, - &client_node_marshal_port_command, - &client_node_marshal_port_set_io, -}; - -static const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = { - PW_TYPE_INTERFACE_ClientNode, - PW_VERSION_CLIENT_NODE0, - PW_CLIENT_NODE0_METHOD_NUM, - PW_CLIENT_NODE0_EVENT_NUM, - 0, - NULL, - .server_demarshal = &pw_protocol_native_client_node_method_demarshal, - .server_marshal = &pw_protocol_native_client_node_event_marshal, - NULL, -}; - -struct pw_protocol *pw_protocol_native_ext_client_node0_init(struct pw_context *context) -{ - struct pw_protocol *protocol; - - protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native); - - if (protocol == NULL) - return NULL; - - pw_protocol_add_marshal(protocol, &pw_protocol_native_client_node_marshal); - - return protocol; -} diff --git a/src/modules/module-client-node/v0/transport.c b/src/modules/module-client-node/v0/transport.c deleted file mode 100644 index d62f23ca2..000000000 --- a/src/modules/module-client-node/v0/transport.c +++ /dev/null @@ -1,241 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2016 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include - -#include -#include - -#include - -#include "ext-client-node.h" - -#include "transport.h" - -/** \cond */ - -#define INPUT_BUFFER_SIZE (1<<12) -#define OUTPUT_BUFFER_SIZE (1<<12) - -struct transport { - struct pw_client_node0_transport trans; - - struct pw_memblock *mem; - size_t offset; - - struct pw_client_node0_message current; - uint32_t current_index; -}; -/** \endcond */ - -static size_t area_get_size(struct pw_client_node0_area *area) -{ - size_t size; - size = sizeof(struct pw_client_node0_area); - size += area->max_input_ports * sizeof(struct spa_io_buffers); - size += area->max_output_ports * sizeof(struct spa_io_buffers); - size += sizeof(struct spa_ringbuffer); - size += INPUT_BUFFER_SIZE; - size += sizeof(struct spa_ringbuffer); - size += OUTPUT_BUFFER_SIZE; - return size; -} - -static void transport_setup_area(void *p, struct pw_client_node0_transport *trans) -{ - struct pw_client_node0_area *a; - - trans->area = a = p; - p = SPA_PTROFF(p, sizeof(struct pw_client_node0_area), struct spa_io_buffers); - - trans->inputs = p; - p = SPA_PTROFF(p, a->max_input_ports * sizeof(struct spa_io_buffers), void); - - trans->outputs = p; - p = SPA_PTROFF(p, a->max_output_ports * sizeof(struct spa_io_buffers), void); - - trans->input_buffer = p; - p = SPA_PTROFF(p, sizeof(struct spa_ringbuffer), void); - - trans->input_data = p; - p = SPA_PTROFF(p, INPUT_BUFFER_SIZE, void); - - trans->output_buffer = p; - p = SPA_PTROFF(p, sizeof(struct spa_ringbuffer), void); - - trans->output_data = p; - p = SPA_PTROFF(p, OUTPUT_BUFFER_SIZE, void); -} - -static void transport_reset_area(struct pw_client_node0_transport *trans) -{ - uint32_t i; - struct pw_client_node0_area *a = trans->area; - - for (i = 0; i < a->max_input_ports; i++) { - trans->inputs[i] = SPA_IO_BUFFERS_INIT; - } - for (i = 0; i < a->max_output_ports; i++) { - trans->outputs[i] = SPA_IO_BUFFERS_INIT; - } - spa_ringbuffer_init(trans->input_buffer); - spa_ringbuffer_init(trans->output_buffer); -} - -static void destroy(struct pw_client_node0_transport *trans) -{ - struct transport *impl = (struct transport *) trans; - - pw_log_debug("transport %p: destroy", trans); - - pw_memblock_free(impl->mem); - free(impl); -} - -static int add_message(struct pw_client_node0_transport *trans, struct pw_client_node0_message *message) -{ - struct transport *impl = (struct transport *) trans; - int32_t filled, avail; - uint32_t size, index; - - if (impl == NULL || message == NULL) - return -EINVAL; - - filled = spa_ringbuffer_get_write_index(trans->output_buffer, &index); - avail = OUTPUT_BUFFER_SIZE - filled; - size = SPA_POD_SIZE(message); - if (avail < (int)size) - return -ENOSPC; - - spa_ringbuffer_write_data(trans->output_buffer, - trans->output_data, OUTPUT_BUFFER_SIZE, - index & (OUTPUT_BUFFER_SIZE - 1), message, size); - spa_ringbuffer_write_update(trans->output_buffer, index + size); - - return 0; -} - -static int next_message(struct pw_client_node0_transport *trans, struct pw_client_node0_message *message) -{ - struct transport *impl = (struct transport *) trans; - int32_t avail; - - if (impl == NULL || message == NULL) - return -EINVAL; - - avail = spa_ringbuffer_get_read_index(trans->input_buffer, &impl->current_index); - if (avail < (int) sizeof(struct pw_client_node0_message)) - return 0; - - spa_ringbuffer_read_data(trans->input_buffer, - trans->input_data, INPUT_BUFFER_SIZE, - impl->current_index & (INPUT_BUFFER_SIZE - 1), - &impl->current, sizeof(struct pw_client_node0_message)); - - if (avail < (int) SPA_POD_SIZE(&impl->current)) - return 0; - - *message = impl->current; - - return 1; -} - -static int parse_message(struct pw_client_node0_transport *trans, void *message) -{ - struct transport *impl = (struct transport *) trans; - uint32_t size; - - if (impl == NULL || message == NULL) - return -EINVAL; - - size = SPA_POD_SIZE(&impl->current); - - spa_ringbuffer_read_data(trans->input_buffer, - trans->input_data, INPUT_BUFFER_SIZE, - impl->current_index & (INPUT_BUFFER_SIZE - 1), message, size); - spa_ringbuffer_read_update(trans->input_buffer, impl->current_index + size); - - return 0; -} - -/** Create a new transport - * \param max_input_ports maximum number of input_ports - * \param max_output_ports maximum number of output_ports - * \return a newly allocated \ref pw_client_node0_transport - * \memberof pw_client_node0_transport - */ -struct pw_client_node0_transport * -pw_client_node0_transport_new(struct pw_context *context, - uint32_t max_input_ports, uint32_t max_output_ports) -{ - struct transport *impl; - struct pw_client_node0_transport *trans; - struct pw_client_node0_area area = { 0 }; - - area.max_input_ports = max_input_ports; - area.n_input_ports = 0; - area.max_output_ports = max_output_ports; - area.n_output_ports = 0; - - impl = calloc(1, sizeof(struct transport)); - if (impl == NULL) - return NULL; - - pw_log_debug("transport %p: new %d %d", impl, max_input_ports, max_output_ports); - - trans = &impl->trans; - impl->offset = 0; - - impl->mem = pw_mempool_alloc(pw_context_get_mempool(context), - PW_MEMBLOCK_FLAG_READWRITE | - PW_MEMBLOCK_FLAG_SEAL | - PW_MEMBLOCK_FLAG_MAP, - SPA_DATA_MemFd, area_get_size(&area)); - if (impl->mem == NULL) { - free(impl); - return NULL; - } - - memcpy(impl->mem->map->ptr, &area, sizeof(struct pw_client_node0_area)); - transport_setup_area(impl->mem->map->ptr, trans); - transport_reset_area(trans); - - trans->destroy = destroy; - trans->add_message = add_message; - trans->next_message = next_message; - trans->parse_message = parse_message; - - return trans; -} - -struct pw_client_node0_transport * -pw_client_node0_transport_new_from_info(struct pw_client_node0_transport_info *info) -{ - errno = ENOTSUP; - return NULL; -} - -/** Get transport info - * \param trans the transport to get info of - * \param[out] info transport info - * \return 0 on success - * - * Fill \a info with the transport info of \a trans. This information can be - * passed to the client to set up the shared transport. - * - * \memberof pw_client_node0_transport - */ -int pw_client_node0_transport_get_info(struct pw_client_node0_transport *trans, - struct pw_client_node0_transport_info *info) -{ - struct transport *impl = (struct transport *) trans; - - info->memfd = impl->mem->fd; - info->offset = impl->offset; - info->size = impl->mem->size; - - return 0; -} diff --git a/src/modules/module-client-node/v0/transport.h b/src/modules/module-client-node/v0/transport.h deleted file mode 100644 index 22b9a3036..000000000 --- a/src/modules/module-client-node/v0/transport.h +++ /dev/null @@ -1,39 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2016 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ -#define __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ - -#include - -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** information about the transport region \memberof pw_client_node */ -struct pw_client_node0_transport_info { - int memfd; /**< the memfd of the transport area */ - uint32_t offset; /**< offset to map \a memfd at */ - uint32_t size; /**< size of memfd mapping */ -}; - -struct pw_client_node0_transport * -pw_client_node0_transport_new(struct pw_context *context, uint32_t max_input_ports, uint32_t max_output_ports); - -struct pw_client_node0_transport * -pw_client_node0_transport_new_from_info(struct pw_client_node0_transport_info *info); - -int -pw_client_node0_transport_get_info(struct pw_client_node0_transport *trans, - struct pw_client_node0_transport_info *info); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ */ diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index c7536bd0f..0917a7365 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -190,7 +190,6 @@ static const struct spa_dict_item module_props[] = { #define LOCK_SUFFIXLEN 5 void pw_protocol_native_init(struct pw_protocol *protocol); -void pw_protocol_native0_init(struct pw_protocol *protocol); void *protocol_native_security_context_init(struct pw_impl_module *module, struct pw_protocol *protocol); void protocol_native_security_context_free(void *data); @@ -271,8 +270,6 @@ struct client_data { unsigned int busy:1; unsigned int need_flush:1; - - struct protocol_compat_v2 compat_v2; }; static void debug_msg(const char *prefix, const struct pw_protocol_native_message *msg, bool hex) @@ -536,8 +533,6 @@ static void client_free(void *data) pw_loop_destroy_source(client->context->main_loop, this->source); if (this->connection) pw_protocol_native_connection_destroy(this->connection); - - pw_map_clear(&this->compat_v2.types); } static const struct pw_impl_client_events client_events = { @@ -567,9 +562,6 @@ static void on_start(void *data, uint32_t version) PW_PERM_ALL, version, 0) < 0) return; - if (version == 0) - client->compat_v2 = &this->compat_v2; - return; } @@ -687,7 +679,6 @@ static struct client_data *client_new(struct server *s, int fd) this->server = s; this->client = client; - pw_map_init(&this->compat_v2.types, 0, 32); pw_impl_client_add_listener(client, &this->client_listener, &client_events, this); @@ -1836,7 +1827,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args_str) this->extension = &protocol_ext_impl; pw_protocol_native_init(this); - pw_protocol_native0_init(this); pw_log_debug("%p: new", this); diff --git a/src/modules/module-protocol-native/defs.h b/src/modules/module-protocol-native/defs.h index 429934e0a..3b5519097 100644 --- a/src/modules/module-protocol-native/defs.h +++ b/src/modules/module-protocol-native/defs.h @@ -27,9 +27,3 @@ static inline void *get_first_pod_from_data(void *data, uint32_t maxsize, uint64 return NULL; return pod; } - -struct protocol_compat_v2 { - /* v2 typemap */ - struct pw_map types; - unsigned int send_types:1; -}; diff --git a/src/modules/module-protocol-native/v0/interfaces.h b/src/modules/module-protocol-native/v0/interfaces.h deleted file mode 100644 index 09d0acfc7..000000000 --- a/src/modules/module-protocol-native/v0/interfaces.h +++ /dev/null @@ -1,514 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2016 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PIPEWIRE_INTERFACES_V0_H -#define PIPEWIRE_INTERFACES_V0_H - -#include -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Core */ - -#define PW_VERSION_CORE_V0 0 - -#define PW_CORE_V0_METHOD_HELLO 0 -#define PW_CORE_V0_METHOD_UPDATE_TYPES 1 -#define PW_CORE_V0_METHOD_SYNC 2 -#define PW_CORE_V0_METHOD_GET_REGISTRY 3 -#define PW_CORE_V0_METHOD_CLIENT_UPDATE 4 -#define PW_CORE_V0_METHOD_PERMISSIONS 5 -#define PW_CORE_V0_METHOD_CREATE_OBJECT 6 -#define PW_CORE_V0_METHOD_DESTROY 7 -#define PW_CORE_V0_METHOD_NUM 8 - -/** - * Key to update default permissions of globals without specific - * permissions. value is "[r][w][x]" */ -#define PW_CORE_PERMISSIONS_DEFAULT "permissions.default" - -/** - * Key to update specific permissions of a global. If the global - * did not have specific permissions, it will first be assigned - * the default permissions before it is updated. - * Value is ":[r][w][x]"*/ -#define PW_CORE_PERMISSIONS_GLOBAL "permissions.global" - -/** - * Key to update specific permissions of all existing globals. - * This is equivalent to using \ref PW_CORE_PERMISSIONS_GLOBAL - * on each global id individually that did not have specific - * permissions. - * Value is "[r][w][x]" */ -#define PW_CORE_PERMISSIONS_EXISTING "permissions.existing" - -#define PW_LINK_OUTPUT_NODE_ID "link.output_node.id" -#define PW_LINK_OUTPUT_PORT_ID "link.output_port.id" -#define PW_LINK_INPUT_NODE_ID "link.input_node.id" -#define PW_LINK_INPUT_PORT_ID "link.input_port.id" - -/** - * \struct pw_core_v0_methods - * \brief Core methods - * - * The core global object. This is a singleton object used for - * creating new objects in the remote PipeWire instance. It is - * also used for internal features. - */ -struct pw_core_v0_methods { -#define PW_VERSION_CORE_V0_METHODS 0 - uint32_t version; - /** - * Start a conversation with the server. This will send - * the core info and server types. - * - * All the existing resources for the client (except the core - * resource) will be destroyed. - */ - void (*hello) (void *object); - /** - * Update the type map - * - * Send a type map update to the PipeWire server. The server uses this - * information to keep a mapping between client types and the server types. - * \param first_id the id of the first type - * \param types the types as a string - * \param n_types the number of types - */ - void (*update_types) (void *object, - uint32_t first_id, - const char **types, - uint32_t n_types); - /** - * Do server roundtrip - * - * Ask the server to emit the 'done' event with \a id. - * Since methods are handled in-order and events are delivered - * in-order, this can be used as a barrier to ensure all previous - * methods and the resulting events have been handled. - * \param seq the sequence number passed to the done event - */ - void (*sync) (void *object, uint32_t seq); - /** - * Get the registry object - * - * Create a registry object that allows the client to list and bind - * the global objects available from the PipeWire server - * \param version the client proxy id - * \param id the client proxy id - */ - void (*get_registry) (void *object, uint32_t version, uint32_t new_id); - /** - * Update the client properties - * \param props the new client properties - */ - void (*client_update) (void *object, const struct spa_dict *props); - /** - * Manage the permissions of the global objects - * - * Update the permissions of the global objects using the - * dictionary with properties. - * - * Globals can use the default permissions or can have specific - * permissions assigned to them. - * - * \param id the global id to change - * \param props dictionary with permission properties - */ - void (*permissions) (void *object, const struct spa_dict *props); - /** - * Create a new object on the PipeWire server from a factory. - * Use a \a factory_name of "client-node" to create a - * \ref pw_client_node. - * - * \param factory_name the factory name to use - * \param type the interface to bind to - * \param version the version of the interface - * \param props extra properties - * \param new_id the client proxy id - */ - void (*create_object) (void *object, - const char *factory_name, - uint32_t type, - uint32_t version, - const struct spa_dict *props, - uint32_t new_id); - - /** - * Destroy an object id - * - * \param id the object id to destroy - */ - void (*destroy) (void *object, uint32_t id); -}; - -#define PW_CORE_V0_EVENT_UPDATE_TYPES 0 -#define PW_CORE_V0_EVENT_DONE 1 -#define PW_CORE_V0_EVENT_ERROR 2 -#define PW_CORE_V0_EVENT_REMOVE_ID 3 -#define PW_CORE_V0_EVENT_INFO 4 -#define PW_CORE_V0_EVENT_NUM 5 - -/** \struct pw_core_v0_events - * \brief Core events - * \ingroup pw_core_interface The pw_core interface - */ -struct pw_core_v0_events { -#define PW_VERSION_CORE_V0_EVENTS 0 - uint32_t version; - /** - * Update the type map - * - * Send a type map update to the client. The client uses this - * information to keep a mapping between server types and the client types. - * \param first_id the id of the first type - * \param types the types as a string - * \param n_types the number of \a types - */ - void (*update_types) (void *data, - uint32_t first_id, - const char **types, - uint32_t n_types); - /** - * Emit a done event - * - * The done event is emitted as a result of a sync method with the - * same sequence number. - * \param seq the sequence number passed to the sync method call - */ - void (*done) (void *data, uint32_t seq); - /** - * Fatal error event - * - * The error event is sent out when a fatal (non-recoverable) - * error has occurred. The id argument is the object where - * the error occurred, most often in response to a request to that - * object. The message is a brief description of the error, - * for (debugging) convenience. - * \param id object where the error occurred - * \param res error code - * \param error error description - */ - void (*error) (void *data, uint32_t id, int res, const char *error, ...); - /** - * Remove an object ID - * - * This event is used internally by the object ID management - * logic. When a client deletes an object, the server will send - * this event to acknowledge that it has seen the delete request. - * When the client receives this event, it will know that it can - * safely reuse the object ID. - * \param id deleted object ID - */ - void (*remove_id) (void *data, uint32_t id); - /** - * Notify new core info - * - * \param info new core info - */ - void (*info) (void *data, struct pw_core_info *info); -}; - -#define pw_core_resource_v0_update_types(r,...) pw_resource_notify(r,struct pw_core_v0_events,update_types,__VA_ARGS__) -#define pw_core_resource_v0_done(r,...) pw_resource_notify(r,struct pw_core_v0_events,done,__VA_ARGS__) -#define pw_core_resource_v0_error(r,...) pw_resource_notify(r,struct pw_core_v0_events,error,__VA_ARGS__) -#define pw_core_resource_v0_remove_id(r,...) pw_resource_notify(r,struct pw_core_v0_events,remove_id,__VA_ARGS__) -#define pw_core_resource_v0_info(r,...) pw_resource_notify(r,struct pw_core_v0_events,info,__VA_ARGS__) - - -#define PW_VERSION_REGISTRY_V0 0 - -/** \page page_registry Registry - * - * \section page_registry_overview Overview - * - * The registry object is a singleton object that keeps track of - * global objects on the PipeWire instance. See also \ref page_global. - * - * Global objects typically represent an actual object in PipeWire - * (for example, a module or node) or they are singleton - * objects such as the core. - * - * When a client creates a registry object, the registry object - * will emit a global event for each global currently in the - * registry. Globals come and go as a result of device hotplugs or - * reconfiguration or other events, and the registry will send out - * global and global_remove events to keep the client up to date - * with the changes. To mark the end of the initial burst of - * events, the client can use the pw_core.sync methosd immediately - * after calling pw_core.get_registry. - * - * A client can bind to a global object by using the bind - * request. This creates a client-side proxy that lets the object - * emit events to the client and lets the client invoke methods on - * the object. See \ref page_proxy - * - * Clients can also change the permissions of the global objects that - * it can see. This is interesting when you want to configure a - * pipewire session before handing it to another application. You - * can, for example, hide certain existing or new objects or limit - * the access permissions on an object. - */ -#define PW_REGISTRY_V0_METHOD_BIND 0 -#define PW_REGISTRY_V0_METHOD_NUM 1 - -/** Registry methods */ -struct pw_registry_v0_methods { -#define PW_VERSION_REGISTRY_V0_METHODS 0 - uint32_t version; - /** - * Bind to a global object - * - * Bind to the global object with \a id and use the client proxy - * with new_id as the proxy. After this call, methods can be - * send to the remote global object and events can be received - * - * \param id the global id to bind to - * \param type the interface type to bind to - * \param version the interface version to use - * \param new_id the client proxy to use - */ - void (*bind) (void *object, uint32_t id, uint32_t type, uint32_t version, uint32_t new_id); -}; - -#define PW_REGISTRY_V0_EVENT_GLOBAL 0 -#define PW_REGISTRY_V0_EVENT_GLOBAL_REMOVE 1 -#define PW_REGISTRY_V0_EVENT_NUM 2 - -/** Registry events */ -struct pw_registry_v0_events { -#define PW_VERSION_REGISTRY_V0_EVENTS 0 - uint32_t version; - /** - * Notify of a new global object - * - * The registry emits this event when a new global object is - * available. - * - * \param id the global object id - * \param parent_id the parent global id - * \param permissions the permissions of the object - * \param type the type of the interface - * \param version the version of the interface - * \param props extra properties of the global - */ - void (*global) (void *data, uint32_t id, uint32_t parent_id, - uint32_t permissions, uint32_t type, uint32_t version, - const struct spa_dict *props); - /** - * Notify of a global object removal - * - * Emitted when a global object was removed from the registry. - * If the client has any bindings to the global, it should destroy - * those. - * - * \param id the id of the global that was removed - */ - void (*global_remove) (void *data, uint32_t id); -}; - -#define pw_registry_resource_v0_global(r,...) pw_resource_notify(r,struct pw_registry_v0_events,global,__VA_ARGS__) -#define pw_registry_resource_v0_global_remove(r,...) pw_resource_notify(r,struct pw_registry_v0_events,global_remove,__VA_ARGS__) - - -#define PW_VERSION_MODULE_V0 0 - -#define PW_MODULE_V0_EVENT_INFO 0 -#define PW_MODULE_V0_EVENT_NUM 1 - -/** Module events */ -struct pw_module_v0_events { -#define PW_VERSION_MODULE_V0_EVENTS 0 - uint32_t version; - /** - * Notify module info - * - * \param info info about the module - */ - void (*info) (void *data, struct pw_module_info *info); -}; - -#define pw_module_resource_v0_info(r,...) pw_resource_notify(r,struct pw_module_v0_events,info,__VA_ARGS__) - -#define PW_VERSION_NODE_V0 0 - -#define PW_NODE_V0_EVENT_INFO 0 -#define PW_NODE_V0_EVENT_PARAM 1 -#define PW_NODE_V0_EVENT_NUM 2 - -/** Node events */ -struct pw_node_v0_events { -#define PW_VERSION_NODE_V0_EVENTS 0 - uint32_t version; - /** - * Notify node info - * - * \param info info about the node - */ - void (*info) (void *data, struct pw_node_info *info); - /** - * Notify a node param - * - * Event emitted as a result of the enum_params method. - * - * \param id the param id - * \param index the param index - * \param next the param index of the next param - * \param param the parameter - */ - void (*param) (void *data, - uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param); -}; - -#define pw_node_resource_v0_info(r,...) pw_resource_notify(r,struct pw_node_v0_events,info,__VA_ARGS__) -#define pw_node_resource_v0_param(r,...) pw_resource_notify(r,struct pw_node_v0_events,param,__VA_ARGS__) - -#define PW_NODE_V0_METHOD_ENUM_PARAMS 0 -#define PW_NODE_V0_METHOD_NUM 1 - -/** Node methods */ -struct pw_node_v0_methods { -#define PW_VERSION_NODE_V0_METHODS 0 - uint32_t version; - /** - * Enumerate node parameters - * - * Start enumeration of node parameters. For each param, a - * param event will be emitted. - * - * \param id the parameter id to enum or PW_ID_ANY for all - * \param start the start index or 0 for the first param - * \param num the maximum number of params to retrieve - * \param filter a param filter or NULL - */ - void (*enum_params) (void *object, uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter); -}; - -#define PW_VERSION_PORT_V0 0 - -#define PW_PORT_V0_EVENT_INFO 0 -#define PW_PORT_V0_EVENT_PARAM 1 -#define PW_PORT_V0_EVENT_NUM 2 - -/** Port events */ -struct pw_port_v0_events { -#define PW_VERSION_PORT_V0_EVENTS 0 - uint32_t version; - /** - * Notify port info - * - * \param info info about the port - */ - void (*info) (void *data, struct pw_port_info *info); - /** - * Notify a port param - * - * Event emitted as a result of the enum_params method. - * - * \param id the param id - * \param index the param index - * \param next the param index of the next param - * \param param the parameter - */ - void (*param) (void *data, - uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param); -}; - -#define pw_port_resource_v0_info(r,...) pw_resource_notify(r,struct pw_port_v0_events,info,__VA_ARGS__) -#define pw_port_resource_v0_param(r,...) pw_resource_notify(r,struct pw_port_v0_events,param,__VA_ARGS__) - -#define PW_PORT_V0_METHOD_ENUM_PARAMS 0 -#define PW_PORT_V0_METHOD_NUM 1 - -/** Port methods */ -struct pw_port_v0_methods { -#define PW_VERSION_PORT_V0_METHODS 0 - uint32_t version; - /** - * Enumerate port parameters - * - * Start enumeration of port parameters. For each param, a - * param event will be emitted. - * - * \param id the parameter id to enumerate - * \param start the start index or 0 for the first param - * \param num the maximum number of params to retrieve - * \param filter a param filter or NULL - */ - void (*enum_params) (void *object, uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter); -}; - -#define PW_VERSION_FACTORY_V0 0 - -#define PW_FACTORY_V0_EVENT_INFO 0 -#define PW_FACTORY_V0_EVENT_NUM 1 - -/** Factory events */ -struct pw_factory_v0_events { -#define PW_VERSION_FACTORY_V0_EVENTS 0 - uint32_t version; - /** - * Notify factory info - * - * \param info info about the factory - */ - void (*info) (void *data, struct pw_factory_info *info); -}; - -#define pw_factory_resource_v0_info(r,...) pw_resource_notify(r,struct pw_factory_v0_events,info,__VA_ARGS__) - -#define PW_VERSION_CLIENT_V0 0 - -#define PW_CLIENT_V0_EVENT_INFO 0 -#define PW_CLIENT_V0_EVENT_NUM 1 - -/** Client events */ -struct pw_client_v0_events { -#define PW_VERSION_CLIENT_V0_EVENTS 0 - uint32_t version; - /** - * Notify client info - * - * \param info info about the client - */ - void (*info) (void *data, struct pw_client_info *info); -}; - -#define pw_client_resource_v0_info(r,...) pw_resource_notify(r,struct pw_client_v0_events,info,__VA_ARGS__) - - -#define PW_VERSION_LINK_V0 0 - -#define PW_LINK_V0_EVENT_INFO 0 -#define PW_LINK_V0_EVENT_NUM 1 - -/** Link events */ -struct pw_link_v0_events { -#define PW_VERSION_LINK_V0_EVENTS 0 - uint32_t version; - /** - * Notify link info - * - * \param info info about the link - */ - void (*info) (void *data, struct pw_link_info *info); -}; - -#define pw_link_resource_v0_info(r,...) pw_resource_notify(r,struct pw_link_v0_events,info,__VA_ARGS__) - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* PIPEWIRE_INTERFACES_V0_H */ diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c deleted file mode 100644 index c50d01d55..000000000 --- a/src/modules/module-protocol-native/v0/protocol-native.c +++ /dev/null @@ -1,1361 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2017 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include - -#include "spa/pod/parser.h" -#include "spa/pod/builder.h" -#include "spa/debug/types.h" -#include "spa/utils/string.h" - -#include "pipewire/pipewire.h" -#include "pipewire/private.h" -#include "pipewire/protocol.h" -#include "pipewire/resource.h" -#include "pipewire/extensions/protocol-native.h" -#include "pipewire/extensions/metadata.h" -#include "pipewire/extensions/session-manager.h" -#include "pipewire/extensions/client-node.h" - -#include "interfaces.h" -#include "typemap.h" - -#include "../defs.h" -#include "../connection.h" - -PW_LOG_TOPIC_EXTERN(mod_topic); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define PW_PROTOCOL_NATIVE_FLAG_REMAP (1<<0) - -SPA_EXPORT -uint32_t pw_protocol_native0_find_type(struct pw_impl_client *client, const char *type) -{ - uint32_t i; - for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) { - if (spa_streq(type_map[i].type, type)) - return i; - } - return SPA_ID_INVALID; -} - -static void -update_types_server(struct pw_resource *resource) -{ - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i; - - b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_UPDATE_TYPES, NULL); - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", 0, - "i", SPA_N_ELEMENTS(type_map), NULL); - - for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) { - spa_pod_builder_add(b, "s", type_map[i].type, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - - -static void core_marshal_info(void *data, const struct pw_core_info *info) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct protocol_compat_v2 *compat_v2 = client->compat_v2; - struct spa_pod_builder *b; - uint32_t i, n_items; - uint64_t change_mask = 0; - struct spa_pod_frame f; - struct pw_protocol_native_message *msg; - -#define PW_CORE_V0_CHANGE_MASK_USER_NAME (1 << 0) -#define PW_CORE_V0_CHANGE_MASK_HOST_NAME (1 << 1) -#define PW_CORE_V0_CHANGE_MASK_VERSION (1 << 2) -#define PW_CORE_V0_CHANGE_MASK_NAME (1 << 3) -#define PW_CORE_V0_CHANGE_MASK_COOKIE (1 << 4) -#define PW_CORE_V0_CHANGE_MASK_PROPS (1 << 5) - - if (compat_v2->send_types) { - update_types_server(resource); - change_mask |= PW_CORE_V0_CHANGE_MASK_USER_NAME | - PW_CORE_V0_CHANGE_MASK_HOST_NAME | - PW_CORE_V0_CHANGE_MASK_VERSION | - PW_CORE_V0_CHANGE_MASK_NAME | - PW_CORE_V0_CHANGE_MASK_COOKIE; - compat_v2->send_types = false; - } - b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_INFO, &msg); - - n_items = info->props ? info->props->n_items : 0; - - if (info->change_mask & PW_CORE_CHANGE_MASK_PROPS) - change_mask |= PW_CORE_V0_CHANGE_MASK_PROPS; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", change_mask, - "s", info->user_name, - "s", info->host_name, - "s", info->version, - "s", info->name, - "i", info->cookie, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void core_marshal_done(void *data, uint32_t id, int seq) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_DONE, NULL); - - spa_pod_builder_add_struct(b, "i", seq); - - pw_protocol_native_end_resource(resource, b); -} - -static void core_marshal_error(void *data, uint32_t id, int seq, int res, const char *error) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_ERROR, NULL); - - spa_pod_builder_add_struct(b, - "i", id, - "i", res, - "s", error); - - pw_protocol_native_end_resource(resource, b); -} - -static void core_marshal_remove_id(void *data, uint32_t id) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_REMOVE_ID, NULL); - - spa_pod_builder_add_struct(b, "i", id); - - pw_protocol_native_end_resource(resource, b); -} - -static int core_demarshal_client_update(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_dict props; - struct spa_pod_parser prs; - struct spa_pod_frame f; - uint32_t i; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f) < 0 || - spa_pod_parser_get(&prs, - "i", &props.n_items, NULL) < 0) - return -EINVAL; - - if (props.n_items > MAX_DICT) - return -ENOSPC; - props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); - for (i = 0; i < props.n_items; i++) { - if (spa_pod_parser_get(&prs, - "s", &props.items[i].key, - "s", &props.items[i].value, - NULL) < 0) - return -EINVAL; - } - pw_impl_client_update_properties(client, &props); - return 0; -} - -static uint32_t parse_perms(const char *str) -{ - uint32_t perms = 0; - - while (*str != '\0') { - switch (*str++) { - case 'r': - perms |= PW_PERM_R; - break; - case 'w': - perms |= PW_PERM_W; - break; - case 'x': - perms |= PW_PERM_X; - break; - } - } - return perms; -} - -static int core_demarshal_permissions(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_dict props; - struct spa_pod_parser prs; - struct spa_pod_frame f; - uint32_t i, n_permissions; - struct pw_permission *permissions, defperm = { 0, }; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f) < 0 || - spa_pod_parser_get(&prs, "i", &props.n_items, NULL) < 0) - return -EINVAL; - - if (props.n_items > MAX_DICT) - return -ENOSPC; - props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); - - n_permissions = 0; - permissions = alloca(props.n_items * sizeof(struct pw_permission)); - - for (i = 0; i < props.n_items; i++) { - uint32_t id, perms; - const char *str; - - if (spa_pod_parser_get(&prs, - "s", &props.items[i].key, - "s", &props.items[i].value, - NULL) < 0) - return -EINVAL; - - str = props.items[i].value; - /* first set global permissions */ - if (spa_streq(props.items[i].key, PW_CORE_PERMISSIONS_GLOBAL)) { - size_t len; - - /* :[r][w][x] */ - len = strcspn(str, ":"); - if (len == 0) - continue; - id = atoi(str); - perms = parse_perms(str + len); - permissions[n_permissions++] = PW_PERMISSION_INIT(id, perms); - } else if (spa_streq(props.items[i].key, PW_CORE_PERMISSIONS_DEFAULT)) { - perms = parse_perms(str); - defperm = PW_PERMISSION_INIT(PW_ID_ANY, perms); - } - } - /* add default permission if set */ - if (defperm.id == PW_ID_ANY) - permissions[n_permissions++] = defperm; - - for (i = 0; i < n_permissions; i++) { - pw_log_debug("%d: %d: %08x", i, permissions[i].id, permissions[i].permissions); - } - - return pw_impl_client_update_permissions(resource->client, - n_permissions, permissions); -} - -static int core_demarshal_hello(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - void *ptr; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "P", &ptr) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_core_methods, hello, 0, 2); -} - -static int core_demarshal_sync(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - uint32_t seq; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, "i", &seq) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_core_methods, sync, 0, 0, seq); -} - -static int core_demarshal_get_registry(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct spa_pod_parser prs; - int32_t version, new_id; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "i", &version, - "i", &new_id) < 0) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_core_methods, get_registry, 0, version, new_id); -} - -SPA_EXPORT -uint32_t pw_protocol_native0_type_from_v2(struct pw_impl_client *client, uint32_t type) -{ - void *t; - uint32_t index; - struct protocol_compat_v2 *compat_v2 = client->compat_v2; - - if ((t = pw_map_lookup(&compat_v2->types, type)) == NULL) - return SPA_ID_INVALID; - - index = PW_MAP_PTR_TO_ID(t); - if (index >= SPA_N_ELEMENTS(type_map)) - return SPA_ID_INVALID; - - return type_map[index].id; -} - -SPA_EXPORT -const char * pw_protocol_native0_name_from_v2(struct pw_impl_client *client, uint32_t type) -{ - void *t; - uint32_t index; - struct protocol_compat_v2 *compat_v2 = client->compat_v2; - - if ((t = pw_map_lookup(&compat_v2->types, type)) == NULL) - return NULL; - - index = PW_MAP_PTR_TO_ID(t); - if (index >= SPA_N_ELEMENTS(type_map)) - return NULL; - - return type_map[index].name; -} - -SPA_EXPORT -uint32_t pw_protocol_native0_name_to_v2(struct pw_impl_client *client, const char *name) -{ - uint32_t i; - /* match name to type table and return index */ - for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) { - if (type_map[i].name != NULL && spa_streq(type_map[i].name, name)) - return i; - } - return SPA_ID_INVALID; -} - -SPA_EXPORT -uint32_t pw_protocol_native0_type_to_v2(struct pw_impl_client *client, - const struct spa_type_info *info, uint32_t type) -{ - const char *name; - - /** find full name of type in type_info */ - if ((name = spa_debug_type_find_name(info, type)) == NULL) - return SPA_ID_INVALID; - - return pw_protocol_native0_name_to_v2(client, name); -} - -struct spa_pod_prop_body0 { - uint32_t key; -#define SPA_POD_PROP0_RANGE_NONE 0 /**< no range */ -#define SPA_POD_PROP0_RANGE_MIN_MAX 1 /**< property has range */ -#define SPA_POD_PROP0_RANGE_STEP 2 /**< property has range with step */ -#define SPA_POD_PROP0_RANGE_ENUM 3 /**< property has enumeration */ -#define SPA_POD_PROP0_RANGE_FLAGS 4 /**< property has flags */ -#define SPA_POD_PROP0_RANGE_MASK 0xf /**< mask to select range type */ -#define SPA_POD_PROP0_FLAG_UNSET (1 << 4) /**< property value is unset */ -#define SPA_POD_PROP0_FLAG_OPTIONAL (1 << 5) /**< property value is optional */ -#define SPA_POD_PROP0_FLAG_READONLY (1 << 6) /**< property is readonly */ -#define SPA_POD_PROP0_FLAG_DEPRECATED (1 << 7) /**< property is deprecated */ -#define SPA_POD_PROP0_FLAG_INFO (1 << 8) /**< property is informational and is not - * used when filtering */ - uint32_t flags; - struct spa_pod value; - /* array with elements of value.size follows, - * first element is value/default, rest are alternatives */ -}; - -/* v2 iterates object as containing spa_pod */ -#define SPA_POD_OBJECT_BODY_FOREACH0(body, size, iter) \ - for ((iter) = SPA_PTROFF((body), sizeof(struct spa_pod_object_body), struct spa_pod); \ - spa_pod_is_inside(body, size, iter); \ - (iter) = spa_pod_next(iter)) - -#define SPA_POD_PROP_ALTERNATIVE_FOREACH0(body, _size, iter) \ - for ((iter) = SPA_PTROFF((body), (body)->value.size + \ - sizeof(struct spa_pod_prop_body0), __typeof__(*iter)); \ - (iter) <= SPA_PTROFF((body), (_size)-(body)->value.size, __typeof__(*iter)); \ - (iter) = SPA_PTROFF((iter), (body)->value.size, __typeof__(*iter))) - -#define SPA0_POD_PROP_N_VALUES(b,size) (((size) - sizeof(struct spa_pod_prop_body0)) / (b)->value.size) - -static int remap_from_v2(uint32_t type, void *body, uint32_t size, struct pw_impl_client *client, - struct spa_pod_builder *builder) -{ - int res = 0; - - switch (type) { - case SPA_TYPE_Id: - spa_pod_builder_id(builder, pw_protocol_native0_type_from_v2(client, *(int32_t*) body)); - break; - - /** choice was props in v2 */ - case SPA_TYPE_Choice: - { - struct spa_pod_prop_body0 *b = body; - struct spa_pod_frame f; - void *alt; - uint32_t key = pw_protocol_native0_type_from_v2(client, b->key); - enum spa_choice_type type; - - spa_pod_builder_prop(builder, key, 0); - - switch (b->flags & SPA_POD_PROP0_RANGE_MASK) { - default: - case SPA_POD_PROP0_RANGE_NONE: - type = SPA_CHOICE_None; - break; - case SPA_POD_PROP0_RANGE_MIN_MAX: - type = SPA_CHOICE_Range; - break; - case SPA_POD_PROP0_RANGE_STEP: - type = SPA_CHOICE_Step; - break; - case SPA_POD_PROP0_RANGE_ENUM: - type = SPA_CHOICE_Enum; - break; - case SPA_POD_PROP0_RANGE_FLAGS: - type = SPA_CHOICE_Flags; - break; - } - if (!SPA_FLAG_IS_SET(b->flags, SPA_POD_PROP0_FLAG_UNSET) && - SPA0_POD_PROP_N_VALUES(b, size) == 1) - type = SPA_CHOICE_None; - - spa_pod_builder_push_choice(builder, &f, type, 0); - - if (b->value.type == SPA_TYPE_Id) { - uint32_t id; - if ((res = spa_pod_get_id(&b->value, &id)) < 0) - goto done; - - spa_pod_builder_id(builder, pw_protocol_native0_type_from_v2(client, id)); - SPA_POD_PROP_ALTERNATIVE_FOREACH0(b, size, alt) - if ((res = remap_from_v2(b->value.type, alt, b->value.size, client, builder)) < 0) - break; - } else { - spa_pod_builder_raw(builder, &b->value, size - sizeof(struct spa_pod)); - } -done: - spa_pod_builder_pop(builder, &f); - break; - } - case SPA_TYPE_Object: - { - struct spa_pod_object_body *b = body; - struct spa_pod *p; - struct spa_pod_frame f; - uint32_t type, count = 0; - - /* type and id are switched */ - type = pw_protocol_native0_type_from_v2(client, b->id), - spa_pod_builder_push_object(builder, &f, type, - pw_protocol_native0_type_from_v2(client, b->type)); - - /* object contained pods in v2 */ - SPA_POD_OBJECT_BODY_FOREACH0(b, size, p) { - if (type == SPA_TYPE_OBJECT_Format && count < 2) { - uint32_t id; - if (spa_pod_get_id(p, &id) < 0) - continue; - id = pw_protocol_native0_type_from_v2(client, id); - - if (count == 0) { - spa_pod_builder_prop(builder, SPA_FORMAT_mediaType, 0); - spa_pod_builder_id(builder, id); - } - if (count == 1) { - spa_pod_builder_prop(builder, SPA_FORMAT_mediaSubtype, 0); - spa_pod_builder_id(builder, id); - } - count++; - continue; - } - if ((res = remap_from_v2(p->type, - SPA_POD_BODY(p), - p->size, - client, builder)) < 0) - break; - } - spa_pod_builder_pop(builder, &f); - break; - } - case SPA_TYPE_Struct: - { - struct spa_pod *b = body, *p; - struct spa_pod_frame f; - - spa_pod_builder_push_struct(builder, &f); - SPA_POD_FOREACH(b, size, p) - if ((res = remap_from_v2(p->type, SPA_POD_BODY(p), p->size, client, builder)) < 0) - break; - spa_pod_builder_pop(builder, &f); - break; - } - default: - break; - } - return res; -} - -static int remap_to_v2(struct pw_impl_client *client, const struct spa_type_info *info, - uint32_t type, void *body, uint32_t size, - struct spa_pod_builder *builder) -{ - int res; - - switch (type) { - case SPA_TYPE_Id: - spa_pod_builder_id(builder, pw_protocol_native0_type_to_v2(client, info, *(int32_t*) body)); - break; - - case SPA_TYPE_Object: - { - struct spa_pod_object_body *b = body; - struct spa_pod_prop *p; - struct spa_pod_frame f[2]; - uint32_t type; - const struct spa_type_info *ti, *ii; - - ti = spa_debug_type_find(info, b->type); - ii = ti ? spa_debug_type_find(ti->values, 0) : NULL; - - if (b->type == SPA_TYPE_COMMAND_Node || - b->type == SPA_TYPE_EVENT_Node) { - spa_pod_builder_push_object(builder, &f[0], 0, - pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, b->id)); - } else { - ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL; - /* type and id are switched */ - type = pw_protocol_native0_type_to_v2(client, info, b->type), - spa_pod_builder_push_object(builder, &f[0], - pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, b->id), type); - } - - - info = ti ? ti->values : info; - - SPA_POD_OBJECT_BODY_FOREACH(b, size, p) { - uint32_t key, flags; - uint32_t n_vals, choice; - struct spa_pod *values; - - ii = spa_debug_type_find(info, p->key); - - values = spa_pod_get_values(&p->value, &n_vals, &choice); - - if (b->type == SPA_TYPE_OBJECT_Format && - (p->key == SPA_FORMAT_mediaType || - p->key == SPA_FORMAT_mediaSubtype)) { - uint32_t val; - - if (spa_pod_get_id(values, &val) < 0) - continue; - spa_pod_builder_id(builder, - pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, val)); - continue; - } - - flags = 0; - switch(choice) { - case SPA_CHOICE_None: - flags |= SPA_POD_PROP0_RANGE_NONE; - break; - case SPA_CHOICE_Range: - flags |= SPA_POD_PROP0_RANGE_MIN_MAX | SPA_POD_PROP0_FLAG_UNSET; - break; - case SPA_CHOICE_Step: - flags |= SPA_POD_PROP0_RANGE_STEP | SPA_POD_PROP0_FLAG_UNSET; - break; - case SPA_CHOICE_Enum: - flags |= SPA_POD_PROP0_RANGE_ENUM | SPA_POD_PROP0_FLAG_UNSET; - break; - case SPA_CHOICE_Flags: - flags |= SPA_POD_PROP0_RANGE_FLAGS | SPA_POD_PROP0_FLAG_UNSET; - break; - } - - key = pw_protocol_native0_type_to_v2(client, info, p->key); - - spa_pod_builder_push_choice(builder, &f[1], key, flags); - - if (values->type == SPA_TYPE_Id) { - uint32_t i, *id = SPA_POD_BODY(values); - - for (i = 0; i < n_vals; i++) { - spa_pod_builder_id(builder, - pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, id[i])); - } - - } else { - spa_pod_builder_raw(builder, values, sizeof(struct spa_pod) + n_vals * values->size); - } - spa_pod_builder_pop(builder, &f[1]); - } - spa_pod_builder_pop(builder, &f[0]); - break; - } - case SPA_TYPE_Struct: - { - struct spa_pod *b = body, *p; - struct spa_pod_frame f; - - spa_pod_builder_push_struct(builder, &f); - SPA_POD_FOREACH(b, size, p) - if ((res = remap_to_v2(client, info, p->type, SPA_POD_BODY(p), p->size, builder)) < 0) - return res; - spa_pod_builder_pop(builder, &f); - break; - } - default: - break; - } - return 0; -} - - - - -SPA_EXPORT -struct spa_pod * pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, const struct spa_pod *pod) -{ - uint8_t buffer[4096]; - struct spa_pod *copy; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 4096); - int res; - - if (pod == NULL) - return NULL; - - if ((res = remap_from_v2(pod->type, - SPA_POD_BODY(pod), - pod->size, - client, - &b)) < 0) { - errno = -res; - return NULL; - } - copy = spa_pod_copy(b.data); - return copy; -} - -SPA_EXPORT -int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod, - struct spa_pod_builder *b) -{ - int res; - - if (pod == NULL) { - spa_pod_builder_none(b); - return 0; - } - - if ((res = remap_to_v2(client, pw_type_info(), - pod->type, - SPA_POD_BODY(pod), - pod->size, - b)) < 0) { - return -res; - } - return 0; -} - -static int core_demarshal_create_object(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - struct spa_pod_frame f; - uint32_t version, type, new_id, i; - const char *factory_name, *type_name; - struct spa_dict props; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f) < 0 || - spa_pod_parser_get(&prs, - "s", &factory_name, - "I", &type, - "i", &version, - "i", &props.n_items, NULL) < 0) - return -EINVAL; - - if (props.n_items > MAX_DICT) - return -ENOSPC; - props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); - for (i = 0; i < props.n_items; i++) { - if (spa_pod_parser_get(&prs, - "s", &props.items[i].key, - "s", &props.items[i].value, NULL) < 0) - return -EINVAL; - } - if (spa_pod_parser_get(&prs, "i", &new_id, NULL) < 0) - return -EINVAL; - - type_name = pw_protocol_native0_name_from_v2(client, type); - if (type_name == NULL) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_core_methods, create_object, 0, factory_name, - type_name, version, - &props, new_id); -} - -static int core_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object, *r; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - uint32_t id; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "i", &id, NULL) < 0) - return -EINVAL; - - pw_log_debug("client %p: destroy resource %u", client, id); - - if ((r = pw_impl_client_find_resource(client, id)) == NULL) - goto no_resource; - - return pw_resource_notify(resource, struct pw_core_methods, destroy, 0, r); - -no_resource: - pw_log_error("client %p: unknown resource %u op:%u", client, id, msg->opcode); - pw_resource_errorf(resource, -ENOENT, "unknown resource %d op:%u", id, msg->opcode); - return 0; -} - -static int core_demarshal_update_types_server(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct protocol_compat_v2 *compat_v2 = client->compat_v2; - struct spa_pod_parser prs; - uint32_t first_id, n_types; - struct spa_pod_frame f; - const char **types; - uint32_t i; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_push_struct(&prs, &f) < 0 || - spa_pod_parser_get(&prs, - "i", &first_id, - "i", &n_types, - NULL) < 0) - return -EINVAL; - - if (first_id == 0) - compat_v2->send_types = true; - - if (n_types > MAX_DICT) - return -ENOSPC; - types = alloca(n_types * sizeof(char *)); - for (i = 0; i < n_types; i++) { - if (spa_pod_parser_get(&prs, "s", &types[i], NULL) < 0) - return -EINVAL; - } - - for (i = 0; i < n_types; i++, first_id++) { - uint32_t type_id = pw_protocol_native0_find_type(client, types[i]); - if (type_id == SPA_ID_INVALID) - continue; - if (pw_map_insert_at(&compat_v2->types, first_id, PW_MAP_ID_TO_PTR(type_id)) < 0) - pw_log_error("can't add type %d->%d for client", first_id, type_id); - } - return 0; -} - -static void registry_marshal_global(void *data, uint32_t id, uint32_t permissions, - const char *type, uint32_t version, const struct spa_dict *props) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items, parent_id; - uint32_t type_id; - const char *str; - - type_id = pw_protocol_native0_find_type(client, type); - if (type_id == SPA_ID_INVALID) - return; - - b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_V0_EVENT_GLOBAL, NULL); - - n_items = props ? props->n_items : 0; - - parent_id = 0; - if (props) { - if (spa_streq(type, PW_TYPE_INTERFACE_Port)) { - if ((str = spa_dict_lookup(props, "node.id")) != NULL) - parent_id = atoi(str); - } else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { - if ((str = spa_dict_lookup(props, "device.id")) != NULL) - parent_id = atoi(str); - } else if (spa_streq(type, PW_TYPE_INTERFACE_Client) || - spa_streq(type, PW_TYPE_INTERFACE_Device) || - spa_streq(type, PW_TYPE_INTERFACE_Factory)) { - if ((str = spa_dict_lookup(props, "module.id")) != NULL) - parent_id = atoi(str); - } - } - - version = 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", id, - "i", parent_id, - "i", permissions, - "I", type_id, - "i", version, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", props->items[i].key, - "s", props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void registry_marshal_global_remove(void *data, uint32_t id) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - - b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_V0_EVENT_GLOBAL_REMOVE, NULL); - - spa_pod_builder_add_struct(b, "i", id); - - pw_protocol_native_end_resource(resource, b); -} - -static int registry_demarshal_bind(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - uint32_t id, version, type, new_id; - const char *type_name; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "i", &id, - "I", &type, - "i", &version, - "i", &new_id) < 0) - return -EINVAL; - - type_name = pw_protocol_native0_name_from_v2(client, type); - if (type_name == NULL) - return -EINVAL; - - return pw_resource_notify(resource, struct pw_registry_methods, bind, 0, id, type_name, version, new_id); -} - -static void module_marshal_info(void *data, const struct pw_module_info *info) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items; - - b = pw_protocol_native_begin_resource(resource, PW_MODULE_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", info->change_mask, - "s", info->name, - "s", info->filename, - "s", info->args, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void factory_marshal_info(void *data, const struct pw_factory_info *info) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items, type, version; - - type = pw_protocol_native0_find_type(client, info->type); - if (type == SPA_ID_INVALID) - return; - - b = pw_protocol_native_begin_resource(resource, PW_FACTORY_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - - version = 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", info->change_mask, - "s", info->name, - "I", type, - "i", version, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void node_marshal_info(void *data, const struct pw_node_info *info) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items; - - b = pw_protocol_native_begin_resource(resource, PW_NODE_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", info->change_mask, - "s", "node.name", - "i", info->max_input_ports, - "i", info->n_input_ports, - "i", info->max_output_ports, - "i", info->n_output_ports, - "i", info->state, - "s", info->error, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void node_marshal_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - - b = pw_protocol_native_begin_resource(resource, PW_NODE_V0_EVENT_PARAM, NULL); - - id = pw_protocol_native0_type_to_v2(client, spa_type_param, id), - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "I", id, - "i", index, - "i", next, - NULL); - pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)param, b); - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static int node_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - uint32_t id, index, num; - struct spa_pod *filter; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "I", &id, - "i", &index, - "i", &num, - "P", &filter) < 0) - return -EINVAL; - - id = pw_protocol_native0_type_from_v2(client, id); - filter = NULL; - - return pw_resource_notify(resource, struct pw_node_methods, enum_params, 0, - 0, id, index, num, filter); -} - -static void port_marshal_info(void *data, const struct pw_port_info *info) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items; - uint64_t change_mask = 0; - const char *port_name; - - b = pw_protocol_native_begin_resource(resource, PW_PORT_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - -#define PW_PORT_V0_CHANGE_MASK_NAME (1 << 0) -#define PW_PORT_V0_CHANGE_MASK_PROPS (1 << 1) -#define PW_PORT_V0_CHANGE_MASK_ENUM_PARAMS (1 << 2) - - change_mask |= PW_PORT_V0_CHANGE_MASK_NAME; - if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) - change_mask |= PW_PORT_V0_CHANGE_MASK_PROPS; - if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) - change_mask |= PW_PORT_V0_CHANGE_MASK_ENUM_PARAMS; - - port_name = NULL; - if (info->props != NULL) - port_name = spa_dict_lookup(info->props, "port.name"); - if (port_name == NULL) - port_name = "port.name"; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", change_mask, - "s", port_name, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void port_marshal_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param) -{ - struct pw_resource *resource = data; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_builder *b; - struct spa_pod_frame f; - - b = pw_protocol_native_begin_resource(resource, PW_PORT_V0_EVENT_PARAM, NULL); - - id = pw_protocol_native0_type_to_v2(client, spa_type_param, id), - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "I", id, - "i", index, - "i", next, - NULL); - pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)param, b); - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static int port_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg) -{ - struct pw_resource *resource = object; - struct pw_impl_client *client = pw_resource_get_client(resource); - struct spa_pod_parser prs; - uint32_t id, index, num; - struct spa_pod *filter; - - spa_pod_parser_init(&prs, msg->data, msg->size); - if (spa_pod_parser_get_struct(&prs, - "I", &id, - "i", &index, - "i", &num, - "P", &filter) < 0) - return -EINVAL; - - id = pw_protocol_native0_type_from_v2(client, id); - filter = NULL; - - return pw_resource_notify(resource, struct pw_port_methods, enum_params, 0, - 0, id, index, num, filter); -} - -static void client_marshal_info(void *data, const struct pw_client_info *info) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items; - - b = pw_protocol_native_begin_resource(resource, PW_CLIENT_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", info->change_mask, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static void client_marshal_permissions(void *data, uint32_t index, uint32_t n_permissions, - const struct pw_permission *permissions) -{ -} - - -static void link_marshal_info(void *data, const struct pw_link_info *info) -{ - struct pw_resource *resource = data; - struct spa_pod_builder *b; - struct spa_pod_frame f; - uint32_t i, n_items; - - b = pw_protocol_native_begin_resource(resource, PW_LINK_V0_EVENT_INFO, NULL); - - n_items = info->props ? info->props->n_items : 0; - - spa_pod_builder_push_struct(b, &f); - spa_pod_builder_add(b, - "i", info->id, - "l", info->change_mask, - "i", info->output_node_id, - "i", info->output_port_id, - "i", info->input_node_id, - "i", info->input_port_id, - "P", info->format, - "i", n_items, NULL); - - for (i = 0; i < n_items; i++) { - spa_pod_builder_add(b, - "s", info->props->items[i].key, - "s", info->props->items[i].value, NULL); - } - spa_pod_builder_pop(b, &f); - - pw_protocol_native_end_resource(resource, b); -} - -static const struct pw_protocol_native_demarshal pw_protocol_native_core_method_demarshal[PW_CORE_V0_METHOD_NUM] = { - [PW_CORE_V0_METHOD_HELLO] = { &core_demarshal_hello, 0, }, - [PW_CORE_V0_METHOD_UPDATE_TYPES] = { &core_demarshal_update_types_server, 0, }, - [PW_CORE_V0_METHOD_SYNC] = { &core_demarshal_sync, 0, }, - [PW_CORE_V0_METHOD_GET_REGISTRY] = { &core_demarshal_get_registry, 0, }, - [PW_CORE_V0_METHOD_CLIENT_UPDATE] = { &core_demarshal_client_update, 0, }, - [PW_CORE_V0_METHOD_PERMISSIONS] = { &core_demarshal_permissions, 0, }, - [PW_CORE_V0_METHOD_CREATE_OBJECT] = { &core_demarshal_create_object, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, }, - [PW_CORE_V0_METHOD_DESTROY] = { &core_demarshal_destroy, 0, } -}; - -static const struct pw_core_events pw_protocol_native_core_event_marshal = { - PW_VERSION_CORE_EVENTS, - .info = &core_marshal_info, - .done = &core_marshal_done, - .error = &core_marshal_error, - .remove_id = &core_marshal_remove_id, -}; - -static const struct pw_protocol_marshal pw_protocol_native_core_marshal = { - PW_TYPE_INTERFACE_Core, - PW_VERSION_CORE_V0, - PW_CORE_V0_METHOD_NUM, - PW_CORE_EVENT_NUM, - 0, - NULL, - pw_protocol_native_core_method_demarshal, - &pw_protocol_native_core_event_marshal, - NULL -}; - -static const struct pw_protocol_native_demarshal pw_protocol_native_registry_method_demarshal[] = { - [PW_REGISTRY_V0_METHOD_BIND] = { ®istry_demarshal_bind, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, }, -}; - -static const struct pw_registry_events pw_protocol_native_registry_event_marshal = { - PW_VERSION_REGISTRY_EVENTS, - .global = ®istry_marshal_global, - .global_remove = ®istry_marshal_global_remove, -}; - -static const struct pw_protocol_marshal pw_protocol_native_registry_marshal = { - PW_TYPE_INTERFACE_Registry, - PW_VERSION_REGISTRY_V0, - PW_REGISTRY_V0_METHOD_NUM, - PW_REGISTRY_EVENT_NUM, - 0, - NULL, - pw_protocol_native_registry_method_demarshal, - &pw_protocol_native_registry_event_marshal, - NULL -}; - -static const struct pw_module_events pw_protocol_native_module_event_marshal = { - PW_VERSION_MODULE_EVENTS, - .info = &module_marshal_info, -}; - -static const struct pw_protocol_marshal pw_protocol_native_module_marshal = { - PW_TYPE_INTERFACE_Module, - PW_VERSION_MODULE_V0, - 0, - PW_MODULE_EVENT_NUM, - 0, - NULL, NULL, - &pw_protocol_native_module_event_marshal, - NULL -}; - -static const struct pw_factory_events pw_protocol_native_factory_event_marshal = { - PW_VERSION_FACTORY_EVENTS, - .info = &factory_marshal_info, -}; - -static const struct pw_protocol_marshal pw_protocol_native_factory_marshal = { - PW_TYPE_INTERFACE_Factory, - PW_VERSION_FACTORY_V0, - 0, - PW_FACTORY_EVENT_NUM, - 0, - NULL, NULL, - &pw_protocol_native_factory_event_marshal, - NULL, -}; - -static const struct pw_protocol_native_demarshal pw_protocol_native_node_method_demarshal[] = { - [PW_NODE_V0_METHOD_ENUM_PARAMS] = { &node_demarshal_enum_params, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, }, -}; - -static const struct pw_node_events pw_protocol_native_node_event_marshal = { - PW_VERSION_NODE_EVENTS, - .info = &node_marshal_info, - .param = &node_marshal_param, -}; - -static const struct pw_protocol_marshal pw_protocol_native_node_marshal = { - PW_TYPE_INTERFACE_Node, - PW_VERSION_NODE_V0, - PW_NODE_V0_METHOD_NUM, - PW_NODE_EVENT_NUM, - 0, - NULL, - pw_protocol_native_node_method_demarshal, - &pw_protocol_native_node_event_marshal, - NULL -}; - - -static const struct pw_protocol_native_demarshal pw_protocol_native_port_method_demarshal[] = { - [PW_PORT_V0_METHOD_ENUM_PARAMS] = { &port_demarshal_enum_params, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, }, -}; - -static const struct pw_port_events pw_protocol_native_port_event_marshal = { - PW_VERSION_PORT_EVENTS, - .info = &port_marshal_info, - .param = &port_marshal_param, -}; - -static const struct pw_protocol_marshal pw_protocol_native_port_marshal = { - PW_TYPE_INTERFACE_Port, - PW_VERSION_PORT_V0, - PW_PORT_V0_METHOD_NUM, - PW_PORT_EVENT_NUM, - 0, - NULL, - pw_protocol_native_port_method_demarshal, - &pw_protocol_native_port_event_marshal, - NULL -}; - -static const struct pw_client_events pw_protocol_native_client_event_marshal = { - PW_VERSION_CLIENT_EVENTS, - .info = &client_marshal_info, - .permissions = &client_marshal_permissions, -}; - -static const struct pw_protocol_marshal pw_protocol_native_client_marshal = { - PW_TYPE_INTERFACE_Client, - PW_VERSION_CLIENT_V0, - 0, - PW_CLIENT_EVENT_NUM, - 0, - NULL, NULL, - &pw_protocol_native_client_event_marshal, - NULL, -}; - -static const struct pw_link_events pw_protocol_native_link_event_marshal = { - PW_VERSION_LINK_EVENTS, - .info = &link_marshal_info, -}; - -static const struct pw_protocol_marshal pw_protocol_native_link_marshal = { - PW_TYPE_INTERFACE_Link, - PW_VERSION_LINK_V0, - 0, - PW_LINK_EVENT_NUM, - 0, - NULL, NULL, - &pw_protocol_native_link_event_marshal, - NULL -}; - -void pw_protocol_native0_init(struct pw_protocol *protocol) -{ - pw_protocol_add_marshal(protocol, &pw_protocol_native_core_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_registry_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_module_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_node_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_port_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_factory_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_client_marshal); - pw_protocol_add_marshal(protocol, &pw_protocol_native_link_marshal); -} diff --git a/src/modules/module-protocol-native/v0/typemap.h b/src/modules/module-protocol-native/v0/typemap.h deleted file mode 100644 index 980cc41cc..000000000 --- a/src/modules/module-protocol-native/v0/typemap.h +++ /dev/null @@ -1,292 +0,0 @@ -enum spa_node0_event { - SPA_NODE0_EVENT_START = SPA_TYPE_VENDOR_PipeWire, - SPA_NODE0_EVENT_RequestClockUpdate, -}; - -enum spa_node0_command { - SPA_NODE0_COMMAND_START = SPA_TYPE_VENDOR_PipeWire, - SPA_NODE0_COMMAND_ClockUpdate, -}; - -static const struct type_info { - const char *type; - const char *name; - uint32_t id; -} type_map[] = { - { "Spa:Interface:TypeMap", SPA_TYPE_INFO_INTERFACE_BASE, 0, }, - { "Spa:Interface:Log", SPA_TYPE_INTERFACE_Log, 0, }, - { "Spa:Interface:Loop", SPA_TYPE_INTERFACE_Loop, 0, }, - { "Spa:Interface:LoopControl", SPA_TYPE_INTERFACE_LoopControl, 0, }, - { "Spa:Interface:LoopUtils", SPA_TYPE_INTERFACE_LoopUtils, 0, }, - { "PipeWire:Interface:Core", PW_TYPE_INTERFACE_Core, 0, }, - { "PipeWire:Interface:Registry", PW_TYPE_INTERFACE_Registry, 0, }, - { "PipeWire:Interface:Node", PW_TYPE_INTERFACE_Node, 0, }, - { "PipeWire:Interface:Port", PW_TYPE_INTERFACE_Port,0, }, - { "PipeWire:Interface:Factory", PW_TYPE_INTERFACE_Factory, 0, }, - { "PipeWire:Interface:Link", PW_TYPE_INTERFACE_Link, 0, }, - { "PipeWire:Interface:Client", PW_TYPE_INTERFACE_Client, 0, }, - { "PipeWire:Interface:Module", PW_TYPE_INTERFACE_Module, 0, }, - { "PipeWire:Interface:Device", PW_TYPE_INTERFACE_Device, 0, }, - - { "PipeWire:Interface:Metadata", PW_TYPE_INTERFACE_Metadata, 0, }, - { "PipeWire:Interface:Session", PW_TYPE_INTERFACE_Session, 0, }, - { "PipeWire:Interface:Endpoint", PW_TYPE_INTERFACE_Endpoint, 0, }, - { "PipeWire:Interface:EndpointStream", PW_TYPE_INTERFACE_EndpointStream, 0, }, - { "PipeWire:Interface:EndpointLink", PW_TYPE_INTERFACE_EndpointLink, 0, }, - - { "PipeWire:Interface:ClientNode", PW_TYPE_INTERFACE_ClientNode, 0, }, - { "PipeWire:Interface:ClientSession", PW_TYPE_INTERFACE_ClientSession, 0, }, - { "PipeWire:Interface:ClientEndpoint", PW_TYPE_INTERFACE_ClientEndpoint, 0, }, - - { "Spa:Interface:Node", SPA_TYPE_INTERFACE_Node, 0, }, - { "Spa:Interface:Clock", }, - { "Spa:Interface:Monitor", }, - { "Spa:Interface:Device", SPA_TYPE_INTERFACE_Device, 0, }, - { "Spa:POD:Object:Param:Format", SPA_TYPE_INFO_Format, SPA_TYPE_OBJECT_Format, }, - { "Spa:POD:Object:Param:Props", SPA_TYPE_INFO_Props, SPA_TYPE_OBJECT_Props, }, - { "Spa:Pointer:IO:Buffers", }, - { "Spa:Pointer:IO:Control:Range", }, - { "Spa:Pointer:IO:Prop", }, - { "Spa:Enum:ParamId:List", }, - { "Spa:POD:Object:Param:List", }, - { "Spa:POD:Object:Param:List:id", }, - { "Spa:Enum:ParamId:PropInfo", SPA_TYPE_INFO_PARAM_ID_BASE "PropInfo", SPA_PARAM_PropInfo, }, - { "Spa:POD:Object:Param:PropInfo", SPA_TYPE_INFO_PROP_INFO_BASE, SPA_PROP_INFO_START, }, - { "Spa:POD:Object:Param:PropInfo:id", SPA_TYPE_INFO_PROP_INFO_BASE "id", SPA_PROP_INFO_id, }, - { "Spa:POD:Object:Param:PropInfo:name", SPA_TYPE_INFO_PROP_INFO_BASE "name", SPA_PROP_INFO_name, }, - { "Spa:POD:Object:Param:PropInfo:type", SPA_TYPE_INFO_PROP_INFO_BASE "typ", SPA_PROP_INFO_type, }, - { "Spa:POD:Object:Param:PropInfo:labels", SPA_TYPE_INFO_PROP_INFO_BASE "labels", SPA_PROP_INFO_labels, }, - { "Spa:Enum:ParamId:Props", SPA_TYPE_INFO_PARAM_ID_BASE "Props", SPA_PARAM_Props, }, - { "Spa:Enum:ParamId:EnumFormat", SPA_TYPE_INFO_PARAM_ID_BASE "EnumFormat", SPA_PARAM_EnumFormat,}, - { "Spa:Enum:ParamId:Format", SPA_TYPE_INFO_PARAM_ID_BASE "Format", SPA_PARAM_Format, }, - { "Spa:Enum:ParamId:Buffers", SPA_TYPE_INFO_PARAM_ID_BASE "Buffers", SPA_PARAM_Buffers }, - { "Spa:Enum:ParamId:Meta", SPA_TYPE_INFO_PARAM_ID_BASE "Meta", SPA_PARAM_Meta, }, - { "Spa:Pointer:Meta:Header", SPA_TYPE_INFO_META_BASE "Header", SPA_META_Header, }, - { "Spa:Pointer:Meta:VideoCrop", SPA_TYPE_INFO_META_REGION_BASE "VideoCrop", SPA_META_VideoCrop, }, - { "Spa:Pointer:Meta:VideoDamage", SPA_TYPE_INFO_META_ARRAY_REGION_BASE "VideoDamage", SPA_META_VideoDamage, }, - { "Spa:Pointer:Meta:Bitmap", SPA_TYPE_INFO_META_BASE "Bitmap", SPA_META_Bitmap, }, - { "Spa:Pointer:Meta:Cursor", SPA_TYPE_INFO_META_BASE "Cursor", SPA_META_Cursor, }, - { "Spa:Enum:DataType:MemPtr", SPA_TYPE_INFO_DATA_BASE "MemPtr", SPA_DATA_MemPtr, }, - { "Spa:Enum:DataType:Fd:MemFd", SPA_TYPE_INFO_DATA_FD_BASE "MemFd", SPA_DATA_MemFd, }, - { "Spa:Enum:DataType:Fd:DmaBuf", SPA_TYPE_INFO_DATA_FD_BASE "DmaBuf", SPA_DATA_DmaBuf, }, - { "Spa:POD:Object:Event:Node:Error", SPA_TYPE_INFO_NODE_EVENT_BASE "Error", SPA_NODE_EVENT_Error, }, - { "Spa:POD:Object:Event:Node:Buffering", SPA_TYPE_INFO_NODE_EVENT_BASE "Buffering", SPA_NODE_EVENT_Buffering, }, - { "Spa:POD:Object:Event:Node:RequestRefresh", SPA_TYPE_INFO_NODE_EVENT_BASE "RequestRefresh", SPA_NODE_EVENT_RequestRefresh, }, - { "Spa:POD:Object:Event:Node:RequestClockUpdate", SPA_TYPE_INFO_NODE_EVENT_BASE "RequestClockUpdate", SPA_NODE0_EVENT_RequestClockUpdate, }, - { "Spa:POD:Object:Command:Node", SPA_TYPE_INFO_COMMAND_BASE "Node", SPA_TYPE_COMMAND_Node,}, - { "Spa:POD:Object:Command:Node:Suspend", SPA_TYPE_INFO_NODE_COMMAND_BASE "Suspend", SPA_NODE_COMMAND_Suspend,}, - { "Spa:POD:Object:Command:Node:Pause", SPA_TYPE_INFO_NODE_COMMAND_BASE "Pause", SPA_NODE_COMMAND_Pause, }, - { "Spa:POD:Object:Command:Node:Start", SPA_TYPE_INFO_NODE_COMMAND_BASE "Start", SPA_NODE_COMMAND_Start, }, - { "Spa:POD:Object:Command:Node:Enable", SPA_TYPE_INFO_NODE_COMMAND_BASE "Enable", SPA_NODE_COMMAND_Enable, }, - { "Spa:POD:Object:Command:Node:Disable", SPA_TYPE_INFO_NODE_COMMAND_BASE "Disable", SPA_NODE_COMMAND_Disable, }, - { "Spa:POD:Object:Command:Node:Flush", SPA_TYPE_INFO_NODE_COMMAND_BASE "Flush", SPA_NODE_COMMAND_Flush, }, - { "Spa:POD:Object:Command:Node:Drain", SPA_TYPE_INFO_NODE_COMMAND_BASE "Drain", SPA_NODE_COMMAND_Drain, }, - { "Spa:POD:Object:Command:Node:Marker", SPA_TYPE_INFO_NODE_COMMAND_BASE "Marker", SPA_NODE_COMMAND_Marker, }, - { "Spa:POD:Object:Command:Node:ClockUpdate", SPA_TYPE_INFO_NODE_COMMAND_BASE "ClockUpdate", SPA_NODE0_COMMAND_ClockUpdate, }, - { "Spa:POD:Object:Event:Monitor:Added", }, - { "Spa:POD:Object:Event:Monitor:Removed", }, - { "Spa:POD:Object:Event:Monitor:Changed", }, - { "Spa:POD:Object:MonitorItem", }, - { "Spa:POD:Object:MonitorItem:id", }, - { "Spa:POD:Object:MonitorItem:flags", }, - { "Spa:POD:Object:MonitorItem:state", }, - { "Spa:POD:Object:MonitorItem:name", }, - { "Spa:POD:Object:MonitorItem:class", }, - { "Spa:POD:Object:MonitorItem:info", }, - { "Spa:POD:Object:MonitorItem:factory", }, - { "Spa:POD:Object:Param:Buffers", SPA_TYPE_INFO_PARAM_Buffers, SPA_TYPE_OBJECT_ParamBuffers, }, - { "Spa:POD:Object:Param:Buffers:size", SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "size", SPA_PARAM_BUFFERS_size, }, - { "Spa:POD:Object:Param:Buffers:stride", SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "stride", SPA_PARAM_BUFFERS_stride, }, - { "Spa:POD:Object:Param:Buffers:buffers", SPA_TYPE_INFO_PARAM_BUFFERS_BASE "buffers", SPA_PARAM_BUFFERS_buffers, }, - { "Spa:POD:Object:Param:Buffers:align",SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "align", SPA_PARAM_BUFFERS_align, }, - { "Spa:POD:Object:Param:Meta", SPA_TYPE_INFO_PARAM_Meta, SPA_TYPE_OBJECT_ParamMeta, }, - { "Spa:POD:Object:Param:Meta:type", SPA_TYPE_INFO_PARAM_META_BASE "type", SPA_PARAM_META_type, }, - { "Spa:POD:Object:Param:Meta:size", SPA_TYPE_INFO_PARAM_META_BASE "size", SPA_PARAM_META_size, }, - { "Spa:POD:Object:Param:IO:id", }, - { "Spa:POD:Object:Param:IO:size", }, - { "Spa:Enum:ParamId:IO:Buffers", }, - { "Spa:POD:Object:Param:IO:Buffers", }, - { "Spa:Enum:ParamId:IO:Control", }, - { "Spa:POD:Object:Param:IO:Control", }, - { "Spa:Enum:ParamId:IO:Props:In", }, - { "Spa:Enum:ParamId:IO:Props:Out", }, - { "Spa:POD:Object:Param:IO:Prop", }, - { "Spa:Interface:DBus", SPA_TYPE_INFO_INTERFACE_BASE "DBus", 0, }, - { "Spa:Enum:MediaType:audio", SPA_TYPE_INFO_MEDIA_TYPE_BASE "audio", SPA_MEDIA_TYPE_audio, }, - { "Spa:Enum:MediaType:video", SPA_TYPE_INFO_MEDIA_TYPE_BASE "video", SPA_MEDIA_TYPE_video, }, - { "Spa:Enum:MediaType:image", SPA_TYPE_INFO_MEDIA_TYPE_BASE "image", SPA_MEDIA_TYPE_image, }, - { "Spa:Enum:MediaType:binary", SPA_TYPE_INFO_MEDIA_TYPE_BASE "binary", SPA_MEDIA_TYPE_binary, }, - { "Spa:Enum:MediaType:stream", SPA_TYPE_INFO_MEDIA_TYPE_BASE "stream", SPA_MEDIA_TYPE_stream, }, - { "Spa:Enum:MediaType:application", SPA_TYPE_INFO_MEDIA_TYPE_BASE "application", SPA_MEDIA_TYPE_application, }, - { "Spa:Enum:MediaSubtype:raw", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "raw", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:dsp", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dsp", SPA_MEDIA_SUBTYPE_dsp, }, - { "Spa:Enum:MediaSubtype:control", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "control", SPA_MEDIA_SUBTYPE_control, }, - { "Spa:Enum:MediaSubtype:h264", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", SPA_MEDIA_SUBTYPE_h264, }, - { "Spa:Enum:MediaSubtype:mjpg", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", SPA_MEDIA_SUBTYPE_mjpg, }, - { "Spa:Enum:MediaSubtype:dv", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dv", SPA_MEDIA_SUBTYPE_dv, }, - { "Spa:Enum:MediaSubtype:mpegts", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegts", SPA_MEDIA_SUBTYPE_mpegts, }, - { "Spa:Enum:MediaSubtype:h263", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h263", SPA_MEDIA_SUBTYPE_h263, }, - { "Spa:Enum:MediaSubtype:mpeg1", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg1", SPA_MEDIA_SUBTYPE_mpeg1, }, - { "Spa:Enum:MediaSubtype:mpeg2", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg2", SPA_MEDIA_SUBTYPE_mpeg2, }, - { "Spa:Enum:MediaSubtype:mpeg4", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg4", SPA_MEDIA_SUBTYPE_mpeg4, }, - { "Spa:Enum:MediaSubtype:xvid", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "xvid", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:vc1", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vc1", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:vp8", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp8", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:vp9", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp9", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:jpeg", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "jpeg", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:bayer", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "bayer", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:mp3", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mp3", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:aac", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "aac", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:vorbis", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vorbis", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:wma", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "wma", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:ra", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ra", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:sbc", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "sbc", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:adpcm", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "adpcm", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:g723", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g723", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:g726", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g726", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:g729", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g729", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:amr", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "amr", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:gsm", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "gsm", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:Enum:MediaSubtype:midi", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "midi", SPA_MEDIA_SUBTYPE_raw, }, - { "Spa:POD:Object:Param:Format:Video:format", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format", SPA_FORMAT_VIDEO_format,}, - { "Spa:POD:Object:Param:Format:Video:size", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "size", SPA_FORMAT_VIDEO_size,}, - { "Spa:POD:Object:Param:Format:Video:framerate", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "framerate", SPA_FORMAT_VIDEO_framerate}, - { "Spa:POD:Object:Param:Format:Video:max-framerate", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "maxFramerate", SPA_FORMAT_VIDEO_maxFramerate}, - { "Spa:POD:Object:Param:Format:Video:views", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "views", SPA_FORMAT_VIDEO_views}, - { "Spa:POD:Object:Param:Format:Video:interlace-mode", }, - { "Spa:POD:Object:Param:Format:Video:pixel-aspect-ratio", }, - { "Spa:POD:Object:Param:Format:Video:multiview-mode", }, - { "Spa:POD:Object:Param:Format:Video:multiview-flags", }, - { "Spa:POD:Object:Param:Format:Video:chroma-site", }, - { "Spa:POD:Object:Param:Format:Video:color-range", }, - { "Spa:POD:Object:Param:Format:Video:color-matrix", }, - { "Spa:POD:Object:Param:Format:Video:transfer-function", }, - { "Spa:POD:Object:Param:Format:Video:color-primaries", }, - { "Spa:POD:Object:Param:Format:Video:profile", }, - { "Spa:POD:Object:Param:Format:Video:level", }, - { "Spa:POD:Object:Param:Format:Video:stream-format", }, - { "Spa:POD:Object:Param:Format:Video:alignment", }, - { "Spa:POD:Object:Param:Format:Audio:format", SPA_TYPE_INFO_FORMAT_AUDIO_BASE "format", SPA_FORMAT_AUDIO_format,}, - { "Spa:POD:Object:Param:Format:Audio:flags", }, - { "Spa:POD:Object:Param:Format:Audio:layout", }, - { "Spa:POD:Object:Param:Format:Audio:rate", }, - { "Spa:POD:Object:Param:Format:Audio:channels", }, - { "Spa:POD:Object:Param:Format:Audio:channel-mask", }, - { "Spa:Enum:VideoFormat:encoded", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "encoded", SPA_VIDEO_FORMAT_ENCODED,}, - { "Spa:Enum:VideoFormat:I420", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420", SPA_VIDEO_FORMAT_I420,}, - { "Spa:Enum:VideoFormat:YV12", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YV12", SPA_VIDEO_FORMAT_YV12,}, - { "Spa:Enum:VideoFormat:YUY2", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUY2", SPA_VIDEO_FORMAT_YUY2,}, - { "Spa:Enum:VideoFormat:UYVY", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVY", SPA_VIDEO_FORMAT_UYVY,}, - { "Spa:Enum:VideoFormat:AYUV", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV", SPA_VIDEO_FORMAT_AYUV,}, - { "Spa:Enum:VideoFormat:RGBx", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx", SPA_VIDEO_FORMAT_RGBx,}, - { "Spa:Enum:VideoFormat:BGRx", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx", SPA_VIDEO_FORMAT_BGRx,}, - { "Spa:Enum:VideoFormat:xRGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB", SPA_VIDEO_FORMAT_xRGB,}, - { "Spa:Enum:VideoFormat:xBGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR", SPA_VIDEO_FORMAT_xBGR,}, - { "Spa:Enum:VideoFormat:RGBA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA", SPA_VIDEO_FORMAT_RGBA,}, - { "Spa:Enum:VideoFormat:BGRA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA", SPA_VIDEO_FORMAT_BGRA,}, - { "Spa:Enum:VideoFormat:ARGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB", SPA_VIDEO_FORMAT_ARGB,}, - { "Spa:Enum:VideoFormat:ABGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR", SPA_VIDEO_FORMAT_ABGR,}, - { "Spa:Enum:VideoFormat:RGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB", SPA_VIDEO_FORMAT_RGB,}, - { "Spa:Enum:VideoFormat:BGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR", SPA_VIDEO_FORMAT_BGR,}, - { "Spa:Enum:VideoFormat:Y41B", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y41B", SPA_VIDEO_FORMAT_Y41B,}, - { "Spa:Enum:VideoFormat:Y42B", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y42B", SPA_VIDEO_FORMAT_Y42B,}, - { "Spa:Enum:VideoFormat:YVYU", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVYU", SPA_VIDEO_FORMAT_YVYU,}, - { "Spa:Enum:VideoFormat:Y444", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444", SPA_VIDEO_FORMAT_Y444,}, - { "Spa:Enum:VideoFormat:v210", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v210", SPA_VIDEO_FORMAT_v210,}, - { "Spa:Enum:VideoFormat:v216", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v216", SPA_VIDEO_FORMAT_v216,}, - { "Spa:Enum:VideoFormat:NV12", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12", SPA_VIDEO_FORMAT_NV12,}, - { "Spa:Enum:VideoFormat:NV21", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV21", SPA_VIDEO_FORMAT_NV21,}, - { "Spa:Enum:VideoFormat:GRAY8", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY8", SPA_VIDEO_FORMAT_GRAY8,}, - { "Spa:Enum:VideoFormat:GRAY16_BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_BE", SPA_VIDEO_FORMAT_GRAY16_BE,}, - { "Spa:Enum:VideoFormat:GRAY16_LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_LE", SPA_VIDEO_FORMAT_GRAY16_LE,}, - { "Spa:Enum:VideoFormat:v308", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v308", SPA_VIDEO_FORMAT_v308,}, - { "Spa:Enum:VideoFormat:RGB16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB16", SPA_VIDEO_FORMAT_RGB16,}, - { "Spa:Enum:VideoFormat:BGR16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR16", SPA_VIDEO_FORMAT_BGR16,}, - { "Spa:Enum:VideoFormat:RGB15", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB15", SPA_VIDEO_FORMAT_RGB15,}, - { "Spa:Enum:VideoFormat:BGR15", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR15", SPA_VIDEO_FORMAT_BGR15,}, - { "Spa:Enum:VideoFormat:UYVP", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVP", SPA_VIDEO_FORMAT_UYVP,}, - { "Spa:Enum:VideoFormat:A420", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420", SPA_VIDEO_FORMAT_A420,}, - { "Spa:Enum:VideoFormat:RGB8P", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB8P", SPA_VIDEO_FORMAT_RGB8P,}, - { "Spa:Enum:VideoFormat:YUV9", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUV9", SPA_VIDEO_FORMAT_YUV9,}, - { "Spa:Enum:VideoFormat:YVU9", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVU9", SPA_VIDEO_FORMAT_YVU9,}, - { "Spa:Enum:VideoFormat:IYU1", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU1", SPA_VIDEO_FORMAT_IYU1,}, - { "Spa:Enum:VideoFormat:ARGB64", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB64", SPA_VIDEO_FORMAT_ARGB64,}, - { "Spa:Enum:VideoFormat:AYUV64", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV64", SPA_VIDEO_FORMAT_AYUV64,}, - { "Spa:Enum:VideoFormat:r210", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "r210", SPA_VIDEO_FORMAT_r210,}, - { "Spa:Enum:VideoFormat:I420_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10BE", SPA_VIDEO_FORMAT_I420_10BE,}, - { "Spa:Enum:VideoFormat:I420_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10LE", SPA_VIDEO_FORMAT_I420_10LE,}, - { "Spa:Enum:VideoFormat:I422_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10BE", SPA_VIDEO_FORMAT_I422_10BE,}, - { "Spa:Enum:VideoFormat:I422_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10LE", SPA_VIDEO_FORMAT_I422_10LE,}, - { "Spa:Enum:VideoFormat:Y444_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10BE", SPA_VIDEO_FORMAT_Y444_10BE,}, - { "Spa:Enum:VideoFormat:Y444_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10LE", SPA_VIDEO_FORMAT_Y444_10LE,}, - { "Spa:Enum:VideoFormat:GBR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR", SPA_VIDEO_FORMAT_GBR,}, - { "Spa:Enum:VideoFormat:GBR_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10BE", SPA_VIDEO_FORMAT_GBR_10BE,}, - { "Spa:Enum:VideoFormat:GBR_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10LE", SPA_VIDEO_FORMAT_GBR_10LE,}, - { "Spa:Enum:VideoFormat:NV16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV16", SPA_VIDEO_FORMAT_NV16,}, - { "Spa:Enum:VideoFormat:NV24", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV24", SPA_VIDEO_FORMAT_NV24,}, - { "Spa:Enum:VideoFormat:NV12_64Z32", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12_64Z32", SPA_VIDEO_FORMAT_NV12_64Z32,}, - { "Spa:Enum:VideoFormat:A420_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10BE", SPA_VIDEO_FORMAT_A420_10BE,}, - { "Spa:Enum:VideoFormat:A420_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10LE", SPA_VIDEO_FORMAT_A420_10LE,}, - { "Spa:Enum:VideoFormat:A422_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10BE", SPA_VIDEO_FORMAT_A422_10BE,}, - { "Spa:Enum:VideoFormat:A422_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10LE", SPA_VIDEO_FORMAT_A422_10LE,}, - { "Spa:Enum:VideoFormat:A444_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10BE", SPA_VIDEO_FORMAT_A444_10BE,}, - { "Spa:Enum:VideoFormat:A444_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10LE", SPA_VIDEO_FORMAT_A444_10LE,}, - { "Spa:Enum:VideoFormat:NV61", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV61", SPA_VIDEO_FORMAT_NV61,}, - { "Spa:Enum:VideoFormat:P010_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10BE", SPA_VIDEO_FORMAT_P010_10BE,}, - { "Spa:Enum:VideoFormat:P010_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10LE", SPA_VIDEO_FORMAT_P010_10LE,}, - { "Spa:Enum:VideoFormat:IYU2", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU2", SPA_VIDEO_FORMAT_IYU2,}, - { "Spa:Enum:VideoFormat:VYUY", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "VYUY", SPA_VIDEO_FORMAT_VYUY,}, - { "Spa:Enum:VideoFormat:GBRA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA", SPA_VIDEO_FORMAT_GBRA,}, - { "Spa:Enum:VideoFormat:GBRA_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10BE", SPA_VIDEO_FORMAT_GBRA_10BE,}, - { "Spa:Enum:VideoFormat:GBRA_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10LE", SPA_VIDEO_FORMAT_GBRA_10LE,}, - { "Spa:Enum:VideoFormat:GBR_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12BE", SPA_VIDEO_FORMAT_GBR_12BE,}, - { "Spa:Enum:VideoFormat:GBR_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12LE", SPA_VIDEO_FORMAT_GBR_12LE,}, - { "Spa:Enum:VideoFormat:GBRA_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12BE", SPA_VIDEO_FORMAT_GBRA_12BE,}, - { "Spa:Enum:VideoFormat:GBRA_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12LE", SPA_VIDEO_FORMAT_GBRA_12LE,}, - { "Spa:Enum:VideoFormat:I420_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12BE", SPA_VIDEO_FORMAT_I420_12BE,}, - { "Spa:Enum:VideoFormat:I420_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12LE", SPA_VIDEO_FORMAT_I420_12LE,}, - { "Spa:Enum:VideoFormat:I422_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12BE", SPA_VIDEO_FORMAT_I422_12BE,}, - { "Spa:Enum:VideoFormat:I422_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12LE", SPA_VIDEO_FORMAT_I422_12LE,}, - { "Spa:Enum:VideoFormat:Y444_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12BE", SPA_VIDEO_FORMAT_Y444_12BE,}, - { "Spa:Enum:VideoFormat:Y444_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12LE", SPA_VIDEO_FORMAT_Y444_12LE,}, - { "Spa:Enum:VideoFormat:xRGB_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB_210LE", SPA_VIDEO_FORMAT_xRGB_210LE,}, - { "Spa:Enum:VideoFormat:xBGR_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR_210LE", SPA_VIDEO_FORMAT_xBGR_210LE,}, - { "Spa:Enum:VideoFormat:RGBx_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx_102LE", SPA_VIDEO_FORMAT_RGBx_102LE,}, - { "Spa:Enum:VideoFormat:BGRx_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx_102LE", SPA_VIDEO_FORMAT_BGRx_102LE,}, - { "Spa:Enum:VideoFormat:ARGB_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB_210LE", SPA_VIDEO_FORMAT_ARGB_210LE,}, - { "Spa:Enum:VideoFormat:ABGR_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR_210LE", SPA_VIDEO_FORMAT_ABGR_210LE,}, - { "Spa:Enum:VideoFormat:RGBA_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_102LE", SPA_VIDEO_FORMAT_RGBA_102LE,}, - { "Spa:Enum:VideoFormat:BGRA_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA_102LE", SPA_VIDEO_FORMAT_BGRA_102LE,}, - { "Spa:Enum:AudioFormat:ENCODED", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ENCODED", SPA_AUDIO_FORMAT_ENCODED,}, - { "Spa:Enum:AudioFormat:S8", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S8", SPA_AUDIO_FORMAT_S8, }, - { "Spa:Enum:AudioFormat:U8", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8", SPA_AUDIO_FORMAT_U8, }, - { "Spa:Enum:AudioFormat:S16LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16_LE", SPA_AUDIO_FORMAT_S16_LE, }, - { "Spa:Enum:AudioFormat:U16LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16_LE", SPA_AUDIO_FORMAT_U16_LE, }, - { "Spa:Enum:AudioFormat:S24_32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32_LE", SPA_AUDIO_FORMAT_S24_32_LE, }, - { "Spa:Enum:AudioFormat:U24_32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32_LE", SPA_AUDIO_FORMAT_U24_32_LE, }, - { "Spa:Enum:AudioFormat:S32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32_LE", SPA_AUDIO_FORMAT_S32_LE, }, - { "Spa:Enum:AudioFormat:U32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32_LE", SPA_AUDIO_FORMAT_U32_LE, }, - { "Spa:Enum:AudioFormat:S24LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_LE", SPA_AUDIO_FORMAT_S24_LE, }, - { "Spa:Enum:AudioFormat:U24LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_LE", SPA_AUDIO_FORMAT_U24_LE, }, - { "Spa:Enum:AudioFormat:S20LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20_LE", SPA_AUDIO_FORMAT_S20_LE, }, - { "Spa:Enum:AudioFormat:U20LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20_LE", SPA_AUDIO_FORMAT_U20_LE, }, - { "Spa:Enum:AudioFormat:S18LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18_LE", SPA_AUDIO_FORMAT_S18_LE, }, - { "Spa:Enum:AudioFormat:U18LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18_LE", SPA_AUDIO_FORMAT_U18_LE, }, - { "Spa:Enum:AudioFormat:F32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32_LE", SPA_AUDIO_FORMAT_F32_LE, }, - { "Spa:Enum:AudioFormat:F64LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64_LE", SPA_AUDIO_FORMAT_F64_LE, }, - { "Spa:Enum:AudioFormat:S16BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16_BE", SPA_AUDIO_FORMAT_S16_BE, }, - { "Spa:Enum:AudioFormat:U16BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16_BE", SPA_AUDIO_FORMAT_U16_BE, }, - { "Spa:Enum:AudioFormat:S24_32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32_BE", SPA_AUDIO_FORMAT_S24_32_BE, }, - { "Spa:Enum:AudioFormat:U24_32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32_BE", SPA_AUDIO_FORMAT_U24_32_BE, }, - { "Spa:Enum:AudioFormat:S32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32_BE", SPA_AUDIO_FORMAT_S32_BE, }, - { "Spa:Enum:AudioFormat:U32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32_BE", SPA_AUDIO_FORMAT_U32_BE, }, - { "Spa:Enum:AudioFormat:S24BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_BE", SPA_AUDIO_FORMAT_S24_BE, }, - { "Spa:Enum:AudioFormat:U24BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_BE", SPA_AUDIO_FORMAT_U24_BE, }, - { "Spa:Enum:AudioFormat:S20BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20_BE", SPA_AUDIO_FORMAT_S20_BE, }, - { "Spa:Enum:AudioFormat:U20BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20_BE", SPA_AUDIO_FORMAT_U20_BE, }, - { "Spa:Enum:AudioFormat:S18BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18_BE", SPA_AUDIO_FORMAT_S18_BE, }, - { "Spa:Enum:AudioFormat:U18BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18_BE", SPA_AUDIO_FORMAT_U18_BE, }, - { "Spa:Enum:AudioFormat:F32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32_BE", SPA_AUDIO_FORMAT_F32_BE, }, - { "Spa:Enum:AudioFormat:F64BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64_BE", SPA_AUDIO_FORMAT_F64_BE, }, - { "Spa:Enum:AudioFormat:F32P", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32P", SPA_AUDIO_FORMAT_F32P, }, -}; diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 320e49912..a98201587 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -269,9 +269,6 @@ struct pw_impl_client { unsigned int destroyed:1; int refcount; - - /* v2 compatibility data */ - void *compat_v2; }; #define pw_global_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_global_events, m, v, ##__VA_ARGS__) From a188f1d29f85721e07f2578e1827b99fb05ef12f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 10 Jul 2025 16:53:39 +0200 Subject: [PATCH 0514/1014] pod: remove alignment checks We currently often create pods in a uint8_t buffer, which is not aligned to 8 and might cause deref and other problems. We should either align the buffer we write into or maybe make the builder add some padding before the buffer to align it. We have to be careful with that when we assume the buffer start is the beginning of the pod.. Fixes #4794 --- spa/include/spa/pod/iter.h | 5 ++--- spa/include/spa/pod/parser.h | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 826ac0d86..0a93b026d 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -35,8 +35,7 @@ struct spa_pod_frame { }; #define SPA_POD_IS_VALID(pod) \ - (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE && \ - SPA_IS_ALIGNED(pod, SPA_POD_ALIGN)) + (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE) #define SPA_POD_CHECK_TYPE(pod,_type) \ (SPA_POD_IS_VALID(pod) && \ @@ -50,7 +49,7 @@ SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const vo size_t remaining; return spa_ptr_type_inside(pod, size, iter, struct spa_pod, &remaining) && - SPA_IS_ALIGNED(iter, SPA_POD_ALIGN) && remaining >= SPA_POD_BODY_SIZE(iter); + remaining >= SPA_POD_BODY_SIZE(iter); } SPA_API_POD_ITER void *spa_pod_next(const void *iter) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 9c128f26a..233304053 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -76,11 +76,9 @@ spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t si /* Use void* because creating a misaligned pointer is undefined. */ void *pod = SPA_PTROFF(parser->data, offset, void); /* - * Check that the pointer is aligned and that the size (rounded - * to the next multiple of 8) is in bounds. + * Check that the size (rounded to the next multiple of 8) is in bounds. */ - if (SPA_IS_ALIGNED(pod, SPA_POD_ALIGN) && - long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), SPA_POD_ALIGN) <= size) + if (long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), SPA_POD_ALIGN) <= size) return (struct spa_pod *)pod; } return NULL; From 74e576c31a1a2b021c91d5718e835edd109ac78e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 10 Jul 2025 18:10:51 +0200 Subject: [PATCH 0515/1014] filter-graph: don't pass NULL label around lv2 does not have a label, make sure we pass "" around like befor because code does not expect this to be NULL. --- spa/plugins/filter-graph/filter-graph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 4bcf889b7..c9d276c5f 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1355,7 +1355,7 @@ static int load_node(struct graph *graph, struct spa_json *json) spa_log_info(impl->log, "loading type:%s plugin:%s label:%s", type, plugin, label); - if ((desc = descriptor_load(graph->impl, type, plugin, label)) == NULL) + if ((desc = descriptor_load(graph->impl, type, plugin, label ? label : "")) == NULL) return -errno; node = calloc(1, sizeof(*node)); From 067e29543a325291b880a36d5e2ef3cf30d8a296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 11 Jul 2025 10:05:14 +0200 Subject: [PATCH 0516/1014] bluez5: backend-native: Fix call held hangup Currently it's not possible to hangup a call place on hold, and request user to swap calls before been able to hangup. --- spa/plugins/bluez5/backend-native.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 7aeefdc9f..707c40be6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1541,6 +1541,7 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t rfcomm_send_cmd(rfcomm, "AT+CHUP"); } break; + case CALL_STATE_HELD: case CALL_STATE_WAITING: if (rfcomm->hfp_hf_in_progress) { *err = BT_TELEPHONY_ERROR_IN_PROGRESS; @@ -1550,7 +1551,7 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t hfp_hf_in_progress = true; break; default: - spa_log_info(backend->log, "Call not incoming, waiting or active: skip hangup"); + spa_log_info(backend->log, "Call invalid state: skip hangup"); *err = BT_TELEPHONY_ERROR_INVALID_STATE; return; } @@ -1566,7 +1567,8 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t } if (hfp_hf_in_progress) { - if (call_data->call->state != CALL_STATE_WAITING) { + if (!rfcomm->hfp_hf_clcc && call_data->call->state != CALL_STATE_HELD && + call_data->call->state != CALL_STATE_WAITING) { spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { call->state = CALL_STATE_DISCONNECTED; From 51d4d5ec3c278a1ae6d745634e1234ef2a74c8d1 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 9 Jul 2025 17:38:43 -0400 Subject: [PATCH 0517/1014] gst: pipewiresrc: Expose cursor position as a ROI meta --- src/gst/gstpipewirepool.c | 1 + src/gst/gstpipewirepool.h | 1 + src/gst/gstpipewiresrc.c | 34 ++++++++++++++++++++++++++-------- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index f82dfbf46..0b4f30309 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -194,6 +194,7 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) gst_buffer_add_video_crop_meta(buf); data->videotransform = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*data->videotransform)); + data->cursor = spa_buffer_find_meta_data (b->buffer, SPA_META_Cursor, sizeof(*data->cursor)); gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf), pool_data_quark, diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index f71344354..4953b689c 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -39,6 +39,7 @@ struct _GstPipeWirePoolData { gboolean queued; struct spa_meta_region *crop; struct spa_meta_videotransform *videotransform; + struct spa_meta_cursor *cursor; }; struct _GstPipeWirePool { diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 686ceb5de..b9c8a7a7b 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -714,6 +714,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) struct spa_meta_header *h; struct spa_meta_region *crop; enum spa_meta_videotransform_value transform_value; + struct spa_meta_cursor *cursor; struct pw_time time; guint i; @@ -806,6 +807,13 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) pwsrc->transform_value = transform_value; } + cursor = data->cursor; + if (cursor && cursor->id != 0) { + /* TODO: at some point, maybe we can figure out width and height from the bitmap, + * and even add that to the meta itself */ + gst_buffer_add_video_region_of_interest_meta (buf, "cursor", cursor->position.x, cursor->position.y, 0, 0); + } + if (pwsrc->is_rawvideo) { GstVideoInfo *info = &pwsrc->video_info; uint32_t n_datas = b->buffer->n_datas; @@ -1341,11 +1349,11 @@ handle_format_change (GstPipeWireSrc *pwsrc, } if (pwsrc->caps) { - const struct spa_pod *params[4]; + const struct spa_pod *params[10]; struct spa_pod_builder b = { NULL }; - uint8_t buffer[512]; + uint8_t buffer[16384]; uint32_t buffers = CLAMP (16, pwsrc->min_buffers, pwsrc->max_buffers); - int buffertypes; + int buffertypes, n_params = 0; buffertypes = (1<caps); spa_pod_builder_init (&b, buffer, sizeof (buffer)); - params[0] = spa_pod_builder_add_object (&b, + params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, pwsrc->min_buffers, @@ -1365,21 +1373,31 @@ handle_format_change (GstPipeWireSrc *pwsrc, SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes)); - params[1] = spa_pod_builder_add_object (&b, + params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header))); - params[2] = spa_pod_builder_add_object (&b, + params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); - params[3] = spa_pod_builder_add_object (&b, + params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_videotransform))); +#define CURSOR_META_SIZE(width, height) \ + (sizeof (struct spa_meta_cursor) + \ + sizeof (struct spa_meta_bitmap) + width * height * 4) + params[n_params++] = spa_pod_builder_add_object (&b, + SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), + SPA_PARAM_META_size, + SPA_POD_CHOICE_RANGE_Int (CURSOR_META_SIZE(384, 384), + sizeof (struct spa_meta_cursor), + CURSOR_META_SIZE(384, 384))); GST_DEBUG_OBJECT (pwsrc, "doing finish format"); - pw_stream_update_params (pwsrc->stream->pwstream, params, SPA_N_ELEMENTS(params)); + pw_stream_update_params (pwsrc->stream->pwstream, params, n_params); } else { GST_WARNING_OBJECT (pwsrc, "finish format with error"); pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "unhandled format"); From 49d9d5e618f01e2ce2f4ae04af6d196c4626e5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 21:04:00 +0200 Subject: [PATCH 0518/1014] spa: param: video: add missing type info for color params Add the missing type info for: * SPA_FORMAT_VIDEO_colorRange * SPA_FORMAT_VIDEO_colorMatrix * SPA_FORMAT_VIDEO_transferFunction * SPA_FORMAT_VIDEO_colorPrimaries --- spa/include/spa/param/format-types.h | 12 ++-- spa/include/spa/param/video/color-types.h | 79 +++++++++++++++++++++++ spa/include/spa/param/video/type-info.h | 1 + 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 spa/include/spa/param/video/color-types.h diff --git a/spa/include/spa/param/format-types.h b/spa/include/spa/param/format-types.h index 13e475141..8b9de29c3 100644 --- a/spa/include/spa/param/format-types.h +++ b/spa/include/spa/param/format-types.h @@ -166,10 +166,14 @@ static const struct spa_type_info spa_type_format[] = { { SPA_FORMAT_VIDEO_multiviewMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewMode", NULL }, { SPA_FORMAT_VIDEO_multiviewFlags, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewFlags", NULL }, { SPA_FORMAT_VIDEO_chromaSite, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "chromaSite", NULL }, - { SPA_FORMAT_VIDEO_colorRange, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorRange", NULL }, - { SPA_FORMAT_VIDEO_colorMatrix, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorMatrix", NULL }, - { SPA_FORMAT_VIDEO_transferFunction, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "transferFunction", NULL }, - { SPA_FORMAT_VIDEO_colorPrimaries, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorPrimaries", NULL }, + { SPA_FORMAT_VIDEO_colorRange, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorRange", + spa_type_video_color_range, }, + { SPA_FORMAT_VIDEO_colorMatrix, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorMatrix", + spa_type_video_color_matrix, }, + { SPA_FORMAT_VIDEO_transferFunction, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "transferFunction", + spa_type_video_transfer_function, }, + { SPA_FORMAT_VIDEO_colorPrimaries, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorPrimaries", + spa_type_video_color_primaries, }, { SPA_FORMAT_VIDEO_profile, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "profile", NULL }, { SPA_FORMAT_VIDEO_level, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "level", NULL }, diff --git a/spa/include/spa/param/video/color-types.h b/spa/include/spa/param/video/color-types.h new file mode 100644 index 000000000..cc8cc25be --- /dev/null +++ b/spa/include/spa/param/video/color-types.h @@ -0,0 +1,79 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 PipeWire authors */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_VIDEO_COLOR_TYPES_H +#define SPA_VIDEO_COLOR_TYPES_H + +#include +#include + +#define SPA_TYPE_INFO_VideColorRange SPA_TYPE_INFO_ENUM_BASE "VideoColorRange" +#define SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE SPA_TYPE_INFO_VideColorRange ":" + +static const struct spa_type_info spa_type_video_color_range[] = { + { SPA_VIDEO_COLOR_RANGE_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "unknown", NULL }, + { SPA_VIDEO_COLOR_RANGE_0_255, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "0-255", NULL }, + { SPA_VIDEO_COLOR_RANGE_16_235, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "16-235", NULL }, + { 0, 0, NULL, NULL }, +}; + +#define SPA_TYPE_INFO_VideoColorMatrix SPA_TYPE_INFO_ENUM_BASE "VideoColorMatrix" +#define SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE SPA_TYPE_INFO_VideoColorMatrix ":" + +static const struct spa_type_info spa_type_video_color_matrix[] = { + { SPA_VIDEO_COLOR_MATRIX_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "unknown", NULL }, + { SPA_VIDEO_COLOR_MATRIX_RGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "rgb", NULL }, + { SPA_VIDEO_COLOR_MATRIX_FCC, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "fcc", NULL }, + { SPA_VIDEO_COLOR_MATRIX_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt709", NULL }, + { SPA_VIDEO_COLOR_MATRIX_BT601, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt601", NULL }, + { SPA_VIDEO_COLOR_MATRIX_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "smpte240m", NULL }, + { SPA_VIDEO_COLOR_MATRIX_BT2020, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt2020", NULL }, + { 0, 0, NULL, NULL }, +}; + +#define SPA_TYPE_INFO_VideoTransferFunction SPA_TYPE_INFO_ENUM_BASE "VideoTransferFunction" +#define SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE SPA_TYPE_INFO_VideoTransferFunction ":" + +static const struct spa_type_info spa_type_video_transfer_function[] = { + { SPA_VIDEO_TRANSFER_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "unknown", NULL }, + { SPA_VIDEO_TRANSFER_GAMMA10, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma10", NULL }, + { SPA_VIDEO_TRANSFER_GAMMA18, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma18", NULL }, + { SPA_VIDEO_TRANSFER_GAMMA20, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma20", NULL }, + { SPA_VIDEO_TRANSFER_GAMMA22, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma22", NULL }, + { SPA_VIDEO_TRANSFER_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt709", NULL }, + { SPA_VIDEO_TRANSFER_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "smpte240m", NULL }, + { SPA_VIDEO_TRANSFER_SRGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "srgb", NULL }, + { SPA_VIDEO_TRANSFER_GAMMA28, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma28", NULL }, + { SPA_VIDEO_TRANSFER_LOG100, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "log100", NULL }, + { SPA_VIDEO_TRANSFER_LOG316, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "log316", NULL }, + { SPA_VIDEO_TRANSFER_BT2020_12, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt2020-12", NULL }, + { SPA_VIDEO_TRANSFER_ADOBERGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "adobergb", NULL }, + { SPA_VIDEO_TRANSFER_BT2020_10, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt2020-10", NULL }, + { SPA_VIDEO_TRANSFER_SMPTE2084, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "smpte2084", NULL }, + { SPA_VIDEO_TRANSFER_ARIB_STD_B67, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "arib-std-b67", NULL }, + { SPA_VIDEO_TRANSFER_BT601, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt601", NULL }, + { 0, 0, NULL, NULL }, +}; + +#define SPA_TYPE_INFO_VideoColorPrimaries SPA_TYPE_INFO_ENUM_BASE "VideoColorPrimaries" +#define SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE SPA_TYPE_INFO_VideoColorPrimaries ":" + +static const struct spa_type_info spa_type_video_color_primaries[] = { + { SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "unknown", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt709", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_BT470M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt470m", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_BT470BG, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt470bg", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpte170m", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpte240m", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_FILM, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "film", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_BT2020, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt2020", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "adobergb", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smptest428", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpterp431", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpteeg432", NULL }, + { SPA_VIDEO_COLOR_PRIMARIES_EBU3213, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "ebu3213", NULL }, + { 0, 0, NULL, NULL }, +}; + +#endif /* SPA_VIDEO_COLOR_TYPES_H */ diff --git a/spa/include/spa/param/video/type-info.h b/spa/include/spa/param/video/type-info.h index 04e00c9c1..cc8d296e0 100644 --- a/spa/include/spa/param/video/type-info.h +++ b/spa/include/spa/param/video/type-info.h @@ -5,6 +5,7 @@ #ifndef SPA_VIDEO_TYPES_H #define SPA_VIDEO_TYPES_H +#include #include #endif /* SPA_VIDEO_TYPES_H */ From 331bb2f1ed6cd7f2fcf549c513a2e45bd4cbc4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 21:00:38 +0200 Subject: [PATCH 0519/1014] spa: debug: `SPA_TYPE_Id` is unsigned The underlying type of `SPA_TYPE_Id` is `uint32_t`, so access and print it as such. --- spa/include/spa/debug/format.h | 7 +++++-- spa/include/spa/debug/pod.h | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 5fe0eac3e..856e9954c 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -5,6 +5,8 @@ #ifndef SPA_DEBUG_FORMAT_H #define SPA_DEBUG_FORMAT_H +#include + #include #include #include @@ -41,10 +43,11 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i break; case SPA_TYPE_Id: { - const char *str = spa_debug_type_find_short_name(info, *(int32_t *) body); + uint32_t value = *(uint32_t *) body; + const char *str = spa_debug_type_find_short_name(info, value); char tmp[64]; if (str == NULL) { - snprintf(tmp, sizeof(tmp), "%d", *(int32_t*)body); + snprintf(tmp, sizeof(tmp), "%" PRIu32, value); str = tmp; } spa_strbuf_append(buffer, "%s", str); diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index 21771691a..4ff3f3e01 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -39,8 +39,8 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa spa_debugc(ctx, "%*s" "Bool %s", indent, "", (*(int32_t *) body) ? "true" : "false"); break; case SPA_TYPE_Id: - spa_debugc(ctx, "%*s" "Id %-8" PRId32 " (%s)", indent, "", *(int32_t *) body, - spa_debug_type_find_name(info, *(int32_t *) body)); + spa_debugc(ctx, "%*s" "Id %-8" PRIu32 " (%s)", indent, "", *(uint32_t *) body, + spa_debug_type_find_name(info, *(uint32_t *) body)); break; case SPA_TYPE_Int: spa_debugc(ctx, "%*s" "Int %" PRId32, indent, "", *(int32_t *) body); From d17f68c047dfe35977d3c63640154a350a20e6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 23:36:46 +0200 Subject: [PATCH 0520/1014] pipewire: thread: impl_join(): return negative error code `pthread_*` functions return a positive error code, but a negative one is expected, so negate the return value. --- src/pipewire/thread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index defa6a686..8bba05501 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -125,7 +125,7 @@ static struct spa_thread *impl_create(void *object, static int impl_join(void *object, struct spa_thread *thread, void **retval) { pthread_t pt = (pthread_t)thread; - return pthread_join(pt, retval); + return -pthread_join(pt, retval); } static int impl_get_rt_range(void *object, const struct spa_dict *props, From 72f1719c957b3c92f122362b4c3ef76ae9ec3b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 23:38:58 +0200 Subject: [PATCH 0521/1014] pipewire: thread: check `sched_get_priority_*()` return value The function can report errors, so let's propagate them. --- src/pipewire/thread.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index 8bba05501..baca12212 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -131,10 +131,18 @@ static int impl_join(void *object, struct spa_thread *thread, void **retval) static int impl_get_rt_range(void *object, const struct spa_dict *props, int *min, int *max) { - if (min) + if (min) { *min = sched_get_priority_min(SCHED_OTHER); - if (max) + if (*min < 0) + return -errno; + } + + if (max) { *max = sched_get_priority_max(SCHED_OTHER); + if (*max < 0) + return -errno; + } + return 0; } static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority) From ca47d0ef1572a85ab2e16b1adc9bc46faed7b43b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 14 May 2025 18:55:10 +0200 Subject: [PATCH 0522/1014] spa: vulkan: map `VK_INCOMPLETE` to `ENOSPC` `VK_INCOMPLETE` means "A return array was too small for the result", so map it to `ENOSPC` since that describes it better than `EBUSY`. --- spa/plugins/vulkan/vulkan-utils.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/vulkan/vulkan-utils.c b/spa/plugins/vulkan/vulkan-utils.c index 64323135d..6a0f693dc 100644 --- a/spa/plugins/vulkan/vulkan-utils.c +++ b/spa/plugins/vulkan/vulkan-utils.c @@ -41,8 +41,9 @@ static int vkresult_to_errno(VkResult result) case VK_EVENT_SET: case VK_EVENT_RESET: return 0; - case VK_NOT_READY: case VK_INCOMPLETE: + return ENOSPC; + case VK_NOT_READY: case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return EBUSY; case VK_TIMEOUT: From 13fe4a5a57112be1c53aaa40344f79b195bb6c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 27 Feb 2025 18:15:30 +0100 Subject: [PATCH 0523/1014] pipewire: module-rt: use `sizeof(variable)` instead of `sizeof(type)` --- src/modules/module-rt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 108627a13..5dc0b65c1 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -1040,7 +1040,7 @@ static int set_uclamp(int uclamp_min, int uclamp_max, pid_t pid) { uint32_t sched_util_max; } attr; - ret = syscall(SYS_sched_getattr, pid, &attr, sizeof(struct sched_attr), 0); + ret = syscall(SYS_sched_getattr, pid, &attr, sizeof(attr), 0); if (ret) { pw_log_warn("Could not retrieve scheduler attributes: %d", -errno); return -errno; From 279b7ee698ee041bd6d9a1440d231923c031da44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 10 Jun 2025 18:14:24 +0200 Subject: [PATCH 0524/1014] pipewire: module-rt: use "tid" instead of "pid" for thread ids --- src/modules/module-rt.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 5dc0b65c1..100401ce8 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -192,7 +192,7 @@ struct thread { struct impl *impl; struct spa_list link; pthread_t thread; - pid_t pid; + pid_t tid; void *(*start)(void*); void *arg; }; @@ -203,7 +203,7 @@ struct impl { struct spa_thread_utils thread_utils; - pid_t main_pid; + pid_t main_tid; struct rlimit rl; int nice_level; int rt_prio; @@ -614,12 +614,12 @@ static int set_nice(struct impl *impl, int nice_level, bool warn) nice_level, impl->min_nice_level); nice_level = impl->min_nice_level; } - res = pw_rtkit_make_high_priority(impl, impl->main_pid, nice_level); + res = pw_rtkit_make_high_priority(impl, impl->main_tid, nice_level); } else #endif if (impl->rlimits_enabled) - res = sched_set_nice(impl->main_pid, nice_level); + res = sched_set_nice(impl->main_tid, nice_level); else res = -ENOTSUP; @@ -716,7 +716,7 @@ static void *custom_start(void *data) struct impl *impl = this->impl; pthread_mutex_lock(&impl->lock); - this->pid = _gettid(); + this->tid = _gettid(); pthread_cond_broadcast(&impl->cond); pthread_mutex_unlock(&impl->lock); @@ -800,7 +800,7 @@ static int impl_get_rt_range(void *object, const struct spa_dict *props, } struct rt_params { - pid_t pid; + pid_t tid; int priority; }; @@ -810,7 +810,7 @@ static int do_make_realtime(struct spa_loop *loop, bool async, uint32_t seq, struct impl *impl = user_data; const struct rt_params *params = data; int err, min, max, priority = params->priority; - pid_t pid = params->pid; + pid_t pid = params->tid; pw_log_debug("rtkit realtime"); @@ -856,9 +856,9 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority pthread_mutex_lock(&impl->lock); if ((thr = find_thread_by_pt(impl, pt)) != NULL) - params.pid = thr->pid; + params.tid = thr->tid; else - params.pid = _gettid(); + params.tid = _gettid(); res = pw_loop_invoke(pw_thread_loop_get_loop(impl->thread_loop), do_make_realtime, 0, ¶ms, sizeof(params), false, impl); @@ -1104,7 +1104,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->rl.rlim_cur = impl->rt_time_soft; impl->rl.rlim_max = impl->rt_time_hard; - impl->main_pid = _gettid(); + impl->main_tid = _gettid(); bool can_use_rtkit = false, use_rtkit = false; @@ -1148,7 +1148,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } if (impl->uclamp_min || impl->uclamp_max < 1024) - set_uclamp(impl->uclamp_min, impl->uclamp_max, impl->main_pid); + set_uclamp(impl->uclamp_min, impl->uclamp_max, impl->main_tid); #ifdef HAVE_DBUS impl->use_rtkit = use_rtkit; From 7eb98a31bbadca79d0c1957bac727e82a90ab9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 27 Feb 2025 18:17:22 +0100 Subject: [PATCH 0525/1014] pipewire: module-rt: define `SCHED_RESET_ON_FORK` if not available Instead of using a new macro with the `PW_` prefix, simply define `SCHED_RESET_ON_FORK` to be `0` when it is not defined; as the prefixed variant can be a bit confusing. --- src/modules/module-rt.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 100401ce8..c6a3cf16b 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -136,11 +136,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define REALTIME_POLICY SCHED_FIFO -#ifdef SCHED_RESET_ON_FORK -#define PW_SCHED_RESET_ON_FORK SCHED_RESET_ON_FORK -#else + /* FreeBSD compat */ -#define PW_SCHED_RESET_ON_FORK 0 +#ifndef SCHED_RESET_ON_FORK +#define SCHED_RESET_ON_FORK 0 #endif #define MIN_NICE_LEVEL -20 @@ -567,8 +566,8 @@ static bool check_realtime_privileges(struct impl *impl) spa_zero(new_sched_params); new_sched_params.sched_priority = SPA_CLAMP((int)priority, min, max); new_policy = REALTIME_POLICY; - if ((old_policy & PW_SCHED_RESET_ON_FORK) != 0) - new_policy |= PW_SCHED_RESET_ON_FORK; + if ((old_policy & SCHED_RESET_ON_FORK) != 0) + new_policy |= SCHED_RESET_ON_FORK; /* Disable RLIMIT_RTTIME in a thread safe way and hope that the application * doesn't also set RLIMIT_RTTIME while trying new_policy. */ @@ -673,7 +672,7 @@ static int acquire_rt_sched(struct spa_thread *thread, int priority) spa_zero(sp); sp.sched_priority = priority; - if ((err = pthread_setschedparam(pt, REALTIME_POLICY | PW_SCHED_RESET_ON_FORK, &sp)) != 0) { + if ((err = pthread_setschedparam(pt, REALTIME_POLICY | SCHED_RESET_ON_FORK, &sp)) != 0) { pw_log_warn("could not make thread %p realtime: %s", thread, strerror(err)); return -err; } @@ -689,7 +688,7 @@ static int impl_drop_rt_generic(void *object, struct spa_thread *thread) int err; spa_zero(sp); - if ((err = pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp)) != 0) { + if ((err = pthread_setschedparam(pt, SCHED_OTHER | SCHED_RESET_ON_FORK, &sp)) != 0) { pw_log_debug("thread %p: SCHED_OTHER|SCHED_RESET_ON_FORK failed: %s", thread, strerror(err)); return -err; @@ -848,7 +847,7 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority struct thread *thr; spa_zero(sp); - if (pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp) == 0) { + if (pthread_setschedparam(pt, SCHED_OTHER | SCHED_RESET_ON_FORK, &sp) == 0) { pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked."); } From 3b4f37ac92c4b436278df783492790ae63029511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 27 Feb 2025 18:44:30 +0100 Subject: [PATCH 0526/1014] pipewire: module-rt: fix function brace style --- src/modules/module-rt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index c6a3cf16b..79cfaa081 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -1023,7 +1023,8 @@ static int do_rtkit_setup(struct spa_loop *loop, bool async, uint32_t seq, } #endif /* HAVE_DBUS */ -static int set_uclamp(int uclamp_min, int uclamp_max, pid_t pid) { +static int set_uclamp(int uclamp_min, int uclamp_max, pid_t pid) +{ #ifdef __linux__ int ret; struct sched_attr { From c4984e33b20603c447634c745b8fc4f1e2b00ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 16:18:35 +0200 Subject: [PATCH 0527/1014] pipewire: module-rt: use `spa_autoptr` for properties --- src/modules/module-rt.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 79cfaa081..5f7182bba 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -1073,7 +1073,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; - struct pw_properties *props; int res = 0; PW_LOG_TOPIC_INIT(mod_topic); @@ -1084,7 +1083,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_log_debug("module %p: new", impl); - props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL); + spa_autoptr(pw_properties) props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL); if (!props) { res = -errno; goto error; @@ -1194,7 +1193,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); pw_impl_module_update_properties(module, &props->dict); - goto done; + return 0; error: #ifdef HAVE_DBUS @@ -1204,8 +1203,6 @@ error: pw_rtkit_bus_free(impl->rtkit_bus); #endif free(impl); -done: - pw_properties_free(props); return res; } From f0579b9b67fc000d7edf040667327b6e4b8da699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 16:50:48 +0200 Subject: [PATCH 0528/1014] pipewire: module-rt: deduplicate log message --- src/modules/module-rt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 5f7182bba..71153a84b 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -1173,12 +1173,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) do_rtkit_setup, 0, NULL, 0, false, impl); pw_log_debug("initialized using RTKit"); - } else { + } else +#endif + { pw_log_debug("initialized using regular realtime scheduling"); } -#else - pw_log_debug("initialized using regular realtime scheduling"); -#endif impl->thread_utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_ThreadUtils, From 10161407ff5ac00208b05802ca26da8211f8f86d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 28 Feb 2025 22:09:36 +0100 Subject: [PATCH 0529/1014] pipewire: module-rt: move `RLIMIT_RTTIME` compat definition Move it next to the other macros. --- src/modules/module-rt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 71153a84b..96499cf8c 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -142,6 +142,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define SCHED_RESET_ON_FORK 0 #endif +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + #define MIN_NICE_LEVEL -20 #define MAX_NICE_LEVEL 19 #define IS_VALID_NICE_LEVEL(l) ((l)>=MIN_NICE_LEVEL && (l)<=MAX_NICE_LEVEL) @@ -239,10 +243,6 @@ struct impl { #endif }; -#ifndef RLIMIT_RTTIME -#define RLIMIT_RTTIME 15 -#endif - static pthread_mutex_t rlimit_lock = PTHREAD_MUTEX_INITIALIZER; static pid_t _gettid(void) From 801ac5ced83c69aa4402afaaee708ad0652efc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 28 Feb 2025 22:51:39 +0100 Subject: [PATCH 0530/1014] pipewire: module-rt: simplify `check_rtkit()` The return value is always 0, and the `impl` parameter is not used, so ues the return value to return the boolean result instead of an out parameter, and get rid of the unused argument. --- src/modules/module-rt.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 96499cf8c..5397dec7c 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -920,19 +920,17 @@ static const struct spa_thread_utils_methods impl_thread_utils = { #ifdef HAVE_DBUS -static int check_rtkit(struct impl *impl, struct pw_context *context, bool *can_use_rtkit) +static bool check_rtkit(struct pw_context *context) { const struct pw_properties *context_props; const char *str; - *can_use_rtkit = true; - if ((context_props = pw_context_get_properties(context)) != NULL && (str = pw_properties_get(context_props, "support.dbus")) != NULL && !pw_properties_parse_bool(str)) - *can_use_rtkit = false; + return false; - return 0; + return true; } static int rtkit_get_bus(struct impl *impl) @@ -1118,9 +1116,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pthread_mutex_init(&impl->lock, NULL); pthread_cond_init(&impl->cond, NULL); - if ((res = check_rtkit(impl, context, &can_use_rtkit)) < 0) - goto error; - + can_use_rtkit = check_rtkit(context); #endif /* If the user has permissions to use regular realtime scheduling, as well as * the nice level we want, then we'll use that instead of RTKit */ From 7a336645fb4af31fcfc43c3dc5db1cd9a56bee80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 21:13:36 +0200 Subject: [PATCH 0531/1014] pipewire: module-rt: get_rtkit_priority_range(): return void This function cannot fail, so make it return `void`. --- src/modules/module-rt.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 5397dec7c..10bce9afb 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -774,7 +774,7 @@ static int impl_join(void *object, struct spa_thread *thread, void **retval) } -static int get_rtkit_priority_range(struct impl *impl, int *min, int *max) +static void get_rtkit_priority_range(struct impl *impl, int *min, int *max) { if (min) *min = 1; @@ -783,16 +783,15 @@ static int get_rtkit_priority_range(struct impl *impl, int *min, int *max) if (*max < 1) *max = 1; } - return 0; } static int impl_get_rt_range(void *object, const struct spa_dict *props, int *min, int *max) { struct impl *impl = object; - int res; + int res = 0; if (impl->use_rtkit) - res = get_rtkit_priority_range(impl, min, max); + get_rtkit_priority_range(impl, min, max); else res = get_rt_priority_range(min, max); return res; @@ -813,8 +812,7 @@ static int do_make_realtime(struct spa_loop *loop, bool async, uint32_t seq, pw_log_debug("rtkit realtime"); - if ((err = get_rtkit_priority_range(impl, &min, &max)) < 0) - return err; + get_rtkit_priority_range(impl, &min, &max); if (priority < min || priority > max) { pw_log_info("clamping requested priority %d for thread %d " From b71d0224db0f1f9a4135ca997af750ab4f3f3874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 28 Feb 2025 23:30:53 +0100 Subject: [PATCH 0532/1014] pipewire: module-rt: remove check for impossible condition Neither `sched_set_nice()` nor `pw_rtkit_make_high_priority()` (should) ever return a positive number, so that case is not possible; remove it. --- src/modules/module-rt.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 10bce9afb..9656e8d70 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -626,9 +626,6 @@ static int set_nice(struct impl *impl, int nice_level, bool warn) if (warn) pw_log_warn("could not set nice-level to %d: %s", nice_level, spa_strerror(res)); - } else if (res > 0) { - pw_log_info("main thread setting nice level to %d: %s", - nice_level, spa_strerror(-res)); } else { pw_log_info("main thread nice level set to %d", nice_level); From b124385fac6c59deab4b6142b1f478895a26229c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 28 Feb 2025 23:33:50 +0100 Subject: [PATCH 0533/1014] pipewire: module-rt: remove `sched_set_nice()` It has only a single caller, so inline it because there is not much point in keeping it separate. --- src/modules/module-rt.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 9656e8d70..d1f14eef6 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -594,14 +594,6 @@ static bool check_realtime_privileges(struct impl *impl) return ret; } -static int sched_set_nice(pid_t pid, int nice_level) -{ - if (setpriority(PRIO_PROCESS, pid, nice_level) == 0) - return 0; - else - return -errno; -} - static int set_nice(struct impl *impl, int nice_level, bool warn) { int res = 0; @@ -617,8 +609,10 @@ static int set_nice(struct impl *impl, int nice_level, bool warn) } else #endif - if (impl->rlimits_enabled) - res = sched_set_nice(impl->main_tid, nice_level); + if (impl->rlimits_enabled) { + if (setpriority(PRIO_PROCESS, impl->main_tid, nice_level) < 0) + res = -errno; + } else res = -ENOTSUP; From 0923e12fa3c4fc8719df1ac1c9ed5f2361f3be8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 1 Mar 2025 00:18:35 +0100 Subject: [PATCH 0534/1014] pipewire: module-rt: have just one `impl_thread_utils` There are two definitions depending on `#ifdef HAVE_DBUS`, however, the two definitions are indentical, so remove one. --- src/modules/module-rt.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index d1f14eef6..6ba838e22 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -858,15 +858,6 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority } } -static const struct spa_thread_utils_methods impl_thread_utils = { - SPA_VERSION_THREAD_UTILS_METHODS, - .create = impl_create, - .join = impl_join, - .get_rt_range = impl_get_rt_range, - .acquire_rt = impl_acquire_rt, - .drop_rt = impl_drop_rt_generic, -}; - #else /* HAVE_DBUS */ static struct spa_thread *impl_create(void *object, const struct spa_dict *props, @@ -897,6 +888,8 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority return acquire_rt_sched(thread, priority); } +#endif /* HAVE_DBUS */ + static const struct spa_thread_utils_methods impl_thread_utils = { SPA_VERSION_THREAD_UTILS_METHODS, .create = impl_create, @@ -905,8 +898,6 @@ static const struct spa_thread_utils_methods impl_thread_utils = { .acquire_rt = impl_acquire_rt, .drop_rt = impl_drop_rt_generic, }; -#endif /* HAVE_DBUS */ - #ifdef HAVE_DBUS static bool check_rtkit(struct pw_context *context) From a55561dcbaad5db5dd21e20bf34799a74e5c6c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 1 Mar 2025 00:21:56 +0100 Subject: [PATCH 0535/1014] pipewire: module-rt: destroy mutex and condition variable --- src/modules/module-rt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 6ba838e22..55579ae9f 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -482,6 +482,9 @@ static void module_destroy(void *data) pw_thread_loop_destroy(impl->thread_loop); if (impl->rtkit_bus) pw_rtkit_bus_free(impl->rtkit_bus); + + pthread_cond_destroy(&impl->cond); + pthread_mutex_destroy(&impl->lock); #endif free(impl); From 90c0d8c2257c6d36fd34570a76a12339c95aa154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 23:40:56 +0200 Subject: [PATCH 0536/1014] pipewire: module-rt: use `pw_thread_utils_join()` `pw_thread_utils_create()` is used to create the thread, so use the corresponding `pw_thread_utils_join()` instead of just `pthread_join()`. --- src/modules/module-rt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index 55579ae9f..dcc9c31a8 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -755,7 +755,7 @@ static int impl_join(void *object, struct spa_thread *thread, void **retval) struct thread *thr; int res; - res = pthread_join(pt, retval); + res = pw_thread_utils_join(thread, retval); pthread_mutex_lock(&impl->lock); if ((thr = find_thread_by_pt(impl, pt)) != NULL) { From ddc023b88343e17fe13657877716f38188f1db61 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 8 Mar 2025 11:35:35 +0200 Subject: [PATCH 0537/1014] bluez5: media-sink: make ISO target latency scale with quantum The ISO target latency should scale with graph quantum, as jitter in the graph processing time probably is proportional to the quantum. --- spa/plugins/bluez5/media-sink.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 7b0bafeb6..16dcd54fe 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -493,7 +493,13 @@ static void set_latency(struct impl *this, bool emit_latency) port->latency.min_ns = port->latency.max_ns = delay; port->latency.min_rate = port->latency.max_rate = 0; - port->latency.min_quantum = port->latency.max_quantum = 0.0f; + + if (this->transport->iso_io) { + /* ISO has different delay */ + port->latency.min_quantum = port->latency.max_quantum = 1.0f; + } else { + port->latency.min_quantum = port->latency.max_quantum = 0.0f; + } spa_log_info(this->log, "%p: total latency:%d ms", this, (int)(delay / SPA_NSEC_PER_MSEC)); @@ -971,6 +977,7 @@ again: if (this->need_flush) { size_t avail = SPA_MIN(this->buffer_used, sizeof(iso_io->buf)); + uint64_t delay = 0; spa_log_trace(this->log, "%p: ISO put fd:%d size:%u sn:%u ts:%u now:%"PRIu64, this, this->transport->fd, (unsigned)avail, @@ -984,7 +991,15 @@ again: reset_buffer(this); - update_packet_delay(this, iso_io->duration * 3/2); + if (this->process_rate) { + /* Match target delay in media_iso_pull() */ + delay = this->process_duration * SPA_NSEC_PER_SEC / this->process_rate; + if (delay < iso_io->duration*3/2) + delay = iso_io->duration*3/2 - delay; + else + delay = 0; + } + update_packet_delay(this, delay); } return 0; } @@ -1179,12 +1194,11 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) } /* - * Rate match sample position so that the graph is 3/2 ISO interval + * Rate match sample position so that the graph is max(ISO interval*3/2, quantum) * ahead of the time instant we have to send data. * - * Being 1 ISO interval ahead is unavoidable otherwise we underrun, - * and the 1/2 is safety margin for the graph to deliver data - * in time. + * Being 1 ISO interval ahead is unavoidable otherwise we underrun, and the + * rest is safety margin for the graph to deliver data in time. * * This is then the part of the TX latency on PipeWire side. There is * another part of TX latency on kernel/controller side before the @@ -1192,7 +1206,11 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) */ value = (int64_t)iso_io->now - (int64_t)get_reference_time(this, &duration_ns); - target = iso_io->duration * 3/2; + if (this->process_rate) + target = this->process_duration * SPA_NSEC_PER_SEC / this->process_rate; + else + target = 0; + target = SPA_MAX(target, iso_io->duration*3/2); err = value - target; max_err = iso_io->duration; From 30047f232baeed445aa5e68ec12a9c9f6d228e67 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 15:11:38 +0300 Subject: [PATCH 0538/1014] bluez5: account for driver clock rate difference in rate matching The rate matching calculations are done in the system clock domain. If the driver ticks at a different rate, the correction factor needs to be adjusted by the rate_diff. This fixes ISO streams getting out of sync with each other when target delay changes. This happens because typically one of them is the driver and the other follower. Driver adjust clock rate, and follower does its own adjustment *on top of that* so it rate matches more or less at double speed. (The DLL of the follower to some degree corrects for this, but can't do that when hitting RATE_CTL_DIFF_MAX and moreover it acts with a delay.) --- spa/plugins/bluez5/media-sink.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 16dcd54fe..711393c23 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -678,6 +678,13 @@ static int setup_matching(struct impl *this) if (port->rate_match) { port->rate_match->rate = 1 / port->ratectl.corr; + /* We rate match in the system clock domain. If driver ticks at a + * different rate, we as follower must compensate. + */ + if (this->following && SPA_LIKELY(this->position && + this->position->clock.rate_diff)) + port->rate_match->rate /= this->position->clock.rate_diff; + SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->following); } From ad90a2d0acbe763742058504852ef9b3b7c4961a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 15:54:08 +0300 Subject: [PATCH 0539/1014] bluez5: take clock rate difference into account in get_reference_time() The calculations is in system clock domain, so when converting from samples/duration to time rate difference should be accounted. This does not have much effect in practice. --- spa/plugins/bluez5/media-sink.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 711393c23..42f99d35a 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -175,6 +175,7 @@ struct impl { uint64_t process_time; uint64_t process_duration; uint64_t process_rate; + double process_rate_diff; uint64_t prev_flush_time; uint64_t next_flush_time; @@ -613,16 +614,19 @@ static uint32_t get_queued_frames(struct impl *this) static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) { struct port *port = &this->port; - uint64_t t, duration_ns; + uint64_t duration_ns; + int64_t t; bool resampling; if (!this->process_rate || !this->process_duration) { if (this->position) { this->process_duration = this->position->clock.duration; this->process_rate = this->position->clock.rate.denom; + this->process_rate_diff = this->position->clock.rate_diff; } else { this->process_duration = 1024; this->process_rate = 48000; + this->process_rate_diff = 1.0; } } @@ -631,7 +635,7 @@ static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) *duration_ns_ret = duration_ns; /* Time at the first sample in the current packet. */ - t = this->process_time + duration_ns; + t = duration_ns; t -= ((uint64_t)get_queued_frames(this) * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); @@ -642,7 +646,10 @@ static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) / port->current_format.info.raw.rate; } - return t; + if (this->process_rate_diff) + t = (int64_t)(t / this->process_rate_diff); + + return this->process_time + t; } static int reset_buffer(struct impl *this) @@ -2305,12 +2312,17 @@ static int impl_node_process(void *object) } } + /* Make copies of current position values, so that they can be used later at any + * time without shared memory races + */ if (this->position) { this->process_duration = this->position->clock.duration; this->process_rate = this->position->clock.rate.denom; + this->process_rate_diff = this->position->clock.rate_diff; } else { this->process_duration = 1024; this->process_rate = 48000; + this->process_rate_diff = 1.0; } this->process_time = this->current_time; From 2c70c13cc3410b494908c499c5533fd8559ca059 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 16:12:32 +0300 Subject: [PATCH 0540/1014] bluez5: rate match ISO only from process() Update rate matching only once per process(). This ensures all nodes in the group update their rate matching in the same way. Also account for audio data in ISO output buffer in the reference time. --- spa/plugins/bluez5/media-sink.c | 38 ++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 42f99d35a..d065592c9 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1194,17 +1194,21 @@ static void drop_frames(struct impl *this, uint32_t req) } } -static void media_iso_pull(struct spa_bt_iso_io *iso_io) +static void media_iso_rate_match(struct impl *this) { - struct impl *this = iso_io->user_data; + struct spa_bt_iso_io *iso_io = this->transport ? this->transport->iso_io : NULL; struct port *port = &this->port; const double period = 0.1 * SPA_NSEC_PER_SEC; + uint64_t ref_time; uint64_t duration_ns; double value, target, err, max_err; + if (!iso_io || !this->transport_started) + return; + if (this->resync || !this->position) { spa_bt_rate_control_init(&port->ratectl, 0); - goto done; + return; } /* @@ -1219,14 +1223,18 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) * controller starts processing the packet. */ - value = (int64_t)iso_io->now - (int64_t)get_reference_time(this, &duration_ns); + ref_time = get_reference_time(this, &duration_ns); + if (iso_io->size) + ref_time -= iso_io->duration; + + value = (int64_t)iso_io->now - (int64_t)ref_time; if (this->process_rate) target = this->process_duration * SPA_NSEC_PER_SEC / this->process_rate; else target = 0; target = SPA_MAX(target, iso_io->duration*3/2); err = value - target; - max_err = iso_io->duration; + max_err = SPA_MAX(40 * SPA_NSEC_PER_MSEC, target); if (iso_io->resync && err >= 0) { unsigned int req = (unsigned int)(err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC); @@ -1252,7 +1260,7 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) this, err / SPA_NSEC_PER_MSEC); } else { spa_bt_rate_control_update(&port->ratectl, err, 0, - iso_io->duration, period, RATE_CTL_DIFF_MAX); + duration_ns, period, RATE_CTL_DIFF_MAX); spa_log_trace(this->log, "%p: ISO sync err:%+.3g value:%.6f target:%.6f (ms) corr:%g", this, port->ratectl.avg / SPA_NSEC_PER_MSEC, @@ -1262,8 +1270,12 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) } iso_io->resync = false; +} + +static void media_iso_pull(struct spa_bt_iso_io *iso_io) +{ + struct impl *this = iso_io->user_data; -done: this->iso_pending = true; flush_data(this, this->current_time); } @@ -1469,12 +1481,14 @@ static void media_asha_cb(struct spa_source *source) } } -static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, +static int do_start_transport(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; - spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); + this->transport_started = true; + if (this->transport->iso_io) + spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); return 0; } @@ -1610,10 +1624,8 @@ static int transport_start(struct impl *this) this->flush_pending = false; this->iso_pending = false; - this->transport_started = true; + spa_loop_locked(this->data_loop, do_start_transport, 0, NULL, 0, this); - if (this->transport->iso_io) - spa_loop_locked(this->data_loop, do_start_iso_io, 0, NULL, 0, this); if (is_asha) { struct spa_bt_asha *asha = this->asha; @@ -2331,6 +2343,8 @@ static int impl_node_process(void *object) setup_matching(this); + media_iso_rate_match(this); + if (this->codec->kind == MEDIA_CODEC_ASHA && !this->asha->set_timer) { struct impl *other = find_other_asha(this); if (other && other->asha->ref_t0 != 0) { From d10249d0ce265266fc2bfd7edb71a02f04188826 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 17:13:31 +0300 Subject: [PATCH 0541/1014] bluez5: allow faster rate matching Bump up DLL maximum rate difference and reduce averaging time. --- spa/plugins/bluez5/media-sink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index d065592c9..2b35ecd5b 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -58,7 +58,7 @@ struct props { #define MIN_BUFFERS 3 #define MAX_BUFFERS 32 #define BUFFER_SIZE (8192*8) -#define RATE_CTL_DIFF_MAX 0.005 +#define RATE_CTL_DIFF_MAX 0.01 #define LATENCY_PERIOD (200 * SPA_NSEC_PER_MSEC) /* Wait for two cycles before trying to sync ISO. On start/driver reassign, @@ -1198,7 +1198,7 @@ static void media_iso_rate_match(struct impl *this) { struct spa_bt_iso_io *iso_io = this->transport ? this->transport->iso_io : NULL; struct port *port = &this->port; - const double period = 0.1 * SPA_NSEC_PER_SEC; + const double period = 0.05 * SPA_NSEC_PER_SEC; uint64_t ref_time; uint64_t duration_ns; double value, target, err, max_err; From 5e79d0fb01e5bcd672c1f2a7328454e2804ca4d6 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 17:56:44 +0300 Subject: [PATCH 0542/1014] bluez5: fix compilation and warnings --- spa/plugins/bluez5/bt-latency.h | 10 +++++----- spa/plugins/bluez5/media-sink.c | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spa/plugins/bluez5/bt-latency.h b/spa/plugins/bluez5/bt-latency.h index 56d517ab3..d8ed24571 100644 --- a/spa/plugins/bluez5/bt-latency.h +++ b/spa/plugins/bluez5/bt-latency.h @@ -106,9 +106,9 @@ static inline ssize_t spa_bt_send(int fd, const void *buf, size_t size, static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int fd, struct spa_log *log) { - struct { - struct cmsghdr cm; - char control[512]; + union { + char buf[CMSG_SPACE(32 * sizeof(struct scm_timestamping))]; + struct cmsghdr align; } control; if (!lat->enabled) @@ -122,8 +122,8 @@ static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int f struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), + .msg_control = control.buf, + .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping *tss = NULL; diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 2b35ecd5b..df6bfd7e4 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -646,7 +646,7 @@ static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) / port->current_format.info.raw.rate; } - if (this->process_rate_diff) + if (this->process_rate_diff > 0) t = (int64_t)(t / this->process_rate_diff); return this->process_time + t; @@ -689,7 +689,7 @@ static int setup_matching(struct impl *this) * different rate, we as follower must compensate. */ if (this->following && SPA_LIKELY(this->position && - this->position->clock.rate_diff)) + this->position->clock.rate_diff > 0)) port->rate_match->rate /= this->position->clock.rate_diff; SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->following); From a2ede934799e9c3776c1a0557e3e156d71c9f723 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 18:36:07 +0300 Subject: [PATCH 0543/1014] bluez5: report ISO latency correctly and refresh when transport starts --- spa/plugins/bluez5/media-sink.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index df6bfd7e4..f5708d080 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -495,7 +495,7 @@ static void set_latency(struct impl *this, bool emit_latency) port->latency.min_ns = port->latency.max_ns = delay; port->latency.min_rate = port->latency.max_rate = 0; - if (this->transport->iso_io) { + if (this->codec->kind == MEDIA_CODEC_BAP) { /* ISO has different delay */ port->latency.min_quantum = port->latency.max_quantum = 1.0f; } else { @@ -1649,6 +1649,7 @@ static int transport_start(struct impl *this) spa_list_append(&asha_sinks, &this->asha_link); } + set_latency(this, true); return 0; From d0680a2b3d85717089e0b8fefdbe2578b7c902c3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 13 Jun 2025 20:53:49 +0300 Subject: [PATCH 0544/1014] bluez5: support packet loss concealment in codecs LC3 and Opus have built-in support for packet loss concealment. Add codec interface for that, and implement for LC3. Extend media_codec interface so that packets not aligned with socket reads can be handled, as in HFP. This is required for correct sequence number counting, and for being able to run codec PLC *before* decoding the next correctly received packet. --- spa/plugins/bluez5/bap-codec-lc3.c | 19 +++++++++++++++++++ spa/plugins/bluez5/hfp-codec-lc3-a127.c | 16 ++++++++++++++++ spa/plugins/bluez5/hfp-codec-lc3-swb.c | 24 ++++++++++++++++++++++-- spa/plugins/bluez5/hfp-codec-msbc.c | 6 ++++-- spa/plugins/bluez5/media-codecs.h | 16 +++++++++++++++- 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 8e2183884..d43186ce9 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -1310,6 +1310,24 @@ static SPA_UNUSED int codec_decode(void *data, return consumed; } +static int codec_produce_plc(void *data, void *dst, size_t dst_size) +{ + struct impl *this = data; + int ich, res; + + if (dst_size < this->codesize) + return -EINVAL; + + for (ich = 0; ich < this->channels; ich++) { + uint8_t *out = (uint8_t *)dst + (ich * 4); + res = lc3_decode(this->dec[ich], NULL, 0, LC3_PCM_FORMAT_S24, out, this->channels); + if (SPA_UNLIKELY(res < 0)) + return -EINVAL; + } + + return this->codesize; +} + static int codec_reduce_bitpool(void *data) { return -ENOTSUP; @@ -1424,6 +1442,7 @@ const struct media_codec bap_codec_lc3 = { .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, + .produce_plc = codec_produce_plc, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, diff --git a/spa/plugins/bluez5/hfp-codec-lc3-a127.c b/spa/plugins/bluez5/hfp-codec-lc3-a127.c index e3f077930..703e153a8 100644 --- a/spa/plugins/bluez5/hfp-codec-lc3-a127.c +++ b/spa/plugins/bluez5/hfp-codec-lc3-a127.c @@ -213,6 +213,21 @@ static int codec_decode(void *data, return H2_PACKET_SIZE - 2; } +static int codec_produce_plc(void *data, void *dst, size_t dst_size) +{ + struct impl *this = data; + int res; + + if (dst_size < LC3_A127_BLOCK_SIZE) + return -EINVAL; + + res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1); + if (res != 1) + return -EINVAL; + + return LC3_A127_BLOCK_SIZE; +} + static void codec_set_log(struct spa_log *global_log) { log = global_log; @@ -233,6 +248,7 @@ static const struct media_codec hfp_codec_a127 = { .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, + .produce_plc = codec_produce_plc, .name = "lc3_a127", .description = "LC3-24kHz", }; diff --git a/spa/plugins/bluez5/hfp-codec-lc3-swb.c b/spa/plugins/bluez5/hfp-codec-lc3-swb.c index edc27e87a..685806286 100644 --- a/spa/plugins/bluez5/hfp-codec-lc3-swb.c +++ b/spa/plugins/bluez5/hfp-codec-lc3-swb.c @@ -195,6 +195,8 @@ static int codec_decode(void *data, int res; *dst_out = 0; + if (dst_size < LC3_SWB_BLOCK_SIZE) + return -EINVAL; if (!this->data) this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); @@ -205,14 +207,30 @@ static int codec_decode(void *data, this->data = NULL; if (res) { - h2_reader_init(&this->h2, true); - return -EINVAL; + /* fail decoding silently, so remainder of packet is processed */ + spa_log_debug(log, "decoding failed: %d", res); + return consumed; } *dst_out = LC3_SWB_BLOCK_SIZE; return consumed; } +static int codec_produce_plc(void *data, void *dst, size_t dst_size) +{ + struct impl *this = data; + int res; + + if (dst_size < LC3_SWB_BLOCK_SIZE) + return -EINVAL; + + res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1); + if (res != 1) + return -EINVAL; + + return LC3_SWB_BLOCK_SIZE; +} + static void codec_set_log(struct spa_log *global_log) { log = global_log; @@ -233,8 +251,10 @@ const struct media_codec hfp_codec_msbc = { .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, + .produce_plc = codec_produce_plc, .name = "lc3_swb", .description = "LC3-SWB", + .stream_pkt = true, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c index c4e9e94b2..2e6f838eb 100644 --- a/spa/plugins/bluez5/hfp-codec-msbc.c +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -193,8 +193,9 @@ static int codec_decode(void *data, this->data = NULL; if (res < 0) { - h2_reader_init(&this->h2, true); - return res; + /* fail decoding silently, so remainder of packet is processed */ + spa_log_debug(log_, "decoding failed: %d", res); + return consumed; } return consumed; @@ -222,6 +223,7 @@ const struct media_codec hfp_codec_msbc = { .decode = codec_decode, .name = "msbc", .description = "MSBC", + .stream_pkt = true, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/media-codecs.h b/spa/plugins/bluez5/media-codecs.h index 0030ee615..4d2827e40 100644 --- a/spa/plugins/bluez5/media-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -26,7 +26,7 @@ #define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 14 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 15 struct spa_bluez5_codec_a2dp { struct spa_interface iface; @@ -88,6 +88,10 @@ struct media_codec { const struct media_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ + const bool stream_pkt; /**< If true, socket data may contain multiple packets. + * After successful decode, start_decode() should be + * called again to parse the remaining data. */ + int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps, uint8_t *caps_size, struct spa_dict *settings, struct bap_codec_qos *qos); @@ -202,6 +206,16 @@ struct media_codec { void *dst, size_t dst_size, size_t *dst_out); + /** + * Generate audio data corresponding to one lost packet, using codec internal + * packet loss concealment. + * + * NULL if not available. + * + * \return number of bytes produced, or < 0 for error + */ + int (*produce_plc) (void *data, void *dst, size_t dst_size); + int (*reduce_bitpool) (void *data); int (*increase_bitpool) (void *data); From 90a1b35017ae5a5e8e7908378e3c6ee2a10078dc Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 13 Jun 2025 23:41:17 +0300 Subject: [PATCH 0545/1014] bluez5: media-source: support codec-provided packet loss concealment If packet sequence number jumps ahead, or we would underflow, use codec-provided packet loss concealment to produce some audio data. When we produce it during underflow, skip the corresponding number of sequence numbers of future packets. If codec doesn't have PLC, keep the previous behavior (pad with zeros, buffering pauses to wait for data). --- spa/plugins/bluez5/decode-buffer.h | 5 + spa/plugins/bluez5/media-source.c | 219 ++++++++++++++++++----------- 2 files changed, 141 insertions(+), 83 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 44bdc15d2..65e69c1ed 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -168,6 +168,11 @@ static inline void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer * return SPA_PTROFF(this->buffer_decoded, this->write_index, void); } +static inline size_t spa_bt_decode_buffer_get_size(struct spa_bt_decode_buffer *this) +{ + return this->write_index - this->read_index; +} + static inline void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size, uint64_t nsec) { int32_t remain; diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 6abafe054..ce33761a4 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -51,6 +51,8 @@ struct props { #define MAX_BUFFERS 32 +#define MAX_PLC_PACKETS 16 + struct buffer { uint32_t id; unsigned int outstanding:1; @@ -163,6 +165,9 @@ struct impl { uint64_t now; uint64_t sample_count; + int seqnum; + uint32_t plc_packets; + uint32_t errqueue_count; struct delay_info delay; @@ -451,38 +456,140 @@ again: return size_read; } +static int produce_plc_data(struct impl *this) +{ + struct port *port = &this->port; + uint32_t avail; + int res; + void *buf; + + if (!this->codec->produce_plc) + return -ENOTSUP; + + buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + res = this->codec->produce_plc(this->codec_data, buf, avail); + if (res <= 0) + return res; + + spa_bt_decode_buffer_write_packet(&port->buffer, res, 0); + + spa_log_debug(this->log, "%p: produced PLC audio, frames:%u", + this, (unsigned int)(res / port->frame_size)); + + this->plc_packets++; + return res; +} + static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, - uint8_t *dst, uint32_t dst_size) + uint8_t *dst, uint32_t dst_size, uint32_t *dst_out) { ssize_t processed; size_t written, avail; + size_t src_avail = src_size; + uint16_t seqnum = this->seqnum + 1; + + *dst_out = 0; if ((processed = this->codec->start_decode(this->codec_data, - src, src_size, NULL, NULL)) < 0) + src, src_avail, &seqnum, NULL)) < 0) return processed; - /* TODO: check seqnum and handle PLC */ - src += processed; - src_size -= processed; + src_avail -= processed; + + if (this->seqnum < 0) { + /* first packet */ + } else if (this->codec->stream_pkt && this->seqnum == seqnum) { + /* previous packet continues */ + } else { + uint16_t lost = seqnum - (uint16_t)(this->seqnum + 1); + if (lost) + spa_log_debug(this->log, "%p: lost packets:%u (%u -> %u)", + this, (unsigned int)lost, this->seqnum + 1, seqnum); + + if (this->plc_packets > MAX_PLC_PACKETS || lost > MAX_PLC_PACKETS) { + /* Don't try to compensate for too big skips */ + this->plc_packets = 0; + lost = 0; + } + + if (lost >= this->plc_packets) { + lost -= this->plc_packets; + } else { + /* We already produced PLC audio for this packet. However, this + * only occurs if we are underflowing, so we should retain this + * packet regardless and let rate matching take care of it. + */ + lost = 0; + } + + /* Pad with PLC audio for any missing packets */ + while (lost > 0 && produce_plc_data(this) > 0) + --lost; + + this->plc_packets = 0; + } /* decode */ avail = dst_size; do { written = 0; if ((processed = this->codec->decode(this->codec_data, - src, src_size, dst, avail, &written)) < 0) + src, src_avail, dst, avail, &written)) < 0) return processed; /* update source and dest pointers */ spa_return_val_if_fail (avail > written, -ENOSPC); - src_size -= processed; + src_avail -= processed; src += processed; avail -= written; dst += written; - } while (src_size && (processed || written)); + } while (src_avail && (processed || written) && !this->codec->stream_pkt); - return dst_size - avail; + this->seqnum = seqnum; + + *dst_out = dst_size - avail; + return src_size - src_avail; +} + +static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now) +{ + struct port *port = &this->port; + uint32_t decoded; + + spa_log_trace(this->log, "read socket data size:%d", src_size); + + do { + int32_t consumed; + uint32_t avail; + void *buf; + uint64_t dt; + + buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); + + consumed = decode_data(this, src, src_size, buf, avail, &decoded); + if (consumed < 0) { + spa_log_debug(this->log, "%p: failed to decode data: %d", this, consumed); + return; + } + + src = SPA_PTROFF(src, consumed, void); + src_size -= consumed; + + /* discard when not started */ + if (this->started) + spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); + + if (decoded) { + dt = now - this->now; + this->now = now; + spa_log_trace(this->log, "decoded socket data seq:%u size:%d frames:%d dt:%d dms", + (unsigned int)this->seqnum, (int)decoded, (int)decoded/port->frame_size, + (int)(dt / 100000)); + } else { + spa_log_trace(this->log, "no decoded socket data"); + } + } while (this->codec->stream_pkt && src_size && decoded); } static void handle_errqueue(struct impl *this) @@ -506,11 +613,7 @@ static void handle_errqueue(struct impl *this) static void media_on_ready_read(struct spa_source *source) { struct impl *this = source->data; - struct port *port = &this->port; - void *buf; - int32_t size_read, decoded; - uint32_t avail; - uint64_t dt; + int32_t size_read; uint64_t now = 0; /* make sure the source is an input */ @@ -547,32 +650,7 @@ static void media_on_ready_read(struct spa_source *source) this->codec_props_changed = false; } - /* decode to buffer */ - buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); - spa_log_trace(this->log, "read socket data size:%d, avail:%d", size_read, avail); - decoded = decode_data(this, this->buffer_read, size_read, buf, avail); - if (decoded < 0) { - spa_log_debug(this->log, "failed to decode data: %d", decoded); - return; - } - if (decoded == 0) { - spa_log_trace(this->log, "no decoded socket data"); - return; - } - - /* discard when not started */ - if (!this->started) - return; - - spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); - - dt = now - this->now; - this->now = now; - - spa_log_trace(this->log, "decoded socket data size:%d frames:%d dt:%d dms", - (int)decoded, (int)decoded/port->frame_size, - (int)(dt / 100000)); - + add_data(this, this->buffer_read, size_read, now); return; stop: @@ -586,11 +664,6 @@ stop: static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, uint64_t now) { struct impl *this = userdata; - struct port *port = &this->port; - void *buf; - int32_t decoded; - uint32_t avail; - uint64_t dt; if (this->transport == NULL) { spa_log_debug(this->log, "no transport, stop reading"); @@ -600,32 +673,7 @@ static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, u if (size_read == 0) return 0; - /* decode to buffer */ - buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); - spa_log_trace(this->log, "read socket data size:%d, avail:%d", size_read, avail); - decoded = decode_data(this, buffer_read, size_read, buf, avail); - if (decoded < 0) { - spa_log_debug(this->log, "failed to decode data: %d", decoded); - return 0; - } - if (decoded == 0) { - spa_log_trace(this->log, "no decoded socket data"); - return 0; - } - - /* discard when not started */ - if (!this->started) - return 0; - - spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); - - dt = now - this->now; - this->now = now; - - spa_log_trace(this->log, "decoded socket data size:%d frames:%d dt:%d dms", - (int)decoded, (int)decoded/port->frame_size, - (int)(dt / 100000)); - + add_data(this, buffer_read, size_read, now); return 0; stop: @@ -851,6 +899,8 @@ static int transport_start(struct impl *this) this->sample_count = 0; this->errqueue_count = 0; + this->seqnum = -1; + this->io_error = false; if (this->codec->kind != MEDIA_CODEC_HFP) { @@ -1603,40 +1653,44 @@ static void process_buffering(struct impl *this) struct port *port = &this->port; uint32_t duration; const uint32_t samples = get_samples(this, &duration); + uint32_t data_size = samples * port->frame_size; uint32_t avail; - void *buf; update_target_latency(this); + if (samples > this->quantum_limit) + return; + + /* Produce PLC data if possible to avoid underrun */ + while (spa_bt_decode_buffer_get_size(&port->buffer) < data_size) { + if (produce_plc_data(this) <= 0) + break; + } + spa_bt_decode_buffer_process(&port->buffer, samples, duration, this->position ? this->position->clock.rate_diff : 1.0, this->position ? this->position->clock.next_nsec : 0); setup_matching(this); - buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); - /* copy data to buffers */ if (!spa_list_is_empty(&port->free)) { struct buffer *buffer; struct spa_data *datas; - uint32_t data_size; + void *buf; buffer = spa_list_first(&port->free, struct buffer, link); datas = buffer->buf->datas; - data_size = samples * port->frame_size; - WARN_ONCE(datas[0].maxsize < data_size && !this->following, this->log, "source buffer too small (%u < %u)", datas[0].maxsize, data_size); data_size = SPA_MIN(data_size, SPA_ROUND_DOWN(datas[0].maxsize, port->frame_size)); + buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); avail = SPA_MIN(avail, data_size); - spa_bt_decode_buffer_read(&port->buffer, avail); - spa_list_remove(&buffer->link); spa_log_trace(this->log, "dequeue %d", buffer->id); @@ -1653,10 +1707,9 @@ static void process_buffering(struct impl *this) memcpy(datas[0].data, buf, avail); - /* pad with silence - * - * TODO: should do PLC instead - */ + spa_bt_decode_buffer_read(&port->buffer, avail); + + /* Pad with silence, if PLC failed to produce enough */ if (avail < data_size) memset(SPA_PTROFF(datas[0].data, avail, void), 0, data_size - avail); From 87843366cecc935ede11bda6e23fada33b4fde23 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 18:52:55 +0300 Subject: [PATCH 0546/1014] bluez5: add PLC for MSBC using spandsp Use spandsp as optional dependency for MSBC codec, for providing PLC. --- meson_options.txt | 4 ++++ spa/meson.build | 3 +++ spa/plugins/bluez5/hfp-codec-msbc.c | 35 ++++++++++++++++++++++++++--- spa/plugins/bluez5/meson.build | 2 +- spa/plugins/bluez5/plc.h | 26 +++++++++++++++++++++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 spa/plugins/bluez5/plc.h diff --git a/meson_options.txt b/meson_options.txt index cd2c839f7..e55fe22a1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -153,6 +153,10 @@ option('bluez5-codec-g722', description: 'Enable G722 open source codec implementation', type: 'feature', value: 'auto') +option('bluez5-plc-spandsp', + description: 'Enable SpanDSP for packet loss concealment', + type: 'feature', + value: 'auto') option('control', description: 'Enable control spa plugin integration', type: 'feature', diff --git a/spa/meson.build b/spa/meson.build index 637aeb8d4..1f749757c 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -111,6 +111,9 @@ if get_option('spa-plugins').allowed() endif g722_codec_option = get_option('bluez5-codec-g722') summary({'G722': g722_codec_option.allowed()}, bool_yn: true, section: 'Bluetooth audio codecs') + + spandsp_dep = dependency('spandsp', required : get_option('bluez5-plc-spandsp')) + cdata.set('HAVE_SPANDSP', spandsp_dep.found()) endif have_vulkan = false diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c index 2e6f838eb..d2aabe2b0 100644 --- a/spa/plugins/bluez5/hfp-codec-msbc.c +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -3,6 +3,8 @@ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include #include #include @@ -24,10 +26,12 @@ #include "media-codecs.h" #include "hfp-h2.h" +#include "plc.h" + #define MSBC_BLOCK_SIZE 240 -static struct spa_log *log; +static struct spa_log *log_; struct impl { sbc_t msbc; @@ -36,6 +40,8 @@ struct impl { void *data; size_t avail; + + plc_state_t *plc; }; static int codec_enum_config(const struct media_codec *codec, uint32_t flags, @@ -104,6 +110,10 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, h2_reader_init(&this->h2, true); + this->plc = plc_init(NULL); + if (!this->plc) + return NULL; + return spa_steal_ptr(this); } @@ -112,6 +122,7 @@ static void codec_deinit(void *data) struct impl *this = data; sbc_finish(&this->msbc); + plc_free(this->plc); free(this); } @@ -198,13 +209,30 @@ static int codec_decode(void *data, return consumed; } + plc_rx(this->plc, dst, *dst_out / sizeof(int16_t)); + return consumed; } +static int codec_produce_plc(void *data, void *dst, size_t dst_size) +{ + struct impl *this = data; + int res; + + if (dst_size < MSBC_BLOCK_SIZE) + return -EINVAL; + + res = plc_fillin(this->plc, dst, MSBC_BLOCK_SIZE / sizeof(int16_t)); + if (res < 0) + return res; + + return MSBC_BLOCK_SIZE; +} + static void codec_set_log(struct spa_log *global_log) { - log = global_log; - spa_log_topic_init(log, &codec_plugin_log_topic); + log_ = global_log; + spa_log_topic_init(log_, &codec_plugin_log_topic); } const struct media_codec hfp_codec_msbc = { @@ -221,6 +249,7 @@ const struct media_codec hfp_codec_msbc = { .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, + .produce_plc = codec_produce_plc, .name = "msbc", .description = "MSBC", .stream_pkt = true, diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index bf9c6374b..01c5f3ac1 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -110,7 +110,7 @@ bluez_codec_hfp_msbc = shared_library('spa-codec-bluez5-hfp-msbc', [ 'hfp-codec-msbc.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, - dependencies : [ spa_dep, sbc_dep ], + dependencies : [ spa_dep, sbc_dep, spandsp_dep ], install : true, install_dir : spa_plugindir / 'bluez5') diff --git a/spa/plugins/bluez5/plc.h b/spa/plugins/bluez5/plc.h new file mode 100644 index 000000000..7ad23697d --- /dev/null +++ b/spa/plugins/bluez5/plc.h @@ -0,0 +1,26 @@ +/* Spa PLC */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_PLC_H +#define SPA_BLUEZ5_PLC_H + +#include +#include +#include + +#ifdef HAVE_SPANDSP +#include +#else +typedef struct { char dummy; } plc_state_t; +static inline int plc_rx(plc_state_t *s, int16_t *data, int len) { return -ENOTSUP; } +static inline int plc_fillin(plc_state_t *s, int16_t *data, int len) { return -ENOTSUP; } +static inline plc_state_t *plc_init(plc_state_t *s) +{ + static plc_state_t state; + return &state; +} +static inline int plc_free(plc_state_t *s) { return 0; } +#endif + +#endif From 02d5d9bc1fcb60feac964b41fa4cbea653c02823 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 22 Jun 2025 18:58:45 +0300 Subject: [PATCH 0547/1014] bluez5: sco-io: remove unnecessary variable --- spa/plugins/bluez5/sco-io.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 94e2e08b8..54047a51f 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -53,8 +53,6 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sco-io"); struct spa_bt_sco_io { - bool started; - uint8_t read_buffer[MAX_MTU]; size_t read_size; @@ -125,10 +123,8 @@ read_done: return; stop: - if (io->source.loop) { + if (io->source.loop) spa_loop_remove_source(io->data_loop, &io->source); - io->started = false; - } } static int write_packets(struct spa_bt_sco_io *io, const uint8_t **buf, size_t *size, size_t packet_size) @@ -260,8 +256,6 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s io->source.rmask = 0; spa_loop_add_source(io->data_loop, &io->source); - io->started = true; - return io; } @@ -282,10 +276,8 @@ static int do_remove_source(struct spa_loop *loop, void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) { - if (io->started) - spa_loop_locked(io->data_loop, do_remove_source, 0, NULL, 0, io); - - io->started = false; + spa_log_debug(io->log, "%p: destroy", io); + spa_loop_locked(io->data_loop, do_remove_source, 0, NULL, 0, io); free(io); } From ff81fc9f7b29bd85111607bbe4dd3ab56517863a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 27 Jun 2025 21:30:55 +0300 Subject: [PATCH 0548/1014] bluez5: fix ISO sequence numbering Pass zero-length packets to the codec. BAP/ISO may use these to indicate missing data. Fix A2DP codecs to not parse input with spa_return_val_if_fail, that's meant for assertions. Just return -EINVAL directly, it's normal that input data may contain garbage. --- spa/plugins/bluez5/a2dp-codec-aac.c | 3 ++- spa/plugins/bluez5/a2dp-codec-aptx.c | 3 ++- spa/plugins/bluez5/a2dp-codec-faststream.c | 3 +++ spa/plugins/bluez5/a2dp-codec-lc3plus.c | 3 ++- spa/plugins/bluez5/a2dp-codec-ldac.c | 3 ++- spa/plugins/bluez5/a2dp-codec-opus-g.c | 3 ++- spa/plugins/bluez5/a2dp-codec-opus.c | 3 ++- spa/plugins/bluez5/a2dp-codec-sbc.c | 3 ++- spa/plugins/bluez5/bap-codec-lc3.c | 16 ++++++++++++++-- spa/plugins/bluez5/media-source.c | 2 -- 10 files changed, 31 insertions(+), 11 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 4e50cae45..505066c3e 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -581,7 +581,8 @@ static int codec_start_decode (void *data, const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index d74238378..7ebdc99d8 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -426,7 +426,8 @@ static int codec_start_decode (void *data, const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header); - spa_return_val_if_fail(src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index 1f1875171..5b78fb38e 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -301,6 +301,9 @@ static int codec_encode(void *data, static SPA_UNUSED int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { + if (!src_size) + return -EINVAL; + return 0; } diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index 808a6b721..c5a191e29 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -637,7 +637,8 @@ static SPA_UNUSED int codec_start_decode (void *data, const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index 4ae86b571..cf1526faf 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -667,7 +667,8 @@ static int codec_start_decode (void *data, const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), struct rtp_payload); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-opus-g.c b/spa/plugins/bluez5/a2dp-codec-opus-g.c index 018a5a9ce..2fe34bf5e 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus-g.c +++ b/spa/plugins/bluez5/a2dp-codec-opus-g.c @@ -444,7 +444,8 @@ static int codec_start_decode (void *data, const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 42cb1caba..efcc10023 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -1188,7 +1188,8 @@ static SPA_UNUSED int codec_start_decode (void *data, const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 5c178c87b..f2bc3e7ef 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -599,7 +599,8 @@ static int codec_start_decode (void *data, const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); - spa_return_val_if_fail (src_size > header_size, -EINVAL); + if (src_size <= header_size) + return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index d43186ce9..f5bc7e111 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -37,6 +37,8 @@ struct impl { int framelen; int samples; unsigned int codesize; + + uint16_t seqnum; }; struct settings { @@ -1274,13 +1276,23 @@ static int codec_encode(void *data, return processed; } -static SPA_UNUSED int codec_start_decode (void *data, +static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { + struct impl *this = data; + + /* packets come from controller, so also invalid ones bump seqnum */ + this->seqnum++; + + if (!src_size) + return -EINVAL; + + if (*seqnum) + *seqnum = this->seqnum; return 0; } -static SPA_UNUSED int codec_decode(void *data, +static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index ce33761a4..d2362c827 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -637,8 +637,6 @@ static void media_on_ready_read(struct spa_source *source) /* read */ size_read = read_data (this, &now); - if (size_read == 0) - return; if (size_read < 0) { spa_log_error(this->log, "failed to read data: %s", spa_strerror(size_read)); goto stop; From cee0c39b00b4c2718c106fb8c4db52efa0185ffb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 6 Jul 2025 18:22:33 +0300 Subject: [PATCH 0549/1014] bluez5: fix decode-buffer buffering threshold The minimum is the number of requested samples, not duration, which can be different when resampling. --- spa/plugins/bluez5/decode-buffer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 65e69c1ed..91ffdac78 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -259,7 +259,7 @@ static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *thi spa_log_trace(this->log, "%p buffering size:%d", this, (int)size); - if (size >= SPA_MAX((int)duration, target)) + if (size >= SPA_MAX((int)samples, target)) this->buffering = false; else return; @@ -275,7 +275,7 @@ static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *thi spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration); - if (this->level > SPA_MAX(4 * target, 3*(int32_t)duration) && + if (this->level > SPA_MAX(4 * target, 3*(int32_t)samples) && avail > data_size) { /* Lagging too much: drop data */ uint32_t size = SPA_MIN(avail - data_size, @@ -307,7 +307,7 @@ static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *thi this->level, target, duration, avg_period, BUFFERING_RATE_DIFF_MAX); - this->level -= duration; + this->level -= samples; spa_bt_decode_buffer_get_read(this, &avail); if (avail < data_size) { From f073a1a59b2849592482bb508a8a16e67f1d1fbb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 14 Jun 2025 21:01:39 +0300 Subject: [PATCH 0550/1014] ci: add spandsp & liblc3 --- .gitlab-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 75427c6fe..efedd6d94 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-06-28.0' + FDO_DISTRIBUTION_TAG: '2025-07-11.0' FDO_DISTRIBUTION_VERSION: '41' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -55,6 +55,7 @@ include: gstreamer1-plugins-base-devel jack-audio-connection-kit-devel libasan + liblc3-devel libcanberra-devel libebur128-devel libffado-devel @@ -75,6 +76,7 @@ include: sbc-devel ShellCheck SDL2-devel + spandsp-devel systemd-devel vulkan-loader-devel webrtc-audio-processing-devel @@ -436,13 +438,12 @@ build_all: extends: - .build_on_fedora variables: - # Fedora doesn't have libfreeaptx, lc3plus, lc3, or roc + # Fedora doesn't have libfreeaptx, lc3plus, or roc # libcamera has no stable API, so let's not chase that target MESON_OPTIONS: >- -Dauto_features=enabled -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled - -Dbluez5-codec-lc3=disabled -Dbluez5-codec-ldac-dec=disabled -Droc=disabled -Dlibcamera=disabled From 5f4f4b5dd31130501ea3ce3198065c0744cf5fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 16:44:31 +0200 Subject: [PATCH 0551/1014] spa: libcamera: use lock when acquiring `CameraManager` Make `libcamera_manager_acquire()` thread safe by locking a mutex when the `CameraManager` instance is created and started. --- spa/plugins/libcamera/libcamera-manager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index db2c73d54..648ea1fe5 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -82,10 +82,13 @@ struct impl { } -static std::weak_ptr global_manager; - std::shared_ptr libcamera_manager_acquire(int& res) { + static std::weak_ptr global_manager; + static std::mutex lock; + + std::lock_guard guard(lock); + if (auto manager = global_manager.lock()) return manager; From 5a9cdd724f693ce5c99b5e071a7192d844991abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 17:45:50 +0200 Subject: [PATCH 0552/1014] spa: libcamera: clean up includes Remove some unnecessarily includes. --- spa/plugins/libcamera/libcamera-device.cpp | 5 +---- spa/plugins/libcamera/libcamera-manager.cpp | 10 +--------- spa/plugins/libcamera/libcamera-source.cpp | 3 --- spa/plugins/libcamera/libcamera-utils.cpp | 3 --- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 5eb46a1ae..d744aed58 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -4,10 +4,7 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include "config.h" - -#include - +#include #include #include diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index 648ea1fe5..d81d81540 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -2,13 +2,7 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include -#include -#include -#include -#include -#include -#include +#include #include #include #include @@ -17,8 +11,6 @@ #include #include -#include - using namespace libcamera; #include diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 5523f7c8b..52fcf4870 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -4,9 +4,6 @@ /* SPDX-License-Identifier: MIT */ #include -#include -#include -#include #include #include diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index a2d69243a..6486d501c 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -6,11 +6,8 @@ #include #include -#include -#include #include #include -#include #include #include From 1a1cf55efbfa1fdee03529188aa31cc215594b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:17:38 +0200 Subject: [PATCH 0553/1014] spa: libcamera: inline `libcamera-utils.cpp` The file is not useful without `libcamera-source.cpp` because it uses symbols only defined there. And being a non-self-contained source file, it also breaks clangd. So move its contents directly to `libcamera-source.cpp`. This makes the file about 2200 lines long, but I feel that is still manageable (and it is by far not the longest). --- spa/plugins/libcamera/libcamera-source.cpp | 1157 ++++++++++++++++++- spa/plugins/libcamera/libcamera-utils.cpp | 1167 -------------------- 2 files changed, 1156 insertions(+), 1168 deletions(-) delete mode 100644 spa/plugins/libcamera/libcamera-utils.cpp diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 52fcf4870..d23b033f0 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1,12 +1,15 @@ /* Spa libcamera source */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* @author Raghavendra Rao Sidlagatta */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include +#include + #include #include #include @@ -29,6 +32,7 @@ #include #include +#include #include #include #include @@ -177,7 +181,1158 @@ struct impl { #define GET_OUT_PORT(impl,p) (&impl->out_ports[p]) #define GET_PORT(impl,d,p) GET_OUT_PORT(impl,p) -#include "libcamera-utils.cpp" +static void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) +{ + /* Libcamera recommends cameras default to manual focus mode, but we don't + * expose any focus controls. So, specifically enable autofocus on + * cameras which support it. */ + auto af_it = ctrl_infos.find(libcamera::controls::AF_MODE); + if (af_it != ctrl_infos.end()) { + const ControlInfo &ctrl_info = af_it->second; + auto is_af_continuous = [](const ControlValue &value) { + return value.get() == libcamera::controls::AfModeContinuous; + }; + if (std::any_of(ctrl_info.values().begin(), + ctrl_info.values().end(), is_af_continuous)) { + ctrls.set(libcamera::controls::AF_MODE, + libcamera::controls::AfModeContinuous); + } + } + + auto ae_it = ctrl_infos.find(libcamera::controls::AE_ENABLE); + if (ae_it != ctrl_infos.end()) { + ctrls.set(libcamera::controls::AE_ENABLE, true); + } +} + +int spa_libcamera_open(struct impl *impl) +{ + if (impl->acquired) + return 0; + + spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); + impl->camera->acquire(); + + impl->allocator = new FrameBufferAllocator(impl->camera); + + const ControlInfoMap &controls = impl->camera->controls(); + setup_initial_controls(controls, impl->initial_controls); + + impl->acquired = true; + return 0; +} + +int spa_libcamera_close(struct impl *impl) +{ + struct port *port = &impl->out_ports[0]; + if (!impl->acquired) + return 0; + if (impl->active || port->current_format) + return 0; + + spa_log_info(impl->log, "close camera %s", impl->device_id.c_str()); + delete impl->allocator; + impl->allocator = nullptr; + + impl->camera->release(); + + impl->acquired = false; + return 0; +} + +static void spa_libcamera_get_config(struct impl *impl) +{ + if (impl->config) + return; + + impl->config = impl->camera->generateConfiguration({ StreamRole::VideoRecording }); +} + +static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) +{ + struct buffer *b = &port->buffers[buffer_id]; + int res; + + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) + return 0; + + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING); + + if (buffer_id >= impl->requestPool.size()) { + spa_log_warn(impl->log, "invalid buffer_id %u >= %zu", + buffer_id, impl->requestPool.size()); + return -EINVAL; + } + Request *request = impl->requestPool[buffer_id].get(); + Stream *stream = port->streamConfig.stream(); + FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); + if ((res = request->addBuffer(stream, buffer)) < 0) { + spa_log_warn(impl->log, "can't add buffer %u for request: %s", + buffer_id, spa_strerror(res)); + return -ENOMEM; + } + if (!impl->active) { + impl->pendingRequests.push_back(request); + return 0; + } else { + request->controls().merge(impl->ctrls); + impl->ctrls.clear(); + if ((res = impl->camera->queueRequest(request)) < 0) { + spa_log_warn(impl->log, "can't queue buffer %u: %s", + buffer_id, spa_strerror(res)); + return res == -EACCES ? -EBUSY : res; + } + } + return 0; +} + +static int allocBuffers(struct impl *impl, struct port *port, unsigned int count) +{ + int res; + + if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) + return res; + + for (unsigned int i = 0; i < count; i++) { + std::unique_ptr request = impl->camera->createRequest(i); + if (!request) { + impl->requestPool.clear(); + return -ENOMEM; + } + impl->requestPool.push_back(std::move(request)); + } + + /* Some devices require data for each output video frame to be + * placed in discontiguous memory buffers. In such cases, one + * video frame has to be addressed using more than one memory. + * address. Therefore, need calculate the number of discontiguous + * memory and allocate the specified amount of memory */ + Stream *stream = impl->config->at(0).stream(); + const std::vector> &bufs = + impl->allocator->buffers(stream); + const std::vector &planes = bufs[0]->planes(); + int fd = -1; + uint32_t buffers_blocks = 0; + + for (const FrameBuffer::Plane &plane : planes) { + const int current_fd = plane.fd.get(); + if (current_fd >= 0 && current_fd != fd) { + buffers_blocks += 1; + fd = current_fd; + } + } + + if (buffers_blocks > 0) { + port->buffers_blocks = buffers_blocks; + } + return res; +} + +static void freeBuffers(struct impl *impl, struct port *port) +{ + impl->pendingRequests.clear(); + impl->requestPool.clear(); + impl->allocator->free(port->streamConfig.stream()); +} + +static int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) +{ + uint32_t i; + + if (port->n_buffers == 0) + return 0; + + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b; + struct spa_data *d; + + b = &port->buffers[i]; + d = b->outbuf->datas; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { + spa_log_debug(impl->log, "queueing outstanding buffer %p", b); + spa_libcamera_buffer_recycle(impl, port, i); + } + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), + d[0].maxsize - d[0].mapoffset); + } + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { + close(d[0].fd); + } + d[0].type = SPA_ID_INVALID; + } + + freeBuffers(impl, port); + port->n_buffers = 0; + port->ring = SPA_RINGBUFFER_INIT(); + + return 0; +} + +struct format_info { + PixelFormat pix; + uint32_t format; + uint32_t media_type; + uint32_t media_subtype; +}; + +#define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } +static const struct format_info format_info[] = { + /* RGB formats */ + MAKE_FMT(formats::RGB565, RGB16, video, raw), + MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), + MAKE_FMT(formats::RGB888, BGR, video, raw), + MAKE_FMT(formats::BGR888, RGB, video, raw), + MAKE_FMT(formats::XRGB8888, BGRx, video, raw), + MAKE_FMT(formats::XBGR8888, RGBx, video, raw), + MAKE_FMT(formats::RGBX8888, xBGR, video, raw), + MAKE_FMT(formats::BGRX8888, xRGB, video, raw), + MAKE_FMT(formats::ARGB8888, BGRA, video, raw), + MAKE_FMT(formats::ABGR8888, RGBA, video, raw), + MAKE_FMT(formats::RGBA8888, ABGR, video, raw), + MAKE_FMT(formats::BGRA8888, ARGB, video, raw), + + MAKE_FMT(formats::YUYV, YUY2, video, raw), + MAKE_FMT(formats::YVYU, YVYU, video, raw), + MAKE_FMT(formats::UYVY, UYVY, video, raw), + MAKE_FMT(formats::VYUY, VYUY, video, raw), + + MAKE_FMT(formats::NV12, NV12, video, raw), + MAKE_FMT(formats::NV21, NV21, video, raw), + MAKE_FMT(formats::NV16, NV16, video, raw), + MAKE_FMT(formats::NV61, NV61, video, raw), + MAKE_FMT(formats::NV24, NV24, video, raw), + + MAKE_FMT(formats::YUV420, I420, video, raw), + MAKE_FMT(formats::YVU420, YV12, video, raw), + MAKE_FMT(formats::YUV422, Y42B, video, raw), + + MAKE_FMT(formats::MJPEG, ENCODED, video, mjpg), +#undef MAKE_FMT +}; + +static const struct format_info *video_format_to_info(const PixelFormat &pix) { + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (format_info[i].pix == pix) + return &format_info[i]; + } + return NULL; +} + +static const struct format_info *find_format_info_by_media_type(uint32_t type, + uint32_t subtype, uint32_t format, int startidx) +{ + size_t i; + + for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { + if ((format_info[i].media_type == type) && + (format_info[i].media_subtype == subtype) && + (format == 0 || format_info[i].format == format)) + return &format_info[i]; + } + return NULL; +} + +static int score_size(Size &a, Size &b) +{ + int x, y; + x = (int)a.width - (int)b.width; + y = (int)a.height - (int)b.height; + return x * x + y * y; +} + +static void +parse_colorimetry(const ColorSpace& colorspace, + struct spa_video_colorimetry *colorimetry) +{ + switch (colorspace.range) { + case ColorSpace::Range::Full: + colorimetry->range = SPA_VIDEO_COLOR_RANGE_0_255; + break; + case ColorSpace::Range::Limited: + colorimetry->range = SPA_VIDEO_COLOR_RANGE_16_235; + break; + } + + switch (colorspace.ycbcrEncoding) { + case ColorSpace::YcbcrEncoding::None: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_RGB; + break; + case ColorSpace::YcbcrEncoding::Rec601: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT601; + break; + case ColorSpace::YcbcrEncoding::Rec709: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT709; + break; + case ColorSpace::YcbcrEncoding::Rec2020: + colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; + break; + } + + switch (colorspace.transferFunction) { + case ColorSpace::TransferFunction::Linear: + colorimetry->transfer = SPA_VIDEO_TRANSFER_UNKNOWN; + break; + case ColorSpace::TransferFunction::Srgb: + colorimetry->transfer = SPA_VIDEO_TRANSFER_SRGB; + break; + case ColorSpace::TransferFunction::Rec709: + colorimetry->transfer = SPA_VIDEO_TRANSFER_BT709; + break; + } + + switch (colorspace.primaries) { + case ColorSpace::Primaries::Raw: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; + break; + case ColorSpace::Primaries::Smpte170m: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; + break; + case ColorSpace::Primaries::Rec709: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; + break; + case ColorSpace::Primaries::Rec2020: + colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; + break; + } +} + +static int +spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, + uint32_t start, uint32_t num, const struct spa_pod *filter) +{ + int res; + const struct format_info *info; + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[2]; + struct spa_result_node_params result; + struct spa_video_colorimetry colorimetry = {}; + struct spa_pod *fmt; + uint32_t i, count = 0, num_sizes; + PixelFormat format; + Size frameSize; + SizeRange sizeRange = SizeRange(); + + spa_libcamera_get_config(impl); + + const StreamConfiguration& streamConfig = impl->config->at(0); + const StreamFormats &formats = streamConfig.formats(); + + if (streamConfig.colorSpace) + parse_colorimetry(*streamConfig.colorSpace, &colorimetry); + + result.id = SPA_PARAM_EnumFormat; + result.next = start; + + if (result.next == 0) { + port->fmt_index = 0; + port->size_index = 0; + } +next: + result.index = result.next++; + +next_fmt: + if (port->fmt_index >= formats.pixelformats().size()) + goto enum_end; + + format = formats.pixelformats()[port->fmt_index]; + + spa_log_debug(impl->log, "format: %s", format.toString().c_str()); + + info = video_format_to_info(format); + if (info == NULL) { + spa_log_debug(impl->log, "unknown format"); + port->fmt_index++; + goto next_fmt; + } + + num_sizes = formats.sizes(format).size(); + if (num_sizes > 0 && port->size_index <= num_sizes) { + if (port->size_index == 0) { + Size wanted = Size(640, 480), test; + int score, best = INT_MAX; + for (i = 0; i < num_sizes; i++) { + test = formats.sizes(format)[i]; + score = score_size(wanted, test); + if (score < best) { + best = score; + frameSize = test; + } + } + } + else { + frameSize = formats.sizes(format)[port->size_index - 1]; + } + } else if (port->size_index < 1) { + sizeRange = formats.range(format); + if (sizeRange.hStep == 0 || sizeRange.vStep == 0) { + port->size_index = 0; + port->fmt_index++; + goto next_fmt; + } + } else { + port->size_index = 0; + port->fmt_index++; + goto next_fmt; + } + port->size_index++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&b, + SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), + 0); + + if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_id(&b, info->format); + } + if (info->pix.modifier()) { + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_modifier, 0); + spa_pod_builder_long(&b, info->pix.modifier()); + } + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); + + if (sizeRange.hStep != 0 && sizeRange.vStep != 0) { + spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Step, 0); + spa_pod_builder_frame(&b, &f[1]); + spa_pod_builder_rectangle(&b, + sizeRange.min.width, + sizeRange.min.height); + spa_pod_builder_rectangle(&b, + sizeRange.min.width, + sizeRange.min.height); + spa_pod_builder_rectangle(&b, + sizeRange.max.width, + sizeRange.max.height); + spa_pod_builder_rectangle(&b, + sizeRange.hStep, + sizeRange.vStep); + spa_pod_builder_pop(&b, &f[1]); + + } else { + spa_pod_builder_rectangle(&b, frameSize.width, frameSize.height); + } + + if (streamConfig.colorSpace) { + spa_pod_builder_add(&b, + SPA_FORMAT_VIDEO_colorRange, + SPA_POD_Id(colorimetry.range), + SPA_FORMAT_VIDEO_colorMatrix, + SPA_POD_Id(colorimetry.matrix), + SPA_FORMAT_VIDEO_transferFunction, + SPA_POD_Id(colorimetry.transfer), + SPA_FORMAT_VIDEO_colorPrimaries, + SPA_POD_Id(colorimetry.primaries), 0); + } + + fmt = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); + + if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) + goto next; + + spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + enum_end: + res = 0; + return res; +} + +static int spa_libcamera_set_format(struct impl *impl, struct port *port, + struct spa_video_info *format, bool try_only) +{ + const struct format_info *info = NULL; + uint32_t video_format; + struct spa_rectangle *size = NULL; + struct spa_fraction *framerate = NULL; + CameraConfiguration::Status validation; + int res; + + switch (format->media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + video_format = format->info.raw.format; + size = &format->info.raw.size; + framerate = &format->info.raw.framerate; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_jpeg: + video_format = SPA_VIDEO_FORMAT_ENCODED; + size = &format->info.mjpg.size; + framerate = &format->info.mjpg.framerate; + break; + case SPA_MEDIA_SUBTYPE_h264: + video_format = SPA_VIDEO_FORMAT_ENCODED; + size = &format->info.h264.size; + framerate = &format->info.h264.framerate; + break; + default: + video_format = SPA_VIDEO_FORMAT_ENCODED; + break; + } + + info = find_format_info_by_media_type(format->media_type, + format->media_subtype, video_format, 0); + if (info == NULL || size == NULL || framerate == NULL) { + spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type, + format->media_subtype, video_format); + return -EINVAL; + } + StreamConfiguration& streamConfig = impl->config->at(0); + + streamConfig.pixelFormat = info->pix; + streamConfig.size.width = size->width; + streamConfig.size.height = size->height; + streamConfig.bufferCount = 8; + + validation = impl->config->validate(); + if (validation == CameraConfiguration::Invalid) + return -EINVAL; + + if (try_only) + return 0; + + if ((res = spa_libcamera_open(impl)) < 0) + return res; + + res = impl->camera->configure(impl->config.get()); + if (res != 0) + goto error; + + port->streamConfig = impl->config->at(0); + + if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) + goto error; + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; + port->info.flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS | + SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->info.rate = SPA_FRACTION(port->rate.num, port->rate.denom); + + return 0; +error: + spa_libcamera_close(impl); + return res; + +} + +static struct { + uint32_t id; + uint32_t spa_id; +} control_map[] = { + { libcamera::controls::BRIGHTNESS, SPA_PROP_brightness }, + { libcamera::controls::CONTRAST, SPA_PROP_contrast }, + { libcamera::controls::SATURATION, SPA_PROP_saturation }, + { libcamera::controls::EXPOSURE_TIME, SPA_PROP_exposure }, + { libcamera::controls::ANALOGUE_GAIN, SPA_PROP_gain }, + { libcamera::controls::SHARPNESS, SPA_PROP_sharpness }, +}; + +static uint32_t control_to_prop_id(struct impl *impl, uint32_t control_id) +{ + SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { + if (c->id == control_id) + return c->spa_id; + } + return SPA_PROP_START_CUSTOM + control_id; +} + +static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) +{ + SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { + if (c->spa_id == prop_id) + return c->id; + } + if (prop_id >= SPA_PROP_START_CUSTOM) + return prop_id - SPA_PROP_START_CUSTOM; + return SPA_ID_INVALID; +} + +static int +spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, + uint32_t start, uint32_t offset, uint32_t num, + const struct spa_pod *filter) +{ + const ControlInfoMap &info = impl->camera->controls(); + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[2]; + struct spa_result_node_params result; + struct spa_pod *ctrl; + uint32_t count = 0, skip, id; + int res; + const ControlId *ctrl_id; + ControlInfo ctrl_info; + + result.id = SPA_PARAM_PropInfo; + result.next = start; + + auto it = info.begin(); + for (skip = result.next - offset; skip; skip--) + it++; + + if (false) { +next: + it++; + } + result.index = result.next++; + if (it == info.end()) + goto enum_end; + + ctrl_id = it->first; + ctrl_info = it->second; + + id = control_to_prop_id(impl, ctrl_id->id()); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add(&b, + SPA_PROP_INFO_id, SPA_POD_Id(id), + SPA_PROP_INFO_description, SPA_POD_String(ctrl_id->name().c_str()), + 0); + + switch (ctrl_id->type()) { + case ControlTypeBool: { + bool def; + if (ctrl_info.def().isNone()) + def = ctrl_info.min().get(); + else + def = ctrl_info.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + def), + 0); + } break; + case ControlTypeFloat: { + float min = ctrl_info.min().get(); + float max = ctrl_info.max().get(); + float def; + + if (ctrl_info.def().isNone()) + def = (min + max) / 2; + else + def = ctrl_info.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + def, min, max), + 0); + } break; + case ControlTypeInteger32: { + int32_t min = ctrl_info.min().get(); + int32_t max = ctrl_info.max().get(); + int32_t def; + + if (ctrl_info.def().isNone()) + def = (min + max) / 2; + else + def = ctrl_info.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + def, min, max), + 0); + } break; + default: + goto next; + } + + ctrl = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); + + if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) + goto next; + + spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + +enum_end: + res = 0; + return res; +} + +struct val { + uint32_t type; + float f_val; + int32_t i_val; + bool b_val; + uint32_t id; +}; + +static int do_update_ctrls(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *impl = (struct impl *)user_data; + const struct val *d = (const struct val *)data; + switch (d->type) { + case ControlTypeBool: + impl->ctrls.set(d->id, d->b_val); + break; + case ControlTypeFloat: + impl->ctrls.set(d->id, d->f_val); + break; + case ControlTypeInteger32: + impl->ctrls.set(d->id, (int32_t)d->i_val); + break; + default: + break; + } + return 0; +} + +static int +spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) +{ + const ControlInfoMap &info = impl->camera->controls(); + const ControlId *ctrl_id; + int res; + struct val d; + uint32_t control_id; + + control_id = prop_id_to_control(impl, prop->key); + if (control_id == SPA_ID_INVALID) + return -ENOENT; + + auto v = info.idmap().find(control_id); + if (v == info.idmap().end()) + return -ENOENT; + + ctrl_id = v->second; + + d.type = ctrl_id->type(); + d.id = ctrl_id->id(); + + switch (d.type) { + case ControlTypeBool: + if ((res = spa_pod_get_bool(&prop->value, &d.b_val)) < 0) + goto done; + break; + case ControlTypeFloat: + if ((res = spa_pod_get_float(&prop->value, &d.f_val)) < 0) + goto done; + break; + case ControlTypeInteger32: + if ((res = spa_pod_get_int(&prop->value, &d.i_val)) < 0) + goto done; + break; + default: + res = -EINVAL; + goto done; + } + spa_loop_invoke(impl->data_loop, do_update_ctrls, 0, &d, sizeof(d), true, impl); + res = 0; +done: + return res; +} + + +static void libcamera_on_fd_events(struct spa_source *source) +{ + struct impl *impl = (struct impl*) source->data; + struct spa_io_buffers *io; + struct port *port = &impl->out_ports[0]; + uint32_t index, buffer_id; + struct buffer *b; + uint64_t cnt; + + if (source->rmask & SPA_IO_ERR) { + spa_log_error(impl->log, "libcamera %p: error %08x", impl, source->rmask); + if (impl->source.loop) + spa_loop_remove_source(impl->data_loop, &impl->source); + return; + } + + if (!(source->rmask & SPA_IO_IN)) { + spa_log_warn(impl->log, "libcamera %p: spurious wakeup %d", impl, source->rmask); + return; + } + + if (spa_system_eventfd_read(impl->system, impl->source.fd, &cnt) < 0) { + spa_log_error(impl->log, "Failed to read on event fd"); + return; + } + + if (spa_ringbuffer_get_read_index(&port->ring, &index) < 1) { + spa_log_error(impl->log, "nothing is queued"); + return; + } + buffer_id = port->ring_ids[index & MASK_BUFFERS]; + spa_ringbuffer_read_update(&port->ring, index + 1); + + b = &port->buffers[buffer_id]; + spa_list_append(&port->queue, &b->link); + + io = port->io; + if (io == NULL) { + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + } else if (io->status != SPA_STATUS_HAVE_DATA) { + if (io->buffer_id < port->n_buffers) + spa_libcamera_buffer_recycle(impl, port, io->buffer_id); + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace(impl->log, "libcamera %p: now queued %d", impl, b->id); + } + spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); +} + +static int spa_libcamera_use_buffers(struct impl *impl, struct port *port, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + return -ENOTSUP; +} + +static const struct { + Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */ + uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */ +} orientation_map[] = { + { Orientation::Rotate0, SPA_META_TRANSFORMATION_None }, + { Orientation::Rotate0Mirror, SPA_META_TRANSFORMATION_Flipped }, + { Orientation::Rotate90, SPA_META_TRANSFORMATION_270 }, + { Orientation::Rotate90Mirror, SPA_META_TRANSFORMATION_Flipped90 }, + { Orientation::Rotate180, SPA_META_TRANSFORMATION_180 }, + { Orientation::Rotate180Mirror, SPA_META_TRANSFORMATION_Flipped180 }, + { Orientation::Rotate270, SPA_META_TRANSFORMATION_90 }, + { Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 }, +}; + +static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation) +{ + for (const auto& t : orientation_map) { + if (t.libcamera_orientation == orientation) + return t.spa_transform_value; + } + return SPA_META_TRANSFORMATION_None; +} + +static int +mmap_init(struct impl *impl, struct port *port, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + unsigned int i, j; + struct spa_data *d; + Stream *stream = impl->config->at(0).stream(); + const std::vector> &bufs = + impl->allocator->buffers(stream); + + if (n_buffers > 0) { + if (bufs.size() != n_buffers) + return -EINVAL; + + d = buffers[0]->datas; + + if (d[0].type != SPA_ID_INVALID && + d[0].type & (1u << SPA_DATA_DmaBuf)) { + port->memtype = SPA_DATA_DmaBuf; + } else if (d[0].type != SPA_ID_INVALID && + d[0].type & (1u << SPA_DATA_MemFd)) { + port->memtype = SPA_DATA_MemFd; + } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { + port->memtype = SPA_DATA_MemPtr; + } else { + spa_log_error(impl->log, "can't use buffers of type %d", d[0].type); + return -EINVAL; + } + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + + if (buffers[i]->n_datas < 1) { + spa_log_error(impl->log, "invalid buffer data"); + return -EINVAL; + } + + b = &port->buffers[i]; + b->id = i; + b->outbuf = buffers[i]; + b->flags = BUFFER_FLAG_OUTSTANDING; + b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); + + b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( + buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform)); + if (b->videotransform) { + b->videotransform->transform = + libcamera_orientation_to_spa_transform_value(impl->config->orientation); + spa_log_debug(impl->log, "Setting videotransform for buffer %u to %u", + i, b->videotransform->transform); + + } + + d = buffers[i]->datas; + for(j = 0; j < buffers[i]->n_datas; ++j) { + d[j].type = port->memtype; + d[j].flags = SPA_DATA_FLAG_READABLE; + d[j].mapoffset = 0; + d[j].chunk->stride = port->streamConfig.stride; + d[j].chunk->flags = 0; + /* Update parameters according to the plane information */ + unsigned int numPlanes = bufs[i]->planes().size(); + if (buffers[i]->n_datas < numPlanes) { + if (j < buffers[i]->n_datas - 1) { + d[j].maxsize = bufs[i]->planes()[j].length; + d[j].chunk->offset = bufs[i]->planes()[j].offset; + d[j].chunk->size = bufs[i]->planes()[j].length; + } else { + d[j].chunk->offset = bufs[i]->planes()[j].offset; + for (uint8_t k = j; k < numPlanes; k++) { + d[j].maxsize += bufs[i]->planes()[k].length; + d[j].chunk->size += bufs[i]->planes()[k].length; + } + } + } else if (buffers[i]->n_datas == numPlanes) { + d[j].maxsize = bufs[i]->planes()[j].length; + d[j].chunk->offset = bufs[i]->planes()[j].offset; + d[j].chunk->size = bufs[i]->planes()[j].length; + } else { + spa_log_warn(impl->log, "buffer index: i: %d, data member " + "numbers: %d is greater than plane number: %d", + i, buffers[i]->n_datas, numPlanes); + d[j].maxsize = port->streamConfig.frameSize; + d[j].chunk->offset = 0; + d[j].chunk->size = port->streamConfig.frameSize; + } + + if (port->memtype == SPA_DATA_DmaBuf || + port->memtype == SPA_DATA_MemFd) { + d[j].flags |= SPA_DATA_FLAG_MAPPABLE; + d[j].fd = bufs[i]->planes()[j].fd.get(); + spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); + d[j].data = NULL; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); + } + else if(port->memtype == SPA_DATA_MemPtr) { + d[j].fd = -1; + d[j].data = mmap(NULL, + d[j].maxsize + d[j].mapoffset, + PROT_READ, MAP_SHARED, + bufs[i]->planes()[j].fd.get(), + 0); + if (d[j].data == MAP_FAILED) { + spa_log_error(impl->log, "mmap: %m"); + continue; + } + b->ptr = d[j].data; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + spa_log_debug(impl->log, "mmap ptr:%p", d[j].data); + } else { + spa_log_error(impl->log, "invalid buffer type"); + return -EIO; + } + } + spa_libcamera_buffer_recycle(impl, port, i); + } + port->n_buffers = n_buffers; + spa_log_debug(impl->log, "we have %d buffers", n_buffers); + + return 0; +} + +static int +spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + int res; + + if (port->n_buffers > 0) + return -EIO; + + if ((res = mmap_init(impl, port, buffers, n_buffers)) < 0) + return res; + + return 0; +} + + +void impl::requestComplete(libcamera::Request *request) +{ + struct impl *impl = this; + struct port *port = &impl->out_ports[0]; + Stream *stream = port->streamConfig.stream(); + uint32_t index, buffer_id; + struct buffer *b; + + spa_log_debug(impl->log, "request complete"); + + buffer_id = request->cookie(); + b = &port->buffers[buffer_id]; + + if ((request->status() == Request::RequestCancelled)) { + spa_log_debug(impl->log, "Request was cancelled"); + request->reuse(); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + return; + } + FrameBuffer *buffer = request->findBuffer(stream); + if (buffer == nullptr) { + spa_log_warn(impl->log, "unknown buffer"); + return; + } + const FrameMetadata &fmd = buffer->metadata(); + + if (impl->clock) { + double target = (double)port->info.rate.num / port->info.rate.denom; + double corr; + + if (impl->dll.bw == 0.0) { + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); + impl->clock->next_nsec = fmd.timestamp; + corr = 1.0; + } else { + double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; + double error = port->info.rate.denom * (diff - target); + corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); + } + /* FIXME, we should follow the driver clock and target_ values. + * for now we ignore and use our own. */ + impl->clock->target_rate = port->rate; + impl->clock->target_duration = 1; + + impl->clock->nsec = fmd.timestamp; + impl->clock->rate = port->rate; + impl->clock->position = fmd.sequence; + impl->clock->duration = 1; + impl->clock->delay = 0; + impl->clock->rate_diff = corr; + impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); + } + if (b->h) { + b->h->flags = 0; + b->h->offset = 0; + b->h->seq = fmd.sequence; + b->h->pts = fmd.timestamp; + b->h->dts_offset = 0; + } + request->reuse(); + + spa_ringbuffer_get_write_index(&port->ring, &index); + port->ring_ids[index & MASK_BUFFERS] = buffer_id; + spa_ringbuffer_write_update(&port->ring, index + 1); + + if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) + spa_log_error(impl->log, "Failed to write on event fd"); + +} + +static int spa_libcamera_stream_on(struct impl *impl) +{ + struct port *port = &impl->out_ports[0]; + int res; + + if (!port->current_format) { + spa_log_error(impl->log, "Exiting %s with -EIO", __FUNCTION__); + return -EIO; + } + + if (impl->active) + return 0; + + impl->camera->requestCompleted.connect(impl, &impl::requestComplete); + + spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); + if ((res = impl->camera->start(&impl->initial_controls)) < 0) + goto error; + + for (Request *req : impl->pendingRequests) { + if ((res = impl->camera->queueRequest(req)) < 0) + goto error_stop; + } + impl->pendingRequests.clear(); + + impl->dll.bw = 0.0; + + impl->source.func = libcamera_on_fd_events; + impl->source.data = impl; + impl->source.fd = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + impl->source.mask = SPA_IO_IN | SPA_IO_ERR; + impl->source.rmask = 0; + if (impl->source.fd < 0) { + spa_log_error(impl->log, "Failed to create eventfd: %s", spa_strerror(impl->source.fd)); + res = impl->source.fd; + goto error_stop; + } + spa_loop_add_source(impl->data_loop, &impl->source); + + impl->active = true; + + return 0; + +error_stop: + impl->camera->stop(); +error: + impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); + return res == -EACCES ? -EBUSY : res; +} + +static int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *impl = (struct impl *)user_data; + if (impl->source.loop) + spa_loop_remove_source(loop, &impl->source); + return 0; +} + +static int spa_libcamera_stream_off(struct impl *impl) +{ + struct port *port = &impl->out_ports[0]; + int res; + + if (!impl->active) { + for (std::unique_ptr &req : impl->requestPool) + req->reuse(); + return 0; + } + + impl->active = false; + spa_log_info(impl->log, "stopping camera %s", impl->device_id.c_str()); + impl->pendingRequests.clear(); + + if ((res = impl->camera->stop()) < 0) { + spa_log_warn(impl->log, "error stopping camera %s: %s", + impl->device_id.c_str(), spa_strerror(res)); + } + + impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); + + spa_loop_locked(impl->data_loop, do_remove_source, 0, NULL, 0, impl); + if (impl->source.fd >= 0) { + spa_system_close(impl->system, impl->source.fd); + impl->source.fd = -1; + } + + spa_list_init(&port->queue); + + return 0; +} static int port_get_format(struct impl *impl, struct port *port, uint32_t index, diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp deleted file mode 100644 index 6486d501c..000000000 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ /dev/null @@ -1,1167 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ -/* @author Raghavendra Rao Sidlagatta */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include - -#include -#include - -static void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) -{ - /* Libcamera recommends cameras default to manual focus mode, but we don't - * expose any focus controls. So, specifically enable autofocus on - * cameras which support it. */ - auto af_it = ctrl_infos.find(libcamera::controls::AF_MODE); - if (af_it != ctrl_infos.end()) { - const ControlInfo &ctrl_info = af_it->second; - auto is_af_continuous = [](const ControlValue &value) { - return value.get() == libcamera::controls::AfModeContinuous; - }; - if (std::any_of(ctrl_info.values().begin(), - ctrl_info.values().end(), is_af_continuous)) { - ctrls.set(libcamera::controls::AF_MODE, - libcamera::controls::AfModeContinuous); - } - } - - auto ae_it = ctrl_infos.find(libcamera::controls::AE_ENABLE); - if (ae_it != ctrl_infos.end()) { - ctrls.set(libcamera::controls::AE_ENABLE, true); - } -} - -int spa_libcamera_open(struct impl *impl) -{ - if (impl->acquired) - return 0; - - spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); - impl->camera->acquire(); - - impl->allocator = new FrameBufferAllocator(impl->camera); - - const ControlInfoMap &controls = impl->camera->controls(); - setup_initial_controls(controls, impl->initial_controls); - - impl->acquired = true; - return 0; -} - -int spa_libcamera_close(struct impl *impl) -{ - struct port *port = &impl->out_ports[0]; - if (!impl->acquired) - return 0; - if (impl->active || port->current_format) - return 0; - - spa_log_info(impl->log, "close camera %s", impl->device_id.c_str()); - delete impl->allocator; - impl->allocator = nullptr; - - impl->camera->release(); - - impl->acquired = false; - return 0; -} - -static void spa_libcamera_get_config(struct impl *impl) -{ - if (impl->config) - return; - - impl->config = impl->camera->generateConfiguration({ StreamRole::VideoRecording }); -} - -static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) -{ - struct buffer *b = &port->buffers[buffer_id]; - int res; - - if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) - return 0; - - SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING); - - if (buffer_id >= impl->requestPool.size()) { - spa_log_warn(impl->log, "invalid buffer_id %u >= %zu", - buffer_id, impl->requestPool.size()); - return -EINVAL; - } - Request *request = impl->requestPool[buffer_id].get(); - Stream *stream = port->streamConfig.stream(); - FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); - if ((res = request->addBuffer(stream, buffer)) < 0) { - spa_log_warn(impl->log, "can't add buffer %u for request: %s", - buffer_id, spa_strerror(res)); - return -ENOMEM; - } - if (!impl->active) { - impl->pendingRequests.push_back(request); - return 0; - } else { - request->controls().merge(impl->ctrls); - impl->ctrls.clear(); - if ((res = impl->camera->queueRequest(request)) < 0) { - spa_log_warn(impl->log, "can't queue buffer %u: %s", - buffer_id, spa_strerror(res)); - return res == -EACCES ? -EBUSY : res; - } - } - return 0; -} - -static int allocBuffers(struct impl *impl, struct port *port, unsigned int count) -{ - int res; - - if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) - return res; - - for (unsigned int i = 0; i < count; i++) { - std::unique_ptr request = impl->camera->createRequest(i); - if (!request) { - impl->requestPool.clear(); - return -ENOMEM; - } - impl->requestPool.push_back(std::move(request)); - } - - /* Some devices require data for each output video frame to be - * placed in discontiguous memory buffers. In such cases, one - * video frame has to be addressed using more than one memory. - * address. Therefore, need calculate the number of discontiguous - * memory and allocate the specified amount of memory */ - Stream *stream = impl->config->at(0).stream(); - const std::vector> &bufs = - impl->allocator->buffers(stream); - const std::vector &planes = bufs[0]->planes(); - int fd = -1; - uint32_t buffers_blocks = 0; - - for (const FrameBuffer::Plane &plane : planes) { - const int current_fd = plane.fd.get(); - if (current_fd >= 0 && current_fd != fd) { - buffers_blocks += 1; - fd = current_fd; - } - } - - if (buffers_blocks > 0) { - port->buffers_blocks = buffers_blocks; - } - return res; -} - -static void freeBuffers(struct impl *impl, struct port *port) -{ - impl->pendingRequests.clear(); - impl->requestPool.clear(); - impl->allocator->free(port->streamConfig.stream()); -} - -static int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) -{ - uint32_t i; - - if (port->n_buffers == 0) - return 0; - - for (i = 0; i < port->n_buffers; i++) { - struct buffer *b; - struct spa_data *d; - - b = &port->buffers[i]; - d = b->outbuf->datas; - - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { - spa_log_debug(impl->log, "queueing outstanding buffer %p", b); - spa_libcamera_buffer_recycle(impl, port, i); - } - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { - munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), - d[0].maxsize - d[0].mapoffset); - } - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { - close(d[0].fd); - } - d[0].type = SPA_ID_INVALID; - } - - freeBuffers(impl, port); - port->n_buffers = 0; - port->ring = SPA_RINGBUFFER_INIT(); - - return 0; -} - -struct format_info { - PixelFormat pix; - uint32_t format; - uint32_t media_type; - uint32_t media_subtype; -}; - -#define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } -static const struct format_info format_info[] = { - /* RGB formats */ - MAKE_FMT(formats::RGB565, RGB16, video, raw), - MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), - MAKE_FMT(formats::RGB888, BGR, video, raw), - MAKE_FMT(formats::BGR888, RGB, video, raw), - MAKE_FMT(formats::XRGB8888, BGRx, video, raw), - MAKE_FMT(formats::XBGR8888, RGBx, video, raw), - MAKE_FMT(formats::RGBX8888, xBGR, video, raw), - MAKE_FMT(formats::BGRX8888, xRGB, video, raw), - MAKE_FMT(formats::ARGB8888, BGRA, video, raw), - MAKE_FMT(formats::ABGR8888, RGBA, video, raw), - MAKE_FMT(formats::RGBA8888, ABGR, video, raw), - MAKE_FMT(formats::BGRA8888, ARGB, video, raw), - - MAKE_FMT(formats::YUYV, YUY2, video, raw), - MAKE_FMT(formats::YVYU, YVYU, video, raw), - MAKE_FMT(formats::UYVY, UYVY, video, raw), - MAKE_FMT(formats::VYUY, VYUY, video, raw), - - MAKE_FMT(formats::NV12, NV12, video, raw), - MAKE_FMT(formats::NV21, NV21, video, raw), - MAKE_FMT(formats::NV16, NV16, video, raw), - MAKE_FMT(formats::NV61, NV61, video, raw), - MAKE_FMT(formats::NV24, NV24, video, raw), - - MAKE_FMT(formats::YUV420, I420, video, raw), - MAKE_FMT(formats::YVU420, YV12, video, raw), - MAKE_FMT(formats::YUV422, Y42B, video, raw), - - MAKE_FMT(formats::MJPEG, ENCODED, video, mjpg), -#undef MAKE_FMT -}; - -static const struct format_info *video_format_to_info(const PixelFormat &pix) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].pix == pix) - return &format_info[i]; - } - return NULL; -} - -static const struct format_info *find_format_info_by_media_type(uint32_t type, - uint32_t subtype, uint32_t format, int startidx) -{ - size_t i; - - for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { - if ((format_info[i].media_type == type) && - (format_info[i].media_subtype == subtype) && - (format == 0 || format_info[i].format == format)) - return &format_info[i]; - } - return NULL; -} - -static int score_size(Size &a, Size &b) -{ - int x, y; - x = (int)a.width - (int)b.width; - y = (int)a.height - (int)b.height; - return x * x + y * y; -} - -static void -parse_colorimetry(const ColorSpace& colorspace, - struct spa_video_colorimetry *colorimetry) -{ - switch (colorspace.range) { - case ColorSpace::Range::Full: - colorimetry->range = SPA_VIDEO_COLOR_RANGE_0_255; - break; - case ColorSpace::Range::Limited: - colorimetry->range = SPA_VIDEO_COLOR_RANGE_16_235; - break; - } - - switch (colorspace.ycbcrEncoding) { - case ColorSpace::YcbcrEncoding::None: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_RGB; - break; - case ColorSpace::YcbcrEncoding::Rec601: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT601; - break; - case ColorSpace::YcbcrEncoding::Rec709: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT709; - break; - case ColorSpace::YcbcrEncoding::Rec2020: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; - break; - } - - switch (colorspace.transferFunction) { - case ColorSpace::TransferFunction::Linear: - colorimetry->transfer = SPA_VIDEO_TRANSFER_UNKNOWN; - break; - case ColorSpace::TransferFunction::Srgb: - colorimetry->transfer = SPA_VIDEO_TRANSFER_SRGB; - break; - case ColorSpace::TransferFunction::Rec709: - colorimetry->transfer = SPA_VIDEO_TRANSFER_BT709; - break; - } - - switch (colorspace.primaries) { - case ColorSpace::Primaries::Raw: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; - break; - case ColorSpace::Primaries::Smpte170m: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; - break; - case ColorSpace::Primaries::Rec709: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; - break; - case ColorSpace::Primaries::Rec2020: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; - break; - } -} - -static int -spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, - uint32_t start, uint32_t num, const struct spa_pod *filter) -{ - int res; - const struct format_info *info; - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - struct spa_pod_frame f[2]; - struct spa_result_node_params result; - struct spa_video_colorimetry colorimetry = {}; - struct spa_pod *fmt; - uint32_t i, count = 0, num_sizes; - PixelFormat format; - Size frameSize; - SizeRange sizeRange = SizeRange(); - - spa_libcamera_get_config(impl); - - const StreamConfiguration& streamConfig = impl->config->at(0); - const StreamFormats &formats = streamConfig.formats(); - - if (streamConfig.colorSpace) - parse_colorimetry(*streamConfig.colorSpace, &colorimetry); - - result.id = SPA_PARAM_EnumFormat; - result.next = start; - - if (result.next == 0) { - port->fmt_index = 0; - port->size_index = 0; - } -next: - result.index = result.next++; - -next_fmt: - if (port->fmt_index >= formats.pixelformats().size()) - goto enum_end; - - format = formats.pixelformats()[port->fmt_index]; - - spa_log_debug(impl->log, "format: %s", format.toString().c_str()); - - info = video_format_to_info(format); - if (info == NULL) { - spa_log_debug(impl->log, "unknown format"); - port->fmt_index++; - goto next_fmt; - } - - num_sizes = formats.sizes(format).size(); - if (num_sizes > 0 && port->size_index <= num_sizes) { - if (port->size_index == 0) { - Size wanted = Size(640, 480), test; - int score, best = INT_MAX; - for (i = 0; i < num_sizes; i++) { - test = formats.sizes(format)[i]; - score = score_size(wanted, test); - if (score < best) { - best = score; - frameSize = test; - } - } - } - else { - frameSize = formats.sizes(format)[port->size_index - 1]; - } - } else if (port->size_index < 1) { - sizeRange = formats.range(format); - if (sizeRange.hStep == 0 || sizeRange.vStep == 0) { - port->size_index = 0; - port->fmt_index++; - goto next_fmt; - } - } else { - port->size_index = 0; - port->fmt_index++; - goto next_fmt; - } - port->size_index++; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(&b, - SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), - 0); - - if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); - spa_pod_builder_id(&b, info->format); - } - if (info->pix.modifier()) { - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_modifier, 0); - spa_pod_builder_long(&b, info->pix.modifier()); - } - spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); - - if (sizeRange.hStep != 0 && sizeRange.vStep != 0) { - spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Step, 0); - spa_pod_builder_frame(&b, &f[1]); - spa_pod_builder_rectangle(&b, - sizeRange.min.width, - sizeRange.min.height); - spa_pod_builder_rectangle(&b, - sizeRange.min.width, - sizeRange.min.height); - spa_pod_builder_rectangle(&b, - sizeRange.max.width, - sizeRange.max.height); - spa_pod_builder_rectangle(&b, - sizeRange.hStep, - sizeRange.vStep); - spa_pod_builder_pop(&b, &f[1]); - - } else { - spa_pod_builder_rectangle(&b, frameSize.width, frameSize.height); - } - - if (streamConfig.colorSpace) { - spa_pod_builder_add(&b, - SPA_FORMAT_VIDEO_colorRange, - SPA_POD_Id(colorimetry.range), - SPA_FORMAT_VIDEO_colorMatrix, - SPA_POD_Id(colorimetry.matrix), - SPA_FORMAT_VIDEO_transferFunction, - SPA_POD_Id(colorimetry.transfer), - SPA_FORMAT_VIDEO_colorPrimaries, - SPA_POD_Id(colorimetry.primaries), 0); - } - - fmt = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); - - if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) - goto next; - - spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - - enum_end: - res = 0; - return res; -} - -static int spa_libcamera_set_format(struct impl *impl, struct port *port, - struct spa_video_info *format, bool try_only) -{ - const struct format_info *info = NULL; - uint32_t video_format; - struct spa_rectangle *size = NULL; - struct spa_fraction *framerate = NULL; - CameraConfiguration::Status validation; - int res; - - switch (format->media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - video_format = format->info.raw.format; - size = &format->info.raw.size; - framerate = &format->info.raw.framerate; - break; - case SPA_MEDIA_SUBTYPE_mjpg: - case SPA_MEDIA_SUBTYPE_jpeg: - video_format = SPA_VIDEO_FORMAT_ENCODED; - size = &format->info.mjpg.size; - framerate = &format->info.mjpg.framerate; - break; - case SPA_MEDIA_SUBTYPE_h264: - video_format = SPA_VIDEO_FORMAT_ENCODED; - size = &format->info.h264.size; - framerate = &format->info.h264.framerate; - break; - default: - video_format = SPA_VIDEO_FORMAT_ENCODED; - break; - } - - info = find_format_info_by_media_type(format->media_type, - format->media_subtype, video_format, 0); - if (info == NULL || size == NULL || framerate == NULL) { - spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type, - format->media_subtype, video_format); - return -EINVAL; - } - StreamConfiguration& streamConfig = impl->config->at(0); - - streamConfig.pixelFormat = info->pix; - streamConfig.size.width = size->width; - streamConfig.size.height = size->height; - streamConfig.bufferCount = 8; - - validation = impl->config->validate(); - if (validation == CameraConfiguration::Invalid) - return -EINVAL; - - if (try_only) - return 0; - - if ((res = spa_libcamera_open(impl)) < 0) - return res; - - res = impl->camera->configure(impl->config.get()); - if (res != 0) - goto error; - - port->streamConfig = impl->config->at(0); - - if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) - goto error; - - port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; - port->info.flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS | - SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL; - port->info.rate = SPA_FRACTION(port->rate.num, port->rate.denom); - - return 0; -error: - spa_libcamera_close(impl); - return res; - -} - -static struct { - uint32_t id; - uint32_t spa_id; -} control_map[] = { - { libcamera::controls::BRIGHTNESS, SPA_PROP_brightness }, - { libcamera::controls::CONTRAST, SPA_PROP_contrast }, - { libcamera::controls::SATURATION, SPA_PROP_saturation }, - { libcamera::controls::EXPOSURE_TIME, SPA_PROP_exposure }, - { libcamera::controls::ANALOGUE_GAIN, SPA_PROP_gain }, - { libcamera::controls::SHARPNESS, SPA_PROP_sharpness }, -}; - -static uint32_t control_to_prop_id(struct impl *impl, uint32_t control_id) -{ - SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { - if (c->id == control_id) - return c->spa_id; - } - return SPA_PROP_START_CUSTOM + control_id; -} - -static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) -{ - SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { - if (c->spa_id == prop_id) - return c->id; - } - if (prop_id >= SPA_PROP_START_CUSTOM) - return prop_id - SPA_PROP_START_CUSTOM; - return SPA_ID_INVALID; -} - -static int -spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, - uint32_t start, uint32_t offset, uint32_t num, - const struct spa_pod *filter) -{ - const ControlInfoMap &info = impl->camera->controls(); - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - struct spa_pod_frame f[2]; - struct spa_result_node_params result; - struct spa_pod *ctrl; - uint32_t count = 0, skip, id; - int res; - const ControlId *ctrl_id; - ControlInfo ctrl_info; - - result.id = SPA_PARAM_PropInfo; - result.next = start; - - auto it = info.begin(); - for (skip = result.next - offset; skip; skip--) - it++; - - if (false) { -next: - it++; - } - result.index = result.next++; - if (it == info.end()) - goto enum_end; - - ctrl_id = it->first; - ctrl_info = it->second; - - id = control_to_prop_id(impl, ctrl_id->id()); - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); - spa_pod_builder_add(&b, - SPA_PROP_INFO_id, SPA_POD_Id(id), - SPA_PROP_INFO_description, SPA_POD_String(ctrl_id->name().c_str()), - 0); - - switch (ctrl_id->type()) { - case ControlTypeBool: { - bool def; - if (ctrl_info.def().isNone()) - def = ctrl_info.min().get(); - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - def), - 0); - } break; - case ControlTypeFloat: { - float min = ctrl_info.min().get(); - float max = ctrl_info.max().get(); - float def; - - if (ctrl_info.def().isNone()) - def = (min + max) / 2; - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - def, min, max), - 0); - } break; - case ControlTypeInteger32: { - int32_t min = ctrl_info.min().get(); - int32_t max = ctrl_info.max().get(); - int32_t def; - - if (ctrl_info.def().isNone()) - def = (min + max) / 2; - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - def, min, max), - 0); - } break; - default: - goto next; - } - - ctrl = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); - - if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) - goto next; - - spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - -enum_end: - res = 0; - return res; -} - -struct val { - uint32_t type; - float f_val; - int32_t i_val; - bool b_val; - uint32_t id; -}; - -static int do_update_ctrls(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *impl = (struct impl *)user_data; - const struct val *d = (const struct val *)data; - switch (d->type) { - case ControlTypeBool: - impl->ctrls.set(d->id, d->b_val); - break; - case ControlTypeFloat: - impl->ctrls.set(d->id, d->f_val); - break; - case ControlTypeInteger32: - impl->ctrls.set(d->id, (int32_t)d->i_val); - break; - default: - break; - } - return 0; -} - -static int -spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) -{ - const ControlInfoMap &info = impl->camera->controls(); - const ControlId *ctrl_id; - int res; - struct val d; - uint32_t control_id; - - control_id = prop_id_to_control(impl, prop->key); - if (control_id == SPA_ID_INVALID) - return -ENOENT; - - auto v = info.idmap().find(control_id); - if (v == info.idmap().end()) - return -ENOENT; - - ctrl_id = v->second; - - d.type = ctrl_id->type(); - d.id = ctrl_id->id(); - - switch (d.type) { - case ControlTypeBool: - if ((res = spa_pod_get_bool(&prop->value, &d.b_val)) < 0) - goto done; - break; - case ControlTypeFloat: - if ((res = spa_pod_get_float(&prop->value, &d.f_val)) < 0) - goto done; - break; - case ControlTypeInteger32: - if ((res = spa_pod_get_int(&prop->value, &d.i_val)) < 0) - goto done; - break; - default: - res = -EINVAL; - goto done; - } - spa_loop_invoke(impl->data_loop, do_update_ctrls, 0, &d, sizeof(d), true, impl); - res = 0; -done: - return res; -} - - -static void libcamera_on_fd_events(struct spa_source *source) -{ - struct impl *impl = (struct impl*) source->data; - struct spa_io_buffers *io; - struct port *port = &impl->out_ports[0]; - uint32_t index, buffer_id; - struct buffer *b; - uint64_t cnt; - - if (source->rmask & SPA_IO_ERR) { - spa_log_error(impl->log, "libcamera %p: error %08x", impl, source->rmask); - if (impl->source.loop) - spa_loop_remove_source(impl->data_loop, &impl->source); - return; - } - - if (!(source->rmask & SPA_IO_IN)) { - spa_log_warn(impl->log, "libcamera %p: spurious wakeup %d", impl, source->rmask); - return; - } - - if (spa_system_eventfd_read(impl->system, impl->source.fd, &cnt) < 0) { - spa_log_error(impl->log, "Failed to read on event fd"); - return; - } - - if (spa_ringbuffer_get_read_index(&port->ring, &index) < 1) { - spa_log_error(impl->log, "nothing is queued"); - return; - } - buffer_id = port->ring_ids[index & MASK_BUFFERS]; - spa_ringbuffer_read_update(&port->ring, index + 1); - - b = &port->buffers[buffer_id]; - spa_list_append(&port->queue, &b->link); - - io = port->io; - if (io == NULL) { - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - } else if (io->status != SPA_STATUS_HAVE_DATA) { - if (io->buffer_id < port->n_buffers) - spa_libcamera_buffer_recycle(impl, port, io->buffer_id); - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - - io->buffer_id = b->id; - io->status = SPA_STATUS_HAVE_DATA; - spa_log_trace(impl->log, "libcamera %p: now queued %d", impl, b->id); - } - spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); -} - -static int spa_libcamera_use_buffers(struct impl *impl, struct port *port, - struct spa_buffer **buffers, uint32_t n_buffers) -{ - return -ENOTSUP; -} - -static const struct { - Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */ - uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */ -} orientation_map[] = { - { Orientation::Rotate0, SPA_META_TRANSFORMATION_None }, - { Orientation::Rotate0Mirror, SPA_META_TRANSFORMATION_Flipped }, - { Orientation::Rotate90, SPA_META_TRANSFORMATION_270 }, - { Orientation::Rotate90Mirror, SPA_META_TRANSFORMATION_Flipped90 }, - { Orientation::Rotate180, SPA_META_TRANSFORMATION_180 }, - { Orientation::Rotate180Mirror, SPA_META_TRANSFORMATION_Flipped180 }, - { Orientation::Rotate270, SPA_META_TRANSFORMATION_90 }, - { Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 }, -}; - -static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation) -{ - for (const auto& t : orientation_map) { - if (t.libcamera_orientation == orientation) - return t.spa_transform_value; - } - return SPA_META_TRANSFORMATION_None; -} - -static int -mmap_init(struct impl *impl, struct port *port, - struct spa_buffer **buffers, uint32_t n_buffers) -{ - unsigned int i, j; - struct spa_data *d; - Stream *stream = impl->config->at(0).stream(); - const std::vector> &bufs = - impl->allocator->buffers(stream); - - if (n_buffers > 0) { - if (bufs.size() != n_buffers) - return -EINVAL; - - d = buffers[0]->datas; - - if (d[0].type != SPA_ID_INVALID && - d[0].type & (1u << SPA_DATA_DmaBuf)) { - port->memtype = SPA_DATA_DmaBuf; - } else if (d[0].type != SPA_ID_INVALID && - d[0].type & (1u << SPA_DATA_MemFd)) { - port->memtype = SPA_DATA_MemFd; - } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { - port->memtype = SPA_DATA_MemPtr; - } else { - spa_log_error(impl->log, "can't use buffers of type %d", d[0].type); - return -EINVAL; - } - } - - for (i = 0; i < n_buffers; i++) { - struct buffer *b; - - if (buffers[i]->n_datas < 1) { - spa_log_error(impl->log, "invalid buffer data"); - return -EINVAL; - } - - b = &port->buffers[i]; - b->id = i; - b->outbuf = buffers[i]; - b->flags = BUFFER_FLAG_OUTSTANDING; - b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); - - b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( - buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform)); - if (b->videotransform) { - b->videotransform->transform = - libcamera_orientation_to_spa_transform_value(impl->config->orientation); - spa_log_debug(impl->log, "Setting videotransform for buffer %u to %u", - i, b->videotransform->transform); - - } - - d = buffers[i]->datas; - for(j = 0; j < buffers[i]->n_datas; ++j) { - d[j].type = port->memtype; - d[j].flags = SPA_DATA_FLAG_READABLE; - d[j].mapoffset = 0; - d[j].chunk->stride = port->streamConfig.stride; - d[j].chunk->flags = 0; - /* Update parameters according to the plane information */ - unsigned int numPlanes = bufs[i]->planes().size(); - if (buffers[i]->n_datas < numPlanes) { - if (j < buffers[i]->n_datas - 1) { - d[j].maxsize = bufs[i]->planes()[j].length; - d[j].chunk->offset = bufs[i]->planes()[j].offset; - d[j].chunk->size = bufs[i]->planes()[j].length; - } else { - d[j].chunk->offset = bufs[i]->planes()[j].offset; - for (uint8_t k = j; k < numPlanes; k++) { - d[j].maxsize += bufs[i]->planes()[k].length; - d[j].chunk->size += bufs[i]->planes()[k].length; - } - } - } else if (buffers[i]->n_datas == numPlanes) { - d[j].maxsize = bufs[i]->planes()[j].length; - d[j].chunk->offset = bufs[i]->planes()[j].offset; - d[j].chunk->size = bufs[i]->planes()[j].length; - } else { - spa_log_warn(impl->log, "buffer index: i: %d, data member " - "numbers: %d is greater than plane number: %d", - i, buffers[i]->n_datas, numPlanes); - d[j].maxsize = port->streamConfig.frameSize; - d[j].chunk->offset = 0; - d[j].chunk->size = port->streamConfig.frameSize; - } - - if (port->memtype == SPA_DATA_DmaBuf || - port->memtype == SPA_DATA_MemFd) { - d[j].flags |= SPA_DATA_FLAG_MAPPABLE; - d[j].fd = bufs[i]->planes()[j].fd.get(); - spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); - d[j].data = NULL; - SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); - } - else if(port->memtype == SPA_DATA_MemPtr) { - d[j].fd = -1; - d[j].data = mmap(NULL, - d[j].maxsize + d[j].mapoffset, - PROT_READ, MAP_SHARED, - bufs[i]->planes()[j].fd.get(), - 0); - if (d[j].data == MAP_FAILED) { - spa_log_error(impl->log, "mmap: %m"); - continue; - } - b->ptr = d[j].data; - SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); - spa_log_debug(impl->log, "mmap ptr:%p", d[j].data); - } else { - spa_log_error(impl->log, "invalid buffer type"); - return -EIO; - } - } - spa_libcamera_buffer_recycle(impl, port, i); - } - port->n_buffers = n_buffers; - spa_log_debug(impl->log, "we have %d buffers", n_buffers); - - return 0; -} - -static int -spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - int res; - - if (port->n_buffers > 0) - return -EIO; - - if ((res = mmap_init(impl, port, buffers, n_buffers)) < 0) - return res; - - return 0; -} - - -void impl::requestComplete(libcamera::Request *request) -{ - struct impl *impl = this; - struct port *port = &impl->out_ports[0]; - Stream *stream = port->streamConfig.stream(); - uint32_t index, buffer_id; - struct buffer *b; - - spa_log_debug(impl->log, "request complete"); - - buffer_id = request->cookie(); - b = &port->buffers[buffer_id]; - - if ((request->status() == Request::RequestCancelled)) { - spa_log_debug(impl->log, "Request was cancelled"); - request->reuse(); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - return; - } - FrameBuffer *buffer = request->findBuffer(stream); - if (buffer == nullptr) { - spa_log_warn(impl->log, "unknown buffer"); - return; - } - const FrameMetadata &fmd = buffer->metadata(); - - if (impl->clock) { - double target = (double)port->info.rate.num / port->info.rate.denom; - double corr; - - if (impl->dll.bw == 0.0) { - spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); - impl->clock->next_nsec = fmd.timestamp; - corr = 1.0; - } else { - double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; - double error = port->info.rate.denom * (diff - target); - corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); - } - /* FIXME, we should follow the driver clock and target_ values. - * for now we ignore and use our own. */ - impl->clock->target_rate = port->rate; - impl->clock->target_duration = 1; - - impl->clock->nsec = fmd.timestamp; - impl->clock->rate = port->rate; - impl->clock->position = fmd.sequence; - impl->clock->duration = 1; - impl->clock->delay = 0; - impl->clock->rate_diff = corr; - impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); - } - if (b->h) { - b->h->flags = 0; - b->h->offset = 0; - b->h->seq = fmd.sequence; - b->h->pts = fmd.timestamp; - b->h->dts_offset = 0; - } - request->reuse(); - - spa_ringbuffer_get_write_index(&port->ring, &index); - port->ring_ids[index & MASK_BUFFERS] = buffer_id; - spa_ringbuffer_write_update(&port->ring, index + 1); - - if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) - spa_log_error(impl->log, "Failed to write on event fd"); - -} - -static int spa_libcamera_stream_on(struct impl *impl) -{ - struct port *port = &impl->out_ports[0]; - int res; - - if (!port->current_format) { - spa_log_error(impl->log, "Exiting %s with -EIO", __FUNCTION__); - return -EIO; - } - - if (impl->active) - return 0; - - impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - - spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); - if ((res = impl->camera->start(&impl->initial_controls)) < 0) - goto error; - - for (Request *req : impl->pendingRequests) { - if ((res = impl->camera->queueRequest(req)) < 0) - goto error_stop; - } - impl->pendingRequests.clear(); - - impl->dll.bw = 0.0; - - impl->source.func = libcamera_on_fd_events; - impl->source.data = impl; - impl->source.fd = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - impl->source.mask = SPA_IO_IN | SPA_IO_ERR; - impl->source.rmask = 0; - if (impl->source.fd < 0) { - spa_log_error(impl->log, "Failed to create eventfd: %s", spa_strerror(impl->source.fd)); - res = impl->source.fd; - goto error_stop; - } - spa_loop_add_source(impl->data_loop, &impl->source); - - impl->active = true; - - return 0; - -error_stop: - impl->camera->stop(); -error: - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - return res == -EACCES ? -EBUSY : res; -} - -static int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - struct impl *impl = (struct impl *)user_data; - if (impl->source.loop) - spa_loop_remove_source(loop, &impl->source); - return 0; -} - -static int spa_libcamera_stream_off(struct impl *impl) -{ - struct port *port = &impl->out_ports[0]; - int res; - - if (!impl->active) { - for (std::unique_ptr &req : impl->requestPool) - req->reuse(); - return 0; - } - - impl->active = false; - spa_log_info(impl->log, "stopping camera %s", impl->device_id.c_str()); - impl->pendingRequests.clear(); - - if ((res = impl->camera->stop()) < 0) { - spa_log_warn(impl->log, "error stopping camera %s: %s", - impl->device_id.c_str(), spa_strerror(res)); - } - - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - - spa_loop_locked(impl->data_loop, do_remove_source, 0, NULL, 0, impl); - if (impl->source.fd >= 0) { - spa_system_close(impl->system, impl->source.fd); - impl->source.fd = -1; - } - - spa_list_init(&port->queue); - - return 0; -} From f53ac8d57c9496993e468bd838e74eb64ca498c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:24:44 +0200 Subject: [PATCH 0554/1014] spa: libcamera: source: handle camera acquire failure Check the return value of `Camera::acquire()` and return the error if that fails. --- spa/plugins/libcamera/libcamera-source.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d23b033f0..0d094bd57 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -211,7 +211,9 @@ int spa_libcamera_open(struct impl *impl) return 0; spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); - impl->camera->acquire(); + + if (int res = impl->camera->acquire(); res < 0) + return res; impl->allocator = new FrameBufferAllocator(impl->camera); From 0ea7dc9f191a229a9417f0526f9cfa135d6400e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:28:23 +0200 Subject: [PATCH 0555/1014] spa: libcamera: source: use enum types Use the appropriate enum types instead of bare `uint32_t`, this provides better type safety in C++. --- spa/plugins/libcamera/libcamera-source.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 0d094bd57..a91933649 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -70,7 +70,7 @@ struct port { struct spa_fraction rate = {}; StreamConfiguration streamConfig; - uint32_t memtype = 0; + spa_data_type memtype = SPA_DATA_Invalid; uint32_t buffers_blocks = 1; struct buffer buffers[MAX_BUFFERS]; @@ -374,9 +374,9 @@ static int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) struct format_info { PixelFormat pix; - uint32_t format; - uint32_t media_type; - uint32_t media_subtype; + spa_video_format format; + spa_media_type media_type; + spa_media_subtype media_subtype; }; #define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } @@ -432,7 +432,7 @@ static const struct format_info *find_format_info_by_media_type(uint32_t type, for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { if ((format_info[i].media_type == type) && (format_info[i].media_subtype == subtype) && - (format == 0 || format_info[i].format == format)) + (format == SPA_VIDEO_FORMAT_UNKNOWN || format_info[i].format == format)) return &format_info[i]; } return NULL; From 489cc499374292af0f769f35c1c0ced47665a606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:37:04 +0200 Subject: [PATCH 0556/1014] spa: libcamera: source: simplify format lookup Use range based for loops instead of indices. --- spa/plugins/libcamera/libcamera-source.cpp | 34 ++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index a91933649..12307b15c 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -414,28 +414,26 @@ static const struct format_info format_info[] = { #undef MAKE_FMT }; -static const struct format_info *video_format_to_info(const PixelFormat &pix) { - size_t i; - - for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { - if (format_info[i].pix == pix) - return &format_info[i]; +static const struct format_info *video_format_to_info(const PixelFormat &pix) +{ + for (const auto& f : format_info) { + if (f.pix == pix) + return &f; } - return NULL; + + return nullptr; } -static const struct format_info *find_format_info_by_media_type(uint32_t type, - uint32_t subtype, uint32_t format, int startidx) +static const struct format_info *find_format_info_by_media_type( + uint32_t type, uint32_t subtype, uint32_t format) { - size_t i; - - for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { - if ((format_info[i].media_type == type) && - (format_info[i].media_subtype == subtype) && - (format == SPA_VIDEO_FORMAT_UNKNOWN || format_info[i].format == format)) - return &format_info[i]; + for (const auto& f : format_info) { + if (f.media_type == type && f.media_subtype == subtype + && (f.format == SPA_VIDEO_FORMAT_UNKNOWN || f.format == format)) + return &f; } - return NULL; + + return nullptr; } static int score_size(Size &a, Size &b) @@ -681,7 +679,7 @@ static int spa_libcamera_set_format(struct impl *impl, struct port *port, } info = find_format_info_by_media_type(format->media_type, - format->media_subtype, video_format, 0); + format->media_subtype, video_format); if (info == NULL || size == NULL || framerate == NULL) { spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type, format->media_subtype, video_format); From 311b3cc37fc03febe54a14428698e5afcc7c5e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:39:29 +0200 Subject: [PATCH 0557/1014] spa: libcamera: source: do not make expensive queries multiple times `StreamFormats::pixelformats()` and `StreamFormats::sizes()` both return newly created `std::vector`s, so do not call them multiple times. --- spa/plugins/libcamera/libcamera-source.cpp | 53 ++++++++++------------ 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 12307b15c..d09e89081 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -4,8 +4,9 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include +#include #include +#include #include #include @@ -94,9 +95,9 @@ struct port { #define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; - uint32_t fmt_index = 0; + std::size_t fmt_index = 0; PixelFormat enum_fmt; - uint32_t size_index = 0; + std::size_t size_index = 0; port(struct impl *impl) : impl(impl) @@ -436,7 +437,7 @@ static const struct format_info *find_format_info_by_media_type( return nullptr; } -static int score_size(Size &a, Size &b) +static int score_size(const Size &a, const Size &b) { int x, y; x = (int)a.width - (int)b.width; @@ -504,23 +505,18 @@ static int spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { - int res; - const struct format_info *info; uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[2]; struct spa_result_node_params result; struct spa_video_colorimetry colorimetry = {}; - struct spa_pod *fmt; - uint32_t i, count = 0, num_sizes; - PixelFormat format; - Size frameSize; - SizeRange sizeRange = SizeRange(); + uint32_t count = 0; spa_libcamera_get_config(impl); const StreamConfiguration& streamConfig = impl->config->at(0); const StreamFormats &formats = streamConfig.formats(); + const auto &pixel_formats = formats.pixelformats(); if (streamConfig.colorSpace) parse_colorimetry(*streamConfig.colorSpace, &colorimetry); @@ -536,28 +532,30 @@ next: result.index = result.next++; next_fmt: - if (port->fmt_index >= formats.pixelformats().size()) - goto enum_end; - - format = formats.pixelformats()[port->fmt_index]; + if (port->fmt_index >= pixel_formats.size()) + return 0; + auto format = pixel_formats[port->fmt_index]; spa_log_debug(impl->log, "format: %s", format.toString().c_str()); - info = video_format_to_info(format); + const auto *info = video_format_to_info(format); if (info == NULL) { spa_log_debug(impl->log, "unknown format"); port->fmt_index++; goto next_fmt; } - num_sizes = formats.sizes(format).size(); - if (num_sizes > 0 && port->size_index <= num_sizes) { + const auto& sizes = formats.sizes(format); + SizeRange sizeRange; + Size frameSize; + + if (!sizes.empty() && port->size_index <= sizes.size()) { if (port->size_index == 0) { - Size wanted = Size(640, 480), test; - int score, best = INT_MAX; - for (i = 0; i < num_sizes; i++) { - test = formats.sizes(format)[i]; - score = score_size(wanted, test); + Size wanted = Size(640, 480); + int best = std::numeric_limits::max(); + + for (const auto& test : sizes) { + int score = score_size(wanted, test); if (score < best) { best = score; frameSize = test; @@ -565,7 +563,7 @@ next_fmt: } } else { - frameSize = formats.sizes(format)[port->size_index - 1]; + frameSize = sizes[port->size_index - 1]; } } else if (port->size_index < 1) { sizeRange = formats.range(format); @@ -631,8 +629,7 @@ next_fmt: SPA_POD_Id(colorimetry.primaries), 0); } - fmt = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); - + const auto *fmt = reinterpret_cast(spa_pod_builder_pop(&b, &f[0])); if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) goto next; @@ -641,9 +638,7 @@ next_fmt: if (++count != num) goto next; - enum_end: - res = 0; - return res; + return 0; } static int spa_libcamera_set_format(struct impl *impl, struct port *port, From f94f4de6ff7e02a23ec51ed278c058e5d10da468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:52:59 +0200 Subject: [PATCH 0558/1014] spa: libcamera: source: simplify control mapping Remove the `impl` parameter as it is not used, and use C++ range based for loops. --- spa/plugins/libcamera/libcamera-source.cpp | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d09e89081..781658949 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -720,7 +720,7 @@ error: } -static struct { +static const struct { uint32_t id; uint32_t spa_id; } control_map[] = { @@ -732,23 +732,26 @@ static struct { { libcamera::controls::SHARPNESS, SPA_PROP_sharpness }, }; -static uint32_t control_to_prop_id(struct impl *impl, uint32_t control_id) +static uint32_t control_to_prop_id(uint32_t control_id) { - SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { - if (c->id == control_id) - return c->spa_id; + for (const auto& c : control_map) { + if (c.id == control_id) + return c.spa_id; } + return SPA_PROP_START_CUSTOM + control_id; } -static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) +static uint32_t prop_id_to_control(uint32_t prop_id) { - SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { - if (c->spa_id == prop_id) - return c->id; + for (const auto& c : control_map) { + if (c.spa_id == prop_id) + return c.id; } + if (prop_id >= SPA_PROP_START_CUSTOM) return prop_id - SPA_PROP_START_CUSTOM; + return SPA_ID_INVALID; } @@ -786,7 +789,7 @@ next: ctrl_id = it->first; ctrl_info = it->second; - id = control_to_prop_id(impl, ctrl_id->id()); + id = control_to_prop_id(ctrl_id->id()); spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); @@ -899,7 +902,7 @@ spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) struct val d; uint32_t control_id; - control_id = prop_id_to_control(impl, prop->key); + control_id = prop_id_to_control(prop->key); if (control_id == SPA_ID_INVALID) return -ENOENT; From 0022fc90b715489d575a440783cdd02aada5474c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:53:35 +0200 Subject: [PATCH 0559/1014] spa: libcamera: source: use `union` for transferring control value Use a union since only one member is active at a time, and use the proper `libcamera::ControlType` enum to store the type instead of a bare number. Also remove an unnecessary cast. --- spa/plugins/libcamera/libcamera-source.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 781658949..217dcd876 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -861,11 +861,13 @@ enum_end: } struct val { - uint32_t type; - float f_val; - int32_t i_val; - bool b_val; + ControlType type; uint32_t id; + union { + bool b_val; + int32_t i_val; + float f_val; + }; }; static int do_update_ctrls(struct spa_loop *loop, @@ -885,7 +887,7 @@ static int do_update_ctrls(struct spa_loop *loop, impl->ctrls.set(d->id, d->f_val); break; case ControlTypeInteger32: - impl->ctrls.set(d->id, (int32_t)d->i_val); + impl->ctrls.set(d->id, d->i_val); break; default: break; From 561a9d6ebb593eaee6586f3ee927933fe5a5a12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 18:56:12 +0200 Subject: [PATCH 0560/1014] spa: libcamera: source: set "corrupted" flag if applicable If the libcamera `FrameMetadata` reports anything other than `FrameSuccess`, then set `SPA_META_HEADER_FLAG_CORRUPTED`, notifying the application that the frame may be unusable. --- spa/plugins/libcamera/libcamera-source.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 217dcd876..11b3b41a2 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1222,6 +1222,8 @@ void impl::requestComplete(libcamera::Request *request) } if (b->h) { b->h->flags = 0; + if (fmd.status != FrameMetadata::Status::FrameSuccess) + b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; b->h->offset = 0; b->h->seq = fmd.sequence; b->h->pts = fmd.timestamp; From e19a8bb5cd0c53ae0f1ceba496c235028efb383d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:01:46 +0200 Subject: [PATCH 0561/1014] spa: libcamera: source: inline `mmap_init()` The function has a single caller is essentially just a wrapper only calling `mmap_init()`. So inline it into `spa_libcamera_alloc_buffers()`. --- spa/plugins/libcamera/libcamera-source.cpp | 44 ++++++++-------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 11b3b41a2..681cc8180 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1028,11 +1028,13 @@ static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orienta } static int -mmap_init(struct impl *impl, struct port *port, - struct spa_buffer **buffers, uint32_t n_buffers) +spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, + struct spa_buffer **buffers, + uint32_t n_buffers) { - unsigned int i, j; - struct spa_data *d; + if (port->n_buffers > 0) + return -EIO; + Stream *stream = impl->config->at(0).stream(); const std::vector> &bufs = impl->allocator->buffers(stream); @@ -1041,13 +1043,11 @@ mmap_init(struct impl *impl, struct port *port, if (bufs.size() != n_buffers) return -EINVAL; - d = buffers[0]->datas; + spa_data *d = buffers[0]->datas; - if (d[0].type != SPA_ID_INVALID && - d[0].type & (1u << SPA_DATA_DmaBuf)) { + if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_DmaBuf)) { port->memtype = SPA_DATA_DmaBuf; - } else if (d[0].type != SPA_ID_INVALID && - d[0].type & (1u << SPA_DATA_MemFd)) { + } else if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_MemFd)) { port->memtype = SPA_DATA_MemFd; } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { port->memtype = SPA_DATA_MemPtr; @@ -1057,7 +1057,7 @@ mmap_init(struct impl *impl, struct port *port, } } - for (i = 0; i < n_buffers; i++) { + for (uint32_t i = 0; i < n_buffers; i++) { struct buffer *b; if (buffers[i]->n_datas < 1) { @@ -1081,8 +1081,8 @@ mmap_init(struct impl *impl, struct port *port, } - d = buffers[i]->datas; - for(j = 0; j < buffers[i]->n_datas; ++j) { + spa_data *d = buffers[i]->datas; + for(uint32_t j = 0; j < buffers[i]->n_datas; ++j) { d[j].type = port->memtype; d[j].flags = SPA_DATA_FLAG_READABLE; d[j].mapoffset = 0; @@ -1123,7 +1123,7 @@ mmap_init(struct impl *impl, struct port *port, d[j].data = NULL; SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); } - else if(port->memtype == SPA_DATA_MemPtr) { + else if (port->memtype == SPA_DATA_MemPtr) { d[j].fd = -1; d[j].data = mmap(NULL, d[j].maxsize + d[j].mapoffset, @@ -1142,30 +1142,16 @@ mmap_init(struct impl *impl, struct port *port, return -EIO; } } + spa_libcamera_buffer_recycle(impl, port, i); } + port->n_buffers = n_buffers; spa_log_debug(impl->log, "we have %d buffers", n_buffers); return 0; } -static int -spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, - struct spa_buffer **buffers, - uint32_t n_buffers) -{ - int res; - - if (port->n_buffers > 0) - return -EIO; - - if ((res = mmap_init(impl, port, buffers, n_buffers)) < 0) - return res; - - return 0; -} - void impl::requestComplete(libcamera::Request *request) { From cb71071d93929f663cd17d790f42e70f5d86218a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:07:25 +0200 Subject: [PATCH 0562/1014] spa: libcamera: device: remove empty line --- spa/plugins/libcamera/libcamera-device.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index d744aed58..64ebe9edb 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -142,7 +142,6 @@ static int emit_info(struct impl *impl, bool full) if (!device_numbers.empty()) { std::ostringstream s; - /* encode device numbers into a json array */ s << "[ "; for (const auto& devid : device_numbers) From bb8223bff192fb831ff9b4c22aa16aa69349b1f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:18:24 +0200 Subject: [PATCH 0563/1014] spa: libcamera: use anon ns instead of `static` Move most things into anonymous namespaces for internal linkage instead of using `static`. This shortes declarations and makes it hard to forget. --- spa/plugins/libcamera/libcamera-device.cpp | 54 +++--- spa/plugins/libcamera/libcamera-manager.cpp | 89 ++++----- spa/plugins/libcamera/libcamera-source.cpp | 202 ++++++++++---------- 3 files changed, 173 insertions(+), 172 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 64ebe9edb..63afae727 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -50,9 +50,7 @@ struct impl { std::string device_id); }; -} - -static const libcamera::Span cameraDevice(const Camera& camera) +const libcamera::Span cameraDevice(const Camera& camera) { if (auto devices = camera.properties().get(properties::SystemDevices)) return devices.value(); @@ -60,7 +58,7 @@ static const libcamera::Span cameraDevice(const Camera& camera) return {}; } -static std::string cameraModel(const Camera& camera) +std::string cameraModel(const Camera& camera) { if (auto model = camera.properties().get(properties::Model)) return std::move(model.value()); @@ -68,7 +66,7 @@ static std::string cameraModel(const Camera& camera) return camera.id(); } -static const char *cameraLoc(const Camera& camera) +const char *cameraLoc(const Camera& camera) { if (auto location = camera.properties().get(properties::Location)) { switch (location.value()) { @@ -84,7 +82,7 @@ static const char *cameraLoc(const Camera& camera) return nullptr; } -static const char *cameraRot(const Camera& camera) +const char *cameraRot(const Camera& camera) { if (auto rotation = camera.properties().get(properties::Rotation)) { switch (rotation.value()) { @@ -102,7 +100,7 @@ static const char *cameraRot(const Camera& camera) return nullptr; } -static int emit_info(struct impl *impl, bool full) +int emit_info(struct impl *impl, bool full) { struct spa_dict_item items[10]; struct spa_dict dict; @@ -179,10 +177,10 @@ static int emit_info(struct impl *impl, bool full) return 0; } -static int impl_add_listener(void *object, - struct spa_hook *listener, - const struct spa_device_events *events, - void *data) +int impl_add_listener(void *object, + struct spa_hook *listener, + const struct spa_device_events *events, + void *data) { struct impl *impl = (struct impl*)object; struct spa_hook_list save; @@ -201,7 +199,7 @@ static int impl_add_listener(void *object, return res; } -static int impl_sync(void *object, int seq) +int impl_sync(void *object, int seq) { struct impl *impl = (struct impl*) object; @@ -212,21 +210,21 @@ static int impl_sync(void *object, int seq) return 0; } -static int impl_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) +int impl_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { return -ENOTSUP; } -static int impl_set_param(void *object, - uint32_t id, uint32_t flags, - const struct spa_pod *param) +int impl_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) { return -ENOTSUP; } -static const struct spa_device_methods impl_device = { +const struct spa_device_methods impl_device = { .version = SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, @@ -234,7 +232,7 @@ static const struct spa_device_methods impl_device = { .set_param = impl_set_param, }; -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; @@ -251,7 +249,7 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void return 0; } -static int impl_clear(struct spa_handle *handle) +int impl_clear(struct spa_handle *handle) { std::destroy_at(reinterpret_cast(handle)); return 0; @@ -277,14 +275,14 @@ impl::impl(spa_log *log, &impl_device, this); } -static size_t +size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } -static int +int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, @@ -320,13 +318,13 @@ impl_init(const struct spa_handle_factory *factory, return 0; } -static const struct spa_interface_info impl_interfaces[] = { +const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; -static int impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) +int impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); @@ -339,6 +337,8 @@ static int impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } +} + extern "C" { const struct spa_handle_factory spa_libcamera_device_factory = { SPA_VERSION_HANDLE_FACTORY, diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index d81d81540..97c2e0b74 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -72,27 +72,7 @@ struct impl { } }; -} - -std::shared_ptr libcamera_manager_acquire(int& res) -{ - static std::weak_ptr global_manager; - static std::mutex lock; - - std::lock_guard guard(lock); - - if (auto manager = global_manager.lock()) - return manager; - - auto manager = std::make_shared(); - if ((res = manager->start()) < 0) - return {}; - - global_manager = manager; - - return manager; -} -static uint32_t get_free_id(struct impl *impl) +uint32_t get_free_id(struct impl *impl) { for (std::size_t i = 0; i < MAX_DEVICES; i++) if (impl->devices[i].camera == nullptr) @@ -100,7 +80,7 @@ static uint32_t get_free_id(struct impl *impl) return 0; } -static struct device *add_device(struct impl *impl, std::shared_ptr camera) +struct device *add_device(struct impl *impl, std::shared_ptr camera) { struct device *device; uint32_t id; @@ -115,7 +95,7 @@ static struct device *add_device(struct impl *impl, std::shared_ptr came return device; } -static struct device *find_device(struct impl *impl, const Camera *camera) +struct device *find_device(struct impl *impl, const Camera *camera) { uint32_t i; for (i = 0; i < impl->n_devices; i++) { @@ -125,7 +105,7 @@ static struct device *find_device(struct impl *impl, const Camera *camera) return NULL; } -static void remove_device(struct impl *impl, struct device *device) +void remove_device(struct impl *impl, struct device *device) { uint32_t old = --impl->n_devices; device->camera.reset(); @@ -133,13 +113,13 @@ static void remove_device(struct impl *impl, struct device *device) impl->devices[old].camera = nullptr; } -static void clear_devices(struct impl *impl) +void clear_devices(struct impl *impl) { while (impl->n_devices > 0) impl->devices[--impl->n_devices].camera.reset(); } -static int emit_object_info(struct impl *impl, struct device *device) +int emit_object_info(struct impl *impl, struct device *device) { struct spa_device_object_info info; uint32_t id = device->id; @@ -169,7 +149,7 @@ static int emit_object_info(struct impl *impl, struct device *device) return 1; } -static void try_add_camera(struct impl *impl, std::shared_ptr camera) +void try_add_camera(struct impl *impl, std::shared_ptr camera) { struct device *device; @@ -184,7 +164,7 @@ static void try_add_camera(struct impl *impl, std::shared_ptr camera) emit_object_info(impl, device); } -static void try_remove_camera(struct impl *impl, const Camera *camera) +void try_remove_camera(struct impl *impl, const Camera *camera) { struct device *device; @@ -197,7 +177,7 @@ static void try_remove_camera(struct impl *impl, const Camera *camera) remove_device(impl, device); } -static void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) +void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) { auto& [ type, camera ] = event; @@ -213,7 +193,7 @@ static void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) } } -static void on_hotplug_event(void *data, std::uint64_t) +void on_hotplug_event(void *data, std::uint64_t) { auto impl = static_cast(data); @@ -256,13 +236,13 @@ void impl::removeCamera(std::shared_ptr camera) spa_loop_utils_signal_event(loop_utils, hotplug_event_source); } -static void start_monitor(struct impl *impl) +void start_monitor(struct impl *impl) { impl->manager->cameraAdded.connect(impl, &impl::addCamera); impl->manager->cameraRemoved.connect(impl, &impl::removeCamera); } -static int stop_monitor(struct impl *impl) +int stop_monitor(struct impl *impl) { if (impl->manager) { impl->manager->cameraAdded.disconnect(impl, &impl::addCamera); @@ -272,7 +252,7 @@ static int stop_monitor(struct impl *impl) return 0; } -static void collect_existing_devices(struct impl *impl) +void collect_existing_devices(struct impl *impl) { auto cameras = impl->manager->cameras(); @@ -280,12 +260,12 @@ static void collect_existing_devices(struct impl *impl) try_add_camera(impl, std::move(camera)); } -static const struct spa_dict_item device_info_items[] = { +const struct spa_dict_item device_info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, { SPA_KEY_DEVICE_NICK, "libcamera-manager" }, }; -static void emit_device_info(struct impl *impl, bool full) +void emit_device_info(struct impl *impl, bool full) { uint64_t old = full ? impl->info.change_mask : 0; if (full) @@ -299,7 +279,7 @@ static void emit_device_info(struct impl *impl, bool full) } } -static void impl_hook_removed(struct spa_hook *hook) +void impl_hook_removed(struct spa_hook *hook) { struct impl *impl = (struct impl*)hook->priv; if (spa_hook_list_is_empty(&impl->hooks)) { @@ -308,7 +288,7 @@ static void impl_hook_removed(struct spa_hook *hook) } } -static int +int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { @@ -344,12 +324,12 @@ impl_device_add_listener(void *object, struct spa_hook *listener, return 0; } -static const struct spa_device_methods impl_device = { +const struct spa_device_methods impl_device = { .version = SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; @@ -366,7 +346,7 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void return 0; } -static int impl_clear(struct spa_handle *handle) +int impl_clear(struct spa_handle *handle) { auto impl = reinterpret_cast(handle); @@ -392,14 +372,14 @@ impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_s &impl_device, this); } -static size_t +size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } -static int +int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, @@ -429,11 +409,11 @@ impl_init(const struct spa_handle_factory *factory, return 0; } -static const struct spa_interface_info impl_interfaces[] = { +const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; -static int +int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) @@ -449,6 +429,8 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } +} + extern "C" { const struct spa_handle_factory spa_libcamera_manager_factory = { SPA_VERSION_HANDLE_FACTORY, @@ -459,3 +441,22 @@ const struct spa_handle_factory spa_libcamera_manager_factory = { impl_enum_interface_info, }; } + +std::shared_ptr libcamera_manager_acquire(int& res) +{ + static std::weak_ptr global_manager; + static std::mutex lock; + + std::lock_guard guard(lock); + + if (auto manager = global_manager.lock()) + return manager; + + auto manager = std::make_shared(); + if ((res = manager->start()) < 0) + return {}; + + global_manager = manager; + + return manager; +} diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 681cc8180..26d448a71 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -175,14 +175,12 @@ struct impl { struct spa_dll dll; }; -} - #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) #define GET_OUT_PORT(impl,p) (&impl->out_ports[p]) #define GET_PORT(impl,d,p) GET_OUT_PORT(impl,p) -static void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) +void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) { /* Libcamera recommends cameras default to manual focus mode, but we don't * expose any focus controls. So, specifically enable autofocus on @@ -243,7 +241,7 @@ int spa_libcamera_close(struct impl *impl) return 0; } -static void spa_libcamera_get_config(struct impl *impl) +void spa_libcamera_get_config(struct impl *impl) { if (impl->config) return; @@ -251,7 +249,7 @@ static void spa_libcamera_get_config(struct impl *impl) impl->config = impl->camera->generateConfiguration({ StreamRole::VideoRecording }); } -static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) +int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; int res; @@ -289,7 +287,7 @@ static int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, ui return 0; } -static int allocBuffers(struct impl *impl, struct port *port, unsigned int count) +int allocBuffers(struct impl *impl, struct port *port, unsigned int count) { int res; @@ -331,14 +329,14 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count return res; } -static void freeBuffers(struct impl *impl, struct port *port) +void freeBuffers(struct impl *impl, struct port *port) { impl->pendingRequests.clear(); impl->requestPool.clear(); impl->allocator->free(port->streamConfig.stream()); } -static int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) +int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) { uint32_t i; @@ -381,7 +379,7 @@ struct format_info { }; #define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } -static const struct format_info format_info[] = { +const struct format_info format_info[] = { /* RGB formats */ MAKE_FMT(formats::RGB565, RGB16, video, raw), MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), @@ -415,7 +413,7 @@ static const struct format_info format_info[] = { #undef MAKE_FMT }; -static const struct format_info *video_format_to_info(const PixelFormat &pix) +const struct format_info *video_format_to_info(const PixelFormat &pix) { for (const auto& f : format_info) { if (f.pix == pix) @@ -425,7 +423,7 @@ static const struct format_info *video_format_to_info(const PixelFormat &pix) return nullptr; } -static const struct format_info *find_format_info_by_media_type( +const struct format_info *find_format_info_by_media_type( uint32_t type, uint32_t subtype, uint32_t format) { for (const auto& f : format_info) { @@ -437,7 +435,7 @@ static const struct format_info *find_format_info_by_media_type( return nullptr; } -static int score_size(const Size &a, const Size &b) +int score_size(const Size &a, const Size &b) { int x, y; x = (int)a.width - (int)b.width; @@ -445,7 +443,7 @@ static int score_size(const Size &a, const Size &b) return x * x + y * y; } -static void +void parse_colorimetry(const ColorSpace& colorspace, struct spa_video_colorimetry *colorimetry) { @@ -501,7 +499,7 @@ parse_colorimetry(const ColorSpace& colorspace, } } -static int +int spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { @@ -641,7 +639,7 @@ next_fmt: return 0; } -static int spa_libcamera_set_format(struct impl *impl, struct port *port, +int spa_libcamera_set_format(struct impl *impl, struct port *port, struct spa_video_info *format, bool try_only) { const struct format_info *info = NULL; @@ -720,7 +718,7 @@ error: } -static const struct { +const struct { uint32_t id; uint32_t spa_id; } control_map[] = { @@ -732,7 +730,7 @@ static const struct { { libcamera::controls::SHARPNESS, SPA_PROP_sharpness }, }; -static uint32_t control_to_prop_id(uint32_t control_id) +uint32_t control_to_prop_id(uint32_t control_id) { for (const auto& c : control_map) { if (c.id == control_id) @@ -742,7 +740,7 @@ static uint32_t control_to_prop_id(uint32_t control_id) return SPA_PROP_START_CUSTOM + control_id; } -static uint32_t prop_id_to_control(uint32_t prop_id) +uint32_t prop_id_to_control(uint32_t prop_id) { for (const auto& c : control_map) { if (c.spa_id == prop_id) @@ -755,7 +753,7 @@ static uint32_t prop_id_to_control(uint32_t prop_id) return SPA_ID_INVALID; } -static int +int spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t offset, uint32_t num, const struct spa_pod *filter) @@ -870,12 +868,12 @@ struct val { }; }; -static int do_update_ctrls(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) +int do_update_ctrls(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) { struct impl *impl = (struct impl *)user_data; const struct val *d = (const struct val *)data; @@ -895,7 +893,7 @@ static int do_update_ctrls(struct spa_loop *loop, return 0; } -static int +int spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) { const ControlInfoMap &info = impl->camera->controls(); @@ -941,7 +939,7 @@ done: } -static void libcamera_on_fd_events(struct spa_source *source) +void libcamera_on_fd_events(struct spa_source *source) { struct impl *impl = (struct impl*) source->data; struct spa_io_buffers *io; @@ -998,13 +996,13 @@ static void libcamera_on_fd_events(struct spa_source *source) spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); } -static int spa_libcamera_use_buffers(struct impl *impl, struct port *port, +int spa_libcamera_use_buffers(struct impl *impl, struct port *port, struct spa_buffer **buffers, uint32_t n_buffers) { return -ENOTSUP; } -static const struct { +const struct { Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */ uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */ } orientation_map[] = { @@ -1018,7 +1016,7 @@ static const struct { { Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 }, }; -static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation) +uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation) { for (const auto& t : orientation_map) { if (t.libcamera_orientation == orientation) @@ -1027,7 +1025,7 @@ static uint32_t libcamera_orientation_to_spa_transform_value(Orientation orienta return SPA_META_TRANSFORMATION_None; } -static int +int spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, struct spa_buffer **buffers, uint32_t n_buffers) @@ -1226,7 +1224,7 @@ void impl::requestComplete(libcamera::Request *request) } -static int spa_libcamera_stream_on(struct impl *impl) +int spa_libcamera_stream_on(struct impl *impl) { struct port *port = &impl->out_ports[0]; int res; @@ -1276,12 +1274,12 @@ error: return res == -EACCES ? -EBUSY : res; } -static int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) +int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) { struct impl *impl = (struct impl *)user_data; if (impl->source.loop) @@ -1289,7 +1287,7 @@ static int do_remove_source(struct spa_loop *loop, return 0; } -static int spa_libcamera_stream_off(struct impl *impl) +int spa_libcamera_stream_off(struct impl *impl) { struct port *port = &impl->out_ports[0]; int res; @@ -1322,11 +1320,11 @@ static int spa_libcamera_stream_off(struct impl *impl) return 0; } -static int port_get_format(struct impl *impl, struct port *port, - uint32_t index, - const struct spa_pod *filter, - struct spa_pod **param, - struct spa_pod_builder *builder) +int port_get_format(struct impl *impl, struct port *port, + uint32_t index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) { struct spa_pod_frame f; @@ -1371,9 +1369,9 @@ static int port_get_format(struct impl *impl, struct port *port, return 1; } -static int impl_node_enum_params(void *object, int seq, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) +int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { struct impl *impl = (struct impl*)object; struct spa_pod *param; @@ -1454,9 +1452,9 @@ next: return 0; } -static int impl_node_set_param(void *object, - uint32_t id, uint32_t flags, - const struct spa_pod *param) +int impl_node_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) { struct impl *impl = (struct impl*)object; @@ -1495,7 +1493,7 @@ static int impl_node_set_param(void *object, return 0; } -static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *impl = (struct impl*)object; @@ -1516,7 +1514,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } -static int impl_node_send_command(void *object, const struct spa_command *command) +int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *impl = (struct impl*)object; int res; @@ -1550,7 +1548,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; } -static void emit_node_info(struct impl *impl, bool full) +void emit_node_info(struct impl *impl, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, @@ -1569,7 +1567,7 @@ static void emit_node_info(struct impl *impl, bool full) } } -static void emit_port_info(struct impl *impl, struct port *port, bool full) +void emit_port_info(struct impl *impl, struct port *port, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_PORT_GROUP, "stream.0" }, @@ -1586,7 +1584,7 @@ static void emit_port_info(struct impl *impl, struct port *port, bool full) } } -static int +int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, @@ -1607,9 +1605,9 @@ impl_node_add_listener(void *object, return 0; } -static int impl_node_set_callbacks(void *object, - const struct spa_node_callbacks *callbacks, - void *data) +int impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) { struct impl *impl = (struct impl*)object; @@ -1620,7 +1618,7 @@ static int impl_node_set_callbacks(void *object, return 0; } -static int impl_node_sync(void *object, int seq) +int impl_node_sync(void *object, int seq) { struct impl *impl = (struct impl*)object; @@ -1631,25 +1629,25 @@ static int impl_node_sync(void *object, int seq) return 0; } -static int impl_node_add_port(void *object, - enum spa_direction direction, - uint32_t port_id, const struct spa_dict *props) +int impl_node_add_port(void *object, + enum spa_direction direction, + uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } -static int impl_node_remove_port(void *object, - enum spa_direction direction, - uint32_t port_id) +int impl_node_remove_port(void *object, + enum spa_direction direction, + uint32_t port_id) { return -ENOTSUP; } -static int impl_node_port_enum_params(void *object, int seq, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, uint32_t start, uint32_t num, - const struct spa_pod *filter) +int impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) { struct impl *impl = (struct impl*)object; @@ -1771,8 +1769,8 @@ next: return 0; } -static int port_set_format(struct impl *impl, struct port *port, - uint32_t flags, const struct spa_pod *format) +int port_set_format(struct impl *impl, struct port *port, + uint32_t flags, const struct spa_pod *format) { struct spa_video_info info; int res; @@ -1869,10 +1867,10 @@ static int port_set_format(struct impl *impl, struct port *port, return 0; } -static int impl_node_port_set_param(void *object, - enum spa_direction direction, uint32_t port_id, - uint32_t id, uint32_t flags, - const struct spa_pod *param) +int impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) { struct impl *impl = (struct impl*)object; struct port *port; @@ -1893,12 +1891,12 @@ static int impl_node_port_set_param(void *object, return res; } -static int impl_node_port_use_buffers(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t flags, - struct spa_buffer **buffers, - uint32_t n_buffers) +int impl_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) { struct impl *impl = (struct impl*)object; struct port *port; @@ -1929,11 +1927,11 @@ static int impl_node_port_use_buffers(void *object, return res; } -static int impl_node_port_set_io(void *object, - enum spa_direction direction, - uint32_t port_id, - uint32_t id, - void *data, size_t size) +int impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) { struct impl *impl = (struct impl*)object; struct port *port; @@ -1956,9 +1954,9 @@ static int impl_node_port_set_io(void *object, return 0; } -static int impl_node_port_reuse_buffer(void *object, - uint32_t port_id, - uint32_t buffer_id) +int impl_node_port_reuse_buffer(void *object, + uint32_t port_id, + uint32_t buffer_id) { struct impl *impl = (struct impl*)object; struct port *port; @@ -1976,7 +1974,7 @@ static int impl_node_port_reuse_buffer(void *object, return res; } -static int process_control(struct impl *impl, struct spa_pod_sequence *control) +int process_control(struct impl *impl, struct spa_pod_sequence *control) { struct spa_pod_control *c; @@ -1999,7 +1997,7 @@ static int process_control(struct impl *impl, struct spa_pod_sequence *control) return 0; } -static int impl_node_process(void *object) +int impl_node_process(void *object) { struct impl *impl = (struct impl*)object; int res; @@ -2045,7 +2043,7 @@ static int impl_node_process(void *object) return SPA_STATUS_HAVE_DATA; } -static const struct spa_node_methods impl_node = { +const struct spa_node_methods impl_node = { .version = SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, @@ -2064,7 +2062,7 @@ static const struct spa_node_methods impl_node = { .process = impl_node_process, }; -static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; @@ -2081,7 +2079,7 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void return 0; } -static int impl_clear(struct spa_handle *handle) +int impl_clear(struct spa_handle *handle) { std::destroy_at(reinterpret_cast(handle)); return 0; @@ -2121,14 +2119,14 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); } -static size_t +size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } -static int +int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, @@ -2177,13 +2175,13 @@ impl_init(const struct spa_handle_factory *factory, return 0; } -static const struct spa_interface_info impl_interfaces[] = { +const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; -static int impl_enum_interface_info(const struct spa_handle_factory *factory, - const struct spa_interface_info **info, - uint32_t *index) +int impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); @@ -2196,6 +2194,8 @@ static int impl_enum_interface_info(const struct spa_handle_factory *factory, return 1; } +} + extern "C" { const struct spa_handle_factory spa_libcamera_source_factory = { SPA_VERSION_HANDLE_FACTORY, From 4fa11619a2d03babf499492e8f8c66980047e7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:25:19 +0200 Subject: [PATCH 0564/1014] spa: libcamera: use C++ style casts --- spa/plugins/libcamera/libcamera-device.cpp | 4 +--- spa/plugins/libcamera/libcamera-manager.cpp | 4 +--- spa/plugins/libcamera/libcamera-source.cpp | 26 ++++++++++----------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 63afae727..40f314f1e 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -234,13 +234,11 @@ const struct spa_device_methods impl_device = { int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - struct impl *impl; + auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); - impl = (struct impl *) handle; - if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; else diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index 97c2e0b74..ea254fa74 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -331,13 +331,11 @@ const struct spa_device_methods impl_device = { int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - struct impl *impl; + auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); - impl = (struct impl *) handle; - if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; else diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 26d448a71..c31d55b9e 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -875,8 +875,9 @@ int do_update_ctrls(struct spa_loop *loop, size_t size, void *user_data) { - struct impl *impl = (struct impl *)user_data; - const struct val *d = (const struct val *)data; + auto *impl = static_cast(user_data); + const auto *d = static_cast(data); + switch (d->type) { case ControlTypeBool: impl->ctrls.set(d->id, d->b_val); @@ -1281,7 +1282,7 @@ int do_remove_source(struct spa_loop *loop, size_t size, void *user_data) { - struct impl *impl = (struct impl *)user_data; + auto *impl = static_cast(user_data); if (impl->source.loop) spa_loop_remove_source(loop, &impl->source); return 0; @@ -1456,15 +1457,15 @@ int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { - struct impl *impl = (struct impl*)object; + auto *impl = static_cast(object); spa_return_val_if_fail(impl != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { - struct spa_pod_object *obj = (struct spa_pod_object *) param; - struct spa_pod_prop *prop; + const auto *obj = reinterpret_cast(param); + const struct spa_pod_prop *prop; if (param == NULL) { impl->device_id.clear(); @@ -1476,8 +1477,9 @@ int impl_node_set_param(void *object, switch (prop->key) { case SPA_PROP_device: - strncpy(device, (char *)SPA_POD_CONTENTS(struct spa_pod_string, &prop->value), - sizeof(device)-1); + strncpy(device, + static_cast(SPA_POD_CONTENTS(struct spa_pod_string, &prop->value)), + sizeof(device) - 1); impl->device_id = device; break; default: @@ -1982,8 +1984,8 @@ int process_control(struct impl *impl, struct spa_pod_sequence *control) switch (c->type) { case SPA_CONTROL_Properties: { - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) &c->value; + const auto *obj = reinterpret_cast(&c->value); + const struct spa_pod_prop *prop; SPA_POD_OBJECT_FOREACH(obj, prop) { spa_libcamera_set_control(impl, prop); @@ -2064,13 +2066,11 @@ const struct spa_node_methods impl_node = { int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - struct impl *impl; + auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); - impl = (struct impl *) handle; - if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &impl->node; else From db3d91ebeb2b3c6e223b9e8aa318113e06824a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:29:04 +0200 Subject: [PATCH 0565/1014] spa: libcamera: use `nullptr` instead of `NULL` --- spa/plugins/libcamera/libcamera-device.cpp | 24 +++---- spa/plugins/libcamera/libcamera-manager.cpp | 32 ++++----- spa/plugins/libcamera/libcamera-source.cpp | 74 ++++++++++----------- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 40f314f1e..49ef957e1 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -186,8 +186,8 @@ int impl_add_listener(void *object, struct spa_hook_list save; int res = 0; - spa_return_val_if_fail(impl != NULL, -EINVAL); - spa_return_val_if_fail(events != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); + spa_return_val_if_fail(events != nullptr, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); @@ -203,9 +203,9 @@ int impl_sync(void *object, int seq) { struct impl *impl = (struct impl*) object; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); - spa_device_emit_result(&impl->hooks, seq, 0, 0, NULL); + spa_device_emit_result(&impl->hooks, seq, 0, 0, nullptr); return 0; } @@ -236,8 +236,8 @@ int impl_get_interface(struct spa_handle *handle, const char *type, void **inter { auto *impl = reinterpret_cast(handle); - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); + spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; @@ -290,8 +290,8 @@ impl_init(const struct spa_handle_factory *factory, const char *str; int res; - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); @@ -324,9 +324,9 @@ int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(info != nullptr, -EINVAL); + spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; @@ -341,7 +341,7 @@ extern "C" { const struct spa_handle_factory spa_libcamera_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_DEVICE, - NULL, + nullptr, impl_get_size, impl_init, impl_enum_interface_info, diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index ea254fa74..4cac76fb2 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -86,7 +86,7 @@ struct device *add_device(struct impl *impl, std::shared_ptr camera) uint32_t id; if (impl->n_devices >= MAX_DEVICES) - return NULL; + return nullptr; id = get_free_id(impl); device = &impl->devices[id]; device->id = id; @@ -102,7 +102,7 @@ struct device *find_device(struct impl *impl, const Camera *camera) if (impl->devices[i].camera.get() == camera) return &impl->devices[i]; } - return NULL; + return nullptr; } void remove_device(struct impl *impl, struct device *device) @@ -153,10 +153,10 @@ void try_add_camera(struct impl *impl, std::shared_ptr camera) { struct device *device; - if ((device = find_device(impl, camera.get())) != NULL) + if ((device = find_device(impl, camera.get())) != nullptr) return; - if ((device = add_device(impl, std::move(camera))) == NULL) + if ((device = add_device(impl, std::move(camera))) == nullptr) return; spa_log_info(impl->log, "camera added: id:%d %s", device->id, @@ -168,12 +168,12 @@ void try_remove_camera(struct impl *impl, const Camera *camera) { struct device *device; - if ((device = find_device(impl, camera)) == NULL) + if ((device = find_device(impl, camera)) == nullptr) return; spa_log_info(impl->log, "camera removed: id:%d %s", device->id, device->camera->id().c_str()); - spa_device_emit_object_info(&impl->hooks, device->id, NULL); + spa_device_emit_object_info(&impl->hooks, device->id, nullptr); remove_device(impl, device); } @@ -297,8 +297,8 @@ impl_device_add_listener(void *object, struct spa_hook *listener, struct spa_hook_list save; bool had_manager = !!impl->manager; - spa_return_val_if_fail(impl != NULL, -EINVAL); - spa_return_val_if_fail(events != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); + spa_return_val_if_fail(events != nullptr, -EINVAL); if (!impl->manager && !(impl->manager = libcamera_manager_acquire(res))) return res; @@ -333,8 +333,8 @@ int impl_get_interface(struct spa_handle *handle, const char *type, void **inter { auto *impl = reinterpret_cast(handle); - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); + spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; @@ -384,8 +384,8 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); @@ -416,9 +416,9 @@ impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(info != nullptr, -EINVAL); + spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; @@ -433,7 +433,7 @@ extern "C" { const struct spa_handle_factory spa_libcamera_manager_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_ENUM_MANAGER, - NULL, + nullptr, impl_get_size, impl_init, impl_enum_interface_info, diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index c31d55b9e..d11cd678c 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -537,7 +537,7 @@ next_fmt: spa_log_debug(impl->log, "format: %s", format.toString().c_str()); const auto *info = video_format_to_info(format); - if (info == NULL) { + if (info == nullptr) { spa_log_debug(impl->log, "unknown format"); port->fmt_index++; goto next_fmt; @@ -642,10 +642,10 @@ next_fmt: int spa_libcamera_set_format(struct impl *impl, struct port *port, struct spa_video_info *format, bool try_only) { - const struct format_info *info = NULL; + const struct format_info *info = nullptr; uint32_t video_format; - struct spa_rectangle *size = NULL; - struct spa_fraction *framerate = NULL; + struct spa_rectangle *size = nullptr; + struct spa_fraction *framerate = nullptr; CameraConfiguration::Status validation; int res; @@ -673,7 +673,7 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port, info = find_format_info_by_media_type(format->media_type, format->media_subtype, video_format); - if (info == NULL || size == NULL || framerate == NULL) { + if (info == nullptr || size == nullptr || framerate == nullptr) { spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type, format->media_subtype, video_format); return -EINVAL; @@ -977,7 +977,7 @@ void libcamera_on_fd_events(struct spa_source *source) spa_list_append(&port->queue, &b->link); io = port->io; - if (io == NULL) { + if (io == nullptr) { b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); @@ -1119,12 +1119,12 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, d[j].flags |= SPA_DATA_FLAG_MAPPABLE; d[j].fd = bufs[i]->planes()[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); - d[j].data = NULL; + d[j].data = nullptr; SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); } else if (port->memtype == SPA_DATA_MemPtr) { d[j].fd = -1; - d[j].data = mmap(NULL, + d[j].data = mmap(nullptr, d[j].maxsize + d[j].mapoffset, PROT_READ, MAP_SHARED, bufs[i]->planes()[j].fd.get(), @@ -1310,7 +1310,7 @@ int spa_libcamera_stream_off(struct impl *impl) impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - spa_loop_locked(impl->data_loop, do_remove_source, 0, NULL, 0, impl); + spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); if (impl->source.fd >= 0) { spa_system_close(impl->system, impl->source.fd); impl->source.fd = -1; @@ -1382,7 +1382,7 @@ int impl_node_enum_params(void *object, int seq, uint32_t count = 0; int res; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; @@ -1459,7 +1459,7 @@ int impl_node_set_param(void *object, { auto *impl = static_cast(object); - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); switch (id) { case SPA_PARAM_Props: @@ -1467,7 +1467,7 @@ int impl_node_set_param(void *object, const auto *obj = reinterpret_cast(param); const struct spa_pod_prop *prop; - if (param == NULL) { + if (param == nullptr) { impl->device_id.clear(); impl->device_name.clear(); return 0; @@ -1499,7 +1499,7 @@ int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *impl = (struct impl*)object; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); switch (id) { case SPA_IO_Clock: @@ -1521,8 +1521,8 @@ int impl_node_send_command(void *object, const struct spa_command *command) struct impl *impl = (struct impl*)object; int res; - spa_return_val_if_fail(impl != NULL, -EINVAL); - spa_return_val_if_fail(command != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); + spa_return_val_if_fail(command != nullptr, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: @@ -1595,7 +1595,7 @@ impl_node_add_listener(void *object, struct impl *impl = (struct impl*)object; struct spa_hook_list save; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); @@ -1613,7 +1613,7 @@ int impl_node_set_callbacks(void *object, { struct impl *impl = (struct impl*)object; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); impl->callbacks = SPA_CALLBACKS_INIT(callbacks, data); @@ -1624,9 +1624,9 @@ int impl_node_sync(void *object, int seq) { struct impl *impl = (struct impl*)object; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); - spa_node_emit_result(&impl->hooks, seq, 0, 0, NULL); + spa_node_emit_result(&impl->hooks, seq, 0, 0, nullptr); return 0; } @@ -1661,7 +1661,7 @@ int impl_node_port_enum_params(void *object, int seq, uint32_t count = 0; int res; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); @@ -1777,7 +1777,7 @@ int port_set_format(struct impl *impl, struct port *port, struct spa_video_info info; int res; - if (format == NULL) { + if (format == nullptr) { if (!port->current_format) return 0; @@ -1840,7 +1840,7 @@ int port_set_format(struct impl *impl, struct port *port, } if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { - spa_libcamera_use_buffers(impl, port, NULL, 0); + spa_libcamera_use_buffers(impl, port, nullptr, 0); port->current_format.reset(); } @@ -1878,7 +1878,7 @@ int impl_node_port_set_param(void *object, struct port *port; int res; - spa_return_val_if_fail(object != NULL, -EINVAL); + spa_return_val_if_fail(object != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); @@ -1904,7 +1904,7 @@ int impl_node_port_use_buffers(void *object, struct port *port; int res; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); @@ -1918,7 +1918,7 @@ int impl_node_port_use_buffers(void *object, return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; - if (buffers == NULL) + if (buffers == nullptr) return 0; if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { @@ -1938,7 +1938,7 @@ int impl_node_port_set_io(void *object, struct impl *impl = (struct impl*)object; struct port *port; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); @@ -1964,7 +1964,7 @@ int impl_node_port_reuse_buffer(void *object, struct port *port; int res; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = GET_OUT_PORT(impl, port_id); @@ -2007,10 +2007,10 @@ int impl_node_process(void *object) struct port *port; struct buffer *b; - spa_return_val_if_fail(impl != NULL, -EINVAL); + spa_return_val_if_fail(impl != nullptr, -EINVAL); port = GET_OUT_PORT(impl, 0); - if ((io = port->io) == NULL) + if ((io = port->io) == nullptr) return -EIO; if (port->control) @@ -2068,8 +2068,8 @@ int impl_get_interface(struct spa_handle *handle, const char *type, void **inter { auto *impl = reinterpret_cast(handle); - spa_return_val_if_fail(handle != NULL, -EINVAL); - spa_return_val_if_fail(interface != NULL, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); + spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &impl->node; @@ -2136,8 +2136,8 @@ impl_init(const struct spa_handle_factory *factory, const char *str; int res; - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); auto data_loop = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop)); @@ -2183,9 +2183,9 @@ int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { - spa_return_val_if_fail(factory != NULL, -EINVAL); - spa_return_val_if_fail(info != NULL, -EINVAL); - spa_return_val_if_fail(index != NULL, -EINVAL); + spa_return_val_if_fail(factory != nullptr, -EINVAL); + spa_return_val_if_fail(info != nullptr, -EINVAL); + spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; @@ -2200,7 +2200,7 @@ extern "C" { const struct spa_handle_factory spa_libcamera_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_SOURCE, - NULL, + nullptr, impl_get_size, impl_init, impl_enum_interface_info, From 2c2808fab1ae27284b51cfabe644836f5b4a93f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 12 Jul 2025 19:38:24 +0200 Subject: [PATCH 0566/1014] spa: libcamera: manager: fix id allocation There is an issue in the id allocation mechanism which can result in the different devices having the same id. Specifically, consider the scenario where there are only two cameras, which have just been added. In this case `impl::devices` looks like this: (0, camA) | (1, camB) | (?, nullptr) | ... Now assume that `camA` is removed, after which the array appears as follows: (1, camB) | (1, nullptr) | (?, nullptr) | ... Then assume that a new camera appears. When `get_free_id()` runs, when `i == 1`, it will observe that `devices[i].camera == nullptr`, so it selects `1` as the id. Leading to the following: (1, camB) | (1, camC) | (?, nullptr) | ... This is of course incorrect. The set of ids must be unique. When wireplumber is faced with this situation it destroys the device object for `camB` when `camC` is emitted. Fix this by simply not moving elements in the `devices` array, leaving everything where it is. In which case the array looks like this: (nullptr) | (camB) | (nullptr) | ... // after `camA` removal (camC) | (camB) | (nullptr) | ... // after `camC` appearance Note that `device::id` is removed, and the id is now derived from the position in `impl::devices`. --- spa/plugins/libcamera/libcamera-manager.cpp | 80 ++++++++++----------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index 4cac76fb2..eda1442ee 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -3,6 +3,7 @@ /* SPDX-License-Identifier: MIT */ #include +#include #include #include #include @@ -27,16 +28,15 @@ using namespace libcamera; #include "libcamera.h" #include "libcamera-manager.hpp" -#define MAX_DEVICES 64 - namespace { struct device { - uint32_t id; std::shared_ptr camera; }; struct impl { + static constexpr std::size_t max_devices = 64; + struct spa_handle handle; struct spa_device device = {}; @@ -52,8 +52,7 @@ struct impl { void addCamera(std::shared_ptr camera); void removeCamera(std::shared_ptr camera); - struct device devices[MAX_DEVICES]; - uint32_t n_devices = 0; + struct device devices[max_devices]; struct hotplug_event { enum class type { add, remove } type; @@ -70,59 +69,50 @@ struct impl { { spa_loop_utils_destroy_source(loop_utils, hotplug_event_source); } -}; -uint32_t get_free_id(struct impl *impl) -{ - for (std::size_t i = 0; i < MAX_DEVICES; i++) - if (impl->devices[i].camera == nullptr) - return i; - return 0; -} + std::uint32_t id_of(const struct device& d) const + { + spa_assert(std::begin(devices) <= &d && &d < std::end(devices)); + return &d - std::begin(devices); + } +}; struct device *add_device(struct impl *impl, std::shared_ptr camera) { - struct device *device; - uint32_t id; + for (auto& d : impl->devices) { + if (!d.camera) { + d.camera = std::move(camera); + return &d; + } + } - if (impl->n_devices >= MAX_DEVICES) - return nullptr; - id = get_free_id(impl); - device = &impl->devices[id]; - device->id = id; - device->camera = std::move(camera); - impl->n_devices++; - return device; + return nullptr; } struct device *find_device(struct impl *impl, const Camera *camera) { - uint32_t i; - for (i = 0; i < impl->n_devices; i++) { - if (impl->devices[i].camera.get() == camera) - return &impl->devices[i]; + for (auto& d : impl->devices) { + if (d.camera.get() == camera) + return &d; } + return nullptr; } void remove_device(struct impl *impl, struct device *device) { - uint32_t old = --impl->n_devices; - device->camera.reset(); - *device = std::move(impl->devices[old]); - impl->devices[old].camera = nullptr; + *device = {}; } void clear_devices(struct impl *impl) { - while (impl->n_devices > 0) - impl->devices[--impl->n_devices].camera.reset(); + for (auto& d : impl->devices) + d = {}; } -int emit_object_info(struct impl *impl, struct device *device) +int emit_object_info(struct impl *impl, const struct device *device) { struct spa_device_object_info info; - uint32_t id = device->id; struct spa_dict_item items[20]; struct spa_dict dict; uint32_t n_items = 0; @@ -144,7 +134,7 @@ int emit_object_info(struct impl *impl, struct device *device) dict = SPA_DICT_INIT(items, n_items); info.props = &dict; - spa_device_emit_object_info(&impl->hooks, id, &info); + spa_device_emit_object_info(&impl->hooks, impl->id_of(*device), &info); return 1; } @@ -159,8 +149,8 @@ void try_add_camera(struct impl *impl, std::shared_ptr camera) if ((device = add_device(impl, std::move(camera))) == nullptr) return; - spa_log_info(impl->log, "camera added: id:%d %s", device->id, - device->camera->id().c_str()); + spa_log_info(impl->log, "camera added: id:%" PRIu32 " %s", + impl->id_of(*device), device->camera->id().c_str()); emit_object_info(impl, device); } @@ -171,9 +161,11 @@ void try_remove_camera(struct impl *impl, const Camera *camera) if ((device = find_device(impl, camera)) == nullptr) return; - spa_log_info(impl->log, "camera removed: id:%d %s", device->id, - device->camera->id().c_str()); - spa_device_emit_object_info(&impl->hooks, device->id, nullptr); + auto id = impl->id_of(*device); + + spa_log_info(impl->log, "camera removed: id:%" PRIu32 " %s", + id, device->camera->id().c_str()); + spa_device_emit_object_info(&impl->hooks, id, nullptr); remove_device(impl, device); } @@ -308,8 +300,10 @@ impl_device_add_listener(void *object, struct spa_hook *listener, emit_device_info(impl, true); if (had_manager) { - for (std::size_t i = 0; i < impl->n_devices; i++) - emit_object_info(impl, &impl->devices[i]); + for (const auto& d : impl->devices) { + if (d.camera) + emit_object_info(impl, &d); + } } else { collect_existing_devices(impl); From 5bfc3e6b038ac4ec9de604bd10717bdef8b41915 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 10 Jul 2025 11:10:25 -0400 Subject: [PATCH 0567/1014] spa: v4l2: avoid integer wraparound to out of bounds read I'm not sure if untrusted input can reach this point. --- spa/plugins/v4l2/v4l2-utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index 96a5d73ed..e607e81d5 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -399,7 +399,7 @@ enum_filter_format(uint32_t media_type, int32_t media_subtype, if (index == 0) video_format = values[0]; } else { - if (index + 1 < n_values) + if (index < n_values - 1) video_format = values[index + 1]; } } else { From 7ac94f1a69609db6362b8dd29f70b83ec086e972 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 10 Jul 2025 12:08:37 -0400 Subject: [PATCH 0568/1014] pod: ensure strings are NUL-terminated before calling strlen() SPA_TYPE_String needs to be NUL-terminated, so check that in spa_pod_compare_value(). --- spa/include/spa/pod/compare.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 1c043927e..84cc3eafd 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -63,7 +63,9 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con return -EINVAL; return SPA_CMP(*(double *)r1, *(double *)r2); case SPA_TYPE_String: - if (size < sizeof(char)) + if (size < sizeof(char) || + ((char *)r1)[size - 1] || + ((char *)r2)[size - 1]) return -EINVAL; return strcmp((char *)r1, (char *)r2); case SPA_TYPE_Rectangle: From fb315b905062149083ea6572d9a50d145e88a74e Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 6 Jun 2025 14:48:33 -0400 Subject: [PATCH 0569/1014] *: Missing bounds checks in POD handling There were missing bounds checks for ill-formed POD all over the place. --- spa/include/spa/debug/format.h | 22 ++++++++++ spa/include/spa/pod/compare.h | 12 +++++- spa/include/spa/pod/simplify.h | 5 +-- .../videoconvert/videoconvert-ffmpeg.c | 43 +++++++++++++++---- src/modules/module-protocol-pulse/collect.c | 7 ++- src/modules/module-protocol-pulse/format.c | 2 +- src/tools/pw-dump.c | 32 ++++++++++++-- 7 files changed, 104 insertions(+), 19 deletions(-) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 856e9954c..4abd36b1e 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -39,10 +39,14 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i switch (type) { case SPA_TYPE_Bool: + if (size < sizeof(int32_t)) + goto bad_body; spa_strbuf_append(buffer, "%s", *(int32_t *) body ? "true" : "false"); break; case SPA_TYPE_Id: { + if (size < sizeof(uint32_t)) + goto bad_body; uint32_t value = *(uint32_t *) body; const char *str = spa_debug_type_find_short_name(info, value); char tmp[64]; @@ -54,28 +58,42 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i break; } case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + goto bad_body; spa_strbuf_append(buffer, "%d", *(int32_t *) body); break; case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + goto bad_body; spa_strbuf_append(buffer, "%" PRIi64, *(int64_t *) body); break; case SPA_TYPE_Float: + if (size < sizeof(float)) + goto bad_body; spa_strbuf_append(buffer, "%f", *(float *) body); break; case SPA_TYPE_Double: + if (size < sizeof(double)) + goto bad_body; spa_strbuf_append(buffer, "%f", *(double *) body); break; case SPA_TYPE_String: + if (size < 1 || ((const char *)body)[size - 1] != '\0') + goto bad_body; spa_strbuf_append(buffer, "%s", (char *) body); break; case SPA_TYPE_Rectangle: { + if (size < sizeof(struct spa_rectangle)) + goto bad_body; struct spa_rectangle *r = (struct spa_rectangle *)body; spa_strbuf_append(buffer, "%" PRIu32 "x%" PRIu32, r->width, r->height); break; } case SPA_TYPE_Fraction: { + if (size < sizeof(struct spa_fraction)) + goto bad_body; struct spa_fraction *f = (struct spa_fraction *)body; spa_strbuf_append(buffer, "%" PRIu32 "/%" PRIu32, f->num, f->denom); break; @@ -91,6 +109,8 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i void *p; struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; int i = 0; + if (size < sizeof(*b)) + goto bad_body; info = info && info->values ? info->values : info; spa_strbuf_append(buffer, "< "); SPA_POD_ARRAY_BODY_FOREACH(b, size, p) { @@ -105,6 +125,8 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i spa_strbuf_append(buffer, "INVALID type %d", type); break; } +bad_body: + spa_strbuf_append(buffer, "INVALID BODY type %d", type); return 0; } diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 84cc3eafd..6ac6b0799 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -186,8 +186,12 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_compatible_flags(uint32_t type, const { switch (type) { case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + return -EINVAL; return ((*(int32_t *) r1) & (*(int32_t *) r2)) != 0; case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + return -EINVAL; return ((*(int64_t *) r1) & (*(int64_t *) r2)) != 0; default: return -ENOTSUP; @@ -197,18 +201,24 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_compatible_flags(uint32_t type, const SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1, - const void *r2, uint32_t size SPA_UNUSED) + const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + return -EINVAL; return *(int32_t *) r1 % *(int32_t *) r2 == 0; case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + return -EINVAL; return *(int64_t *) r1 % *(int64_t *) r2 == 0; case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; + if (size < sizeof(struct spa_rectangle)) + return -EINVAL; return (rec1->width % rec2->width == 0 && rec1->height % rec2->height == 0); } diff --git a/spa/include/spa/pod/simplify.h b/spa/include/spa/pod/simplify.h index dc7803194..8fbe7153c 100644 --- a/spa/include/spa/pod/simplify.h +++ b/spa/include/spa/pod/simplify.h @@ -43,9 +43,8 @@ spa_pod_simplify_merge(struct spa_pod_builder *b, const struct spa_pod *pod1, co struct spa_pod_frame f[2]; int res = 0, count = 0; - if (pod1->type != pod2->type) - return -ENOTSUP; - if (pod1->type != SPA_TYPE_Object) + if (!spa_pod_is_object(pod1) || + !spa_pod_is_object(pod2)) return -ENOTSUP; o1 = (const struct spa_pod_object*) pod1; diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 3a0f3903c..2b2c62e83 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1229,29 +1229,50 @@ static int diff_value(struct impl *impl, uint32_t type, uint32_t size, const voi { switch (type) { case SPA_TYPE_None: - return 0; + return INT_MAX; case SPA_TYPE_Bool: + if (size < sizeof(int32_t)) + return INT_MAX; return (!!*(int32_t *)v1) - (!!*(int32_t *)v2); case SPA_TYPE_Id: + if (size < sizeof(uint32_t)) + return INT_MAX; return (*(uint32_t *)v1) != (*(uint32_t *)v2); case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + return INT_MAX; return *(int32_t *)v1 - *(int32_t *)v2; case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + return INT_MAX; return *(int64_t *)v1 - *(int64_t *)v2; case SPA_TYPE_Float: + if (size < sizeof(float)) + return INT_MAX; return (int)(*(float *)v1 - *(float *)v2); case SPA_TYPE_Double: + if (size < sizeof(double)) + return INT_MAX; return (int)(*(double *)v1 - *(double *)v2); case SPA_TYPE_String: + if (size < 1 || + ((const char *)v1)[size - 1] != 0 || + ((const char *)v2)[size - 1] != 0) + return INT_MAX; return strcmp((char *)v1, (char *)v2); case SPA_TYPE_Bytes: return memcmp((char *)v1, (char *)v2, size); case SPA_TYPE_Rectangle: { - const struct spa_rectangle *rec1 = (struct spa_rectangle *) v1, - *rec2 = (struct spa_rectangle *) v2; - uint64_t n1 = ((uint64_t) rec1->width) * rec1->height; - uint64_t n2 = ((uint64_t) rec2->width) * rec2->height; + const struct spa_rectangle *rec1, *rec2; + uint64_t n1, n2; + + if (size < sizeof(*rec1)) + return INT_MAX; + rec1 = (struct spa_rectangle *) v1; + rec2 = (struct spa_rectangle *) v2; + n1 = ((uint64_t) rec1->width) * rec1->height; + n2 = ((uint64_t) rec2->width) * rec2->height; if (rec1->width == rec2->width && rec1->height == rec2->height) return 0; else if (n1 < n2) @@ -1259,15 +1280,19 @@ static int diff_value(struct impl *impl, uint32_t type, uint32_t size, const voi else if (n1 > n2) return n1 - n2; else if (rec1->width == rec2->width) - return (int)rec1->height - (int)rec2->height; + return (int32_t)rec1->height - (int32_t)rec2->height; else - return (int)rec1->width - (int)rec2->width; + return (int32_t)rec1->width - (int32_t)rec2->width; } case SPA_TYPE_Fraction: { - const struct spa_fraction *f1 = (struct spa_fraction *) v1, - *f2 = (struct spa_fraction *) v2; + const struct spa_fraction *f1, *f2; uint64_t n1, n2; + + if (size < sizeof(*f1)) + return INT_MAX; + f1 = (struct spa_fraction *) v1; + f2 = (struct spa_fraction *) v2; n1 = ((uint64_t) f1->num) * f2->denom; n2 = ((uint64_t) f2->num) * f1->denom; return (int) (n1 - n2); diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index 720577216..1f0578026 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -524,8 +524,11 @@ uint32_t collect_transport_codec_info(struct pw_manager_object *card, if (iid != SPA_PROP_bluetoothAudioCodec) continue; - if (type->body.type != SPA_CHOICE_Enum || - type->body.child.type != SPA_TYPE_Int) + if (type->pod.size < sizeof(struct spa_pod_choice_body) + + 2 * sizeof(int32_t) || + type->body.type != SPA_CHOICE_Enum || + type->body.child.type != SPA_TYPE_Int || + type->body.child.size != sizeof(int32_t)) continue; /* diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 34a835ef8..e3d438c93 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -683,7 +683,7 @@ static int add_int(struct format_info *info, const char *k, struct spa_pod *para return -ENOENT; val = spa_pod_get_values(&prop->value, &n_values, &choice); - if (val->type != SPA_TYPE_Int) + if (!spa_pod_is_int(val)) return -ENOTSUP; if (n_values == 0) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index e04cf74e1..6b4e7b5ef 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -341,12 +341,16 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type put_key(d, key); switch (type) { case SPA_TYPE_Bool: + if (size < sizeof(int32_t)) + break; put_value(d, NULL, *(int32_t*)body ? "true" : "false"); break; case SPA_TYPE_Id: { const char *str; char fallback[32]; + if (size < sizeof(uint32_t)) + break; uint32_t id = *(uint32_t*)body; str = spa_debug_type_find_short_name(info, *(uint32_t*)body); if (str == NULL) { @@ -357,24 +361,38 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type break; } case SPA_TYPE_Int: + if (size < sizeof(int32_t)) + break; put_int(d, NULL, *(int32_t*)body); break; case SPA_TYPE_Fd: case SPA_TYPE_Long: + if (size < sizeof(int64_t)) + break; put_int(d, NULL, *(int64_t*)body); break; case SPA_TYPE_Float: + if (size < sizeof(float)) + break; put_double(d, NULL, *(float*)body); break; case SPA_TYPE_Double: + if (size < sizeof(double)) + break; put_double(d, NULL, *(double*)body); break; case SPA_TYPE_String: + if (size < 1 || ((const char *)body)[size - 1]) + break; put_string(d, NULL, (const char*)body); break; case SPA_TYPE_Rectangle: { - struct spa_rectangle *r = (struct spa_rectangle *)body; + struct spa_rectangle *r; + + if (size < sizeof(*r)) + break; + r = (struct spa_rectangle *)body; put_begin(d, NULL, "{", STATE_SIMPLE); put_int(d, "width", r->width); put_int(d, "height", r->height); @@ -383,7 +401,11 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type } case SPA_TYPE_Fraction: { - struct spa_fraction *f = (struct spa_fraction *)body; + struct spa_fraction *f; + + if (size < sizeof(*f)) + break; + f = (struct spa_fraction *)body; put_begin(d, NULL, "{", STATE_SIMPLE); put_int(d, "num", f->num); put_int(d, "denom", f->denom); @@ -392,8 +414,12 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type } case SPA_TYPE_Array: { - struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; + struct spa_pod_array_body *b; void *p; + + if (size < sizeof(*b)) + break; + b = (struct spa_pod_array_body *)body; info = info && info->values ? info->values: info; put_begin(d, NULL, "[", STATE_SIMPLE); SPA_POD_ARRAY_BODY_FOREACH(b, size, p) From edef57f6c3bfc74148164ab2760cd68982183a73 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 10 Jul 2025 12:10:28 -0400 Subject: [PATCH 0570/1014] pod: avoid modulo by 0 a % b is undefined behavior if b is 0. --- spa/include/spa/pod/compare.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 6ac6b0799..fa575e781 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -205,11 +205,11 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1 { switch (type) { case SPA_TYPE_Int: - if (size < sizeof(int32_t)) + if (size < sizeof(int32_t) || *(int32_t *)r2 < 1) return -EINVAL; return *(int32_t *) r1 % *(int32_t *) r2 == 0; case SPA_TYPE_Long: - if (size < sizeof(int64_t)) + if (size < sizeof(int64_t) || *(int64_t *)r2 < 1) return -EINVAL; return *(int64_t *) r1 % *(int64_t *) r2 == 0; case SPA_TYPE_Rectangle: @@ -217,8 +217,12 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1 const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; - if (size < sizeof(struct spa_rectangle)) + if (size < sizeof(struct spa_rectangle) || + rec2->width < 1 || + rec2->height < 1) + { return -EINVAL; + } return (rec1->width % rec2->width == 0 && rec1->height % rec2->height == 0); } From d37bdf5cbf8a72ec046eca70076cc2787491ffb8 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Fri, 4 Jul 2025 13:52:40 -0400 Subject: [PATCH 0571/1014] pod: check for NULL strings in tag utils SPA_POD_String allows SPA_TYPE_None PODs and turns them to NULL, so check for them. --- spa/include/spa/param/tag-utils.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/include/spa/param/tag-utils.h b/spa/include/spa/param/tag-utils.h index 590448ae0..331def883 100644 --- a/spa/include/spa/param/tag-utils.h +++ b/spa/include/spa/param/tag-utils.h @@ -90,6 +90,8 @@ spa_tag_info_parse(const struct spa_tag_info *info, struct spa_dict *dict, struc SPA_POD_String(&value), NULL) < 0) break; + if (key == NULL || value == NULL) + return -EINVAL; items[n].key = key; items[n].value = value; } From b04da87e380080dc568d7646d910011245c260a9 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Tue, 8 Jul 2025 18:25:51 -0400 Subject: [PATCH 0572/1014] pod: check that choices are not empty Before using the contents of a choice, check that it is not empty to avoid reading out of bounds. --- pipewire-v4l2/src/pipewire-v4l2.c | 2 ++ spa/include/spa/debug/format.h | 2 +- spa/include/spa/pod/compare.h | 3 +++ spa/include/spa/pod/filter.h | 7 ++++++- spa/include/spa/pod/simplify.h | 2 +- spa/plugins/videoconvert/videoconvert-ffmpeg.c | 5 ++++- spa/tests/benchmark-pod.c | 1 + 7 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 691c8ec37..779c52f7d 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -2311,6 +2311,8 @@ static int vidioc_s_ctrl(struct file *file, struct v4l2_control *arg) struct spa_pod_frame f[1]; struct spa_pod *param; pod = spa_pod_get_values(type, &n_vals, &choice); + if (n_vals < 1) + break; spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 4abd36b1e..4bcf4d294 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -185,7 +185,7 @@ SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int in size = val->size; vals = SPA_POD_BODY(val); - if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST) + if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1) continue; ti = spa_debug_type_find(info, prop->key); diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index fa575e781..67c18ad46 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -117,6 +117,9 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, if (pod1->type != pod2->type) return -EINVAL; + if (n_vals1 < 1) + return -EINVAL; /* empty choice */ + switch (pod1->type) { case SPA_TYPE_Struct: { diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6c92e910d..534d1a827 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -80,8 +80,13 @@ spa_pod_filter_prop(struct spa_pod_builder *b, int res, n_copied = 0; v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c); - alt1 = SPA_POD_BODY(v1); v2 = spa_pod_get_values(&p2->value, &nalt2, &p2c); + + /* empty choices */ + if (nalt1 < 1 || nalt2 < 1) + return -EINVAL; + + alt1 = SPA_POD_BODY(v1); alt2 = SPA_POD_BODY(v2); type = v1->type; diff --git a/spa/include/spa/pod/simplify.h b/spa/include/spa/pod/simplify.h index 8fbe7153c..1070e0a6f 100644 --- a/spa/include/spa/pod/simplify.h +++ b/spa/include/spa/pod/simplify.h @@ -72,7 +72,7 @@ spa_pod_simplify_merge(struct spa_pod_builder *b, const struct spa_pod *pod1, co vals1 = spa_pod_get_values(&p1->value, &n_vals1, &choice1); vals2 = spa_pod_get_values(&p2->value, &n_vals2, &choice2); - if (vals1->type != vals2->type) + if (vals1->type != vals2->type || n_vals1 < 1 || n_vals2 < 1) goto error_einval; size = vals1->size; diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c index 2b2c62e83..525b2c933 100644 --- a/spa/plugins/videoconvert/videoconvert-ffmpeg.c +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -1209,6 +1209,9 @@ static struct spa_pod *transform_format(struct impl *this, struct port *port, co uint32_t n_vals, choice, *id_vals; struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); + if (n_vals < 1) + return 0; + if (!spa_pod_is_id(val)) return 0; @@ -1311,7 +1314,7 @@ static int diff_prop(struct impl *impl, struct spa_pod_prop *prop, void *vals, *v, *best = NULL; int res = INT_MAX; - if (val->type != type) + if (n_vals < 1 || val->type != type) return -EINVAL; size = SPA_POD_BODY_SIZE(val); diff --git a/spa/tests/benchmark-pod.c b/spa/tests/benchmark-pod.c index 9979228fe..f54452b2a 100644 --- a/spa/tests/benchmark-pod.c +++ b/spa/tests/benchmark-pod.c @@ -161,6 +161,7 @@ static void test_parse(void) uint32_t n_vals, choice; struct spa_pod *pod = spa_pod_get_values(&prop->value, &n_vals, &choice); + spa_assert_se(n_vals > 0); switch(prop->key) { case SPA_FORMAT_mediaType: spa_pod_get_id(pod, &vals.media_type); From b8e29d471b053f97b61c9639ba75ed084df77faa Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 8 Jan 2024 14:33:00 -0500 Subject: [PATCH 0573/1014] module-rtp: Fix bounds checks in MIDI parsing These are potential security problems. --- src/modules/module-rtp/midi.c | 104 ++++++++++++++++++++++++---------- src/modules/module-rtp/rtp.h | 2 +- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index b03dbd116..d9c7cd443 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -2,6 +2,9 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include +#include + static void rtp_midi_process_playback(void *data) { struct impl *impl = data; @@ -96,12 +99,15 @@ static int parse_varlen(uint8_t *p, uint32_t avail, uint32_t *result) uint32_t value = 0, offs = 0; while (offs < avail) { uint8_t b = p[offs++]; + if (value > (UINT32_MAX >> 7)) + return -ERANGE; value = (value << 7) | (b & 0x7f); - if ((b & 0x80) == 0) - break; + if ((b & 0x80) == 0) { + *result = value; + return offs; + } } - *result = value; - return offs; + return -EINVAL; } static int get_midi_size(uint8_t *p, uint32_t avail) @@ -109,6 +115,8 @@ static int get_midi_size(uint8_t *p, uint32_t avail) int size; uint32_t offs = 0, value; + if (avail < 1) + return -EINVAL; switch (p[offs++]) { case 0xc0 ... 0xdf: size = 2; @@ -120,8 +128,11 @@ static int get_midi_size(uint8_t *p, uint32_t avail) case 0xff: case 0xf0: case 0xf7: - size = parse_varlen(&p[offs], avail - offs, &value); - size += value + 1; + if ((size = parse_varlen(&p[offs], avail - offs, &value)) < 0) + return size; + if ((unsigned int)(INT_MAX - size - 1) > value) + return -EINVAL; + size += (int)value + 1; break; default: return -EINVAL; @@ -130,7 +141,11 @@ static int get_midi_size(uint8_t *p, uint32_t avail) } static int parse_journal(struct impl *impl, uint8_t *packet, uint16_t seq, uint32_t len) { - struct rtp_midi_journal *j = (struct rtp_midi_journal*)packet; + struct rtp_midi_journal *j; + + if (len < sizeof(*j)) + return -EINVAL; + j = (struct rtp_midi_journal*)packet; uint16_t seqnum = ntohs(j->checkpoint_seqnum); rtp_stream_emit_send_feedback(impl, seqnum); return 0; @@ -156,7 +171,7 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti uint16_t seq, uint32_t payload_offset, uint32_t plen) { uint32_t write; - struct rtp_midi_header *hdr; + struct rtp_midi_header hdr; int32_t filled; struct spa_pod_builder b; struct spa_pod_frame f[1]; @@ -164,6 +179,8 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti uint32_t offs = payload_offset, len, end; bool first = true; + if (plen <= payload_offset) + return -EINVAL; if (impl->direct_timestamp) { /* in direct timestamp we attach the RTP timestamp directly on the * midi events and render them in the corresponding cycle */ @@ -219,18 +236,25 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti return -ENOSPC; } - hdr = (struct rtp_midi_header *)&packet[offs++]; - len = hdr->len; - if (hdr->b) { - len = (len << 8) | hdr->len_b; - offs++; + SPA_STATIC_ASSERT(sizeof hdr == 2); + memcpy(&hdr, &packet[offs++], 1); + if (hdr.b) { + if (offs >= plen) { + pw_log_warn("invalid packet: no room for long length byte"); + return -EINVAL; + } + hdr.len_b = packet[offs++]; + len = (hdr.len << 8) | hdr.len_b; + } else { + hdr.len_b = 0; + len = hdr.len; } - end = len + offs; - if (end > plen) { - pw_log_warn("invalid packet %d > %d", end, plen); + if (plen - offs < len) { + pw_log_warn("invalid packet %" PRIu64 " > %" PRIu32, (uint64_t)offs + len, plen); return -EINVAL; } - if (hdr->j) + end = len + offs; + if (hdr.j) parse_journal(impl, &packet[end], seq, plen - end); ptr = SPA_PTROFF(impl->buffer, write & BUFFER_MASK2, void); @@ -247,17 +271,23 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti uint8_t *d; size_t s; - if (first && !hdr->z) + if (first && !hdr.z) delta = 0; - else - offs += parse_varlen(&packet[offs], end - offs, &delta); + else { + size = parse_varlen(&packet[offs], end - offs, &delta); + if (size < 0) { + pw_log_warn("invalid offset at offset %u", offs); + return size; + } + offs += size; + } timestamp += (uint32_t)(delta * impl->corr); size = get_midi_size(&packet[offs], end - offs); - if (size <= 0 || offs + size > end) { + if (size <= 0 || (unsigned int)size > end - offs) { pw_log_warn("invalid size (%08x) %d (%u %u)", packet[offs], size, offs, end); - break; + return -EINVAL; } d = &packet[offs]; @@ -274,8 +304,10 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti offs += size; first = false; } - spa_pod_builder_pop(&b, &f[0]); - + if (spa_pod_builder_pop(&b, &f[0]) == NULL) { + pw_log_warn("overflow"); + return -ENOSPC; + } write += b.state.offset; spa_ringbuffer_write_update(&impl->ring, write); @@ -289,6 +321,7 @@ static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) uint16_t seq; uint32_t timestamp; + SPA_STATIC_ASSERT(sizeof(struct rtp_header) == 12); if (len < 12) goto short_packet; @@ -297,7 +330,7 @@ static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) goto invalid_version; hlen = 12 + hdr->cc * 4; - if (hlen > len) + if (hlen >= len) goto invalid_len; if (impl->have_ssrc && impl->ssrc != hdr->ssrc) @@ -340,25 +373,34 @@ unexpected_ssrc: return -EINVAL; } -static int write_event(uint8_t *p, uint32_t value, void *ev, uint32_t size) +static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, void *ev, uint32_t size) { uint64_t buffer; uint8_t b; - int count = 0; + unsigned int count = 0; + if (buffer_size <= size) + return -ENOSPC; buffer = value & 0x7f; while ((value >>= 7)) { + if (buffer > (UINT64_MAX >> 8)) + return -ERANGE; buffer <<= 8; buffer |= ((value & 0x7f) | 0x80); } do { + if (count >= buffer_size) + return -ENOSPC; b = buffer & 0xff; p[count++] = b; buffer >>= 8; } while (b & 0x80); + if (buffer_size - size < count || + count + size > (unsigned int)INT_MAX) + return -ENOSPC; memcpy(&p[count], ev, size); - return count + size; + return (int)(count + size); } static void rtp_midi_flush_packets(struct impl *impl, @@ -426,6 +468,10 @@ static void rtp_midi_flush_packets(struct impl *impl, impl->seq++; len = 0; } + if (size > BUFFER_SIZE || len > BUFFER_SIZE - size) { + pw_log_error("Buffer overflow prevented!"); + return; // FIXME: what to do instead? + } if (len == 0) { /* start new packet */ base = prev_offset = offset; @@ -437,7 +483,7 @@ static void rtp_midi_flush_packets(struct impl *impl, } else { delta = offset - prev_offset; prev_offset = offset; - len += write_event(&impl->buffer[len], delta, event, size); + len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, event, size); } } if (len > 0) { diff --git a/src/modules/module-rtp/rtp.h b/src/modules/module-rtp/rtp.h index 1fd3bedef..24111dc67 100644 --- a/src/modules/module-rtp/rtp.h +++ b/src/modules/module-rtp/rtp.h @@ -32,7 +32,7 @@ struct rtp_header { uint16_t sequence_number; uint32_t timestamp; uint32_t ssrc; - uint32_t csrc[0]; + uint32_t csrc[]; } __attribute__ ((packed)); struct rtp_payload { From da9bd36cbbe9894e35c5a414c485e40f2712072d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Jul 2025 14:51:24 +0200 Subject: [PATCH 0574/1014] spa: return before entering the error branch In the normal case we should not go into the error path. --- spa/include/spa/debug/format.h | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 4bcf4d294..31104c678 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -125,6 +125,7 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i spa_strbuf_append(buffer, "INVALID type %d", type); break; } + return 0; bad_body: spa_strbuf_append(buffer, "INVALID BODY type %d", type); return 0; From 0be61add02277c3018b6553e508216a718ae3e1a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Jul 2025 14:52:25 +0200 Subject: [PATCH 0575/1014] Revert "spa: alsa: Try to get driver rate before setting up matching" This reverts commit 019b53ace8761174c9fd38aedca81696ce671eb2. This is a result of a different problem, that the rates are compared when they are unset. --- spa/plugins/alsa/alsa-pcm.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 3e5e67b38..2c750f8eb 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2607,7 +2607,7 @@ static inline int do_start(struct state *state) return 0; } -static inline int check_position_config(struct state *state, bool starting, bool check_driver_only); +static inline int check_position_config(struct state *state, bool starting); static void update_sources(struct state *state, bool active); static int alsa_recover(struct state *state) @@ -2675,7 +2675,7 @@ recover: spa_list_for_each(follower, &driver->rt.followers, rt.driver_link) { if (follower != driver && follower->linked) { do_drop(follower); - check_position_config(follower, false, false); + check_position_config(follower, false); } } do_prepare(driver); @@ -2911,8 +2911,6 @@ static int setup_matching(struct state *state) spa_log_debug(state->log, "driver clock:'%s' our clock:'%s'", state->position->clock.name, state->clock_name); - check_position_config(state, false, true); - if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; @@ -2937,7 +2935,7 @@ static void update_sources(struct state *state, bool active) } } -static inline int check_position_config(struct state *state, bool starting, bool check_driver_only) +static inline int check_position_config(struct state *state, bool starting) { uint64_t target_duration; struct spa_fraction target_rate; @@ -2971,16 +2969,13 @@ static inline int check_position_config(struct state *state, bool starting, bool state->driver_duration = target_duration; state->driver_rate = target_rate; - - if (!check_driver_only) { - state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); - state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); - state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); - state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; - state->resample = !state->pitch_elem && - (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); - state->alsa_sync = true; - } + state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); + state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); + state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); + state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; + state->resample = !state->pitch_elem && + (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); + state->alsa_sync = true; } return 0; } @@ -2991,7 +2986,7 @@ static int alsa_write_sync(struct state *state, uint64_t current_time) snd_pcm_uframes_t avail, delay, target; bool following = state->following; - if (SPA_UNLIKELY((res = check_position_config(state, false, false)) < 0)) + if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { @@ -3258,7 +3253,7 @@ static int alsa_read_sync(struct state *state, uint64_t current_time) if (SPA_UNLIKELY(!state->alsa_started)) return 0; - if (SPA_UNLIKELY((res = check_position_config(state, false, false)) < 0)) + if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { @@ -3668,7 +3663,7 @@ int spa_alsa_prepare(struct state *state) if (state->prepared) return 0; - if (check_position_config(state, true, false) < 0) { + if (check_position_config(state, true) < 0) { spa_log_error(state->log, "%s: invalid position config", state->name); return -EIO; } From 328e101f37ac478d4832844d977e026cc9c25540 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 15 Jul 2025 15:06:24 +0200 Subject: [PATCH 0576/1014] alsa: don't try to activate resampling with unknown rates If the driver or our rate is not known yet, don't assume we will need to resample. --- spa/plugins/alsa/alsa-pcm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2c750f8eb..e10326b65 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2369,13 +2369,13 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ spa_log_info(state->log, "%s: format:%s access:%s-%s rate:%d channels:%d " "buffer frames %lu, period frames %lu, periods %u, frame_size %zd " - "headroom %u start-delay:%u batch:%u tsched:%u", + "headroom %u start-delay:%u batch:%u tsched:%u resample:%u", state->name, snd_pcm_format_name(state->format), state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", state->rate, state->channels, state->buffer_frames, state->period_frames, periods, state->frame_size, state->headroom, state->start_delay, - state->is_batch, !state->disable_tsched); + state->is_batch, !state->disable_tsched, state->resample); /* write the parameters to device */ CHECK(snd_pcm_hw_params(hndl, params), "set_hw_params"); @@ -2914,8 +2914,8 @@ static int setup_matching(struct state *state) if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; - state->resample = !state->pitch_elem && - (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); + check_position_config(state, false); + recalc_headroom(state); spa_log_info(state->log, "driver clock:'%s'@%d our clock:'%s'@%d matching:%d resample:%d", @@ -2974,7 +2974,8 @@ static inline int check_position_config(struct state *state, bool starting) state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; state->resample = !state->pitch_elem && - (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); + ((state->rate != 0 && state->driver_rate.denom != 0 && + (uint32_t)state->rate != state->driver_rate.denom) || state->matching); state->alsa_sync = true; } return 0; From 67711e899cd6ca2eb6f1ba6a6fbef89c188b631a Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 14 Jul 2025 12:33:53 +0200 Subject: [PATCH 0577/1014] audioadapter: Add more log lines --- spa/plugins/audioconvert/audioadapter.c | 44 ++++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index b904923ac..1b6fd55f9 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -794,15 +794,29 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, switch (id) { case SPA_PARAM_Format: - if (this->started) + if (this->started) { + spa_log_error(this->log, "%p: cannot set Format param: " + "node already started", this); return -EIO; - if (param == NULL) + } + if (param == NULL) { + spa_log_error(this->log, "%p: attempted to set NULL Format POD", this); return -EINVAL; + } - 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: " + "parsing the POD failed", this); return -EINVAL; - if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + } + if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { + const char *subtype_name = spa_type_to_short_name(info.media_subtype, + spa_type_media_subtype, + ""); + spa_log_error(this->log, "%p: cannot set Format param: " + "expected raw subtype, got subtype \"%s\"", this, subtype_name); return -EINVAL; + } this->follower_current_format = info; break; @@ -814,7 +828,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_pod *format = NULL; if (this->started) { - spa_log_error(this->log, "was started"); + spa_log_error(this->log, "%p: cannot set PortConfig param: " + "node already started", this); return -EIO; } @@ -822,8 +837,11 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), - 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: " + "parsing the POD failed", this); return -EINVAL; + } if (format) { struct spa_audio_info info; @@ -832,16 +850,24 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if ((res = spa_format_audio_parse(format, &info)) < 0) return res; - if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) + if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { info.info.raw.rate = 0; - else + } else { + const char *subtype_name = spa_type_to_short_name(info.media_subtype, + spa_type_media_subtype, + ""); + spa_log_error(this->log, "%p: cannot set PortConfig param: " + "subtype \"%s\" is not supported", this, subtype_name); return -ENOTSUP; + } this->default_format = info; } switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_none: + spa_log_error(this->log, "%p: cannot set PortConfig param: " + "\"none\" config mode is not supported", this); return -ENOTSUP; case SPA_PARAM_PORT_CONFIG_MODE_passthrough: if ((res = reconfigure_mode(this, mode, dir, format)) < 0) @@ -853,6 +879,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, return res; break; default: + spa_log_error(this->log, "%p: invalid config mode when setting PortConfig param", + this); return -EINVAL; } From eb3d14053dde62212bb54537328867864268a8e7 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 14 Jul 2025 12:33:22 +0200 Subject: [PATCH 0578/1014] doc: spa: Add more docs about SPA_IO_Clock and driver operations --- doc/dox/internals/driver.dox | 111 +++++++++++++++++++++++++++++++++++ doc/dox/internals/index.dox | 1 + doc/meson.build | 1 + spa/include/spa/node/io.h | 18 +++--- 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 doc/dox/internals/driver.dox diff --git a/doc/dox/internals/driver.dox b/doc/dox/internals/driver.dox new file mode 100644 index 000000000..68cf1dd5e --- /dev/null +++ b/doc/dox/internals/driver.dox @@ -0,0 +1,111 @@ +/** \page page_driver Driver architecture and workflow + +This document explains how drivers are structured and how they operate. +This is useful to know both for debugging and for writing new drivers. + +(For details about how the graph does scheduling, which is tied to the driver, +see \ref page_scheduling ). + +# Clocks + +A driver is a node that starts graph cycles. Typically, this is accomplished +by using a timer that periodically invokes a callback, or by an interrupt. + +Drivers use the monotonic system clock as the reference for timestamping. Note +that "monotonic system clock" does not refer to the \c MONOTONIC_RAW clock in +Linux, but rather, to the regular monotonic clock. + +Drivers may actually be run by a custom internal clock instead of the monotonic +system clock. One example would be a sound card DAC's clock. Another would be +a network adapter with a built in PHC. Or, the driver may be using a system +clock other than the monotonic system clock. The driver then needs to perform +some sort of timestamp translation and drift compensation from that internal +clock to the monotonic clock, since it still needs to generate monotonic clock +timestamps for the beginning cycle. (More on that below.) + +# Updates and graph cycle start + +Every time a driver starts a graph cycle, it must update the contents of the +\ref spa_io_clock instance that is assigned to them through the +\ref spa_node_methods::set_io callback. The fields of the struct must be +updated as follows: + +- \ref spa_io_clock::nsec : Must be set to the time (according to the monotonic + system clock) when the cycle that the driver is about to trigger started. To + minimize jitter, it is usually a good idea to increment this by a fixed amount + except for when the driver starts and when discontinuities occur in its clock. +- \ref spa_io_clock::rate : Set to a value that can translate samples to nanoseconds. +- \ref spa_io_clock::position : Current cycle position, in samples. This is the + ideal position of the graph cycle (this is explained in greater detail further below). + It is incremented by the dduration (in samples) at the beginning of each cycle. If + a discontinuity is experienced by the driver that results in a discontinuity in the + position of the old and the current cycle, consider setting the + \ref SPA_IO_CLOCK_FLAG_DISCONT flag to inform other nodes about this. +- \ref spa_io_clock::duration : Duration of this new cycle, in samples. +- \ref spa_io_clock::rate_diff : A decimal value that is set to whatever correction + factor the driver applied to for a drift between an internal driver clock and the + monotonic system clock. A value above 1.0 means that the internal driver clock + is faster than the monotonic system clock, and vice versa. Always set this to + 1.0 if the driver is directly using the monotonic clock. +- \ref spa_io_clock::next_nsec : Must be set to the time (according to the monotonic + system clock) when the cycle that comes after the current one is to be started. In + some cases, this may actually be in the past relative to nsec, for example, when + some internal driver clock experienced a discontinuity. Consider setting the + \ref SPA_IO_CLOCK_FLAG_DISCONT flag in such a case. Just like with nsec, to + minimize jitter, it is usually a good idea to increment this by a fixed amount + except for when the driver starts and when discontinuities occur in its clock. + +The driver node signals the start of the graph cycle by calling \ref spa_node_call_ready +with the \ref SPA_STATUS_HAVE_DATA and \ref SPA_STATUS_NEED_DATA flags passed +to that function call. That call must happen inside the thread that runs the +data loop assigned to the driver node. + +As mentioned above, the \ref spa_io_clock::position field is the _ideal_ position +of the graph cycle, in samples. This contrasts with \ref spa_io_clock::nsec, which +is the moment in monotonic clock time when the cycle _actually_ happens. This is +an important distinction when driver is run by a clock that is different to the monotonic +cloc. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the pace +of that different clock (explained in the section below). In such a case, +\ref spa_io_clock::position still is incremented by the duration in samples. + +# Using clocks other than the monotonic clock + +As mentioned earlier, the driver may be run by an internal clock that is different +to the monotonic clock. If that other clock can be directly used for scheduling +graph cycle initiations, then it is sufficient to compute the offset between that +clock and the monotonic clock (that is, offset = other_clock_time - monotonic_clock_time) +at each cycle and use that offset to translate that other clock's time to the monotonic +clock time. This is accomplished by adding that offset to the \ref spa_io_clock::nsec +and \ref spa_io_clock::next_nsec fields. For example, when the driver uses the realtime +system clock instead of the monotonic system clock, then that realtime clock can still +be used with \c timerfd to schedule callback invocations within the data loop. Then, computing +the (realtime_clock_time - monotonic_clock_time) offset is sufficient, as mentioned, +to fulfill the requirements of the \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec +fields that their timestamps must be given in monotonic clock time. + +If however that other clock cannot be used for scheduling graph cycle initiations directly +(for example, because the API of that clock has no functionality to trigger callbacks), +then, in addition to the aforementioned offset, the driver has to use the monotonic clock +for triggering callbacks (usually via \c timerfd) and adjust the time when callbacks are +invoked such that they match the pace of that other clock. + +As an example (clock speed difference exaggerated for sake of clarity), suppose the other +clock is twice as fast as the monotonic clock. Then the monotonic clock timestamps have +to be calculated in a manner that halves the durations between said timestamps, and the +\ref spa_io_clock::rate_diff field is set to 2.0. + +The dummy node driver uses a DLL for this purpose. It is fed the difference between the +expected position (in samples) and the actual position (derived from the current time +of the driver's internal clock), passes the delta between these two quantities into the +DLL, and the DLL computes a correction factor (2.0 in the above example) which is used +for scaling durations between \c timerfd timeouts. This forms a control loop, since the +correction factor causes the durations between the timeouts to be adjusted such that the +difference between the expected position and the actual position reaches zero. Keep in +mind the notes above about \ref spa_io_clock::position being the ideal position of the +graph cycle, meaning that even in this case, the duration it is incremented by is +_not_ scaled by the correction factor; the duration in samples remains unchanged. + +(Other popular control loop mechanisms that are suitable alternatives to the DLL are +PID controllers and Kalman filters.) + +*/ diff --git a/doc/dox/internals/index.dox b/doc/dox/internals/index.dox index 64f548ccc..89d2e9da3 100644 --- a/doc/dox/internals/index.dox +++ b/doc/dox/internals/index.dox @@ -11,6 +11,7 @@ - \subpage page_library - \subpage page_dma_buf - \subpage page_scheduling +- \subpage page_driver - \subpage page_latency - \subpage page_tag - \subpage page_native_protocol diff --git a/doc/meson.build b/doc/meson.build index dc90e122b..7bd86bfac 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -68,6 +68,7 @@ extra_docs = [ 'dox/internals/objects.dox', 'dox/internals/audio.dox', 'dox/internals/scheduling.dox', + 'dox/internals/driver.dox', 'dox/internals/protocol.dox', 'dox/internals/pulseaudio.dox', 'dox/internals/dma-buf.dox', diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 01983a26d..2920c1b3b 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -114,15 +114,16 @@ struct spa_io_range { * Driver nodes are supposed to update the contents of \ref SPA_IO_Clock before * signaling the start of a graph cycle. These updated clock values become * visible to other nodes in \ref SPA_IO_Position. Non-driver nodes do - * not need to update the contents of their \ref SPA_IO_Clock. + * not need to update the contents of their \ref SPA_IO_Clock. Also + * see \ref page_driver for further details. * * The host generally gives each node a separate \ref spa_io_clock in \ref * SPA_IO_Clock, so that updates made by the driver are not visible in the * contents of \ref SPA_IO_Clock of other nodes. Instead, \ref SPA_IO_Position * is used to look up the current graph time. * - * A node is a driver when \ref spa_io_clock.id in \ref SPA_IO_Clock and - * \ref spa_io_position.clock.id in \ref SPA_IO_Position are the same. + * A node is a driver when \ref spa_io_clock::id and the ID in + * \ref spa_io_position.clock in \ref SPA_IO_Position are the same. * * The flags are set by the graph driver at the start of each cycle. */ @@ -168,13 +169,16 @@ struct spa_io_clock { * can be used to check if nodes share the same clock. */ uint64_t nsec; /**< Time in nanoseconds against monotonic clock * (CLOCK_MONOTONIC). This fields reflects a real time instant - * in the past. The value may have jitter. */ + * in the past, when the current cycle started. The value may + * have jitter. */ struct spa_fraction rate; /**< Rate for position/duration/delay/xrun */ uint64_t position; /**< Current position, in samples @ \ref rate */ uint64_t duration; /**< Duration of current cycle, in samples @ \ref rate */ int64_t delay; /**< Delay between position and hardware, in samples @ \ref rate */ double rate_diff; /**< Rate difference between clock and monotonic time, as a ratio of - * clock speeds. */ + * clock speeds. A value higher than 1.0 means that the driver's + * internal clock is faster than the monotonic clock (by that + * factor), and vice versa. */ uint64_t next_nsec; /**< Estimated next wakeup time in nanoseconds. * This time is a logical start time of the next cycle, and * is not necessarily in the future. @@ -308,8 +312,8 @@ enum spa_io_position_state { * * It is set on all nodes in \ref SPA_IO_Position, and the contents of \ref * spa_io_position.clock contain the clock updates made by the driving node in - * the graph in its \ref SPA_IO_Clock. Also, \ref spa_io_position.clock.id - * will contain the clock id of the driving node in the graph. + * the graph in its \ref SPA_IO_Clock. Also, the ID in \ref spa_io_position.clock + * will be the clock id of the driving node in the graph. * * The position clock indicates the logical start time of the current graph * cycle. From 4b37f3db3dc172f4fcab917a09d625106d2c4046 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 12:01:14 +0200 Subject: [PATCH 0579/1014] filter-graph: move loop out of the NULL check --- spa/plugins/filter-graph/plugin_builtin.c | 79 ++++++++++++----------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 0d39bbe70..ffef8adfe 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -2821,48 +2821,49 @@ static void zeroramp_run(void * Instance, unsigned long SampleCount) if (out == NULL) return; - if (in != NULL) { - for (n = 0; n < SampleCount; n++) { - if (impl->mode == 0) { - /* normal mode, finding gaps */ - out[n] = in[n]; - if (in[n] == 0.0f) { - if (++impl->count == gap) { - /* we found gap zeroes, fade out last - * sample and go into zero mode */ - for (c = 1, i = n; c < duration && i > 0; i--, c++) - out[i-1] = impl->last * - (0.5f + 0.5f * cosf(M_PIf + M_PIf * c / duration)); - impl->mode = 1; - } - } else { - /* keep last sample to fade out when needed */ - impl->count = 0; - impl->last = in[n]; - } - } - if (impl->mode == 1) { - /* zero mode */ - if (in[n] != 0.0f) { - /* gap ended, move to fade-in mode */ - impl->mode = 2; - impl->count = 0; - } else { - out[n] = 0.0f; - } - } - if (impl->mode == 2) { - /* fade-in mode */ - out[n] = in[n] * (0.5f + 0.5f * cosf(M_PIf + (M_PIf * ++impl->count / duration))); - if (impl->count == duration) { - /* fade in complete, back to normal mode */ - impl->count = 0; - impl->mode = 0; + if (in == NULL) { + memset(out, 0, SampleCount * sizeof(float)); + return; + } + + for (n = 0; n < SampleCount; n++) { + if (impl->mode == 0) { + /* normal mode, finding gaps */ + out[n] = in[n]; + if (in[n] == 0.0f) { + if (++impl->count == gap) { + /* we found gap zeroes, fade out last + * sample and go into zero mode */ + for (c = 1, i = n; c < duration && i > 0; i--, c++) + out[i-1] = impl->last * + (0.5f + 0.5f * cosf(M_PIf + M_PIf * c / duration)); + impl->mode = 1; } + } else { + /* keep last sample to fade out when needed */ + impl->count = 0; + impl->last = in[n]; + } + } + if (impl->mode == 1) { + /* zero mode */ + if (in[n] != 0.0f) { + /* gap ended, move to fade-in mode */ + impl->mode = 2; + impl->count = 0; + } else { + out[n] = 0.0f; + } + } + if (impl->mode == 2) { + /* fade-in mode */ + out[n] = in[n] * (0.5f + 0.5f * cosf(M_PIf + (M_PIf * ++impl->count / duration))); + if (impl->count == duration) { + /* fade in complete, back to normal mode */ + impl->count = 0; + impl->mode = 0; } } - } else { - memset(out, 0, SampleCount * sizeof(float)); } } From b3dddfed6a6c13e0fe69cfa2e661e9c2857ba084 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 12:10:25 +0200 Subject: [PATCH 0580/1014] filter-chain: add Level control input port for noisegate This makes it possible to use another volume measurement algorithm to drive the noise gate, such as a VAD algorithm. --- spa/plugins/filter-graph/plugin_builtin.c | 45 ++++++++++++++--------- src/modules/module-filter-chain.c | 4 ++ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index ffef8adfe..bdc2e71de 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -2892,26 +2892,31 @@ static struct spa_fga_port noisegate_ports[] = { .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, - .name = "Open Threshold", + .name = "Level", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 0.004f, .min = 0.0f, .max = 1.0f + .def = NAN }, { .index = 3, - .name = "Close Threshold", + .name = "Open Threshold", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 0.003f, .min = 0.0f, .max = 1.0f + .def = 0.04f, .min = 0.0f, .max = 1.0f }, { .index = 4, + .name = "Close Threshold", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.03f, .min = 0.0f, .max = 1.0f + }, + { .index = 5, .name = "Attack (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.005f, .min = 0.0f, .max = 1.0f }, - { .index = 5, + { .index = 6, .name = "Hold (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.050f, .min = 0.0f, .max = 1.0f }, - { .index = 6, + { .index = 7, .name = "Release (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.010f, .min = 0.0f, .max = 1.0f @@ -2923,9 +2928,10 @@ static void noisegate_run(void * Instance, unsigned long SampleCount) struct builtin *impl = Instance; float *in = impl->port[0]; float *out = impl->port[1]; + float in_lev = impl->port[2][0]; unsigned long n; - float o_thres = impl->port[2][0]; - float c_thres = impl->port[3][0]; + float o_thres = impl->port[3][0]; + float c_thres = impl->port[4][0]; float gate, hold, o_rate, c_rate, level; int mode; @@ -2937,20 +2943,25 @@ static void noisegate_run(void * Instance, unsigned long SampleCount) return; } - o_rate = 1.0f / (impl->port[4][0] * impl->rate); - c_rate = 1.0f / (impl->port[6][0] * impl->rate); + o_rate = 1.0f / (impl->port[5][0] * impl->rate); + c_rate = 1.0f / (impl->port[7][0] * impl->rate); gate = impl->gate; hold = impl->hold; mode = impl->mode; level = impl->last; - for (n = 0; n < SampleCount; n++) { - float lev = fabsf(in[n]); + spa_log_trace_fp(impl->log, "%f %d %f", level, mode, gate); - if (lev > level) - level = lev; - else - level = lev * 0.05f + level * 0.95f; + for (n = 0; n < SampleCount; n++) { + if (isnan(in_lev)) { + float lev = fabsf(in[n]); + if (lev > level) + level = lev; + else + level = lev * 0.05f + level * 0.95f; + } else { + level = in_lev; + } switch (mode) { case 0: @@ -2964,7 +2975,7 @@ static void noisegate_run(void * Instance, unsigned long SampleCount) if (gate >= 1.0f) { gate = 1.0f; mode = 2; - hold = impl->port[5][0] * impl->rate; + hold = impl->port[6][0] * impl->rate; } break; case 2: diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 263767068..a02364abd 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -630,6 +630,10 @@ extern struct spa_handle_factory spa_filter_graph_factory; * It has an "In" input port and an "Out" output data ports. Normally the input * data is passed directly to the output. * + * The "Level" control port can be used to control the measured volume of the "In" + * port. When not connected, a simple volume algorithm on the "In" port will be + * used. + * * If the volume drops below "Close threshold", the noisegate will ramp down the * volume to zero for a duration of "Release (s)" seconds. When the volume is above * "Open threshold", the noisegate will ramp up the volume to 1 for a duration From 6605caa39eb9d108af7ee4145d607fc7637c4075 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 9 Jul 2025 14:12:36 +0200 Subject: [PATCH 0581/1014] filter-graph: add ONNX plugin It uses the onnxruntime library to parse the onnx file and construct a neural network. It uses the label field to setup the plugin and how to map the various tensors of the model to input, output, control and notify ports. Add an example config for how to use the silero VAD ONNX model with the noise gate. --- meson_options.txt | 4 + spa/meson.build | 3 + spa/plugins/filter-graph/meson.build | 11 + spa/plugins/filter-graph/plugin_onnx.c | 772 +++++++++++++++++++++++ src/daemon/filter-chain/22-onnx-vad.conf | 83 +++ src/modules/module-filter-chain.c | 113 ++++ 6 files changed, 986 insertions(+) create mode 100644 spa/plugins/filter-graph/plugin_onnx.c create mode 100644 src/daemon/filter-chain/22-onnx-vad.conf diff --git a/meson_options.txt b/meson_options.txt index e55fe22a1..c5464e063 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -391,3 +391,7 @@ option('fftw', description: 'Enable code that depends on fftw', type: 'feature', value: 'auto') +option('onnxruntime', + description: 'Enable code that depends on onnxruntime', + type: 'feature', + value: 'auto') diff --git a/spa/meson.build b/spa/meson.build index 1f749757c..798ef373f 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -147,6 +147,9 @@ if get_option('spa-plugins').allowed() summary({'ffmpeg': avfilter_dep.found()}, bool_yn: true, section: 'filter-graph') + onnxruntime_dep = dependency('onnxruntime', required: get_option('onnxruntime')) + summary({'onnxruntime': onnxruntime_dep.found()}, bool_yn: true, section: 'filter-graph') + cdata.set('HAVE_SPA_PLUGINS', true) subdir('plugins') endif diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build index 715fa7cfa..59c7c8739 100644 --- a/spa/plugins/filter-graph/meson.build +++ b/spa/plugins/filter-graph/meson.build @@ -125,3 +125,14 @@ spa_filter_graph_plugin_ffmpeg = shared_library('spa-filter-graph-plugin-ffmpeg' ) endif +if onnxruntime_dep.found() +spa_filter_graph_plugin_onnx = shared_library('spa-filter-graph-plugin-onnx', + [ 'plugin_onnx.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, onnxruntime_dep, avutil_dep] +) +endif + + diff --git a/spa/plugins/filter-graph/plugin_onnx.c b/spa/plugins/filter-graph/plugin_onnx.c new file mode 100644 index 000000000..73a41268e --- /dev/null +++ b/spa/plugins/filter-graph/plugin_onnx.c @@ -0,0 +1,772 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "audio-plugin.h" + +#define MAX_PORTS 256 +#define MAX_CTX 64 + +const OrtApi* ort = NULL; + +#define CHECK(expr) \ + if ((status = (expr)) != NULL) \ + goto error_onnx; + +struct plugin { + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_log *log; + + OrtEnv *env; + OrtAllocator *allocator; + OrtSessionOptions *session_options; +}; + +struct tensor_info { + int index; + enum spa_direction direction; + char *name; + enum ONNXTensorElementDataType type; + int64_t dimensions[64]; + size_t n_dimensions; + int retain; +#define DATA_NONE 0 +#define DATA_PORT 1 +#define DATA_CONTROL 2 +#define DATA_PARAM_RATE 3 +#define DATA_TENSOR 4 + uint32_t data_type; + char data_name[128]; + uint32_t data_index; + uint32_t data_size; +}; + +struct descriptor { + struct spa_fga_descriptor desc; + struct plugin *p; + + int blocksize; + OrtSession *session; + struct tensor_info tensors[MAX_PORTS]; + size_t n_tensors; +}; + +struct instance { + struct descriptor *desc; + + uint32_t rate; + + OrtRunOptions *run_options; + OrtValue *tensor[MAX_PORTS]; + + uint32_t offset; + float *data[MAX_PORTS]; +}; + +static struct tensor_info *find_tensor(struct descriptor *d, const char *name, enum spa_direction direction) +{ + size_t i; + for (i = 0; i < d->n_tensors; i++) { + struct tensor_info *ti = &d->tensors[i]; + if (spa_streq(ti->name, name) && ti->direction == direction) + return ti; + } + return NULL; +} + +/* + * { + * dimensions = [ 1, 576 ] + * retain = 64 + * data = "tensor:"|"param:rate"|"port:"|"control:" + * } + */ +static int parse_tensor_info(struct descriptor *d, struct spa_json *it, + struct tensor_info *info) +{ + struct plugin *p = d->p; + struct spa_json sub; + const char *val; + int len; + char key[256]; + char data[512]; + + while ((len = spa_json_object_next(it, key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "dimensions")) { + int64_t dimensions[64]; + size_t i, n_dimensions = 0; + if (!spa_json_is_array(val, len)) { + spa_log_error(p->log, "onnx: %s expects an array", key); + return -EINVAL; + } + spa_json_enter(it, &sub); + while (spa_json_get_string(&sub, data, sizeof(data)) > 0 && n_dimensions < 64) + dimensions[n_dimensions++] = atoi(data); + + if (info->n_dimensions == 0) + info->n_dimensions = n_dimensions; + else if (n_dimensions != info->n_dimensions) { + spa_log_error(p->log, "onnx: %s expected %zu dimensions, got %zu", + key, info->n_dimensions, n_dimensions); + return -EINVAL; + } + for (i = 0; i < n_dimensions; i++) { + if (info->dimensions[i] <= 0) + info->dimensions[i] = dimensions[i]; + else if (info->dimensions[i] != dimensions[i]) { + spa_log_error(p->log, "onnx: %s mismatched %zu dimension, got %" + PRIi64" expected %"PRIi64, + key, i, dimensions[i], info->dimensions[i]); + return -EINVAL; + } + } + } else if (spa_streq(key, "retain")) { + if (spa_json_parse_int(val, len, &info->retain) <= 0) { + spa_log_error(p->log, "onnx: %s expects an int", key); + return -EINVAL; + } + } else if (spa_streq(key, "data")) { + if (spa_json_parse_stringn(val, len, data, sizeof(data)) <= 0) { + spa_log_error(p->log, "onnx: %s expects a string", key); + return -EINVAL; + } + if (spa_strstartswith(data, "tensor:")) { + struct tensor_info *ti; + spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+7); + ti = find_tensor(d, info->data_name, SPA_DIRECTION_REVERSE(info->direction)); + if (ti == NULL) { + spa_log_error(p->log, "onnx: unknown tensor %s", info->data_name); + return -EINVAL; + } + info->data_type = DATA_TENSOR; + info->data_index = ti->index; + } + else if (spa_strstartswith(data, "param:rate")) { + info->data_type = DATA_PARAM_RATE; + } + else if (spa_strstartswith(data, "port:")) { + info->data_type = DATA_PORT; + spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+5); + } + else if (spa_strstartswith(data, "control:")) { + info->data_type = DATA_CONTROL; + spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+8); + } + else { + spa_log_warn(p->log, "onnx: unknown %s value: %s", key, data); + } + } else { + spa_log_warn(p->log, "unexpected onnx tensor-info key '%s'", key); + } + } + return 0; +} + +/* + * { + * = { + * + * } + * .... + * } + */ +static int parse_tensors(struct descriptor *d, struct spa_json *it, enum spa_direction direction) +{ + struct plugin *p = d->p; + struct spa_json sub; + const char *val; + int len, res; + char key[256]; + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + struct tensor_info *info; + + if ((info = find_tensor(d, key, direction)) == NULL) { + spa_log_error(p->log, "onnx: unknown tensor name %s", key); + return -EINVAL; + } + if (!spa_json_is_object(val, len)) { + spa_log_error(p->log, "onnx: tensors %s expects an object", key); + return -EINVAL; + } + spa_json_enter(it, &sub); + if ((res = parse_tensor_info(d, &sub, info)) < 0) + return res; + } + return 0; +} + +#define SET_VAL(data,type,val) \ +{ type _v = (type) (val); memcpy(data, &_v, sizeof(_v)); } \ + +static int set_value(void *data, enum ONNXTensorElementDataType type, double val) +{ + switch (type) { + case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: + SET_VAL(data, uint8_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: + SET_VAL(data, int8_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16: + SET_VAL(data, uint16_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16: + SET_VAL(data, int16_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: + SET_VAL(data, int32_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: + SET_VAL(data, int64_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: + SET_VAL(data, bool, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE: + SET_VAL(data, double, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: + SET_VAL(data, uint32_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: + SET_VAL(data, uint64_t, val); + break; + case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: + SET_VAL(data, float, val); + break; + default: + return -ENOTSUP; + } + return 0; +} +/* + * config = { + * blocksize = 512 + * input-tensors = { + * + * ... + * } + * output-tensors = { + * + * ... + * } + * } + */ + +static void *onnx_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, + unsigned long SampleRate, int index, const char *config) +{ + struct descriptor *d = (struct descriptor *)desc; + struct plugin *p = d->p; + struct instance *i; + OrtStatus *status; + size_t n, j; + int res; + + errno = EINVAL; + + i = calloc(1, sizeof(*i)); + if (i == NULL) + return NULL; + + i->desc = d; + i->rate = SampleRate; + + for (n = 0; n < d->n_tensors; n++) { + struct tensor_info *ti = &d->tensors[n]; + void *data; + + spa_log_debug(p->log, "%zd %s %zd", n, ti->name, ti->n_dimensions); + ti->data_size = 1; + for (j = 0; j < ti->n_dimensions; j++) { + spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, n, j, ti->n_dimensions, + ti->dimensions[j]); + if (ti->dimensions[j] != -1) + ti->data_size *= ti->dimensions[j]; + + } + CHECK(ort->CreateTensorAsOrtValue(p->allocator, ti->dimensions, ti->n_dimensions, + ti->type, &i->tensor[n])); + CHECK(ort->GetTensorMutableData(i->tensor[n], (void**)&data)); + + if (ti->data_type == DATA_PARAM_RATE) { + if ((res = set_value(data, ti->type, (double)i->rate)) < 0) { + errno = -res; + goto error; + } + + } + } + return i; + +error_onnx: + const char* msg = ort->GetErrorMessage(status); + spa_log_error(p->log, "%s", msg); + ort->ReleaseStatus(status); +error: + free(i); + return NULL; +} + +static void onnx_cleanup(void *instance) +{ + struct instance *i = instance; + free(i); +} + +static void onnx_free(const struct spa_fga_descriptor *desc) +{ + struct descriptor *d = (struct descriptor*)desc; + free((char*)d->desc.name); + free(d->desc.ports); + free(d); +} + +static void onnx_connect_port(void *instance, unsigned long port, float *data) +{ + struct instance *i = instance; + i->data[port] = data; +} + +static void move_samples(float *dst, uint32_t dst_offs, float *src, uint32_t src_offs, uint32_t n_samples) +{ + memmove(SPA_PTROFF(dst, dst_offs * sizeof(float), void), + SPA_PTROFF(src, src_offs * sizeof(float), void), n_samples * sizeof(float)); +} + +static void onnx_run(void *instance, unsigned long SampleCount) +{ + OrtStatus *status; + struct instance *i = instance; + struct descriptor *d = i->desc; + struct plugin *p = d->p; + const char *input_names[MAX_PORTS]; + const OrtValue *inputs[MAX_PORTS]; + const char *output_names[MAX_PORTS]; + OrtValue *outputs[MAX_PORTS]; + size_t n, n_inputs = 0, n_outputs = 0; + float *data; + uint32_t offset = i->offset, blocksize = d->blocksize; + + while (SampleCount > 0) { + uint32_t chunk = SPA_MIN(SampleCount, blocksize - offset); + uint32_t next_offset; + + for (n = 0; n < d->n_tensors; n++) { + struct tensor_info *ti = &d->tensors[n]; + if (ti->direction == SPA_DIRECTION_INPUT) { + input_names[n_inputs] = ti->name; + inputs[n_inputs++] = i->tensor[ti->index]; + + if (ti->data_type == DATA_PORT) { + CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&data)); + + if (ti->retain > 0 && offset == 0) + move_samples(data, 0, data, ti->data_size - ti->retain, ti->retain); + + move_samples(data, ti->retain + offset, + i->data[ti->data_index], offset, chunk); + } + else if (ti->data_type == DATA_TENSOR) { + if (offset == 0) { + void *src, *dst; + CHECK(ort->GetTensorMutableData(i->tensor[ti->data_index], &src)); + CHECK(ort->GetTensorMutableData(i->tensor[ti->index], &dst)); + move_samples(dst, 0, src, 0, ti->data_size); + } + } + } else { + output_names[n_outputs] = ti->name; + outputs[n_outputs++] = i->tensor[ti->index]; + } + } + if (offset + chunk >= blocksize) { + CHECK(ort->Run(d->session, i->run_options, + input_names, (const OrtValue *const*)inputs, n_inputs, + output_names, n_outputs, (OrtValue **)outputs)); + next_offset = 0; + } else { + next_offset = offset + chunk; + } + + for (n = 0; n < d->n_tensors; n++) { + struct tensor_info *ti = &d->tensors[n]; + if (ti->direction != SPA_DIRECTION_OUTPUT) + continue; + + if (ti->data_type == DATA_CONTROL) { + if (next_offset == 0) { + float *src, *dst; + CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&src)); + dst = i->data[ti->data_index]; + if (src && dst) + dst[0] = src[0]; + } + } + else if (ti->data_type == DATA_PORT) { + CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&data)); + move_samples(i->data[ti->data_index], offset, data, offset, chunk); + } + } + SampleCount -= chunk; + offset = next_offset; + } + i->offset = offset; + return; + +error_onnx: + const char* msg = ort->GetErrorMessage(status); + spa_log_error(p->log, "%s", msg); + ort->ReleaseStatus(status); +} + +static const struct spa_fga_descriptor *onnx_plugin_make_desc(void *plugin, const char *name) +{ + OrtStatus *status; + struct plugin *p = (struct plugin *)plugin; + struct descriptor *desc; + size_t i, j, n_inputs, n_outputs; + OrtTypeInfo *tinfo; + const OrtTensorTypeAndShapeInfo *tt; + char path[PATH_MAX]; + struct spa_json it[2]; + const char *val; + int len; + char key[256]; + + if (spa_json_begin_object(&it[0], name, strlen(name)) <= 0) { + spa_log_error(p->log, "onnx: expected object in label"); + return NULL; + } + if (spa_json_str_object_find(name, strlen(name), "filename", path, sizeof(path)) <= 0) { + spa_log_error(p->log, "onnx: could not find filename in label"); + return NULL; + } + + desc = calloc(1, sizeof(*desc)); + if (desc == NULL) + return NULL; + + desc->p = p; + + desc->desc.instantiate = onnx_instantiate; + desc->desc.cleanup = onnx_cleanup; + desc->desc.free = onnx_free; + desc->desc.connect_port = onnx_connect_port; + desc->desc.run = onnx_run; + + desc->desc.name = strdup(name); + desc->desc.flags = 0; + + spa_log_info(p->log, "onnx: loading model %s", path); + CHECK(ort->CreateSession(p->env, path, p->session_options, &desc->session)); + + CHECK(ort->SessionGetInputCount(desc->session, &n_inputs)); + CHECK(ort->SessionGetOutputCount(desc->session, &n_outputs)); + + spa_log_info(p->log, "found %zd input and %zd output tensors", n_inputs, n_outputs); + + /* first go over all tensors and collect info */ + for (i = 0; i < n_inputs; i++) { + struct tensor_info *ti = &desc->tensors[i]; + + ti->index = i; + ti->direction = SPA_DIRECTION_INPUT; + CHECK(ort->SessionGetInputName(desc->session, i, p->allocator, (char**)&ti->name)); + + CHECK(ort->SessionGetInputTypeInfo(desc->session, i, &tinfo)); + CHECK(ort->CastTypeInfoToTensorInfo(tinfo, &tt)); + + CHECK(ort->GetTensorElementType(tt, &ti->type)); + CHECK(ort->GetDimensionsCount(tt, &ti->n_dimensions)); + if (ti->n_dimensions > SPA_N_ELEMENTS(ti->dimensions)) { + spa_log_warn(p->log, "too many dimensions"); + errno = ENOTSUP; + goto error; + } + CHECK(ort->GetDimensions(tt, ti->dimensions, ti->n_dimensions)); + + spa_log_debug(p->log, "%zd %s %zd", i, ti->name, ti->n_dimensions); + for (j = 0; j < ti->n_dimensions; j++) { + spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, i, j, ti->n_dimensions, + ti->dimensions[j]); + } + } + for (i = 0; i < n_outputs; i++) { + struct tensor_info *ti = &desc->tensors[i + n_inputs]; + + ti->index = i + n_inputs; + ti->direction = SPA_DIRECTION_OUTPUT; + CHECK(ort->SessionGetOutputName(desc->session, i, p->allocator, (char**)&ti->name)); + + CHECK(ort->SessionGetOutputTypeInfo(desc->session, i, &tinfo)); + CHECK(ort->CastTypeInfoToTensorInfo(tinfo, &tt)); + + CHECK(ort->GetTensorElementType(tt, &ti->type)); + CHECK(ort->GetDimensionsCount(tt, &ti->n_dimensions)); + if (ti->n_dimensions > SPA_N_ELEMENTS(ti->dimensions)) { + spa_log_error(p->log, "too many dimensions"); + errno = ENOTSUP; + goto error; + } + CHECK(ort->GetDimensions(tt, ti->dimensions, ti->n_dimensions)); + + spa_log_debug(p->log, "%zd %s %zd", i, ti->name, ti->n_dimensions); + for (j = 0; j < ti->n_dimensions; j++) { + spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, i, j, ti->n_dimensions, + ti->dimensions[j]); + } + } + desc->n_tensors = n_inputs + n_outputs; + + /* enhance the tensor info */ + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "blocksize")) { + if (spa_json_parse_int(val, len, &desc->blocksize) <= 0) { + spa_log_error(p->log, "onnx:blocksize requires a number"); + errno = EINVAL; + goto error; + } + } + else if (spa_streq(key, "input-tensors")) { + if (!spa_json_is_object(val, len)) { + spa_log_error(p->log, "onnx: %s expects an object", key); + errno = EINVAL; + goto error; + } + spa_json_enter(&it[0], &it[1]); + parse_tensors(desc, &it[1], SPA_DIRECTION_INPUT); + } + else if (spa_streq(key, "output-tensors")) { + if (!spa_json_is_object(val, len)) { + spa_log_error(p->log, "onnx: %s expects an object", key); + errno = EINVAL; + goto error; + } + spa_json_enter(&it[0], &it[1]); + parse_tensors(desc, &it[1], SPA_DIRECTION_OUTPUT); + } + } + + desc->desc.ports = calloc(desc->n_tensors, sizeof(struct spa_fga_port)); + desc->desc.n_ports = 0; + + /* make ports */ + for (i = 0; i < desc->n_tensors; i++) { + struct tensor_info *ti = &desc->tensors[i]; + struct spa_fga_port *fp = &desc->desc.ports[desc->desc.n_ports]; + + fp->flags = 0; + fp->index = desc->desc.n_ports; + if (ti->type != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) + continue; + + if (ti->data_type == DATA_PORT) + fp->flags |= SPA_FGA_PORT_AUDIO; + else if (ti->data_type == DATA_CONTROL) + fp->flags |= SPA_FGA_PORT_CONTROL; + else + continue; + + if (ti->direction == SPA_DIRECTION_INPUT) + fp->flags |= SPA_FGA_PORT_INPUT; + else + fp->flags |= SPA_FGA_PORT_OUTPUT; + + fp->name = ti->data_name; + ti->data_index = desc->desc.n_ports; + + desc->desc.n_ports++; + if (desc->desc.n_ports > MAX_PORTS) { + spa_log_error(p->log, "too many ports"); + errno = -ENOSPC; + goto error; + } + } + return &desc->desc; + +error_onnx: + const char* msg = ort->GetErrorMessage(status); + spa_log_error(p->log, "%s", msg); + ort->ReleaseStatus(status); + +error: + if (desc->session) + ort->ReleaseSession(desc->session); + onnx_free(&desc->desc); + return NULL; +} + +static int load_model(struct plugin *impl, const char *path) +{ + OrtStatus *status; + + ort = OrtGetApiBase()->GetApi(ORT_API_VERSION); + if (ort == NULL) { + spa_log_error(impl->log, "Failed to init ONNX Runtime engine"); + return -EINVAL; + } + CHECK(ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "onnx-filter-graph", &impl->env)); + CHECK(ort->GetAllocatorWithDefaultOptions(&impl->allocator)); + + CHECK(ort->CreateSessionOptions(&impl->session_options)); + CHECK(ort->SetIntraOpNumThreads(impl->session_options, 1)); + CHECK(ort->SetInterOpNumThreads(impl->session_options, 1)); + CHECK(ort->SetSessionGraphOptimizationLevel(impl->session_options, ORT_ENABLE_ALL)); + + return 0; + +error_onnx: + const char* msg = ort->GetErrorMessage(status); + spa_log_error(impl->log, "%s", msg); + ort->ReleaseStatus(status); + + if (impl->env) + ort->ReleaseEnv(impl->env); + impl->env = NULL; + if (impl->session_options) + ort->ReleaseSessionOptions(impl->session_options); + impl->session_options = NULL; + + return -EINVAL; +} + +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = onnx_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct plugin); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct plugin *impl; + uint32_t i; + const char *path = NULL; + int res; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "filter.graph.path")) + path = s; + } + + if ((res = load_model(impl, path)) < 0) + return res; + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +static struct spa_handle_factory spa_fga_plugin_onnx_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.onnx", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_fga_plugin_onnx_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/src/daemon/filter-chain/22-onnx-vad.conf b/src/daemon/filter-chain/22-onnx-vad.conf new file mode 100644 index 000000000..35ed8dde8 --- /dev/null +++ b/src/daemon/filter-chain/22-onnx-vad.conf @@ -0,0 +1,83 @@ +context.modules = [ +{ name = libpipewire-module-filter-chain + flags = [ nofail ] + args = { + node.description = "ONNX Example" + node.name = "neural.example" + audio.rate = 16000 + node.latency = "512/16000" + filter.graph = { + nodes = [ + { + type = builtin + name = copy + label = copy + } + { + type = onnx + name = onnx + label = { + #filename = "/home/wim/src/silero-vad/src/silero_vad/data/silero_vad_half.onnx" + filename = "/home/wim/src/silero-vad/src/silero_vad/data/silero_vad.onnx" + blocksize = 512 + input-tensors = { + "input" = { + dimensions = [ 1, 576 ] + retain = 64 + data = "port:input" + } + "state" = { + dimensions = [ 2, 1, 128 ] + data = "tensor:stateN" + } + "sr" = { + dimensions = [ 1 ] + data = "param:rate" + } + } + output-tensors = { + "output" = { + dimensions = [ 1, 1 ] + data = "control:speech" + } + "stateN" = { + dimensions = [ 2, 1, 128 ] + } + } + } + control = { + } + config = { + } + } + { + type = builtin + name = noisegate + label = noisegate + control = { + "Open Threshold" 0.1 + "Close Threshold" 0.02 + } + } + ] + links = [ + { output = "copy:Out" input="onnx:input" } + { output = "copy:Out" input="noisegate:In" } + { output = "onnx:speech" input="noisegate:Level" } + ] + inputs = [ "copy:In" ] + outputs = [ "noisegate:Out" ] + } + + capture.props = { + node.name = "capture.neural" + audio.position = [ MONO ] + } + + playback.props = { + node.name = "playback.neural" + audio.position = [ MONO ] + } + } +} +] diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index a02364abd..6c2f16200 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -870,6 +870,119 @@ extern struct spa_handle_factory spa_filter_graph_factory; * ... * } *\endcode + + * ## ONNX filters + * + * There is an optional ONNX filter available (when compiled with `libonnxruntime`) + * that can be selected with the `onnx` type. Use the `label` field to select + * the model to use and how to map the tensors to ports. + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = onnx + * name = onnx + * label = { + * filename = "..." + * blocksize = 512 + * input-tensors = { + * "" = { + * dimensions = [ ... ] + * #retain = 64 + * data = "port:..."|"tensor:..."|"param:..."|"control:..." + * } + * ... + * } + * output-tensors = { + * "" = { + * dimensions = [ ... ] + * #retain = 64 + * data = "port:..."|"tensor:..."|"param:..."|"control:..." + * } + * ... + * } + * } + * } + * } + * ... + * } + *\endcode + * + * The label must contain an object with the configuration of the plugin. + * + * - `filename` the ONNX model to load. It must point to an existing onnx file. + * - `blocksize` the number of samples to give to the model. This depends on the model + * and the input/output tensor sizes. + * - `input-tensors` an object of input tensors of the model and how they should be + * used. Unlisted tensors will not be used. + * - `output-tensors` an object of output tensors of the model and how they should be + * used. Unlisted tensors will not be used. + * + * The `input-tensors` and `output-tensors` configuration must contain an object with + * keys named after the tensors in the model and the value must be an object with the + * the following keys: + * + * - `dimensions` and array of dimensions of the tensors. + * - `retain` an optional key for input tensors. This will prepend the last `retain` samples + * from the previous block to the input tensor. The size of the tensor should + * therefore at least be blocksize + retain samples large. + * - `data` where the data for the tensor is comming from. There are different options + * based on the value of this file, selected with a prefix: + * - `port:` a new input/output port is created on the plugin with the + * name and the data for the tensor will be obtained + * or copied from/to the port data. + * - `tensor:` the data of this tensor is copied from the given + * . You can use this to copy output state + * info to the input state, for example. + * - `param:` the data of this tensor is obtained from a parameter with + * . Currently only `rate` is a valid paramname, + * which has the value of the filter samplerate. + * - `control:` a new input/output control port is created and the tensor + * data will be obtained/copied from/to the control data. + * + * Here is an example of the silero VAD model: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = onnx + * name = onnx + * label = { + * filename = "/home/wim/src/silero-vad/src/silero_vad/data/silero_vad.onnx" + * blocksize = 512 + * input-tensors = { + * "input" = { + * dimensions = [ 1, 576 ] + * retain = 64 + * data = "port:input" + * } + * "state" = { + * dimensions = [ 2, 1, 128 ] + * data = "tensor:stateN" + * } + * "sr" = { + * dimensions = [ 1 ] + * data = "param:rate" + * } + * } + * output-tensors = { + * "output" = { + * dimensions = [ 1, 1 ] + * data = "control:speech" + * } + * "stateN" = { + * dimensions = [ 2, 1, 128 ] + * } + * } + * } + * } + * ... + * ] + * .... + * } + *\endcode * * ## General options * From c38dc1cf72777303ff3661dcda604b6332610715 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 13:29:03 +0200 Subject: [PATCH 0582/1014] CI: add onnxruntime --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index efedd6d94..3212b770e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -90,6 +90,7 @@ include: pandoc fftw-libs-single fftw-devel + onnxruntime-devel # Uncommenting the following two lines and disabling the meson entry above # will re-enable use of Meson via pip but please consider using a newer distro # image first or making the build system compatible instead! This is because From ae226a12b63d747ede54cf80e1c5500093d3bc63 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 13:47:59 +0200 Subject: [PATCH 0583/1014] CI: trigger rebuild --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3212b770e..0c286ccf1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-07-11.0' + FDO_DISTRIBUTION_TAG: '2025-07-17.0' FDO_DISTRIBUTION_VERSION: '41' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel From efa4d31cfbb4b72b282969c907748e18641bf4e9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 14:02:19 +0200 Subject: [PATCH 0584/1014] meson: use libonnxruntime so that the .pc file can be found --- spa/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/meson.build b/spa/meson.build index 798ef373f..5087c0073 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -147,7 +147,7 @@ if get_option('spa-plugins').allowed() summary({'ffmpeg': avfilter_dep.found()}, bool_yn: true, section: 'filter-graph') - onnxruntime_dep = dependency('onnxruntime', required: get_option('onnxruntime')) + onnxruntime_dep = dependency('libonnxruntime', required: get_option('onnxruntime')) summary({'onnxruntime': onnxruntime_dep.found()}, bool_yn: true, section: 'filter-graph') cdata.set('HAVE_SPA_PLUGINS', true) From ece2890c8bead4832356941c8ef680a5b50ed293 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 17 Jul 2025 14:08:20 +0200 Subject: [PATCH 0585/1014] meson: remove avutils as a ONNX dependency --- spa/plugins/filter-graph/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build index 59c7c8739..1995ae4c1 100644 --- a/spa/plugins/filter-graph/meson.build +++ b/spa/plugins/filter-graph/meson.build @@ -131,7 +131,7 @@ spa_filter_graph_plugin_onnx = shared_library('spa-filter-graph-plugin-onnx', include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', - dependencies : [ filter_graph_dependencies, onnxruntime_dep, avutil_dep] + dependencies : [ filter_graph_dependencies, onnxruntime_dep] ) endif From bb022c1b84d8c3f272ba01d89dd239e2e1465d0e Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 17 Jul 2025 12:52:09 +0200 Subject: [PATCH 0586/1014] node-driver: Handle realtime clock modifications If the user alters the realtime clock (for example by using the "date" command in the shell), and the node driver uses the realtime clock as the timerfd clock, then the scheduled graph cycle invocation may not take place, or may take place much later than planned, because the timestamp that was passed to spa_system_timerfd_settime() is now invalid. Configure the timer to automatically be canceled if the realtime clock is modified so that the graph cycle can be rescheduled with an updated timestamp that is actually usable with the altered realtime clock. --- spa/plugins/support/node-driver.c | 80 ++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index 701a5b7bf..1ca186a49 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -154,11 +154,25 @@ static const char *clock_id_to_name(clockid_t id) static void set_timeout(struct impl *this, uint64_t next_time) { + /* The realtime system clock may be modified by the user. In such a case, + * a scheduled timer must be canceled, since its timeout is no longer + * correctly corresponding to the duration of a graph cycle. Worse, if + * for example the user resets the realtime clock way back to the past, + * then the timeout may now be far in the future, meaning that the next + * graph cycle never takes place. The SPA_FD_TIMER_CANCEL_ON_SET flag is + * used here to automatically cancel the timer if the user sets the realtime + * clock so that the driver can reschedule the cycle. (Timer cancelation + * will trigger an on_timeout() invocation with spa_system_timerfd_read() + * returning -ECANCELED.) + * If timerfd is used with a non-realtime clock, the flag is ignored. + * (Note that the flag only works in combination with SPA_FD_TIMER_ABSTIME.) */ + spa_log_trace(this->log, "set timeout %"PRIu64, next_time); this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; spa_system_timerfd_settime(this->data_system, - this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); + this->timer_source.fd, SPA_FD_TIMER_ABSTIME | + SPA_FD_TIMER_CANCEL_ON_SET, &this->timerspec, NULL); } static inline uint64_t gettime_nsec(struct impl *this, clockid_t clock_id) @@ -329,14 +343,25 @@ static void on_timeout(struct spa_source *source) uint32_t rate; double corr = 1.0, err = 0.0; int res; + bool timer_was_canceled = false; + + /* See set_timeout() for an explanation about timer cancelation. */ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { - if (res != -EAGAIN) + if (res == -EAGAIN) { + return; + } else if (res == -ECANCELED) { + spa_log_debug(this->log, "%p: timer was canceled; " + "rescheduling graph cycle", this); + timer_was_canceled = true; + } else { spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); - return; + return; + } } + if (SPA_LIKELY(this->position)) { duration = this->position->clock.target_duration; rate = this->position->clock.target_rate.denom; @@ -344,24 +369,62 @@ static void on_timeout(struct spa_source *source) duration = 1024; rate = 48000; } - if (this->props.freewheel) + + /* In freewheel mode, graph cycles are run as fast as possible, + * especially if the "freewheel.wait" period is 0. In such a case, + * as soon as the mainloop encounters the scheduled timer timeout, + * it will execute it immediately. Since it is not possible to + * measure how long it takes the mainloop to do that, it is not + * possible to rely on this->next_time as the nsec value in + * freewheel mode (this->next_time does not factor in the mainloop + * invocation time mentioned earlier). Instead, sample the current + * monotonic time when freewheel mode is active, to account for + * that invocation time. + * + * Also, if the timer was canceled, the graph cycle needs to be + * rescheduled, and it cannot be assumed that the this->next_time + * and this->clock->position values are correct anymore. (Timer + * cancellations happen when the realtime clock is being used by + * this driver and the user modified the realtime clock for example.) + */ + if (this->props.freewheel || SPA_UNLIKELY(timer_was_canceled)) nsec = gettime_nsec(this, this->timer_clockid); else nsec = this->next_time; + /* "tracking" means that the driver is following a clock that is not + * usable by timerfd. It is an entirely separate clock, for example, + * a network interface PHC. If tracking is true, timer_clockid is + * always the monotonic clock, and this->props.clock_id is that entirely + * separate clock. If tracking is false, then this->props.clock_id + * equals timer_clockid, so "nsec" can directly be used as the current + * driver clock time in that case. */ if (this->tracking) - /* we are actually following another clock */ current_time = gettime_nsec(this, this->props.clock_id); else current_time = nsec; current_position = scale_u64(current_time, rate, SPA_NSEC_PER_SEC); - if (this->last_time == 0) { + if ((this->last_time == 0) || SPA_UNLIKELY(timer_was_canceled)) { spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate); this->max_error = rate * MAX_ERROR_MS / 1000; this->max_resync = rate * this->props.resync_ms / 1000; position = current_position; + + /* If the timer was canceled, then it is assumed that a + * discontinuity occurred. Accumulated nsec_offset values + * cannot be relied upon anymore, and need to be reset. + * Also, base_time is set back to 0 to make sure the log line + * further below (which prints current stats) continues to + * be printed. (If for example the clock was set to an + * earlier time, then the base_time might contain a future + * timestamp that the clock won't reach for a long while.) */ + if (timer_was_canceled) { + this->base_time = 0; + this->nsec_offset.offset = get_nsec_offset(this, NULL); + this->nsec_offset.err = 0; + } } else if (SPA_LIKELY(this->clock)) { position = this->clock->position + this->clock->duration; } else { @@ -415,6 +478,11 @@ static void on_timeout(struct spa_source *source) this->clock->delay = 0; this->clock->rate_diff = corr; this->clock->next_nsec = this->next_time + nsec_offset; + + if (SPA_UNLIKELY(timer_was_canceled)) { + SPA_FLAG_UPDATE(this->clock->flags, + SPA_IO_CLOCK_FLAG_DISCONT, true); + } } spa_node_call_ready(&this->callbacks, From f4f548fbe643fc315d2c1b8ff94eaa75adac62cc Mon Sep 17 00:00:00 2001 From: Melvin Manninen Date: Thu, 17 Jul 2025 14:21:10 +0200 Subject: [PATCH 0587/1014] module-rtp-sap: Fix Message Identifier Hash generation for first session and update SDP on grandmaster change --- src/modules/module-rtp-sap.c | 63 +++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 3fa69ef74..9c6135701 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -544,10 +544,10 @@ error: return res; } -static void update_ts_refclk(struct impl *impl) +static bool update_ts_refclk(struct impl *impl) { if (!impl->ptp_mgmt_socket || impl->ptp_fd < 0) - return; + return false; // Read if something is left in the socket int avail; @@ -579,13 +579,13 @@ static void update_ts_refclk(struct impl *impl) if (write(impl->ptp_fd, &req, sizeof(req)) == -1) { pw_log_warn("Failed to send PTP management request: %m"); - return; + return false; } uint8_t buf[sizeof(struct ptp_management_msg) + sizeof(struct ptp_parent_data_set)]; if (read(impl->ptp_fd, &buf, sizeof(buf)) == -1) { pw_log_warn("Failed to receive PTP management response: %m"); - return; + return false; } struct ptp_management_msg res = *(struct ptp_management_msg *)buf; @@ -594,27 +594,27 @@ static void update_ts_refclk(struct impl *impl) if ((res.ver & 0x0f) != 2) { pw_log_warn("PTP major version is %d, expected 2", res.ver); - return; + return false; } if ((res.major_sdo_id_message_type & 0x0f) != PTP_MESSAGE_TYPE_MANAGEMENT) { pw_log_warn("PTP management returned type %x, expected management", res.major_sdo_id_message_type); - return; + return false; } if (res.action != PTP_MGMT_ACTION_RESPONSE) { pw_log_warn("PTP management returned action %d, expected response", res.action); - return; + return false; } if (be16toh(res.tlv_type_be) != PTP_TLV_TYPE_MGMT) { pw_log_warn("PTP management returned tlv type %d, expected management", be16toh(res.tlv_type_be)); - return; + return false; } if (be16toh(res.management_id_be) != PTP_MGMT_ID_PARENT_DATA_SET) { pw_log_warn("PTP management returned ID %d, expected PARENT_DATA_SET", be16toh(res.management_id_be)); - return; + return false; } uint16_t data_len = be16toh(res.management_message_length_be) - 2; @@ -637,7 +637,8 @@ static void update_ts_refclk(struct impl *impl) ); uint8_t *gmid = parent.gm_clock_id; - if (memcmp(gmid, impl->gm_id, 8) != 0) + bool gmid_changed = false; + if (memcmp(gmid, impl->gm_id, 8) != 0) { pw_log_info( "GM ID: IEEE1588-2008:%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X:%d", gmid[0], @@ -650,12 +651,15 @@ static void update_ts_refclk(struct impl *impl) gmid[7], 0 /* domain */ ); + gmid_changed = true; + } // When GM is not equal to own clock we are clocked by external master pw_log_debug("Synced to GM: %s", (memcmp(cid, gmid, 8) != 0) ? "true" : "false"); memcpy(impl->clock_id, cid, 8); memcpy(impl->gm_id, gmid, 8); + return gmid_changed; } static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_t buffer_size) @@ -883,18 +887,37 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) return res; } +static uint16_t generate_hash(uint16_t prev) +{ + uint16_t hash = pw_rand32(); + if (hash == prev) hash++; + if (hash == 0) hash++; + return hash; +} + static void on_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; struct session *sess, *tmp; uint64_t timestamp, interval; + bool clk_changed; timestamp = get_time_nsec(impl); interval = impl->cleanup_interval * SPA_NSEC_PER_SEC; - update_ts_refclk(impl); + clk_changed = update_ts_refclk(impl); spa_list_for_each_safe(sess, tmp, &impl->sessions, link) { if (sess->announce) { + if (clk_changed) { + // The clock has changed: Send bye and create new SDP. + send_sap(impl, sess, 1); + sess->info.hash = generate_hash(sess->info.hash); + int res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); + + if (res != 0) { + pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + } + } send_sap(impl, sess, 0); } else { if (sess->timestamp + interval < timestamp) { @@ -1024,12 +1047,12 @@ static struct session *session_new_announce(struct impl *impl, struct node *node res = make_sdp(impl, sess, buffer, sizeof(buffer)); /* we had no sdp or something changed */ - if (res == 0 && (!sess->has_sdp || strcmp(buffer, sess->sdp) != 0)) { + if (!sess->has_sdp || ((res == 0) && strcmp(buffer, sess->sdp) != 0)) { /* send bye on the old session */ send_sap(impl, sess, 1); /* update the version and hash */ - sdp->hash = pw_rand32(); + sdp->hash = generate_hash(sdp->hash); if ((str = pw_properties_get(props, "sess.id")) != NULL) { if (!spa_atou32(str, &sdp->session_id, 10)) { pw_log_error("Invalid session id: %s (must be a uint32)", str); @@ -1050,13 +1073,15 @@ static struct session *session_new_announce(struct impl *impl, struct node *node sdp->session_version = sdp->t_ntp; } - /* make an updated SDP for sending, this should not actually fail */ - res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); + if (res == 0) { + /* make an updated SDP for sending, this should not actually fail */ + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); - if (res == 0) - sess->has_sdp = true; - else - pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + if (res == 0) + sess->has_sdp = true; + else + pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + } } send_sap(impl, sess, 0); From 14b242c7372e98fbc2cf07f219c6d2d68dd2c098 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 17 Jul 2025 16:32:15 +0200 Subject: [PATCH 0588/1014] node-driver: Make sure the discont clock flag does not remain set forever If the timer was canceled, the discont flag needs to be set. But in the next cycle, unless the timer was canceled again, that flag should not remain set. --- spa/plugins/support/node-driver.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index 1ca186a49..e7b2c702b 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -479,10 +479,8 @@ static void on_timeout(struct spa_source *source) this->clock->rate_diff = corr; this->clock->next_nsec = this->next_time + nsec_offset; - if (SPA_UNLIKELY(timer_was_canceled)) { - SPA_FLAG_UPDATE(this->clock->flags, - SPA_IO_CLOCK_FLAG_DISCONT, true); - } + SPA_FLAG_UPDATE(this->clock->flags, SPA_IO_CLOCK_FLAG_DISCONT, + timer_was_canceled); } spa_node_call_ready(&this->callbacks, From 1c991a329ea627f2d61f9f0cc879ab4f987ef2e3 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Tue, 5 Dec 2023 20:12:37 +0100 Subject: [PATCH 0589/1014] pw-cat: Use multiple encoded frames if single frames don't fill the quantum --- src/tools/pw-cat.c | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 406675c7a..f66ea90d6 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -253,8 +253,10 @@ static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_fram { AVPacket *packet = d->encoded.packet; int ret; + uint8_t *dest_ptr = (uint8_t *)dest; struct pw_time time; int64_t quantum_duration; + int64_t accumulated_duration; int64_t excess_playtime; int64_t cycle_length; int64_t av_time_base_num, av_time_base_denom; @@ -302,26 +304,38 @@ static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_fram return 0; } - /* Keep reading packets until we get one from the stream we are - * interested in. This is relevant when playing data that contains - * several multiplexed streams. */ - while (true) { - if ((ret = av_read_frame(d->encoded.format_context, packet) < 0)) - break; + accumulated_duration = 0; - if (packet->stream_index == d->encoded.stream_index) + /* Continue filling the buffer until at least a quantum's worth of data + * has been written. Encoded frames may have a playtime that is _shorter_ + * than a quantum. In that case, multiple frames must be written into the + * buffer to cover a quantum length. */ + while (true) { + /* Keep reading packets until we get one from the stream we are + * interested in. This is relevant when playing data that contains + * several multiplexed streams. */ + while (true) { + if ((ret = av_read_frame(d->encoded.format_context, packet) < 0)) + break; + + if (packet->stream_index == d->encoded.stream_index) + break; + } + + memcpy(dest_ptr, packet->data, packet->size); + + accumulated_duration += packet->duration; + dest_ptr += packet->size; + + if (accumulated_duration >= quantum_duration) + { + excess_playtime = accumulated_duration - quantum_duration; + d->encoded.accumulated_excess_playtime += excess_playtime; break; + } } - memcpy(dest, packet->data, packet->size); - - if (packet->duration > quantum_duration) - excess_playtime = packet->duration - quantum_duration; - else - excess_playtime = 0; - d->encoded.accumulated_excess_playtime += excess_playtime; - - return packet->size; + return (dest_ptr - ((uint8_t*)dest)); } static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *codec_params, struct spa_audio_info *info) From 48716a72b13793322469cdfa94d73cdd75b71158 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 7 Dec 2023 12:30:10 +0100 Subject: [PATCH 0590/1014] alsa-compress-offload-sink: Remove unused variable --- spa/plugins/alsa/compress-offload-api.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/spa/plugins/alsa/compress-offload-api.c b/spa/plugins/alsa/compress-offload-api.c index 3f140c7a0..9ff0f8772 100644 --- a/spa/plugins/alsa/compress-offload-api.c +++ b/spa/plugins/alsa/compress-offload-api.c @@ -16,7 +16,6 @@ struct compress_offload_api_context { int fd; struct snd_compr_caps caps; struct spa_log *log; - bool was_configured; uint32_t fragment_size; uint32_t num_fragments; }; @@ -110,8 +109,6 @@ int compress_offload_api_set_params(struct compress_offload_api_context *context return -errno; } - context->was_configured = true; - return 0; } From c504851dca833df4af6ce83bf917604b768da01b Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 7 Dec 2023 13:08:39 +0100 Subject: [PATCH 0591/1014] alsa-compress-offload-sink: Improve write_queued_output_buffers comments --- spa/plugins/alsa/alsa-compress-offload-sink.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spa/plugins/alsa/alsa-compress-offload-sink.c b/spa/plugins/alsa/alsa-compress-offload-sink.c index 1477b5e4d..c9691a18f 100644 --- a/spa/plugins/alsa/alsa-compress-offload-sink.c +++ b/spa/plugins/alsa/alsa-compress-offload-sink.c @@ -967,11 +967,15 @@ static int write_queued_output_buffers(struct impl *this) * If during the write attempts, only a portion of a chunk * is written, we must keep track of the portion that hasn't * been consumed yet. offset_within_oldest_output_buffer - * exists for this purpose. In this sink node, each SPA - * buffer has exactly one chunk, so when a chunk is fully - * consumed, the corresponding buffer is removed from the - * queued_output_buffers list, marked as available, and - * returned to the pool through spa_node_call_reuse_buffer(). */ + * exists for this purpose. This can happen when the + * device_write() call below returns 0. The loop is then + * aborted, and the chunk is not fully written. + * + * In this sink node, each SPA buffer has exactly one chunk, + * so when a chunk is fully consumed, the corresponding buffer + * is removed from the queued_output_buffers list, marked as + * available, and returned to the pool through + * spa_node_call_reuse_buffer(). */ again: total_num_written_bytes = 0; From 97996a6e20dc8a3b9597c91675959c2e099efe21 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 4 May 2025 23:11:31 +0200 Subject: [PATCH 0592/1014] module-rtp-sap: Take RTP dest port into account when matching sessions This is important if several sessions use the same multicast IP address. --- src/modules/module-rtp-sap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 9c6135701..79af04f2e 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -935,6 +935,7 @@ static struct session *session_find(struct impl *impl, const struct sdp_info *in struct session *sess; spa_list_for_each(sess, &impl->sessions, link) { if (info->hash == sess->info.hash && + info->dst_port == sess->info.dst_port && spa_streq(info->origin, sess->info.origin)) return sess; } From f2c878a2c175d0bece4571df691a8af02e7a3ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 10 May 2025 17:43:28 +0200 Subject: [PATCH 0593/1014] meson.build: rework `systemd` related options One issues is that the `systemd-{system,user}-service` feature options do not anything without the `systemd` option. This makes it more complicated to arrive at the desired build configuration since there are 3^3 = 27 possible ways to set each of them, but if `systemd=disabled`, then the other two are just ignored. Secondly, the `systemd` option also influences whether or not libsystemd will be used or not. This is not strictly necessary, since the "systemd" and "libsystemd" pkg-config files might be split, and one might wish to disable any kind of service file generation, but use libsystemd. Solve the first issues by using the `systemd-{system,user}-service` options when looking up the "systemd" dependency for generating service files. This means that the corresponding option is in full control, no secondary options are necessary. This means that the "systemd" dependency is looked up potentially twice, but that should not be a significant issue since meson caches dependecy lookups. And solve the second issue by renaming the now unused `systemd` option to `libsystemd` and using it solely to control whether or not libsystemd will be used. Furthermore, the default value of `systemd-user-service` is set to "auto" to prevent the dependency lookup from failing on non-systemd systemd out of the box. And the journal tests in "test-support" are extended to return "skip" if `sd_journal_open()` returns `ENOSYS`, which is needed because "elogind" ships the systemd pkg-config files and headers. --- meson.build | 6 ++---- meson_options.txt | 8 ++++---- src/daemon/meson.build | 4 +--- src/daemon/systemd/meson.build | 8 ++------ src/daemon/systemd/system/meson.build | 5 +++++ src/daemon/systemd/user/meson.build | 5 +++++ test/test-logger.c | 6 ++++++ 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/meson.build b/meson.build index 0ba5c57a8..472b97705 100644 --- a/meson.build +++ b/meson.build @@ -277,11 +277,9 @@ endforeach cdata.set('HAVE_PIDFD_OPEN', cc.get_define('SYS_pidfd_open', prefix: '#include ') != '') -systemd = dependency('systemd', required: get_option('systemd')) -systemd_dep = dependency('libsystemd',required: get_option('systemd')) -summary({'systemd conf data': systemd.found()}, bool_yn: true) +systemd_dep = dependency('libsystemd', required: get_option('libsystemd')) summary({'libsystemd': systemd_dep.found()}, bool_yn: true) -cdata.set('HAVE_SYSTEMD', systemd.found() and systemd_dep.found()) +cdata.set('HAVE_SYSTEMD', systemd_dep.found()) logind_dep = dependency(get_option('logind-provider'), required: get_option('logind')) summary({'logind': logind_dep.found()}, bool_yn: true) diff --git a/meson_options.txt b/meson_options.txt index c5464e063..206d68659 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,8 +30,8 @@ option('gstreamer-device-provider', description: 'Build GStreamer device provider plugin', type: 'feature', value: 'auto') -option('systemd', - description: 'Enable systemd integration', +option('libsystemd', + description: 'Enable code that depends on libsystemd', type: 'feature', value: 'auto') option('logind', @@ -48,9 +48,9 @@ option('systemd-system-service', type: 'feature', value: 'disabled') option('systemd-user-service', - description: 'Install systemd user service file (ignored without systemd)', + description: 'Install systemd user service file', type: 'feature', - value: 'enabled') + value: 'auto') option('selinux', description: 'Enable SELinux integration', type: 'feature', diff --git a/src/daemon/meson.build b/src/daemon/meson.build index b2ebb9375..e7e482f73 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -161,6 +161,4 @@ custom_target('pipewire-uninstalled', #endif subdir('filter-chain') -if systemd.found() - subdir('systemd') -endif +subdir('systemd') diff --git a/src/daemon/systemd/meson.build b/src/daemon/systemd/meson.build index 482a44c4b..e07449c00 100644 --- a/src/daemon/systemd/meson.build +++ b/src/daemon/systemd/meson.build @@ -1,6 +1,2 @@ -if get_option('systemd-system-service').allowed() - subdir('system') -endif -if get_option('systemd-user-service').allowed() - subdir('user') -endif +subdir('system') +subdir('user') diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build index 0cc17670e..02efc7a41 100644 --- a/src/daemon/systemd/system/meson.build +++ b/src/daemon/systemd/system/meson.build @@ -1,3 +1,8 @@ +systemd = dependency('systemd', required : get_option('systemd-system-service')) +if not systemd.found() + subdir_done() +endif + systemd_system_services_dir = systemd.get_variable('systemdsystemunitdir', pkgconfig_define : [ 'rootprefix', prefix]) if get_option('systemd-system-unit-dir') != '' systemd_system_services_dir = get_option('systemd-system-unit-dir') diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build index a96409f2b..1b65d5f7a 100644 --- a/src/daemon/systemd/user/meson.build +++ b/src/daemon/systemd/user/meson.build @@ -1,3 +1,8 @@ +systemd = dependency('systemd', required : get_option('systemd-user-service')) +if not systemd.found() + subdir_done() +endif + systemd_user_services_dir = systemd.get_variable('systemduserunitdir', pkgconfig_define : [ 'prefix', prefix]) if get_option('systemd-user-unit-dir') != '' systemd_user_services_dir = get_option('systemd-user-unit-dir') diff --git a/test/test-logger.c b/test/test-logger.c index 81132bffa..178263ada 100644 --- a/test/test-logger.c +++ b/test/test-logger.c @@ -496,6 +496,9 @@ PWTEST(logger_journal) pwtest_ptr_notnull(iface); rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_CURRENT_USER); + if (rc == -ENOSYS) + return PWTEST_SKIP; + pwtest_neg_errno_ok(rc); sd_journal_seek_head(journal); @@ -565,6 +568,9 @@ PWTEST(logger_journal_chain) pwtest_ptr_notnull(iface); rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY); + if (rc == -ENOSYS) + return PWTEST_SKIP; + pwtest_neg_errno_ok(rc); sd_journal_seek_head(journal); if (sd_journal_next(journal) == 0) { /* No entries? We don't have a journal */ From 8babd0bc4e56107de995113ae9c748b6633b2918 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 25 Jun 2025 17:39:16 +0200 Subject: [PATCH 0594/1014] audioconvert: remove unused field --- spa/plugins/audioconvert/audioconvert.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 743feb765..bab5def32 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -220,7 +220,6 @@ struct stage_context { struct stage { struct impl *impl; - bool passthrough; uint32_t in_idx; uint32_t out_idx; void *data; @@ -3425,7 +3424,6 @@ static void add_wav_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->src_idx; s->data = NULL; @@ -3448,7 +3446,6 @@ static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->dst_idx; s->out_idx = CTX_DATA_REMAP_DST; s->data = NULL; @@ -3473,7 +3470,6 @@ static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = CTX_DATA_REMAP_SRC; s->data = NULL; @@ -3509,7 +3505,6 @@ static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; s->data = NULL; @@ -3537,7 +3532,6 @@ static void add_resample_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; s->data = NULL; @@ -3585,7 +3579,6 @@ static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; s->data = fg; @@ -3599,7 +3592,6 @@ static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->dst_idx; s->data = NULL; @@ -3635,7 +3627,6 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; - s->passthrough = false; s->in_idx = ctx->src_idx; s->out_idx = ctx->final_idx; s->data = NULL; From ec5d2d2a29340e57e9ea8ac9107a15b01b8e8282 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 26 Jun 2025 11:26:05 +0200 Subject: [PATCH 0595/1014] audioconvert: rework the stage recalc a little Use bits to capture the work that is needed. We clear the bit when we added the stage, when all bits are cleared we have nothing more to do. This avoids having to check multiple bookleans. Make a helper function to calculate the destination buffer. When all bits are cleared, we can use the output buffer. --- spa/plugins/audioconvert/audioconvert.c | 170 ++++++++++++------------ 1 file changed, 86 insertions(+), 84 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index bab5def32..5c411efcf 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -214,6 +214,13 @@ struct stage_context { uint32_t src_idx; uint32_t dst_idx; uint32_t final_idx; + uint32_t tmp; +#define SRC_CONVERT_BIT (1<<0) +#define RESAMPLE_BIT (1<<1) +#define FILTER_BIT (1<<2) +#define MIX_BIT (1<<3) +#define DST_CONVERT_BIT (1<<4) + uint32_t bits; struct port *ctrlport; bool empty; }; @@ -3392,6 +3399,16 @@ static uint64_t get_time_ns(struct impl *impl) return SPA_TIMESPEC_TO_NSEC(&now); } +static uint32_t get_dst_idx(struct stage_context *ctx) +{ + uint32_t res; + if (ctx->bits == 0) + res = ctx->final_idx; + else + res = CTX_DATA_TMP_0 + ((ctx->tmp++) & 1); + return res; +} + static void run_wav_stage(struct stage *stage, struct stage_context *c) { struct impl *impl = stage->impl; @@ -3504,14 +3521,15 @@ static void run_src_convert_stage(struct stage *s, struct stage_context *c) static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; + SPA_FLAG_CLEAR(ctx->bits, SRC_CONVERT_BIT); s->impl = impl; s->in_idx = ctx->src_idx; - s->out_idx = ctx->dst_idx; + s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_src_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; - ctx->src_idx = ctx->dst_idx; + ctx->src_idx = s->out_idx; } static void run_resample_stage(struct stage *s, struct stage_context *c) @@ -3531,14 +3549,36 @@ static void run_resample_stage(struct stage *s, struct stage_context *c) static void add_resample_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; + SPA_FLAG_CLEAR(ctx->bits, RESAMPLE_BIT); s->impl = impl; s->in_idx = ctx->src_idx; - s->out_idx = ctx->dst_idx; + s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_resample_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; - ctx->src_idx = ctx->dst_idx; + ctx->src_idx = s->out_idx; +} + +static void run_filter_stage(struct stage *s, struct stage_context *c) +{ + struct filter_graph *fg = s->data; + + spa_log_trace_fp(s->impl->log, "%p: filter-graph %d", s->impl, c->n_samples); + spa_filter_graph_process(fg->graph, (const void **)c->datas[s->in_idx], + c->datas[s->out_idx], c->n_samples); +} +static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph *fg, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->in_idx = ctx->src_idx; + s->out_idx = get_dst_idx(ctx); + s->data = fg; + s->run = run_filter_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = s->out_idx; } static void run_channelmix_stage(struct stage *s, struct stage_context *c) @@ -3567,38 +3607,18 @@ static void run_channelmix_stage(struct stage *s, struct stage_context *c) } } -static void run_filter_stage(struct stage *s, struct stage_context *c) -{ - struct filter_graph *fg = s->data; - - spa_log_trace_fp(s->impl->log, "%p: filter-graph %d", s->impl, c->n_samples); - spa_filter_graph_process(fg->graph, (const void **)c->datas[s->in_idx], - c->datas[s->out_idx], c->n_samples); -} -static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph *fg, struct stage_context *ctx) -{ - struct stage *s = &impl->stages[impl->n_stages]; - s->impl = impl; - s->in_idx = ctx->src_idx; - s->out_idx = ctx->dst_idx; - s->data = fg; - s->run = run_filter_stage; - spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); - impl->n_stages++; - ctx->src_idx = ctx->dst_idx; -} - static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; + SPA_FLAG_CLEAR(ctx->bits, MIX_BIT); s->impl = impl; s->in_idx = ctx->src_idx; - s->out_idx = ctx->dst_idx; + s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_channelmix_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; - ctx->src_idx = ctx->dst_idx; + ctx->src_idx = s->out_idx; } static void run_dst_convert_stage(struct stage *s, struct stage_context *c) @@ -3639,8 +3659,7 @@ static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) static void recalc_stages(struct impl *this, struct stage_context *ctx) { struct dir *dir; - bool filter_passthrough, in_passthrough, mix_passthrough, resample_passthrough, out_passthrough; - int tmp = 0; + bool test, do_wav; struct port *ctrlport = ctx->ctrlport; bool in_need_remap, out_need_remap; uint32_t i; @@ -3648,36 +3667,44 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx) this->recalc = false; this->n_stages = 0; + ctx->tmp = 0; + ctx->bits = 0; + ctx->src_idx = CTX_DATA_SRC; + ctx->dst_idx = CTX_DATA_DST; + ctx->final_idx = CTX_DATA_DST; + + /* set bits for things we need to do */ dir = &this->dir[SPA_DIRECTION_INPUT]; - in_passthrough = dir->conv.is_passthrough; + SPA_FLAG_UPDATE(ctx->bits, SRC_CONVERT_BIT, !dir->conv.is_passthrough); in_need_remap = dir->need_remap; dir = &this->dir[SPA_DIRECTION_OUTPUT]; - out_passthrough = dir->conv.is_passthrough; + SPA_FLAG_UPDATE(ctx->bits, DST_CONVERT_BIT, !dir->conv.is_passthrough); out_need_remap = dir->need_remap; - resample_passthrough = resample_is_passthrough(this); - filter_passthrough = this->n_graph == 0; - this->resample_passthrough = resample_passthrough; - mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && + this->resample_passthrough = resample_is_passthrough(this); + SPA_FLAG_UPDATE(ctx->bits, RESAMPLE_BIT, !this->resample_passthrough); + + SPA_FLAG_UPDATE(ctx->bits, FILTER_BIT, this->n_graph != 0); + + test = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && (ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL); + SPA_FLAG_UPDATE(ctx->bits, MIX_BIT, !test); - if (in_passthrough && filter_passthrough && mix_passthrough && resample_passthrough) - out_passthrough = false; + /* if we have nothing to do, force a conversion to the destination to make sure we + * actually write something to the destination buffer */ + if (ctx->bits == 0) + SPA_FLAG_SET(ctx->bits, DST_CONVERT_BIT); - if (out_passthrough && out_need_remap) + do_wav = this->props.wav_path[0] || this->wav_file != NULL; + + if (!SPA_FLAG_IS_SET(ctx->bits, DST_CONVERT_BIT) && out_need_remap) add_dst_remap_stage(this, ctx); - if (this->direction == SPA_DIRECTION_INPUT && - (this->props.wav_path[0] || this->wav_file != NULL)) + if (this->direction == SPA_DIRECTION_INPUT && do_wav) add_wav_stage(this, ctx); - if (!in_passthrough) { - if (filter_passthrough && mix_passthrough && resample_passthrough && out_passthrough) - ctx->dst_idx = ctx->final_idx; - else - ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); - + if (SPA_FLAG_IS_SET(ctx->bits, SRC_CONVERT_BIT)) { add_src_convert_stage(this, ctx); } else { if (in_need_remap) @@ -3685,55 +3712,34 @@ static void recalc_stages(struct impl *this, struct stage_context *ctx) } if (this->direction == SPA_DIRECTION_INPUT) { - if (!resample_passthrough) { - if (filter_passthrough && mix_passthrough && out_passthrough) - ctx->dst_idx = ctx->final_idx; - else - ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); - + if (SPA_FLAG_IS_SET(ctx->bits, RESAMPLE_BIT)) add_resample_stage(this, ctx); - resample_passthrough = true; - } } - if (!filter_passthrough) { + if (SPA_FLAG_IS_SET(ctx->bits, FILTER_BIT)) { for (i = 0; i < this->n_graph; i++) { struct filter_graph *fg = this->filter_graph[i]; - if (mix_passthrough && resample_passthrough && out_passthrough && - i + 1 == this->n_graph) - ctx->dst_idx = ctx->final_idx; - else - ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + if (i + 1 == this->n_graph) + SPA_FLAG_CLEAR(ctx->bits, FILTER_BIT); add_filter_stage(this, i, fg, ctx); } } - if (!mix_passthrough) { - if (resample_passthrough && out_passthrough) - ctx->dst_idx = ctx->final_idx; - else - ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); - + if (SPA_FLAG_IS_SET(ctx->bits, MIX_BIT)) add_channelmix_stage(this, ctx); - } - if (this->direction == SPA_DIRECTION_OUTPUT) { - if (!resample_passthrough) { - if (out_passthrough) - ctx->dst_idx = ctx->final_idx; - else - ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + if (this->direction == SPA_DIRECTION_OUTPUT) { + if (SPA_FLAG_IS_SET(ctx->bits, RESAMPLE_BIT)) add_resample_stage(this, ctx); - } } - if (!out_passthrough) { + + if (SPA_FLAG_IS_SET(ctx->bits, DST_CONVERT_BIT)) add_dst_convert_stage(this, ctx); - } - if (this->direction == SPA_DIRECTION_OUTPUT && - (this->props.wav_path[0] || this->wav_file != NULL)) + + if (this->direction == SPA_DIRECTION_OUTPUT && do_wav) add_wav_stage(this, ctx); - spa_log_trace(this->log, "got %u processing stages", this->n_stages); + spa_log_debug(this->log, "got %u processing stages", this->n_stages); } static int impl_node_process(void *object) @@ -4027,12 +4033,8 @@ static int impl_node_process(void *object) ctx.ctrlport = ctrlport; ctx.empty = in_empty; - if (SPA_UNLIKELY(this->recalc)) { - ctx.src_idx = CTX_DATA_SRC; - ctx.dst_idx = CTX_DATA_DST; - ctx.final_idx = CTX_DATA_DST; + if (SPA_UNLIKELY(this->recalc)) recalc_stages(this, &ctx); - } for (i = 0; i < this->n_stages; i++) { struct stage *s = &this->stages[i]; From 5cd7b1de166a41983217c67d745933fa45027d9d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 18 Jul 2025 16:43:22 +0200 Subject: [PATCH 0596/1014] mixer-dsp: rework the port management Keep a list of active ports in the port_list. These are all ports added with add_port and not yet removed. When a port is removed, move it to the free_list and reuse the port later when needed. Update a mix_list of ports when a valid io is set on a port. This then makes it possible to more efficiently and safely iterate the ports in the processing loop. --- spa/plugins/audiomixer/mixer-dsp.c | 136 ++++++++++++++++------------- 1 file changed, 75 insertions(+), 61 deletions(-) diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index b4ee7d18d..12344477c 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -56,6 +56,8 @@ struct buffer { }; struct port { + struct spa_list link; + uint32_t direction; uint32_t id; @@ -67,7 +69,6 @@ struct port { struct spa_port_info info; struct spa_param_info params[8]; - unsigned int valid:1; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; @@ -75,6 +76,9 @@ struct port { struct spa_list queue; size_t queued_bytes; + + struct spa_list mix_link; + bool active:1; }; struct impl { @@ -100,10 +104,10 @@ struct impl { struct spa_hook_list hooks; - uint32_t port_count; - uint32_t last_port; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; + struct spa_list port_list; + struct spa_list free_list; struct buffer *mix_buffers[MAX_PORTS]; const void *mix_datas[MAX_PORTS]; @@ -114,12 +118,13 @@ struct impl { unsigned int have_format:1; unsigned int started:1; + + struct spa_list mix_list; }; -#define PORT_VALID(p) ((p) != NULL && (p)->valid) #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) -#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && !PORT_VALID(this->in_ports[(p)])) -#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && PORT_VALID(this->in_ports[(p)])) +#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) +#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) @@ -205,7 +210,7 @@ static int impl_node_add_listener(void *object, { struct impl *this = object; struct spa_hook_list save; - uint32_t i; + struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -213,10 +218,8 @@ static int impl_node_add_listener(void *object, emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); - for (i = 0; i < this->last_port; i++) { - if (PORT_VALID(this->in_ports[i])) - emit_port_info(this, GET_IN_PORT(this, i), true); - } + spa_list_for_each(port, &this->port_list, link) + emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); @@ -231,6 +234,18 @@ impl_node_set_callbacks(void *object, return 0; } +static struct port *get_free_port(struct impl *this) +{ + struct port *port; + if (!spa_list_is_empty(&this->free_list)) { + port = spa_list_first(&this->free_list, struct port, link); + spa_list_remove(&port->link); + } else { + port = calloc(1, sizeof(struct port)); + } + return port; +} + static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { @@ -240,13 +255,8 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); - port = GET_IN_PORT (this, port_id); - if (port == NULL) { - port = calloc(1, sizeof(struct port)); - if (port == NULL) - return -errno; - this->in_ports[port_id] = port; - } + if ((port = get_free_port(this)) == NULL) + return -errno; port->direction = direction; port->id = port_id; @@ -269,13 +279,10 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 port->info.params = port->params; port->info.n_params = 5; - this->port_count++; - if (this->last_port <= port_id) - this->last_port = port_id + 1; - port->valid = true; + this->in_ports[port_id] = port; + spa_list_append(&this->port_list, &port->link); - spa_log_debug(this->log, "%p: add port %d:%d %d", this, - direction, port_id, this->last_port); + spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; @@ -291,26 +298,18 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); + this->in_ports[port_id] = NULL; + spa_list_remove(&port->link); - port->valid = false; - this->port_count--; if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); + spa_list_append(&this->free_list, &port->link); - if (port_id + 1 == this->last_port) { - int i; - - for (i = this->last_port - 1; i >= 0; i--) - if (PORT_VALID(GET_IN_PORT(this, i))) - break; - - this->last_port = i + 1; - } - spa_log_debug(this->log, "%p: remove port %d:%d %d", this, - direction, port_id, this->last_port); + spa_log_debug(this->log, "%p: remove port %d:%d", this, + direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); @@ -633,6 +632,7 @@ impl_node_port_use_buffers(void *object, } struct io_info { + struct impl *impl; struct port *port; void *data; size_t size; @@ -642,16 +642,28 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; - if (info->size >= sizeof(struct spa_io_async_buffers)) { - struct spa_io_async_buffers *ab = info->data; - info->port->io[0] = &ab->buffers[info->port->direction]; - info->port->io[1] = &ab->buffers[info->port->direction^1]; - } else if (info->size >= sizeof(struct spa_io_buffers)) { - info->port->io[0] = info->data; - info->port->io[1] = info->data; + struct port *port = info->port; + + if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { + port->io[0] = NULL; + port->io[1] = NULL; + if (port->active) { + spa_list_remove(&port->mix_link); + port->active = false; + } } else { - info->port->io[0] = NULL; - info->port->io[1] = NULL; + if (info->size >= sizeof(struct spa_io_async_buffers)) { + struct spa_io_async_buffers *ab = info->data; + port->io[0] = &ab->buffers[port->direction]; + port->io[1] = &ab->buffers[port->direction^1]; + } else { + port->io[0] = info->data; + port->io[1] = info->data; + } + if (!port->active) { + spa_list_append(&info->impl->mix_list, &port->mix_link); + port->active = true; + } } return 0; } @@ -673,6 +685,7 @@ impl_node_port_set_io(void *object, spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); + info.impl = this; info.port = port; info.data = data; info.size = size; @@ -707,9 +720,9 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t static int impl_node_process(void *object) { struct impl *this = object; - struct port *outport; + struct port *outport, *inport; struct spa_io_buffers *outio; - uint32_t n_buffers, i, maxsize; + uint32_t n_buffers, maxsize; struct buffer **buffers; struct buffer *outb; const void **datas; @@ -739,19 +752,12 @@ static int impl_node_process(void *object) maxsize = UINT32_MAX; - for (i = 0; i < this->last_port; i++) { - struct port *inport = GET_IN_PORT(this, i); - struct spa_io_buffers *inio = NULL; + spa_list_for_each(inport, &this->mix_list, mix_link) { + struct spa_io_buffers *inio = inport->io[cycle]; struct buffer *inb; struct spa_data *bd; uint32_t size, offs; - if (SPA_UNLIKELY(!PORT_VALID(inport) || (inio = inport->io[cycle]) == NULL)) { - spa_log_trace_fp(this->log, "%p: skip input idx:%d valid:%d io:%p/%p/%d", - this, i, PORT_VALID(inport), - inport->io[0], inport->io[1], cycle); - continue; - } if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " @@ -851,14 +857,20 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this; - uint32_t i; + struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; - for (i = 0; i < MAX_PORTS; i++) - free(this->in_ports[i]); + spa_list_consume(port, &this->port_list, link) { + spa_list_remove(&port->link); + free(port); + } + spa_list_consume(port, &this->free_list, link) { + spa_list_remove(&port->link); + free(port); + } return 0; } @@ -911,6 +923,9 @@ impl_init(const struct spa_handle_factory *factory, } spa_hook_list_init(&this->hooks); + spa_list_init(&this->port_list); + spa_list_init(&this->free_list); + spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -923,7 +938,6 @@ impl_init(const struct spa_handle_factory *factory, this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; port = GET_OUT_PORT(this, 0); - port->valid = true; port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); From ce2989891db17b7a7f7f82a3f01835d7b923c46f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 18 Jul 2025 18:19:16 +0200 Subject: [PATCH 0597/1014] alsa: init the bw_max value Otherwise, rate matching doesn't work when it's left to 0.0. --- spa/plugins/alsa/alsa-pcm.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index e10326b65..57d915334 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -979,6 +979,7 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->htimestamp = false; state->htimestamp_max_errors = MAX_HTIMESTAMP_ERROR; state->card_index = SPA_ID_INVALID; + state->dll_bw_max = SPA_DLL_BW_MAX; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; @@ -2894,9 +2895,9 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram state->clock->next_nsec = state->next_time; } - spa_log_trace_fp(state->log, "%p: follower:%d %"PRIu64" %"PRIu64" %f %ld %ld %f %f %u", + spa_log_trace_fp(state->log, "%p: follower:%d %"PRIu64" %"PRIu64" %f %ld %ld %f %f %u %d", state, follower, current_time, state->next_time, corr, delay, target, - err, state->threshold * corr, state->threshold); + err, state->threshold * corr, state->threshold, state->matching); return 0; } @@ -3628,7 +3629,7 @@ static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq, rt->driver = state->driver; spa_log_debug(state->log, "state:%p -> driver:%p", state, state->driver); - if(state->linked && state->matching) + if (state->linked && state->matching) try_unlink(state); } if (state->following) { From 5fba59b41d12e4a3a28ead33bb83e9b4396087ba Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 18 Jul 2025 19:19:34 +0200 Subject: [PATCH 0598/1014] mixer-dsp: fix compilation with fastpath debug --- spa/plugins/audiomixer/mixer-dsp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index 12344477c..a65268fb1 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -762,7 +762,7 @@ static int impl_node_process(void *object) inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, - i, inio, inio->status, inio->buffer_id, inport->n_buffers); + inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } @@ -774,7 +774,7 @@ static int impl_node_process(void *object) maxsize = SPA_MIN(maxsize, size); spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d/%d %d:%d/%d %u", this, - i, inio, outio, inio->status, inio->buffer_id, inport->n_buffers, + inport->id, inio, outio, inio->status, inio->buffer_id, inport->n_buffers, offs, size, (int)sizeof(float), bd->chunk->flags); From f2fb0b0aa597b5e450d5ef814418d6f17d19223c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 18 Jul 2025 19:20:00 +0200 Subject: [PATCH 0599/1014] alsa: update resample state in all cases We need to manually recheck the resample state when the matching state got updated. --- spa/plugins/alsa/alsa-pcm.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 57d915334..2e3427526 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2902,6 +2902,13 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram return 0; } +static bool need_resample(struct state *state) +{ + return !state->pitch_elem && + ((state->rate != 0 && state->driver_rate.denom != 0 && + (uint32_t)state->rate != state->driver_rate.denom) || state->matching); +} + static int setup_matching(struct state *state) { state->matching = state->following; @@ -2915,6 +2922,8 @@ static int setup_matching(struct state *state) if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; + state->resample = need_resample(state); + check_position_config(state, false); recalc_headroom(state); @@ -2974,9 +2983,7 @@ static inline int check_position_config(struct state *state, bool starting) state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; - state->resample = !state->pitch_elem && - ((state->rate != 0 && state->driver_rate.denom != 0 && - (uint32_t)state->rate != state->driver_rate.denom) || state->matching); + state->resample = need_resample(state); state->alsa_sync = true; } return 0; From e6e36c4d348745629b431f287583cc62a1ce2bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 20 Jul 2025 18:29:16 +0200 Subject: [PATCH 0600/1014] tests: endpoint: fix valgrind uninit warning Previously, valgrind was warning that Syscall param sendmsg(msg.msg_iov[0]) points to uninitialised byte(s) this was caused by uninitialized values being serialized for IPC. Specifically, not all members of the `pw_endpoint_info` struct were initialized, which caused uninitialized bytes to end up in the IPC buffers due to the `pw_endpoint_emit_info()` in `endpoint_add_listener()`. Fix that by initializing the missed `id` and `flags` members. --- src/tests/test-endpoint.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/tests/test-endpoint.c b/src/tests/test-endpoint.c index c6956c2e2..412fa6703 100644 --- a/src/tests/test-endpoint.c +++ b/src/tests/test-endpoint.c @@ -199,10 +199,12 @@ endpoint_init(struct endpoint * self) spa_hook_list_init (&self->hooks); self->info.version = PW_VERSION_ENDPOINT_INFO; + self->info.id = SPA_ID_INVALID; self->info.change_mask = PW_ENDPOINT_CHANGE_MASK_ALL; self->info.name = "test-endpoint"; self->info.media_class = "Audio/Sink"; self->info.direction = PW_DIRECTION_OUTPUT; + self->info.flags = 0; self->info.n_streams = 0; self->info.session_id = SPA_ID_INVALID; @@ -434,12 +436,6 @@ static void test_endpoint(void) int main(int argc, char *argv[]) { - /* FIXME: This test has a leak and a use of uninitialized buffer - * that needs to be debugged and fixed (or excluded). Meanwhile - - * skip it from valgrind so we can at least use the others. */ - if (RUNNING_ON_VALGRIND) - return 77; - pw_init(&argc, &argv); alarm(5); /* watchdog; terminate after 5 seconds */ From 36f288f8840359c8e5a440e72c73106554c33a57 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 21 Jul 2025 10:14:39 +0200 Subject: [PATCH 0601/1014] pod: require at least 1 choice value in _can_collect() We can't collect the choice value when there is none so check for the number of choice values in _can_collect() as well. --- spa/include/spa/pod/parser.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 233304053..bab03092a 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -286,6 +286,8 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch return true; if (SPA_POD_CHOICE_TYPE(pod) != SPA_CHOICE_None) return false; + if (SPA_POD_CHOICE_N_VALUES(pod) < 1) + return false; pod = SPA_POD_CHOICE_CHILD(pod); } From 47e8984450787d11bb0c8abb555bad119c4c1bf0 Mon Sep 17 00:00:00 2001 From: Karl Relton Date: Mon, 21 Jul 2025 12:17:40 +0000 Subject: [PATCH 0602/1014] Upload New File --- .../alsa/mixer/profile-sets/logi407.conf | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 spa/plugins/alsa/mixer/profile-sets/logi407.conf diff --git a/spa/plugins/alsa/mixer/profile-sets/logi407.conf b/spa/plugins/alsa/mixer/profile-sets/logi407.conf new file mode 100644 index 000000000..61cea9554 --- /dev/null +++ b/spa/plugins/alsa/mixer/profile-sets/logi407.conf @@ -0,0 +1,38 @@ +# This file is part of PulseAudio. +# +# PulseAudio is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# PulseAudio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PulseAudio; if not, see . + +; Logitech Z407 Stereo PC Speaker Set +; +; These are copies of the mappings we find in default.conf, but with +; the 'PCM' control used also in the iec958 output path +; +; See default.conf for an explanation on the directives used here. + +[General] +auto-profiles = yes + +# Based on analog-stereo +[Mapping analog-stereo] +device-strings = front:%f +channel-map = left,right +paths-output = analog-output +priority = 15 + +# Based on iec958-stereo +[Mapping iec958-stereo] +device-strings = iec958:%f +channel-map = left,right +paths-output = logi407-iec958-stereo-output +priority = 5 From eac495f0e7b2ac214dbbfc61f25fd5865f93e14b Mon Sep 17 00:00:00 2001 From: Karl Relton Date: Mon, 21 Jul 2025 12:19:13 +0000 Subject: [PATCH 0603/1014] Option of analog or iec958 stereo output, both merging volume switches --- .../mixer/paths/logi407-iec958-stereo-output.conf | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 spa/plugins/alsa/mixer/paths/logi407-iec958-stereo-output.conf diff --git a/spa/plugins/alsa/mixer/paths/logi407-iec958-stereo-output.conf b/spa/plugins/alsa/mixer/paths/logi407-iec958-stereo-output.conf new file mode 100644 index 000000000..4a35f1fdb --- /dev/null +++ b/spa/plugins/alsa/mixer/paths/logi407-iec958-stereo-output.conf @@ -0,0 +1,13 @@ +; Mixer path for Logitech 407 PC Speakers +; +; See analog-output.conf.common for an explanation on the directives + +[General] +priority = 99 +description-key = iec958-stereo-output + +[Element PCM] +switch = mute +volume = merge +override-map.1 = all +override-map.2 = all-left,all-right From 9189b1d8b757e9fbde69b2b5abdee230184463e6 Mon Sep 17 00:00:00 2001 From: Karl Relton Date: Mon, 21 Jul 2025 12:25:26 +0000 Subject: [PATCH 0604/1014] Add ACP for Logitech 407 USB PC Speaker set --- spa/plugins/alsa/90-pipewire-alsa.rules | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 9ef3d533b..631c24852 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -118,6 +118,7 @@ ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="402e", ENV{ACP_PROFILE_SET}="dell-do ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{ACP_PROFILE_SET}="texas-instruments-pcm2902.conf" ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0269", ENV{ACP_PROFILE_SET}="hp-tbt-dock-120w-g2.conf" ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0567", ENV{ACP_PROFILE_SET}="hp-tbt-dock-audio-module.conf" +ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a4c", ENV{ACP_PROFILE_SET}="logi407.conf" # ID 1038:12ad is for the 2018 refresh of the Arctis 7. # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). From ff7fb675f12aeecdb7603d244f940286e00fa106 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 09:43:50 +0200 Subject: [PATCH 0605/1014] pod: avoid overflow in _frame() --- spa/include/spa/pod/builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 2e3f5982a..9fe6e6375 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -108,7 +108,7 @@ spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_frame(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { - if (frame->offset + SPA_POD_SIZE(&frame->pod) <= builder->size) + if (frame->offset + (uint64_t)SPA_POD_SIZE(&frame->pod) <= builder->size) return SPA_PTROFF(builder->data, frame->offset, struct spa_pod); return NULL; } From ed7398a64afc50a8cf56fa1446ecb22e14d3530c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 10:06:07 +0200 Subject: [PATCH 0606/1014] pod: use _deref and _frame in safe place In the filter, don't _deref or use _frame before we are going to add more pods to the builder. If we are using a dynamic builder, the dereffed pod might become invalid when the memory is reallocated. Instead, take the offset of the frame and deref later when we are not going to add more things. --- spa/include/spa/pod/builder.h | 10 ++++++++-- spa/include/spa/pod/filter.h | 15 ++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 9fe6e6375..fb53f39a5 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -93,7 +93,7 @@ SPA_API_POD_BUILDER void spa_pod_builder_init(struct spa_pod_builder *builder, v } SPA_API_POD_BUILDER struct spa_pod * -spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) +spa_pod_builder_deref_fallback(struct spa_pod_builder *builder, uint32_t offset, struct spa_pod *fallback) { uint32_t size = builder->size; if (offset + UINT64_C(8) <= size) { @@ -102,7 +102,13 @@ spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) SPA_POD_IS_VALID(pod)) return pod; } - return NULL; + return fallback; +} + +SPA_API_POD_BUILDER struct spa_pod * +spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) +{ + return (struct spa_pod*)spa_pod_builder_deref_fallback(builder, offset, NULL); } SPA_API_POD_BUILDER struct spa_pod * diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 534d1a827..da99363b9 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -73,7 +73,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, { const struct spa_pod *v1, *v2; struct spa_pod_choice *nc, dummy; - uint32_t j, k, nalt1, nalt2; + uint32_t j, k, nalt1, nalt2, nc_offs; void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; struct spa_pod_frame f; @@ -98,12 +98,10 @@ spa_pod_filter_prop(struct spa_pod_builder *b, /* start with copying the property */ spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); - spa_pod_builder_push_choice(b, &f, 0, 0); - nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f); - /* write to dummy value when builder overflows. We don't want to error - * because overflowing is a way to determine the required buffer size. */ - if (nc == NULL) - nc = &dummy; + spa_pod_builder_push_choice(b, &f, SPA_CHOICE_None, 0); + spa_zero(dummy); + + nc_offs = f.offset; /* start with an empty child and we will select a good default * below */ @@ -221,6 +219,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, min1, size); spa_pod_builder_raw(b, max1, size); + nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); nc->body.type = SPA_CHOICE_Range; } else if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Flags) || @@ -228,6 +227,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Flags)) { if (spa_pod_filter_flags_value(b, type, alt1, alt2, size) != 1) return -EINVAL; + nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); nc->body.type = SPA_CHOICE_Flags; } else if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags) @@ -243,6 +243,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Enum) return -ENOTSUP; + nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); if (nc->body.type == SPA_CHOICE_None) { if (n_copied == 0) { return -EINVAL; From da1d4fb30ce4ae83f9158d9680ef4370b2b7c7d0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 12:14:06 +0200 Subject: [PATCH 0607/1014] pod: also check choice size before cast Do a more thorough test of the choice type by not only checking the type but also if the size is at least large enough to be able to cast it to the pod_choice type and look at the contents. --- spa/include/spa/pod/filter.h | 2 +- spa/include/spa/pod/iter.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index da99363b9..b7a477a21 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -397,7 +397,7 @@ SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) int count = 0; SPA_POD_OBJECT_FOREACH(pod, res) { - if (res->value.type == SPA_TYPE_Choice && + if (spa_pod_is_choice(res->value) && !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) { uint32_t nvals, choice; struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 0a93b026d..72df7fb81 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -451,7 +451,7 @@ SPA_API_POD_ITER int spa_pod_object_fixate(struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { - if (res->value.type == SPA_TYPE_Choice && + if (spa_pod_is_choice(&res->value) && !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) ((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None; } @@ -461,7 +461,7 @@ SPA_API_POD_ITER int spa_pod_object_is_fixated(const struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { - if (res->value.type == SPA_TYPE_Choice && + if (spa_pod_is_choice(&res->value) && ((struct spa_pod_choice*)&res->value)->body.type != SPA_CHOICE_None) return 0; } From 05bd4547d0d82735c3c85520a0618e5a17507710 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 21 Jul 2025 16:49:12 -0400 Subject: [PATCH 0608/1014] pod: parser: avoid unneeded integer division SPA_POD_CHOICE_N_VALUES involves an integer division, which is slow. Replace it with subtraction and comparison. No functional change intended. --- spa/include/spa/pod/parser.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index bab03092a..a76f2fef4 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -286,7 +286,8 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch return true; if (SPA_POD_CHOICE_TYPE(pod) != SPA_CHOICE_None) return false; - if (SPA_POD_CHOICE_N_VALUES(pod) < 1) + if (pod->size - sizeof(struct spa_pod_choice_body) < + SPA_POD_CHOICE_VALUE_SIZE(pod)) return false; pod = SPA_POD_CHOICE_CHILD(pod); } From a03bbc79fe23e5d85783802e429c7e0ed7b63bda Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 12:16:59 +0200 Subject: [PATCH 0609/1014] pod: fix compilation --- spa/include/spa/pod/filter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index b7a477a21..6e1d7e17d 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -397,7 +397,7 @@ SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) int count = 0; SPA_POD_OBJECT_FOREACH(pod, res) { - if (spa_pod_is_choice(res->value) && + if (spa_pod_is_choice(&res->value) && !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) { uint32_t nvals, choice; struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); From 0a52f959ac1cc5f2130b43e4a86c599d9dd59094 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 13:13:05 +0200 Subject: [PATCH 0610/1014] pod: add a function to return the min size of a type --- spa/include/spa/pod/iter.h | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 72df7fb81..f2400a16f 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -44,6 +44,49 @@ struct spa_pod_frame { #define SPA_POD_CHECK(pod,_type,_size) \ (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) +SPA_API_POD_ITER uint32_t spa_pod_type_size(uint32_t type) +{ + switch (type) { + case SPA_TYPE_None: + case SPA_TYPE_Bytes: + case SPA_TYPE_Struct: + case SPA_TYPE_Pod: + return 0; + case SPA_TYPE_String: + return 1; + case SPA_TYPE_Bool: + case SPA_TYPE_Int: + return sizeof(int32_t); + case SPA_TYPE_Id: + return sizeof(uint32_t); + case SPA_TYPE_Long: + return sizeof(int64_t); + case SPA_TYPE_Float: + return sizeof(float); + case SPA_TYPE_Double: + return sizeof(double); + case SPA_TYPE_Rectangle: + return sizeof(struct spa_rectangle); + case SPA_TYPE_Fraction: + return sizeof(struct spa_fraction); + case SPA_TYPE_Bitmap: + return sizeof(uint8_t); + case SPA_TYPE_Array: + return sizeof(struct spa_pod_array_body); + case SPA_TYPE_Object: + return sizeof(struct spa_pod_object_body); + case SPA_TYPE_Sequence: + return sizeof(struct spa_pod_sequence_body); + case SPA_TYPE_Pointer: + return sizeof(struct spa_pod_pointer_body); + case SPA_TYPE_Fd: + return sizeof(int64_t); + case SPA_TYPE_Choice: + return sizeof(struct spa_pod_choice_body); + } + return 0; +} + SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; From b904cb14a9702b2d3854c20d9cb9b03a6a2a3267 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 13:14:17 +0200 Subject: [PATCH 0611/1014] pod: do size check before calling type/size/data functions Assume that all the functions that take a type/size/data from a pod have at least the right number of bytes in the data for the given type. Callers need to ensure this. Fix the callers of such functions to always make sure they deref a pod type/size/body into something of at least the min size of the type. --- spa/include/spa/debug/format.h | 40 ++++++----------------- spa/include/spa/debug/pod.h | 59 +++++++++++++++++++++++++--------- spa/include/spa/pod/compare.h | 24 ++------------ spa/include/spa/pod/filter.h | 6 ++++ 4 files changed, 63 insertions(+), 66 deletions(-) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 31104c678..571b4df20 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -39,14 +39,10 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i switch (type) { case SPA_TYPE_Bool: - if (size < sizeof(int32_t)) - goto bad_body; spa_strbuf_append(buffer, "%s", *(int32_t *) body ? "true" : "false"); break; case SPA_TYPE_Id: { - if (size < sizeof(uint32_t)) - goto bad_body; uint32_t value = *(uint32_t *) body; const char *str = spa_debug_type_find_short_name(info, value); char tmp[64]; @@ -58,42 +54,28 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i break; } case SPA_TYPE_Int: - if (size < sizeof(int32_t)) - goto bad_body; spa_strbuf_append(buffer, "%d", *(int32_t *) body); break; case SPA_TYPE_Long: - if (size < sizeof(int64_t)) - goto bad_body; spa_strbuf_append(buffer, "%" PRIi64, *(int64_t *) body); break; case SPA_TYPE_Float: - if (size < sizeof(float)) - goto bad_body; spa_strbuf_append(buffer, "%f", *(float *) body); break; case SPA_TYPE_Double: - if (size < sizeof(double)) - goto bad_body; spa_strbuf_append(buffer, "%f", *(double *) body); break; case SPA_TYPE_String: - if (size < 1 || ((const char *)body)[size - 1] != '\0') - goto bad_body; - spa_strbuf_append(buffer, "%s", (char *) body); + spa_strbuf_append(buffer, "%-*s", size, (char *) body); break; case SPA_TYPE_Rectangle: { - if (size < sizeof(struct spa_rectangle)) - goto bad_body; struct spa_rectangle *r = (struct spa_rectangle *)body; spa_strbuf_append(buffer, "%" PRIu32 "x%" PRIu32, r->width, r->height); break; } case SPA_TYPE_Fraction: { - if (size < sizeof(struct spa_fraction)) - goto bad_body; struct spa_fraction *f = (struct spa_fraction *)body; spa_strbuf_append(buffer, "%" PRIu32 "/%" PRIu32, f->num, f->denom); break; @@ -109,14 +91,14 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i void *p; struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; int i = 0; - if (size < sizeof(*b)) - goto bad_body; info = info && info->values ? info->values : info; spa_strbuf_append(buffer, "< "); - SPA_POD_ARRAY_BODY_FOREACH(b, size, p) { - if (i++ > 0) - spa_strbuf_append(buffer, ", "); - spa_debug_strbuf_format_value(buffer, info, b->child.type, p, b->child.size); + if (b->child.size >= spa_pod_type_size(b->child.type)) { + SPA_POD_ARRAY_BODY_FOREACH(b, size, p) { + if (i++ > 0) + spa_strbuf_append(buffer, ", "); + spa_debug_strbuf_format_value(buffer, info, b->child.type, p, b->child.size); + } } spa_strbuf_append(buffer, " >"); break; @@ -126,9 +108,6 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i break; } return 0; -bad_body: - spa_strbuf_append(buffer, "INVALID BODY type %d", type); - return 0; } SPA_API_DEBUG_FORMAT int @@ -184,11 +163,12 @@ SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int in type = val->type; size = val->size; - vals = SPA_POD_BODY(val); - if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1) + if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1 || + size < spa_pod_type_size(type)) continue; + vals = SPA_POD_BODY(val); ti = spa_debug_type_find(info, prop->key); key = ti ? ti->name : NULL; diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index 4ff3f3e01..a6d3b91db 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -87,13 +87,18 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; void *p; const struct spa_type_info *ti = spa_debug_type_find(SPA_TYPE_ROOT, b->child.type); + uint32_t min_size = spa_pod_type_size(b->child.type); spa_debugc(ctx, "%*s" "Array: child.size %" PRIu32 ", child.type %s", indent, "", b->child.size, ti ? ti->name : "unknown"); - info = info && info->values ? info->values : info; - SPA_POD_ARRAY_BODY_FOREACH(b, size, p) - spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); + if (b->child.size < min_size) { + spa_debugc(ctx, "%*s" " INVALID child.size < %" PRIu32, indent, "", min_size); + } else { + info = info && info->values ? info->values : info; + SPA_POD_ARRAY_BODY_FOREACH(b, size, p) + spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); + } break; } case SPA_TYPE_Choice: @@ -101,20 +106,31 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa struct spa_pod_choice_body *b = (struct spa_pod_choice_body *)body; void *p; const struct spa_type_info *ti = spa_debug_type_find(spa_type_choice, b->type); + uint32_t min_size = spa_pod_type_size(b->child.type); spa_debugc(ctx, "%*s" "Choice: type %s, flags %08" PRIx32 " %" PRIu32 " %" PRIu32, indent, "", ti ? ti->name : "unknown", b->flags, size, b->child.size); - SPA_POD_CHOICE_BODY_FOREACH(b, size, p) - spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); + if (b->child.size < min_size) { + spa_debugc(ctx, "%*s" "INVALID child.size < %" PRIu32, indent, "", min_size); + } else { + SPA_POD_CHOICE_BODY_FOREACH(b, size, p) + spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); + } break; } case SPA_TYPE_Struct: { struct spa_pod *b = (struct spa_pod *)body, *p; spa_debugc(ctx, "%*s" "Struct: size %" PRIu32, indent, "", size); - SPA_POD_FOREACH(b, size, p) - spa_debugc_pod_value(ctx, indent + 2, info, p->type, SPA_POD_BODY(p), p->size); + SPA_POD_FOREACH(b, size, p) { + uint32_t min_size = spa_pod_type_size(p->type); + if (p->size < min_size) { + spa_debugc(ctx, "%*s" "INVALID child.size < %" PRIu32, indent, "", min_size); + } else { + spa_debugc_pod_value(ctx, indent + 2, info, p->type, SPA_POD_BODY(p), p->size); + } + } break; } case SPA_TYPE_Object: @@ -136,6 +152,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa static const char custom_prefix[] = SPA_TYPE_INFO_PROPS_BASE "Custom:"; char custom_name[sizeof(custom_prefix) + 16]; const char *name = "unknown"; + uint32_t min_size = spa_pod_type_size(p->value.type); ii = spa_debug_type_find(info, p->key); if (ii) { @@ -149,10 +166,14 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa spa_debugc(ctx, "%*s" "Prop: key %s (%" PRIu32 "), flags %08" PRIx32, indent+2, "", name, p->key, p->flags); - spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, - p->value.type, - SPA_POD_CONTENTS(struct spa_pod_prop, p), - p->value.size); + if (p->value.size < min_size) { + spa_debugc(ctx, "%*s" "INVALID value.size < %" PRIu32, indent, "", min_size); + } else { + spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, + p->value.type, + SPA_POD_CONTENTS(struct spa_pod_prop, p), + p->value.size); + } } break; } @@ -168,15 +189,21 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa ti ? ti->name : "unknown"); SPA_POD_SEQUENCE_BODY_FOREACH(b, size, c) { + uint32_t min_size = spa_pod_type_size(c->value.type); + ii = spa_debug_type_find(spa_type_control, c->type); spa_debugc(ctx, "%*s" "Control: offset %" PRIu32 ", type %s", indent+2, "", c->offset, ii ? ii->name : "unknown"); - spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, - c->value.type, - SPA_POD_CONTENTS(struct spa_pod_control, c), - c->value.size); + if (c->value.size < min_size) { + spa_debugc(ctx, "%*s" "INVALID value.size < %" PRIu32, indent, "", min_size); + } else { + spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, + c->value.type, + SPA_POD_CONTENTS(struct spa_pod_control, c), + c->value.size); + } } break; } @@ -198,6 +225,8 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa SPA_API_DEBUG_POD int spa_debugc_pod(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *pod) { + if (pod->size < spa_pod_type_size(pod->type)) + return -EINVAL; return spa_debugc_pod_value(ctx, indent, info ? info : SPA_TYPE_ROOT, pod->type, SPA_POD_BODY(pod), pod->size); } diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index 67c18ad46..d5d4b3dcc 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -39,41 +39,23 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con case SPA_TYPE_None: return 0; case SPA_TYPE_Bool: - if (size < sizeof(int32_t)) - return -EINVAL; return SPA_CMP(!!*(int32_t *)r1, !!*(int32_t *)r2); case SPA_TYPE_Id: - if (size < sizeof(uint32_t)) - return -EINVAL; return SPA_CMP(*(uint32_t *)r1, *(uint32_t *)r2); case SPA_TYPE_Int: - if (size < sizeof(int32_t)) - return -EINVAL; return SPA_CMP(*(int32_t *)r1, *(int32_t *)r2); case SPA_TYPE_Long: - if (size < sizeof(int64_t)) - return -EINVAL; return SPA_CMP(*(int64_t *)r1, *(int64_t *)r2); case SPA_TYPE_Float: - if (size < sizeof(float)) - return -EINVAL; return SPA_CMP(*(float *)r1, *(float *)r2); case SPA_TYPE_Double: - if (size < sizeof(double)) - return -EINVAL; return SPA_CMP(*(double *)r1, *(double *)r2); case SPA_TYPE_String: - if (size < sizeof(char) || - ((char *)r1)[size - 1] || - ((char *)r2)[size - 1]) - return -EINVAL; - return strcmp((char *)r1, (char *)r2); + return strncmp((char *)r1, (char *)r2, size); case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; - if (size < sizeof(struct spa_rectangle)) - return -EINVAL; if (rec1->width == rec2->width && rec1->height == rec2->height) return 0; else if (rec1->width < rec2->width || rec1->height < rec2->height) @@ -86,8 +68,6 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con const struct spa_fraction *f1 = (struct spa_fraction *) r1, *f2 = (struct spa_fraction *) r2; uint64_t n1, n2; - if (size < sizeof(struct spa_fraction)) - return -EINVAL; n1 = ((uint64_t) f1->num) * f2->denom; n2 = ((uint64_t) f2->num) * f1->denom; return SPA_CMP(n1, n2); @@ -176,6 +156,8 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, default: if (pod1->size != pod2->size) return -EINVAL; + if (pod1->size < spa_pod_type_size(pod1->type)) + return -EINVAL; res = spa_pod_compare_value(pod1->type, SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), pod1->size); diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6e1d7e17d..836095029 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -95,6 +95,8 @@ spa_pod_filter_prop(struct spa_pod_builder *b, /* incompatible property types */ if (type != v2->type || size != v2->size || p1->key != p2->key) return -EINVAL; + if (size < spa_pod_type_size(type)) + return -EINVAL; /* start with copying the property */ spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); @@ -402,6 +404,10 @@ SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) uint32_t nvals, choice; struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); const void *vals = SPA_POD_BODY(v); + + if (v->size < spa_pod_type_size(v->type)) + continue; + if (spa_pod_compare_is_valid_choice(v->type, v->size, vals, vals, nvals, choice)) { ((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None; From e91c541446087684af16dcf472ef2215bf43cf1b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 13:58:11 +0200 Subject: [PATCH 0612/1014] pod: disable padding when in body Disable the padding to pod alignment for everything when we are building the body of an array or choice. This makes it possible to use bytes or strings or any other pod of a fixed size as entries in arrays or choice. --- spa/include/spa/pod/builder.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index fb53f39a5..79e982bf7 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -183,6 +183,8 @@ SPA_API_POD_BUILDER void spa_pod_builder_remove(struct spa_pod_builder *builder, SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) { uint64_t zeroes = 0; + if (builder->state.flags == SPA_POD_BUILDER_FLAG_BODY) + return 0; size = SPA_ROUND_UP_N(size, SPA_POD_ALIGN) - size; return size ? spa_pod_builder_raw(builder, &zeroes, size) : 0; } @@ -218,7 +220,6 @@ spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod { const void *data; uint32_t size; - int r, res; if (builder->state.flags == SPA_POD_BUILDER_FLAG_BODY) { data = SPA_POD_BODY_CONST(p); @@ -228,11 +229,7 @@ spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod size = SPA_POD_SIZE(p); SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); } - res = spa_pod_builder_raw(builder, data, size); - if (builder->state.flags != SPA_POD_BUILDER_FLAG_BODY) - if ((r = spa_pod_builder_pad(builder, size)) < 0) - res = r; - return res; + return spa_pod_builder_raw_padded(builder, data, size); } #define SPA_POD_INIT(size,type) ((struct spa_pod) { (size), (type) }) From dd9d8038da1b9e60ac0182cf73dc0133b3cf967c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 14:00:09 +0200 Subject: [PATCH 0613/1014] client-node: close SyncObj fd as well We also need to close the SynObj fd we got, just like we close any DmaBuf or MemFd. Make sure we get a compiler error when we add more items to the data type enumeration later. Fixes #4807 --- src/modules/module-client-node/client-node.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 21f47538a..a17dc87ce 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -242,7 +242,11 @@ fail: static void clear_data(struct impl *impl, struct spa_data *d) { - switch (d->type) { + switch ((enum spa_data_type)d->type) { + case SPA_DATA_Invalid: + case SPA_DATA_MemPtr: + case _SPA_DATA_LAST: + break; case SPA_DATA_MemId: { uint32_t id; @@ -258,11 +262,10 @@ static void clear_data(struct impl *impl, struct spa_data *d) } case SPA_DATA_MemFd: case SPA_DATA_DmaBuf: + case SPA_DATA_SyncObj: pw_log_debug("%p: close fd:%d", impl, (int)d->fd); close(d->fd); break; - default: - break; } } From 495d6ba7964e433fecf2ea89f4d60ed7f9254506 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 14:09:10 +0200 Subject: [PATCH 0614/1014] pod: remove some size checks These are already done by the caller. --- spa/include/spa/pod/compare.h | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index d5d4b3dcc..cb8d5d3b1 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -171,12 +171,8 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_compatible_flags(uint32_t type, const { switch (type) { case SPA_TYPE_Int: - if (size < sizeof(int32_t)) - return -EINVAL; return ((*(int32_t *) r1) & (*(int32_t *) r2)) != 0; case SPA_TYPE_Long: - if (size < sizeof(int64_t)) - return -EINVAL; return ((*(int64_t *) r1) & (*(int64_t *) r2)) != 0; default: return -ENOTSUP; @@ -190,11 +186,11 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1 { switch (type) { case SPA_TYPE_Int: - if (size < sizeof(int32_t) || *(int32_t *)r2 < 1) + if (*(int32_t *)r2 < 1) return -EINVAL; return *(int32_t *) r1 % *(int32_t *) r2 == 0; case SPA_TYPE_Long: - if (size < sizeof(int64_t) || *(int64_t *)r2 < 1) + if (*(int64_t *)r2 < 1) return -EINVAL; return *(int64_t *) r1 % *(int64_t *) r2 == 0; case SPA_TYPE_Rectangle: @@ -202,12 +198,9 @@ SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1 const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; - if (size < sizeof(struct spa_rectangle) || - rec2->width < 1 || - rec2->height < 1) - { + if (rec2->width < 1 || rec2->height < 1) return -EINVAL; - } + return (rec1->width % rec2->width == 0 && rec1->height % rec2->height == 0); } From 5b436abef700b74b8e51082af2dab4c29762a5ca Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 14:19:08 +0200 Subject: [PATCH 0615/1014] pod: improve compare function Use the area to compare two rectangles. Use the width to break a tie. This way the sorting is at least a bit more predictable and independent of the order of the arguments. --- spa/include/spa/pod/compare.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index cb8d5d3b1..144bd4a5a 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -56,12 +56,14 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; - if (rec1->width == rec2->width && rec1->height == rec2->height) - return 0; - else if (rec1->width < rec2->width || rec1->height < rec2->height) + uint64_t a1, a2; + a1 = ((uint64_t) rec1->width) * rec1->height; + a2 = ((uint64_t) rec2->width) * rec2->height; + if (a1 < a2) return -1; - else + if (a1 > a2) return 1; + return SPA_CMP(rec1->width, rec2->width); } case SPA_TYPE_Fraction: { From fc3a199ca25ebcffbf77012c9a77e61ae8064a0c Mon Sep 17 00:00:00 2001 From: Niklas Carlsson Date: Tue, 22 Jul 2025 15:23:00 +0200 Subject: [PATCH 0616/1014] filter-graph: fix index off by one in dsp_delay_c Checking w + 1 > n_buffer means that w will go to n_buffer, which in turn leads to reading buffer[2 * n_buffer]. --- spa/plugins/filter-graph/audio-dsp-c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/audio-dsp-c.c b/spa/plugins/filter-graph/audio-dsp-c.c index ee22b481e..6ea559908 100644 --- a/spa/plugins/filter-graph/audio-dsp-c.c +++ b/spa/plugins/filter-graph/audio-dsp-c.c @@ -218,7 +218,7 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, for (i = 0; i < n_samples; i++) { buffer[w] = buffer[w + n_buffer] = src[i]; dst[i] = buffer[w + o]; - w = w + 1 > n_buffer ? 0 : w + 1; + w = w + 1 >= n_buffer ? 0 : w + 1; } *pos = w; } From 685aed1de2ba5a966b5bb733c175aeadf047a3a3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 22 Jul 2025 16:48:43 +0200 Subject: [PATCH 0617/1014] alsa: update resampler requested size before reading spa_alsa_read is called from the source process function when we are a follower and no buffer is ready yet. Part of the rate correction was performed by the ALSA driver when it woke up but now, the resampler has updated the requested size and we need to requery it before we can start reading samples. Otherwise, we end up with requested samples from before the rate update and we might not give enough samples to the resampler. In that case, the adapter will call us again and we will again try to produce a buffer worth of the requested samples, which will xrun. --- spa/plugins/alsa/alsa-pcm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2e3427526..fad4e0852 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3386,6 +3386,10 @@ int spa_alsa_read(struct state *state) uint64_t current_time = state->position->clock.nsec; alsa_read_sync(state, current_time); } + else if (state->resample && state->rate_match) { + state->read_size = state->rate_match->size; + state->max_read = SPA_MIN(state->buffer_frames, state->read_size); + } return alsa_read_frames(state); } From fcc49ad517bd709092adc40fafb17201de349e0d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jul 2025 11:55:49 +0200 Subject: [PATCH 0618/1014] resample: reorder resample function setup We also don't need to copy the resampler function name with each dynamic function update, this is just for debugging. --- spa/plugins/audioconvert/resample-native.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index f393e3dce..ece55f504 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -160,17 +160,14 @@ static void impl_native_update_rate(struct resample *r, double rate) data->inc = data->in_rate / data->out_rate; data->frac = data->in_rate % data->out_rate; - if (data->in_rate == data->out_rate && rate == 1.0) { - data->func = data->info->process_copy; - r->func_name = data->info->copy_name; + if (rate != 1.0) { + data->func = data->info->process_inter; } - else if (rate == 1.0) { - data->func = data->info->process_full; - r->func_name = data->info->full_name; + else if (data->in_rate == data->out_rate) { + data->func = data->info->process_copy; } else { - data->func = data->info->process_inter; - r->func_name = data->info->inter_name; + data->func = data->info->process_full; } spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d gcd:%d phase:%f inc:%d frac:%d", r, @@ -436,5 +433,12 @@ int resample_native_init(struct resample *r) impl_native_reset(r); impl_native_update_rate(r, 1.0); + if (d->func == d->info->process_copy) + r->func_name = d->info->copy_name; + else if (d->func == d->info->process_full) + r->func_name = d->info->full_name; + else + r->func_name = d->info->inter_name; + return 0; } From d2a9141913cff78b267cd04fd45fa85d6afa9f6c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jul 2025 12:23:20 +0200 Subject: [PATCH 0619/1014] resample: avoid calculating GCD in rate updates We don't actually need to calculate the GCD for each resampler rate update. The GCD is only used to scale the in/out rates when using the full resampler and this we can cache and reuse when we did the setup. The interpolating resampler can work perfectly fine with a GCD of 1 and so we can just assume that. --- .../audioconvert/resample-native-impl.h | 1 + spa/plugins/audioconvert/resample-native.c | 34 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index e3ad54dba..02cb501a6 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -34,6 +34,7 @@ struct native_data { uint32_t frac; uint32_t filter_stride; uint32_t filter_stride_os; + uint32_t gcd; uint32_t hist; float **history; resample_func_t func; diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index ece55f504..2bfbf5b96 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -137,39 +137,36 @@ static inline uint32_t calc_gcd(uint32_t a, uint32_t b) static void impl_native_update_rate(struct resample *r, double rate) { struct native_data *data = r->data; - uint32_t in_rate, out_rate, gcd, old_out_rate; - float phase; + uint32_t in_rate, out_rate; if (SPA_LIKELY(data->rate == rate)) return; - old_out_rate = data->out_rate; - in_rate = (uint32_t)(r->i_rate / rate); - out_rate = r->o_rate; - phase = data->phase; - - gcd = calc_gcd(in_rate, out_rate); - in_rate /= gcd; - out_rate /= gcd; - data->rate = rate; - data->phase = phase * out_rate / (float)old_out_rate; - data->in_rate = in_rate; - data->out_rate = out_rate; - - data->inc = data->in_rate / data->out_rate; - data->frac = data->in_rate % data->out_rate; + in_rate = r->i_rate; + out_rate = r->o_rate; if (rate != 1.0) { + in_rate = (uint32_t)(in_rate / rate); data->func = data->info->process_inter; } - else if (data->in_rate == data->out_rate) { + else if (in_rate == out_rate) { data->func = data->info->process_copy; } else { + in_rate /= data->gcd; + out_rate /= data->gcd; data->func = data->info->process_full; } + data->in_rate = in_rate; + if (data->out_rate != out_rate) { + data->phase = data->phase * out_rate / (float)data->out_rate; + data->out_rate = out_rate; + } + data->inc = data->in_rate / data->out_rate; + data->frac = data->in_rate % data->out_rate; + spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d gcd:%d phase:%f inc:%d frac:%d", r, rate, r->i_rate, r->o_rate, gcd, data->phase, data->inc, data->frac); @@ -390,6 +387,7 @@ int resample_native_init(struct resample *r) d->n_phases = n_phases; d->in_rate = in_rate; d->out_rate = out_rate; + d->gcd = gcd; d->filter = SPA_PTROFF_ALIGN(d, sizeof(struct native_data), 64, float); d->hist_mem = SPA_PTROFF_ALIGN(d->filter, filter_size, 64, float); d->history = SPA_PTROFF(d->hist_mem, history_size, float*); From c54d3764b2f69b1521558ef2291d1eeb12deac3d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jul 2025 12:46:45 +0200 Subject: [PATCH 0620/1014] stream: update the docs a little Give some more information about the delay field and how it can be negative. --- src/pipewire/stream.h | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index ea884337b..c636497d7 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -273,7 +273,7 @@ struct pw_buffer { uint64_t time; /**< For capture streams, this field contains the * cycle time in nanoseconds when this buffer was * queued in the stream. It can be compared against - * the pw_time values or pw_stream_get_nsec() + * the \ref pw_time values or pw_stream_get_nsec() * Since 1.0.5 */ }; @@ -311,15 +311,20 @@ struct pw_stream_control { * * pw_time.delay contains the total delay that a signal will travel through the * graph. This includes the delay caused by filters in the graph as well as delays - * caused by the hardware. The delay is usually quite stable and should only change when - * the topology, quantum or samplerate of the graph changes. + * caused by the hardware and extra delay offsets added to this. The delay is usually + * quite stable and should only change when the topology, quantum or samplerate of + * the graph changes. * - * The delay requires the application to send the stream early relative to other synchronized - * streams in order to arrive at the edge of the graph in time. This is usually done by - * delaying the other streams with the given delay. + * The (positive) delay requires the application to send the stream early relative to other + * synchronized streams in order to arrive at the edge of the graph in time. This is usually + * done by delaying the other streams with the given delay. * - * Note that the delay can be negative. A negative delay means that this stream should be - * delayed with the (positive) delay relative to other streams. + * A delay offset is sometimes added (by the user) to improve synchronization of the streams + * when the reported latency is incorrect in some way. This means that with a large enough + * negative offset, the delay can become negative as well. A negative delay in this context + * means that the user would like this stream to be delayed with the (positive) delay amount + * in order to synchronize it with other streams. Streams are in general not expected to be + * able to delay themselves and it is acceptable to clamp negative delays to 0. * * pw_time.queued and pw_time.buffered is expressed in the time domain of the stream, * or the format that is used for the buffers of this stream. @@ -385,10 +390,11 @@ struct pw_time { * the playback device or the time a sample traveled * from the capture device. This delay includes the * delay introduced by all filters on the path between - * the stream and the device. The delay is normally - * constant in a graph and can change when the topology - * of the graph or the quantum changes. This delay does - * not include the delay caused by queued buffers. */ + * the stream and the device and extra delay offsets. The + * delay is normally constant in a graph and can change when + * the topology of the graph or the quantum changes. This delay + * does not include the delay caused by queued buffers. + * The delay can be negative, see \ref pw_time . */ uint64_t queued; /**< data queued in the stream, this is the sum * of the size fields in the pw_buffer that are * currently queued */ From b52c4907097924072d9023a705422bb0795529aa Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jul 2025 12:52:27 +0200 Subject: [PATCH 0621/1014] resample: fix compilation Also fix a compiler warning in clang --- spa/plugins/audioconvert/resample-native.c | 4 ++-- src/pipewire/stream.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index 2bfbf5b96..b91d87115 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -167,8 +167,8 @@ static void impl_native_update_rate(struct resample *r, double rate) data->inc = data->in_rate / data->out_rate; data->frac = data->in_rate % data->out_rate; - spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d gcd:%d phase:%f inc:%d frac:%d", r, - rate, r->i_rate, r->o_rate, gcd, data->phase, data->inc, data->frac); + spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%f inc:%d frac:%d", r, + rate, r->i_rate, r->o_rate, data->phase, data->inc, data->frac); } diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index db5269371..8cdef61ad 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -242,6 +242,7 @@ static int add_param(struct stream *impl, switch (id) { case SPA_PARAM_Latency: + { struct spa_latency_info info; if ((res = spa_latency_parse(param, &info)) < 0) return res; @@ -254,6 +255,7 @@ static int add_param(struct stream *impl, return 0; } } + } p = malloc(sizeof(struct param) + SPA_POD_SIZE(param)); if (p == NULL) From 0b0912cc5b716a3887c6f261d92f16b52cad46b2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 23 Jul 2025 14:11:11 +0200 Subject: [PATCH 0622/1014] resample: optimize phase scaling Precalculate the constant factor to avoid a division for each sample. --- spa/plugins/audioconvert/resample-native-impl.h | 12 ++++++------ spa/plugins/audioconvert/resample-native.c | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index 02cb501a6..023b350d7 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -30,6 +30,7 @@ struct native_data { uint32_t in_rate; uint32_t out_rate; float phase; + float pm; uint32_t inc; uint32_t frac; uint32_t filter_stride; @@ -85,7 +86,7 @@ DEFINE_RESAMPLER(full,arch) \ { \ struct native_data *data = r->data; \ uint32_t n_taps = data->n_taps, stride = data->filter_stride_os; \ - uint32_t index, phase, n_phases = data->out_rate; \ + uint32_t index, phase, out_rate = data->out_rate; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ uint32_t inc = data->inc, frac = data->frac, ch = r->channels; \ \ @@ -99,7 +100,7 @@ DEFINE_RESAMPLER(full,arch) \ inner_product_##arch(&d[o], &s[index], \ filter, n_taps); \ } \ - INC(index, phase, n_phases); \ + INC(index, phase, out_rate); \ } \ *in_len = index; \ *out_len = o; \ @@ -111,16 +112,15 @@ DEFINE_RESAMPLER(inter,arch) \ { \ struct native_data *data = r->data; \ uint32_t index, stride = data->filter_stride; \ - uint32_t n_phases = data->n_phases, out_rate = data->out_rate; \ - uint32_t n_taps = data->n_taps; \ + uint32_t n_taps = data->n_taps, out_rate = data->out_rate; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ uint32_t inc = data->inc, frac = data->frac, ch = r->channels; \ - float phase; \ + float phase, pm = data->pm; \ \ index = ioffs; \ phase = data->phase; \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ - float ph = phase * n_phases / out_rate; \ + float ph = phase * pm; \ uint32_t offset = (uint32_t)floorf(ph); \ float *filter0 = &data->filter[(offset+0) * stride]; \ float *filter1 = &data->filter[(offset+1) * stride]; \ diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index b91d87115..a3c812c28 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -388,6 +388,7 @@ int resample_native_init(struct resample *r) d->in_rate = in_rate; d->out_rate = out_rate; d->gcd = gcd; + d->pm = (float)n_phases / r->o_rate; d->filter = SPA_PTROFF_ALIGN(d, sizeof(struct native_data), 64, float); d->hist_mem = SPA_PTROFF_ALIGN(d->filter, filter_size, 64, float); d->history = SPA_PTROFF(d->hist_mem, history_size, float*); From 5fa137cc0d973bddb1959154f5e840ca48821c65 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 23 Jul 2025 13:59:05 +0300 Subject: [PATCH 0623/1014] meson.build: make spa-json-dump available for subprojects Add override that provides host binary for subprojects to use. Also fix cross-compilation to use the host binary. --- spa/meson.build | 7 +++++++ spa/tools/meson.build | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/spa/meson.build b/spa/meson.build index 5087c0073..1137adf60 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -18,6 +18,13 @@ spa_dep = declare_dependency( }, ) +spa_inc_dep = declare_dependency( + include_directories : [ + include_directories('include'), + include_directories('include-private'), + ], +) + meson.override_dependency('lib@0@'.format(spa_name), spa_dep) pkgconfig.generate(filebase : 'lib@0@'.format(spa_name), diff --git a/spa/tools/meson.build b/spa/tools/meson.build index 9508e65ca..60e66b55c 100644 --- a/spa/tools/meson.build +++ b/spa/tools/meson.build @@ -6,6 +6,12 @@ executable('spa-monitor', 'spa-monitor.c', dependencies : [ spa_dep, dl_lib ], install : true) -spa_json_dump_exe = executable('spa-json-dump', 'spa-json-dump.c', - dependencies : [ spa_dep, dl_lib, ], +spa_json_dump = executable('spa-json-dump', 'spa-json-dump.c', + dependencies : [ spa_dep ], install : true) + +spa_json_dump_exe = executable('spa-json-dump-native', 'spa-json-dump.c', + dependencies : [ spa_inc_dep ], + native : true) + +meson.override_find_program('spa-json-dump', spa_json_dump_exe) From c7838cbbcb56045cf6d237866fadc20458b7a8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 19:59:48 +0200 Subject: [PATCH 0624/1014] spa: node: io: fix typo in documentation `SPA_IO_RATE_MATCH_ACTIVE` -> `SPA_IO_RATE_MATCH_FLAG_ACTIVE` --- spa/include/spa/node/io.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 2920c1b3b..c03ef3e02 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -347,7 +347,7 @@ struct spa_io_position { * and node rates. The \a flags and \a rate fields may be modified by the node. * * The node can request a correction to the resampling rate in its process(), by setting - * \ref SPA_IO_RATE_MATCH_ACTIVE on \a flags, and setting \a rate to the desired rate + * \ref SPA_IO_RATE_MATCH_FLAG_ACTIVE on \a flags, and setting \a rate to the desired rate * correction. Usually the rate is obtained from DLL or other adaptive mechanism that * e.g. drives the node buffer fill level toward a specific value. * From 1ed8f771bda4577edd58b16ed6405fb2c92798ac Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 23 Jul 2025 21:01:59 +0200 Subject: [PATCH 0625/1014] doc: spa: Minor improvements to driver architecture documentation --- doc/dox/internals/driver.dox | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/dox/internals/driver.dox b/doc/dox/internals/driver.dox index 68cf1dd5e..c31982b1d 100644 --- a/doc/dox/internals/driver.dox +++ b/doc/dox/internals/driver.dox @@ -37,7 +37,7 @@ updated as follows: - \ref spa_io_clock::rate : Set to a value that can translate samples to nanoseconds. - \ref spa_io_clock::position : Current cycle position, in samples. This is the ideal position of the graph cycle (this is explained in greater detail further below). - It is incremented by the dduration (in samples) at the beginning of each cycle. If + It is incremented by the duration (in samples) at the beginning of each cycle. If a discontinuity is experienced by the driver that results in a discontinuity in the position of the old and the current cycle, consider setting the \ref SPA_IO_CLOCK_FLAG_DISCONT flag to inform other nodes about this. @@ -66,7 +66,11 @@ is the moment in monotonic clock time when the cycle _actually_ happens. This is an important distinction when driver is run by a clock that is different to the monotonic cloc. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the pace of that different clock (explained in the section below). In such a case, -\ref spa_io_clock::position still is incremented by the duration in samples. +\ref spa_io_clock::position still is incremented by the duration in samples. This +is important, since nodes and modules may use this field as an offset within their own +internal ring buffers or similar structures, using the position field as an offset within +said data structures. This requires the position field to advance in a continuous way. +By incrementing by the duration, this requirement is met. # Using clocks other than the monotonic clock From f8b0d0a43cb6246dabad7a2574c9907cf1a0b289 Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Wed, 21 May 2025 12:14:58 +0200 Subject: [PATCH 0626/1014] rtp: include stream delay to a read position When a stream has some delay, a time t1 + delay has to be read in time t1 to play it when expected. Decrease target_buffer by delay to start playback sooner, so sound is played at correct time when delay is applied. Signed-off-by: Martin Geier --- src/modules/module-rtp/audio.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 65a05fee1..74ff52511 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -7,7 +7,9 @@ static void rtp_audio_process_playback(void *data) struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; + struct pw_time pwt; uint32_t wanted, timestamp, target_buffer, stride, maxsize; + uint32_t device_delay; int32_t avail, flags = 0; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { @@ -21,16 +23,36 @@ static void rtp_audio_process_playback(void *data) maxsize = d[0].maxsize / stride; wanted = buf->requested ? SPA_MIN(buf->requested, maxsize) : maxsize; + pw_stream_get_time_n(impl->stream, &pwt, sizeof(pwt)); + + /* Negative delay is used rarely, mostly for the combine stream. + * There, the delay is used as an offset value between streams. + * Here, negative delay values make no sense. It is safe to clamp + * delay values to 0 (see docs), so do that here. */ + device_delay = SPA_MAX(pwt.delay, 0LL); + if (impl->io_position && impl->direct_timestamp) { /* in direct mode, read directly from the timestamp index, * because sender and receiver are in sync, this would keep * target_buffer of samples available. */ + + /* Shift clock position by stream delay to compensate + * for processing and output delay. */ spa_ringbuffer_read_update(&impl->ring, - impl->io_position->clock.position); + impl->io_position->clock.position + device_delay); } avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); - target_buffer = impl->target_buffer; + /* Reduce target buffer by the delay amount to start playback sooner. + * This compensates for the delay to the device. */ + if (SPA_UNLIKELY(impl->target_buffer < device_delay)) { + pw_log_error("Delay to device (%" PRIu32 ") is higher than " + "the target buffer size (%" PRIu32 ")", device_delay, + impl->target_buffer); + target_buffer = 0; + } else { + target_buffer = impl->target_buffer - device_delay; + } if (avail < (int32_t)wanted) { enum spa_log_level level; From 2bcc8589faba1e5d43ae692b58d8e56229dfba06 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 23 Jul 2025 21:05:10 +0200 Subject: [PATCH 0627/1014] module-rtp: Fix and improve direct timestamp mode and documentation Direct timestamp mode was incorrectly using over/underrun detection logic and fill level tracking logic that is actually meant for the other mode (referred to from now on as "constant latency mode"). Over/underruns are tracked implicitly in the direct timestamp mode, and the absolute fill level is not relevant in that mode, since the latency is not needed to be constant then. Also improve log lines and the RTP module documentation to define these buffer modes clearly and explain their differences and use cases. Opus and MIDI code get TODOs added, since their direct timestamp mode implementations still may be incorrect. Fixing those will be done in a separate commit. --- src/modules/module-rtp-source.c | 60 +++++++-- src/modules/module-rtp/audio.c | 230 +++++++++++++++++++++++--------- src/modules/module-rtp/midi.c | 2 + src/modules/module-rtp/opus.c | 2 + 4 files changed, 218 insertions(+), 76 deletions(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 185d82954..7bdaa1122 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -35,11 +35,12 @@ /** \page page_module_rtp_source RTP source * - * The `rtp-source` module creates a PipeWire source that receives audio - * and midi RTP packets. + * The `rtp-source` module creates a PipeWire source that receives audio RTP packets. + * These RTP packets may contain raw PCM data, Opus encoded audio, or midi audio. * * This module is usually loaded from the \ref page_module_rtp_sap so that the - * source.ip and source.port and format parameters matches that of the sender. + * source.ip and source.port and format parameters matches that of the sender that + * is announced via SAP. * * ## Module Name * @@ -57,19 +58,11 @@ * - `sess.latency.msec = `: target network latency in milliseconds, default 100 * - `sess.ignore-ssrc = `: ignore SSRC, default false * - `sess.media = `: the media type audio|midi|opus, default audio - * - `sess.ts-direct = `: directly synchronize output against the current - * graph driver time, using the RTP timestamps, default false + * - `sess.ts-direct = `: use direct timestamp mode, default false + * (see the Buffer Modes section below) * - `stream.may-pause = `: pause the stream when no data is reveived, default false * - `stream.props = {}`: properties to be passed to the stream * - * Set `sess.ts-direct` to true if receivers shall play precisely in sync with the sender even - * if the transport delay differs. This can be important for use cases like AES67 sessions. - * The graph driver must then produce time that is in sync with the sender's graph driver. - * If it is set to false, the RTP timestamps will be used to reproduce the pace of the sender, - * but not directly for synchronizing when output starts. Note though that this requires that - * the receivers and senders have synchronized clocks. In PTP, the reference clocks must then - * be the same. Otherwise, senders and receives will be out of sync. - * * ## General options * * Options with well-known behavior: @@ -114,6 +107,47 @@ * ] *\endcode * + * ## Buffer modes + * + * RTP source nodes created by this module use an internal ring buffer. Received RTP audio + * data is written into this ring buffer. When the node's process callback is run, it reads + * from that ring buffer and provides audio data from it to the graph. + * + * The `sess.ts-direct` option controls the _buffer mode_, which defines how this ring buffer + * is used. The RTP source nodes created by this module can operate in one of two of these + * buffer modes. In both modes, the RTP source node uses the timestamps of incoming RTP + * packets to write into the ring buffer (more specifically, at the position + * `timestamp + latency from the sess.latency.msec option`). The modes are: + * + * -# *Constant latency mode*: This is the default mode. It is used when `sess.ts-direct` + * is set to false. `sess.latency.msec` then defines the ideal fill level of the ring + * buffer. If the fill level is above or below this, then a DLL is used to adjust the + * consumption of the buffer contents. If the fill level is below a critical value + * (that's the amount of data that is needed in a cycle), or if the fill level equals + * the total buffer size (meaning that no more data can be fed into the buffer), the + * buffer contents are resynchronized, meaning that the existing contents are thrown + * away, and the ring buffer is reset. This buffer mode is useful for when a constant + * latency is desired, and the actual moment playback starts is unimportant (meaning + * that playback is not necessarily in sync with other devices). This mode requires + * no special graph driver. + * -# *Direct timestamp mode*: This is an alternate mode, used when `sess.ts-direct` is + * set to true. In this mode, ring buffer over- and underrun and fill level are not + * directly tracked; instead, they are handled implicitly. There is no constant latency + * maintained. The current time (more specifically, the \ref spa_io_clock::position field + * of \ref spa_io_position::clock) is directly used during playback to retrieve audio + * data. This assumes that a graph driver is used whose time is somehow synchronized + * to the sender's. Since the current time is directly used as an offset within the + * ring buffer, the correct data is always pulled from the ring buffer, that is, the + * data that shall be played now, in sync with the sender (and with other receivers). + * This buffer mode is useful for when receivers shall play in sync with each other, + * and shall use one common synchronized time, provided through the \ref spa_io_clock . + * `sess.latency.msec` functions as a configurable assumed maximum transport delay + * instead of a constant latency quantity in this mode. The DLL is not used in this + * mode, since the graph driver is assumed to be synchronized to the sender, as said, + * so any output sinks in the graph will already adjust their consumption pace to + * match the pace of the graph driver. + * AES67 sessions use this mode, for example. + * * \since 0.3.60 */ diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 74ff52511..e262091c6 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -31,62 +31,133 @@ static void rtp_audio_process_playback(void *data) * delay values to 0 (see docs), so do that here. */ device_delay = SPA_MAX(pwt.delay, 0LL); - if (impl->io_position && impl->direct_timestamp) { - /* in direct mode, read directly from the timestamp index, - * because sender and receiver are in sync, this would keep - * target_buffer of samples available. */ + /* IMPORTANT: In the explanations below, sometimes, "reading/writing from/to the + * ring buffer at a position X" is mentioned. To be exact, that buffer is actually + * impl->buffer. And since X can be a timestamp whose value is far higher than the + * buffer size (and the fact that impl->buffer is a _ring_ buffer), reads and writes + * actually first apply BUFFER_MASK to the position to implement a ring buffer + * index wrap-around. (Wrap-around when reading / writing the data bytes is + * handled by the spa_ringbuffer code; this is about the wrap around of the + * read or write index itself.) */ - /* Shift clock position by stream delay to compensate - * for processing and output delay. */ - spa_ringbuffer_read_update(&impl->ring, - impl->io_position->clock.position + device_delay); - } - avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + if (impl->direct_timestamp) { + /* In direct timestamp mode, the focus lies on synchronized playback, not + * on a constant latency. The ring buffer fill level is not of interest + * here. The code in rtp_audio_receive() writes to the ring buffer at + * position (RTP timestamp + target_buffer), just like in the constant + * latency mode. Crucially however, in direct timestamp mode, it is assumed + * that the RTP timestamps are based on the same synchronized clock that + * runs the graph driver here, so the clock position is using the same + * time base as these timestamps. + * + * If the transport delay from the sender to this receiver were zero, then + * the data with the given RTP timestamp could in theory be played right + * away, since that timestamp would equal the clock position (or, in other + * words, it would be the present time). Since the transport takes some + * time, writing the data at the position (RTP timestamp + target_buffer) + * shifts the timestamp into the future sufficiently enough that no data + * is lost. (target_buffer corresponds to the `sess.latency.msec` RTP + * source module option, and that option has to be chosen by the user + * to be of a sensible size - high enough to at least match the maximum + * transport delay, but not too high to not risk too much latency + * Also, `sess.latency.msec` must be the same value across all RTP + * source nodes that shall play in sync.) + * + * When the code here reads from the position defined by the current + * clock position, it is then guaranteed that the data is accessed in + * sync with other RTP source nodes which also run in the direct + * timestamp mode, since all of them shift the timestamp by the same + * `sess.latency.msec` into the future. + * + * "Fill level" makes no sense in this mode, since a constant latency + * is not important in this mode, so no DLL is needed. Also, matching + * the pace of the synchronized clock is done by having the graph + * driver be synchronized to that clock, which will in turn cause + * any output sinks to adjust their DLLs (or similar control loop + * mechanisms) to match the pace of their data consumption with the + * pace of the driver. */ - /* Reduce target buffer by the delay amount to start playback sooner. - * This compensates for the delay to the device. */ - if (SPA_UNLIKELY(impl->target_buffer < device_delay)) { - pw_log_error("Delay to device (%" PRIu32 ") is higher than " - "the target buffer size (%" PRIu32 ")", device_delay, - impl->target_buffer); - target_buffer = 0; - } else { - target_buffer = impl->target_buffer - device_delay; - } - - if (avail < (int32_t)wanted) { - enum spa_log_level level; - memset(d[0].data, 0, wanted * stride); - flags |= SPA_CHUNK_FLAG_EMPTY; - - if (impl->have_sync) { - impl->have_sync = false; - level = SPA_LOG_LEVEL_INFO; + if (impl->io_position) { + /* Shift clock position by stream delay to compensate + * for processing and output delay. */ + timestamp = impl->io_position->clock.position + device_delay; + spa_ringbuffer_read_update(&impl->ring, timestamp); } else { - level = SPA_LOG_LEVEL_DEBUG; + /* In the unlikely case that no spa_io_position pointer + * was passed yet by PipeWire to this node, resort to a + * default behavior: just use the current read index. + * This most likely is not in sync with other nodes, + * but _something_ is needed as read index until the + * spa_io_position is available. */ + spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + } + + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (timestamp * stride) & BUFFER_MASK, + d[0].data, wanted * stride); + + if (!impl->io_position) { + /* In the unlikely case that no spa_io_position pointer + * was passed yet by PipeWire to this node, monotonically + * increment the read index like this to not consume from + * the same position in the ring buffer over and over again. */ + timestamp += wanted; + spa_ringbuffer_read_update(&impl->ring, timestamp); } - pw_log(level, "underrun %d/%u < %u", - avail, target_buffer, wanted); } else { - double error, corr; - if (impl->first) { - if ((uint32_t)avail > target_buffer) { - uint32_t skip = avail - target_buffer; - pw_log_debug("first: avail:%d skip:%u target:%u", - avail, skip, target_buffer); - timestamp += skip; + /* In the constant delay mode, it is assumed that the ring buffer fill + * level matches impl->target_buffer. If not, check for over- and + * underruns. Adjust the DLL as needed. If the over/underruns are too + * severe, resynchronize. */ + + avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); + + /* Reduce target buffer by the delay amount to start playback sooner. + * This compensates for the delay to the device. */ + if (SPA_UNLIKELY(impl->target_buffer < device_delay)) { + pw_log_error("Delay to device (%" PRIu32 ") is higher than " + "the target buffer size (%" PRIu32 ")", device_delay, + impl->target_buffer); + target_buffer = 0; + } else { + target_buffer = impl->target_buffer - device_delay; + } + + if (avail < (int32_t)wanted) { + enum spa_log_level level; + memset(d[0].data, 0, wanted * stride); + flags |= SPA_CHUNK_FLAG_EMPTY; + + if (impl->have_sync) { + impl->have_sync = false; + level = SPA_LOG_LEVEL_INFO; + } else { + level = SPA_LOG_LEVEL_DEBUG; + } + pw_log(level, "receiver read underrun %d/%u < %u", + avail, target_buffer, wanted); + } else { + double error, corr; + if (impl->first) { + if ((uint32_t)avail > target_buffer) { + uint32_t skip = avail - target_buffer; + pw_log_debug("first: avail:%d skip:%u target:%u", + avail, skip, target_buffer); + timestamp += skip; + avail = target_buffer; + } + impl->first = false; + } else if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE / stride)) { + pw_log_warn("receiver read overrun %u > %u", avail, target_buffer * 8); + timestamp += avail - target_buffer; avail = target_buffer; } - impl->first = false; - } else if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE / stride)) { - pw_log_warn("overrun %u > %u", avail, target_buffer * 8); - timestamp += avail - target_buffer; - avail = target_buffer; - } - if (!impl->direct_timestamp) { - /* when not using direct timestamp and clocks are not - * in sync, try to adjust our playback rate to keep the - * requested target_buffer bytes in the ringbuffer */ + + /* when the speed of the sender clock and our clock are + * not in sync, try to adjust our playback rate to keep + * the requested target_buffer bytes in the ringbuffer */ double in_flight = 0; struct spa_io_position *pos = impl->io_position; @@ -109,16 +180,18 @@ static void rtp_audio_process_playback(void *data) target_buffer, error, corr); pw_stream_set_rate(impl->stream, 1.0 / corr); - } - spa_ringbuffer_read_data(&impl->ring, - impl->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, - d[0].data, wanted * stride); - timestamp += wanted; - spa_ringbuffer_read_update(&impl->ring, timestamp); + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (timestamp * stride) & BUFFER_MASK, + d[0].data, wanted * stride); + + timestamp += wanted; + spa_ringbuffer_read_update(&impl->ring, timestamp); + } } + d[0].chunk->offset = 0; d[0].chunk->size = wanted * stride; d[0].chunk->stride = stride; @@ -157,7 +230,10 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) if (impl->have_seq && impl->seq != seq) { pw_log_info("unexpected seq (%d != %d) SSRC:%u", seq, impl->seq, hdr->ssrc); - impl->have_sync = false; + /* No need to resynchronize here. If packets arrive out of + * order, then they are still written in order into the ring + * buffer, since they are written according to where the + * RTP timestamp points to. */ } impl->seq = seq + 1; impl->have_seq = true; @@ -195,8 +271,11 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) write, expected_write); } - if (filled + samples > BUFFER_SIZE / stride) { - pw_log_debug("capture overrun %u + %u > %u", filled, samples, + /* Write overrun only makes sense in constant delay mode. See the + * RTP source module documentation and the rtp_audio_process_playback() + * code for an explanation why. */ + if (!impl->direct_timestamp && (filled + samples > BUFFER_SIZE / stride)) { + pw_log_debug("receiver write overrun %u + %u > %u", filled, samples, BUFFER_SIZE / stride); impl->have_sync = false; } else { @@ -206,9 +285,34 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) BUFFER_SIZE, (write * stride) & BUFFER_MASK, &buffer[hlen], (samples * stride)); - write += samples; - spa_ringbuffer_write_update(&impl->ring, write); + + /* Only update the write index if data was actually _appended_. + * If packets arrived out of order, then it may be that parts + * of the ring buffer further ahead were written to first, and + * now, unwritten parts preceding those other parts were now + * written to. For example, if previously, 10 samples were + * written to index 100, even though 10 samples were expected + * to be written at index 90, then there is a "hole" at index + * 90. If now, the packet that contains data for index 90 + * arrived, then this data will be _inserted_ at index 90, + * and not _appended_. In this example, `expected_write` would + * be 100 (since `expected_write` is the current write index), + * `write` would be 90, `samples` would be 10. In this case, + * the inequality below does not hold, so data is being + * _inserted_. By contrast, during normal operation, `write` + * and `expected_write` are equal, so the inequality below + * _does_ hold, meaning that data is being appended. + * + * (Note that this write index update is only important if + * the constant delay mode is active, or if no spa_io_position + * was provided yet. See the rtp_audio_process_playback() + * code for more about this.) */ + if (expected_write < (write + samples)) { + write += samples; + spa_ringbuffer_write_update(&impl->ring, write); + } } + return 0; short_packet: @@ -399,7 +503,7 @@ static void rtp_audio_process_capture(void *data) pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); impl->have_sync = false; } else if (filled + wanted > (int32_t)SPA_MIN(impl->target_buffer * 8, BUFFER_SIZE / stride)) { - pw_log_warn("overrun %u + %u > %u/%u", filled, wanted, + pw_log_warn("sender write overrun %u + %u > %u/%u", filled, wanted, impl->target_buffer * 8, BUFFER_SIZE / stride); impl->have_sync = false; filled = 0; diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index d9c7cd443..b4d716cb3 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -5,6 +5,8 @@ #include #include +/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. */ + static void rtp_midi_process_playback(void *data) { struct impl *impl = data; diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 70e2b0fd4..8a01d36fb 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -7,6 +7,8 @@ #include #include +/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. */ + static void rtp_opus_process_playback(void *data) { struct impl *impl = data; From a09bf579443860f0b4a4ddd0810f0a05c892752b Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 23 Jul 2025 19:48:00 -0400 Subject: [PATCH 0628/1014] meson: Always use -fno-strict-aliasing and -fno-strict-overflow SPA does not respect the C strict aliasing rules at all, so any code that uses it must be built with -fno-strict-aliasing. Furthermore, there is code in SPA that compares pointers that point to different objects, so -fno-strict-overflow is also needed. --- meson.build | 1 + spa/meson.build | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 472b97705..40c1e4d28 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,7 @@ pkgconfig = import('pkgconfig') common_flags = [ '-fvisibility=hidden', '-fno-strict-aliasing', + '-fno-strict-overflow', '-Werror=suggest-attribute=format', '-Wsign-compare', '-Wpointer-arith', diff --git a/spa/meson.build b/spa/meson.build index 1137adf60..f8acaec6a 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -32,7 +32,7 @@ pkgconfig.generate(filebase : 'lib@0@'.format(spa_name), subdirs : spa_name, description : 'Simple Plugin API', version : spaversion, - extra_cflags : '-D_REENTRANT', + extra_cflags : ['-D_REENTRANT', '-fno-strict-aliasing', '-fno-strict-overflow'], variables : ['plugindir=${libdir}/@0@'.format(spa_name)], uninstalled_variables : ['plugindir=${prefix}/spa/plugins'], ) From 42b779974c50598614f05b79ede4455e5a655480 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Jul 2025 13:16:15 +0200 Subject: [PATCH 0629/1014] module-rtp: don't leak opus codec and ptp_sender Add a deinit() function and use it to free the opus codec we created in init(). Also free the ptp_sender when it was created. --- src/modules/module-rtp/opus.c | 11 +++++++++++ src/modules/module-rtp/stream.c | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 8a01d36fb..fa646f1da 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -320,6 +320,16 @@ static void rtp_opus_process_capture(void *data) rtp_opus_flush_packets(impl); } +static void rtp_opus_deinit(struct impl *impl, enum spa_direction direction) +{ + if (impl->stream_data) { + if (direction == SPA_DIRECTION_INPUT) + opus_multistream_encoder_destroy(impl->stream_data); + else + opus_multistream_decoder_destroy(impl->stream_data); + } +} + static int rtp_opus_init(struct impl *impl, enum spa_direction direction) { int err; @@ -342,6 +352,7 @@ static int rtp_opus_init(struct impl *impl, enum spa_direction direction) for (i = 0; i < impl->info.info.opus.channels; i++) mapping[i] = i; + impl->deinit = rtp_opus_deinit; impl->receive_rtp = rtp_opus_receive; if (direction == SPA_DIRECTION_INPUT) { impl->stream_events.process = rtp_opus_process_capture; diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 9d3713dd5..e44289a7e 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -57,6 +57,7 @@ struct impl { const struct format_info *format_info; + enum spa_direction direction; void *stream_data; uint32_t rate; @@ -103,6 +104,7 @@ struct impl { int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); void (*flush_timeout)(struct impl *impl, uint64_t expirations); + void (*deinit)(struct impl *impl, enum spa_direction direction); /* * pw_filter where the filter would be driven at the PTP clock @@ -304,7 +306,7 @@ static void on_flush_timeout(void *d, uint64_t expirations) } struct rtp_stream *rtp_stream_new(struct pw_core *core, - enum pw_direction direction, struct pw_properties *props, + enum spa_direction direction, struct pw_properties *props, const struct rtp_stream_events *events, void *data) { struct impl *impl; @@ -326,6 +328,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, } impl->first = true; spa_hook_list_init(&impl->listener_list); + impl->direction = direction; impl->stream_events = stream_events; impl->context = pw_core_get_context(core); impl->main_loop = pw_context_get_main_loop(impl->context); @@ -631,6 +634,12 @@ void rtp_stream_destroy(struct rtp_stream *s) rtp_stream_emit_destroy(impl); + if (impl->deinit) + impl->deinit(impl, impl->direction); + + if (impl->ptp_sender) + pw_filter_destroy(impl->ptp_sender); + if (impl->stream) pw_stream_destroy(impl->stream); From a85c24e9ca6300fac36d4dd53557a0088ba32f05 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Jul 2025 16:21:08 +0200 Subject: [PATCH 0630/1014] builder: support building pod + data + suffix Add a function that can build a pod from a pod definition, a body data and a suffix. We can use this to build strings and bytes and arrays like the other primitives, which makes it possible to add them to choices or arrays. Fixes #4588 --- spa/include/spa/pod/builder.h | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 79e982bf7..7ea130123 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -215,21 +215,30 @@ SPA_API_POD_BUILDER void *spa_pod_builder_pop(struct spa_pod_builder *builder, s return pod; } +SPA_API_POD_BUILDER int +spa_pod_builder_primitive_body(struct spa_pod_builder *builder, const struct spa_pod *p, + const void *body, uint32_t body_size, const char *suffix, uint32_t suffix_size) +{ + int res = 0, r; + uint32_t size = SPA_POD_SIZE(p) - body_size - suffix_size; + if (builder->state.flags != SPA_POD_BUILDER_FLAG_BODY) { + SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); + res = spa_pod_builder_raw(builder, p, size); + } + if (body_size > 0 && (r = spa_pod_builder_raw(builder, body, body_size)) < 0) + res = r; + if (suffix_size > 0 && (r = spa_pod_builder_raw(builder, suffix, suffix_size)) < 0) + res = r; + if ((r = spa_pod_builder_pad(builder, builder->state.offset)) < 0) + res = r; + return res; +} + SPA_API_POD_BUILDER int spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod *p) { - const void *data; - uint32_t size; - - if (builder->state.flags == SPA_POD_BUILDER_FLAG_BODY) { - data = SPA_POD_BODY_CONST(p); - size = SPA_POD_BODY_SIZE(p); - } else { - data = p; - size = SPA_POD_SIZE(p); - SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); - } - return spa_pod_builder_raw_padded(builder, data, size); + return spa_pod_builder_primitive_body(builder, p, + SPA_POD_BODY_CONST(p), p->size, NULL, 0); } #define SPA_POD_INIT(size,type) ((struct spa_pod) { (size), (type) }) @@ -315,10 +324,7 @@ SPA_API_POD_BUILDER int spa_pod_builder_string_len(struct spa_pod_builder *builder, const char *str, uint32_t len) { const struct spa_pod_string p = SPA_POD_INIT_String(len+1); - int r, res = spa_pod_builder_raw(builder, &p, sizeof(p)); - if ((r = spa_pod_builder_write_string(builder, str, len)) < 0) - res = r; - return res; + return spa_pod_builder_primitive_body(builder, &p.pod, str, len, "", 1); } SPA_API_POD_BUILDER int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str) @@ -333,10 +339,7 @@ SPA_API_POD_BUILDER int spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len) { const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(len); - int r, res = spa_pod_builder_raw(builder, &p, sizeof(p)); - if ((r = spa_pod_builder_raw_padded(builder, bytes, len)) < 0) - res = r; - return res; + return spa_pod_builder_primitive_body(builder, &p.pod, bytes, len, NULL, 0); } SPA_API_POD_BUILDER void * spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) @@ -427,10 +430,7 @@ spa_pod_builder_array(struct spa_pod_builder *builder, {(uint32_t)(sizeof(struct spa_pod_array_body) + n_elems * child_size), SPA_TYPE_Array}, {{child_size, child_type}} }; - int r, res = spa_pod_builder_raw(builder, &p, sizeof(p)); - if ((r = spa_pod_builder_raw_padded(builder, elems, child_size * n_elems)) < 0) - res = r; - return res; + return spa_pod_builder_primitive_body(builder, &p.pod, elems, n_elems * child_size, NULL, 0); } #define SPA_POD_INIT_CHOICE_BODY(type, flags, child_size, child_type) \ From 0c33101a4286cd61bd1f36a6f28d08ec7fa9f21e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Jul 2025 18:22:14 +0200 Subject: [PATCH 0631/1014] pod: remove unused function --- spa/include/spa/pod/builder.h | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 7ea130123..da66267ea 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -350,31 +350,6 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); } -SPA_API_POD_BUILDER uint32_t -spa_pod_builder_bytes_start(struct spa_pod_builder *builder) -{ - uint32_t offset = builder->state.offset; - const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(0); - spa_pod_builder_raw(builder, &p, sizeof(p)); - return offset; -} -SPA_API_POD_BUILDER int -spa_pod_builder_bytes_append(struct spa_pod_builder *builder, uint32_t offset, - const void *data, uint32_t size) -{ - int res = spa_pod_builder_raw(builder, data, size); - struct spa_pod *pod = spa_pod_builder_deref(builder, offset); - if (pod) - pod->size += size; - return res; -} - -SPA_API_POD_BUILDER int -spa_pod_builder_bytes_end(struct spa_pod_builder *builder, uint32_t offset SPA_UNUSED) -{ - return spa_pod_builder_pad(builder, builder->state.offset); -} - #define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) SPA_API_POD_BUILDER int From 0f6b3651387ebf2971b6b766b0d16ca1ff5c4998 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 24 Jul 2025 18:35:21 +0200 Subject: [PATCH 0632/1014] pod: don't call deref in reserve bytes We already know that we could succesfully allocate enough space for the bytes because we checked that before so simply move to the body of the new bytes pod. We don't need to do the extensive checks we do in deref. --- spa/include/spa/pod/builder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index da66267ea..b30e97b9e 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -347,7 +347,7 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) uint32_t offset = builder->state.offset; if (spa_pod_builder_bytes(builder, NULL, len) < 0) return NULL; - return SPA_POD_BODY(spa_pod_builder_deref(builder, offset)); + return SPA_PTROFF(builder->data, offset + sizeof(struct spa_pod), void); } #define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) From 9e789c65c22e5671d88dc32006e2f32a563e340f Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sun, 20 Jul 2025 15:06:49 -0400 Subject: [PATCH 0633/1014] src: check that POD arrays have the correct size for their type The parser does not check that POD arrays have the correct size for their type, so the calling code must do that. This also enumerates some of the code that cannot handle the size of the values of an array not being the exact expected size for its type. There is a lot of it. --- .../module-protocol-native/protocol-native.c | 6 ++--- src/modules/module-protocol-pulse/collect.c | 24 +++++++++++++------ .../module-session-manager/protocol-native.c | 16 ++++++------- src/pipewire/stream.c | 7 +++--- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c index 3a7a1a179..3a8580efb 100644 --- a/src/modules/module-protocol-native/protocol-native.c +++ b/src/modules/module-protocol-native/protocol-native.c @@ -977,7 +977,7 @@ static int device_demarshal_subscribe_params(void *object, const struct pw_proto SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_device_methods, subscribe_params, 0, @@ -1238,7 +1238,7 @@ static int node_demarshal_subscribe_params(void *object, const struct pw_protoco SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_node_methods, subscribe_params, 0, @@ -1462,7 +1462,7 @@ static int port_demarshal_subscribe_params(void *object, const struct pw_protoco SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_port_methods, subscribe_params, 0, diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index 1f0578026..a02f82f62 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -369,7 +369,9 @@ uint32_t collect_port_info(struct pw_manager_object *card, struct card_info *car n = 0; spa_list_for_each(p, &card->param_list, link) { - struct spa_pod *devices = NULL, *profiles = NULL; + int32_t *devices = NULL, *profiles = NULL; + uint32_t devices_size = 0, devices_type = 0, n_devices = 0; + uint32_t profiles_size = 0, profiles_type = 0, n_profiles = 0; struct port_info *pi; if (p->id != SPA_PARAM_EnumRoute) @@ -387,16 +389,24 @@ uint32_t collect_port_info(struct pw_manager_object *card, struct card_info *car SPA_PARAM_ROUTE_priority, SPA_POD_OPT_Int(&pi->priority), SPA_PARAM_ROUTE_available, SPA_POD_OPT_Id(&pi->available), SPA_PARAM_ROUTE_info, SPA_POD_OPT_Pod(&pi->info), - SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Pod(&devices), - SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Pod(&profiles)) < 0) + SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Array(&devices_size, + &devices_type, &n_devices, &devices), + SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Array(&profiles_size, + &profiles_type, &n_profiles, &profiles)) < 0) continue; if (pi->description == NULL) pi->description = pi->name; - if (devices) - pi->devices = spa_pod_get_array(devices, &pi->n_devices); - if (profiles) - pi->profiles = spa_pod_get_array(profiles, &pi->n_profiles); + if (devices && devices_size == sizeof(pi->devices[0]) && + devices_type == SPA_TYPE_Int) { + pi->devices = devices; + pi->n_devices = n_devices; + } + if (profiles && profiles_size == sizeof(pi->profiles[0]) && + profiles_type == SPA_TYPE_Int) { + pi->profiles = profiles; + pi->n_profiles = n_profiles; + } if (dev_info != NULL) { if (pi->direction != dev_info->direction) diff --git a/src/modules/module-session-manager/protocol-native.c b/src/modules/module-session-manager/protocol-native.c index 98be24429..01be455ae 100644 --- a/src/modules/module-session-manager/protocol-native.c +++ b/src/modules/module-session-manager/protocol-native.c @@ -1284,7 +1284,7 @@ static int endpoint_link_proxy_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_proxy_notify(proxy, struct pw_endpoint_link_methods, @@ -1304,7 +1304,7 @@ static int endpoint_link_resource_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_endpoint_link_methods, @@ -1806,7 +1806,7 @@ static int endpoint_stream_proxy_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_proxy_notify(proxy, struct pw_endpoint_stream_methods, @@ -1826,7 +1826,7 @@ static int endpoint_stream_resource_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_endpoint_stream_methods, @@ -2320,7 +2320,7 @@ static int endpoint_proxy_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_proxy_notify(proxy, struct pw_endpoint_methods, @@ -2340,7 +2340,7 @@ static int endpoint_resource_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_endpoint_methods, @@ -2842,7 +2842,7 @@ static int session_proxy_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_proxy_notify(proxy, struct pw_session_methods, @@ -2862,7 +2862,7 @@ static int session_resource_demarshal_subscribe_params(void *object, SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0) return -EINVAL; - if (ctype != SPA_TYPE_Id) + if (ctype != SPA_TYPE_Id || csize != sizeof(uint32_t)) return -EINVAL; return pw_resource_notify(resource, struct pw_session_methods, diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 8cdef61ad..ddd53ebd0 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1347,7 +1347,7 @@ static int node_event_param(void *object, int seq, double value_d; bool value_b; float *values; - uint32_t i, n_values; + uint32_t i, n_values, val_size, val_type; SPA_POD_OBJECT_FOREACH(obj, prop) { struct control *c; @@ -1378,8 +1378,9 @@ static int node_event_param(void *object, int seq, values = &value_f; break; case SPA_TYPE_Array: - if ((values = spa_pod_get_array(&prop->value, &n_values)) == NULL || - !spa_pod_is_float(SPA_POD_ARRAY_CHILD(&prop->value))) + if ((values = spa_pod_get_array_full(&prop->value, &n_values, &val_size, &val_type)) == NULL || + val_type != SPA_TYPE_Float || + val_size != sizeof(float)) continue; n_values = SPA_MIN(n_values, MAX_VALUES); break; From b991e9acc94db3eac14eeaf047710b53b6831ab1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 25 Jul 2025 17:01:24 +0200 Subject: [PATCH 0634/1014] pod: check string zero byte only when parsing The _is_type() macros should simply check the type in the header and if the size is large enough to look into the type specifics. Further validation of the values should be done when the value is retrieved. Following this logic, the String zero byte check should be done in the get_string() function. --- spa/include/spa/pod/iter.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index f2400a16f..e5ce3f719 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -279,24 +279,28 @@ SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value SPA_API_POD_ITER int spa_pod_is_string(const struct spa_pod *pod) { - const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); - return SPA_POD_CHECK(pod, SPA_TYPE_String, 1) && - s[pod->size-1] == '\0'; + return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { + const char *s; if (!spa_pod_is_string(pod)) return -EINVAL; - *value = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); + s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); + if (s[pod->size-1] != '\0') + return -EINVAL; + *value = s; return 0; } SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { - const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); + const char *s; if (!spa_pod_is_string(pod) || maxlen < 1) return -EINVAL; + maxlen = SPA_MIN(maxlen, pod->size); + s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); strncpy(dest, s, maxlen-1); dest[maxlen-1]= '\0'; return 0; From 87333537d2e97f2a9cd5b4e6492ba5c386b3c1ed Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 25 Jul 2025 17:31:42 +0200 Subject: [PATCH 0635/1014] pod: also check 0 terminted strings in copy_string Use get_string() to get a pointer to the string in the pod so that we also check if it has a 0 terminator. Fix the test case now that is_string returns true even for non zero-terminated strings. --- spa/include/spa/pod/iter.h | 4 +--- test/test-spa-pod.c | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index e5ce3f719..cf65b8c62 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -297,10 +297,8 @@ SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char ** SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { const char *s; - if (!spa_pod_is_string(pod) || maxlen < 1) + if (spa_pod_get_string(pod, &s) < 0 || maxlen < 1) return -EINVAL; - maxlen = SPA_MIN(maxlen, pod->size); - s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); strncpy(dest, s, maxlen-1); dest[maxlen-1]= '\0'; return 0; diff --git a/test/test-spa-pod.c b/test/test-spa-pod.c index f499e818b..5c4308b46 100644 --- a/test/test-spa-pod.c +++ b/test/test-spa-pod.c @@ -286,7 +286,7 @@ PWTEST(pod_init) spa_assert_se(SPA_POD_SIZE(&pod) == 14); spa_assert_se(SPA_POD_TYPE(&pod) == SPA_TYPE_String); spa_assert_se(SPA_POD_BODY_SIZE(&pod) == 6); - spa_assert_se(!spa_pod_is_string(&pod.pod.pod)); + spa_assert_se(spa_pod_is_string(&pod.pod.pod)); spa_assert_se(spa_pod_copy_string(&pod.pod.pod, sizeof(val), val) < 0); } { From 1957d3fb0058a2b45744e56570415ac69cdf9203 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 25 Jul 2025 17:37:56 +0200 Subject: [PATCH 0636/1014] pulse: avoid a compiler warning --- src/modules/module-protocol-pulse/collect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index a02f82f62..32e30d120 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -399,12 +399,12 @@ uint32_t collect_port_info(struct pw_manager_object *card, struct card_info *car pi->description = pi->name; if (devices && devices_size == sizeof(pi->devices[0]) && devices_type == SPA_TYPE_Int) { - pi->devices = devices; + pi->devices = (uint32_t*)devices; pi->n_devices = n_devices; } if (profiles && profiles_size == sizeof(pi->profiles[0]) && profiles_type == SPA_TYPE_Int) { - pi->profiles = profiles; + pi->profiles = (uint32_t*)profiles; pi->n_profiles = n_profiles; } From 927ab0f4b88e125105ca764b8e7d888fe67b8b17 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sun, 27 Jul 2025 16:51:52 +0200 Subject: [PATCH 0637/1014] control: improve port handling in control mixer Use lists to manage the free, allocated and acitve mixer ports. We can then avoid some checks and make things more threadsafe. --- spa/plugins/control/mixer.c | 138 ++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 9085da5c5..c19d82ebc 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -39,6 +39,8 @@ struct buffer { }; struct port { + struct spa_list link; + uint32_t direction; uint32_t id; @@ -48,7 +50,6 @@ struct port { struct spa_port_info info; struct spa_param_info params[8]; - unsigned int valid:1; unsigned int have_format:1; uint32_t types; @@ -57,6 +58,9 @@ struct port { uint32_t n_buffers; struct spa_list queue; + + struct spa_list mix_link; + bool active; }; struct impl { @@ -77,10 +81,10 @@ struct impl { struct spa_hook_list hooks; - uint32_t port_count; - uint32_t last_port; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; + struct spa_list port_list; + struct spa_list free_list; struct spa_pod_control *mix_ctrl[MAX_PORTS]; struct spa_pod_sequence *mix_seq[MAX_PORTS]; @@ -89,12 +93,13 @@ struct impl { unsigned int have_format:1; unsigned int started:1; + + struct spa_list mix_list; }; -#define PORT_VALID(p) ((p) != NULL && (p)->valid) #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) -#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && !PORT_VALID(this->in_ports[(p)])) -#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && PORT_VALID(this->in_ports[(p)])) +#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) +#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) @@ -180,7 +185,7 @@ static int impl_node_add_listener(void *object, { struct impl *this = object; struct spa_hook_list save; - uint32_t i; + struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -188,10 +193,8 @@ static int impl_node_add_listener(void *object, emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); - for (i = 0; i < this->last_port; i++) { - if (PORT_VALID(this->in_ports[i])) - emit_port_info(this, GET_IN_PORT(this, i), true); - } + spa_list_for_each(port, &this->port_list, link) + emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); @@ -206,6 +209,18 @@ impl_node_set_callbacks(void *object, return 0; } +static struct port *get_free_port(struct impl *this) +{ + struct port *port; + if (!spa_list_is_empty(&this->free_list)) { + port = spa_list_first(&this->free_list, struct port, link); + spa_list_remove(&port->link); + } else { + port = calloc(1, sizeof(struct port)); + } + return port; +} + static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { @@ -216,12 +231,8 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); - if (port == NULL) { - port = calloc(1, sizeof(struct port)); - if (port == NULL) - return -errno; - this->in_ports[port_id] = port; - } + if ((port = get_free_port(this)) == NULL) + return -errno; port->direction = direction; port->id = port_id; @@ -242,12 +253,10 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 port->info.params = port->params; port->info.n_params = 5; - this->port_count++; - if (this->last_port <= port_id) - this->last_port = port_id + 1; - port->valid = true; + this->in_ports[port_id] = port; + spa_list_append(&this->port_list, &port->link); - spa_log_debug(this->log, "%p: add port %d %d", this, port_id, this->last_port); + spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; @@ -263,25 +272,17 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); + this->in_ports[port_id] = NULL; + spa_list_remove(&port->link); - port->valid = false; - this->port_count--; if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); + spa_list_append(&this->free_list, &port->link); - if (port_id + 1 == this->last_port) { - int i; - - for (i = this->last_port - 1; i >= 0; i--) - if (GET_IN_PORT (this, i)->valid) - break; - - this->last_port = i + 1; - } - spa_log_debug(this->log, "%p: remove port %d %d", this, port_id, this->last_port); + spa_log_debug(this->log, "%p: remove port %d:%d", this, direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); @@ -558,6 +559,7 @@ impl_node_port_use_buffers(void *object, } struct io_info { + struct impl *impl; struct port *port; void *data; size_t size; @@ -567,16 +569,28 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; - if (info->size >= sizeof(struct spa_io_async_buffers)) { - struct spa_io_async_buffers *ab = info->data; - info->port->io[0] = &ab->buffers[0]; - info->port->io[1] = &ab->buffers[1]; - } else if (info->size >= sizeof(struct spa_io_buffers)) { - info->port->io[0] = info->data; - info->port->io[1] = info->data; + struct port *port = info->port; + + if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { + port->io[0] = NULL; + port->io[1] = NULL; + if (port->active) { + spa_list_remove(&port->mix_link); + port->active = false; + } } else { - info->port->io[0] = NULL; - info->port->io[1] = NULL; + if (info->size >= sizeof(struct spa_io_async_buffers)) { + struct spa_io_async_buffers *ab = info->data; + port->io[0] = &ab->buffers[port->direction]; + port->io[1] = &ab->buffers[port->direction^1]; + } else { + port->io[0] = info->data; + port->io[1] = info->data; + } + if (!port->active) { + spa_list_append(&info->impl->mix_list, &port->mix_link); + port->active = true; + } } return 0; } @@ -598,6 +612,7 @@ impl_node_port_set_io(void *object, spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); + info.impl = this; info.port = port; info.data = data; info.size = size; @@ -682,14 +697,14 @@ static inline bool control_needs_conversion(struct port *port, uint32_t type) static int impl_node_process(void *object) { struct impl *this = object; - struct port *outport; + struct port *outport, *inport; struct spa_io_buffers *outio; - uint32_t n_seq, i; - struct spa_pod_sequence **seq; - struct spa_pod_control **ctrl; + uint32_t n_seq; + struct spa_pod_sequence **seq; + struct spa_pod_control **ctrl; struct spa_pod_builder builder; struct spa_pod_frame f; - struct buffer *outb; + struct buffer *outb; struct spa_data *d; uint32_t cycle = this->position->clock.cycle & 1; @@ -724,17 +739,10 @@ static int impl_node_process(void *object) n_seq = 0; /* collect all sequence pod on input ports */ - for (i = 0; i < this->last_port; i++) { - struct port *inport = GET_IN_PORT(this, i); - struct spa_io_buffers *inio = NULL; + spa_list_for_each(inport, &this->mix_list, mix_link) { + struct spa_io_buffers *inio = inport->io[cycle]; void *pod; - if (SPA_UNLIKELY(!PORT_VALID(inport) || (inio = inport->io[cycle]) == NULL)) { - spa_log_trace_fp(this->log, "%p: skip input idx:%d valid:%d io:%p/%p/%d", - this, i, PORT_VALID(inport), - inport->io[0], inport->io[1], cycle); - continue; - } if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " @@ -775,7 +783,7 @@ static int impl_node_process(void *object) /* merge sort all sequences into output buffer */ while (true) { struct spa_pod_control *next = NULL; - uint32_t next_index = 0; + uint32_t i, next_index = 0; for (i = 0; i < n_seq; i++) { if (!spa_pod_control_is_inside(&seq[i]->body, @@ -883,14 +891,18 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this; - uint32_t i; + struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; - for (i = 0; i < MAX_PORTS; i++) - free(this->in_ports[i]); + spa_list_insert_list(&this->free_list, &this->port_list); + spa_list_insert_list(&this->free_list, &this->mix_list); + spa_list_consume(port, &this->free_list, link) { + spa_list_remove(&port->link); + free(port); + } return 0; } @@ -939,6 +951,9 @@ impl_init(const struct spa_handle_factory *factory, } spa_hook_list_init(&this->hooks); + spa_list_init(&this->port_list); + spa_list_init(&this->free_list); + spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -951,7 +966,6 @@ impl_init(const struct spa_handle_factory *factory, this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; port = GET_OUT_PORT(this, 0); - port->valid = true; port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); From 56e2d52b65f4cd9246f229deea15c34721aed831 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Sun, 27 Jul 2025 17:17:55 +0200 Subject: [PATCH 0638/1014] control: fix fastpath compilation --- spa/plugins/control/mixer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index c19d82ebc..c30c13715 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -747,24 +747,24 @@ static int impl_node_process(void *object) inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, - i, inio, inio->status, inio->buffer_id, inport->n_buffers); + inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d", this, - i, inio, outio, inio->status, inio->buffer_id); + inport->id, inio, outio, inio->status, inio->buffer_id); d = inport->buffers[inio->buffer_id].buffer->datas; if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL) { spa_log_trace_fp(this->log, "%p: skip input idx:%d max:%u " - "offset:%u size:%u", this, i, + "offset:%u size:%u", this, inport->id, d->maxsize, d->chunk->offset, d->chunk->size); continue; } if (!spa_pod_is_sequence(pod)) { - spa_log_trace_fp(this->log, "%p: skip input idx:%d", this, i); + spa_log_trace_fp(this->log, "%p: skip input idx:%d", this, inport->id); continue; } From e2c291d18e4a47ffd34f146814ff297ea0dbc0a9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Jul 2025 14:55:40 +0200 Subject: [PATCH 0639/1014] pw-top: reduce flicker when updating Use wearase() instead of wclear() before redrawing Fixes #4818 --- src/tools/pw-top.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index d92462fa1..6972ff2b7 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -562,7 +562,7 @@ static void do_refresh(struct data *d, bool force_refresh) return; if (!d->batch_mode) { - wclear(d->win); + werase(d->win); wattron(d->win, A_REVERSE); wprintw(d->win, "%-*.*s", COLS, COLS, HEADER); wattroff(d->win, A_REVERSE); From e317edcfb9da360a01aeb706923f0041e960ab6f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Jul 2025 15:15:02 +0200 Subject: [PATCH 0640/1014] pod: rework the parser Make a new body.h file with some functions to deal with pod and their body. Make the iter.h functions use mostly this. Rework the parser so that it only uses body.h functions. With the separation of pod+body, we can read and verify the pod once and then use the verified copy to handle the rest of the body safely. We do this because iter.h only works in pods in memory that doesn't change because it is vulnerable to modifications of the data after verifying it. The new parser is not vulnerable to this and will not cause invalid memory access when used on shared memory. There is however no need for atomic operations to read the headers, whever is read is either valid and useable of invalid and rejected. See #4822 --- spa/include/spa/debug/format.h | 2 +- spa/include/spa/param/audio/dsd-utils.h | 1 + spa/include/spa/param/audio/raw-utils.h | 1 + spa/include/spa/param/tag-utils.h | 1 + spa/include/spa/param/video/dsp-utils.h | 1 + spa/include/spa/param/video/raw-utils.h | 1 + spa/include/spa/pod/body.h | 392 ++++++++++++++++++ spa/include/spa/pod/builder.h | 2 +- spa/include/spa/pod/iter.h | 268 ++---------- spa/include/spa/pod/parser.h | 522 +++++++++++++++++------- spa/include/spa/pod/pod.h | 9 + spa/lib/lib.c | 1 + src/gst/gstpipewiresrc.c | 1 + src/modules/module-protocol-native.c | 1 - src/pipewire/buffers.c | 2 +- src/tools/pw-profiler.c | 1 + 16 files changed, 826 insertions(+), 380 deletions(-) create mode 100644 spa/include/spa/pod/body.h diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 571b4df20..874dd094a 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -7,7 +7,7 @@ #include -#include +#include #include #include #include diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h index 5b93b7a8b..980bdf971 100644 --- a/spa/include/spa/param/audio/dsd-utils.h +++ b/spa/include/spa/param/audio/dsd-utils.h @@ -5,6 +5,7 @@ #ifndef SPA_AUDIO_DSD_UTILS_H #define SPA_AUDIO_DSD_UTILS_H +#include #include #include #include diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index d0a174e85..c36491445 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -6,6 +6,7 @@ #define SPA_AUDIO_RAW_UTILS_H #include +#include #include #include #include diff --git a/spa/include/spa/param/tag-utils.h b/spa/include/spa/param/tag-utils.h index 331def883..7d7fbb092 100644 --- a/spa/include/spa/param/tag-utils.h +++ b/spa/include/spa/param/tag-utils.h @@ -9,6 +9,7 @@ #include #include +#include #include #include diff --git a/spa/include/spa/param/video/dsp-utils.h b/spa/include/spa/param/video/dsp-utils.h index 338b21608..26018cab9 100644 --- a/spa/include/spa/param/video/dsp-utils.h +++ b/spa/include/spa/param/video/dsp-utils.h @@ -5,6 +5,7 @@ #ifndef SPA_VIDEO_DSP_UTILS_H #define SPA_VIDEO_DSP_UTILS_H +#include #include #include #include diff --git a/spa/include/spa/param/video/raw-utils.h b/spa/include/spa/param/video/raw-utils.h index 067cf32e5..738f64cf4 100644 --- a/spa/include/spa/param/video/raw-utils.h +++ b/spa/include/spa/param/video/raw-utils.h @@ -5,6 +5,7 @@ #ifndef SPA_VIDEO_RAW_UTILS_H #define SPA_VIDEO_RAW_UTILS_H +#include #include #include #include diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h new file mode 100644 index 000000000..9ab2ae7e3 --- /dev/null +++ b/spa/include/spa/pod/body.h @@ -0,0 +1,392 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_POD_BODY_H +#define SPA_POD_BODY_H + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SPA_API_POD_BODY + #ifdef SPA_API_IMPL + #define SPA_API_POD_BODY SPA_API_IMPL + #else + #define SPA_API_POD_BODY static inline + #endif +#endif + +/** + * \addtogroup spa_pod + * \{ + */ + +struct spa_pod_frame { + struct spa_pod pod; + struct spa_pod_frame *parent; + uint32_t offset; + uint32_t flags; +}; + +SPA_API_POD_BODY uint32_t spa_pod_type_size(uint32_t type) +{ + switch (type) { + case SPA_TYPE_None: + case SPA_TYPE_Bytes: + case SPA_TYPE_Struct: + case SPA_TYPE_Pod: + return 0; + case SPA_TYPE_String: + return 1; + case SPA_TYPE_Bool: + case SPA_TYPE_Int: + return sizeof(int32_t); + case SPA_TYPE_Id: + return sizeof(uint32_t); + case SPA_TYPE_Long: + return sizeof(int64_t); + case SPA_TYPE_Float: + return sizeof(float); + case SPA_TYPE_Double: + return sizeof(double); + case SPA_TYPE_Rectangle: + return sizeof(struct spa_rectangle); + case SPA_TYPE_Fraction: + return sizeof(struct spa_fraction); + case SPA_TYPE_Bitmap: + return sizeof(uint8_t); + case SPA_TYPE_Array: + return sizeof(struct spa_pod_array_body); + case SPA_TYPE_Object: + return sizeof(struct spa_pod_object_body); + case SPA_TYPE_Sequence: + return sizeof(struct spa_pod_sequence_body); + case SPA_TYPE_Pointer: + return sizeof(struct spa_pod_pointer_body); + case SPA_TYPE_Fd: + return sizeof(int64_t); + case SPA_TYPE_Choice: + return sizeof(struct spa_pod_choice_body); + } + return 0; +} + +SPA_API_POD_BODY int spa_pod_body_from_data(void *data, size_t maxsize, off_t offset, size_t size, + struct spa_pod *pod, const void **body) +{ + if (offset < 0 || offset > (int64_t)UINT32_MAX) + return -EINVAL; + if (size < sizeof(struct spa_pod) || + size > maxsize || + maxsize - size < (uint32_t)offset) + return -EINVAL; + memcpy(pod, SPA_PTROFF(data, offset, void), sizeof(struct spa_pod)); + if (!SPA_POD_IS_VALID(pod)) + return -EINVAL; + if (pod->size > size - sizeof(struct spa_pod)) + return -EINVAL; + *body = SPA_PTROFF(data, offset + sizeof(struct spa_pod), void); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_none(const struct spa_pod *pod) +{ + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_None); +} + +SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); +} + +SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) +{ + if (!spa_pod_is_bool(pod)) + return -EINVAL; + *value = !!*((int32_t*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); +} + +SPA_API_POD_BODY int spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) +{ + if (!spa_pod_is_id(pod)) + return -EINVAL; + *value = *((uint32_t*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); +} + +SPA_API_POD_BODY int spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) +{ + if (!spa_pod_is_int(pod)) + return -EINVAL; + *value = *((int32_t*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); +} + +SPA_API_POD_BODY int spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) +{ + if (!spa_pod_is_long(pod)) + return -EINVAL; + *value = *((int64_t*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); +} + +SPA_API_POD_BODY int spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) +{ + if (!spa_pod_is_float(pod)) + return -EINVAL; + *value = *((float*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); +} + +SPA_API_POD_BODY int spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) +{ + if (!spa_pod_is_double(pod)) + return -EINVAL; + *value = *((double*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); +} + +SPA_API_POD_BODY int spa_pod_body_get_string(const struct spa_pod *pod, + const void *body, const char **value) +{ + const char *s; + if (!spa_pod_is_string(pod)) + return -EINVAL; + s = (const char *)body; + if (s[pod->size-1] != '\0') + return -EINVAL; + *value = s; + return 0; +} + +SPA_API_POD_BODY int spa_pod_body_copy_string(const struct spa_pod *pod, const void *body, + size_t maxlen, char *dest) +{ + const char *s; + if (spa_pod_body_get_string(pod, body, &s) < 0 || maxlen < 1) + return -EINVAL; + strncpy(dest, s, maxlen-1); + dest[maxlen-1]= '\0'; + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_bytes(const struct spa_pod *pod) +{ + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); +} + +SPA_API_POD_BODY int spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, + const void **value, uint32_t *len) +{ + if (!spa_pod_is_bytes(pod)) + return -EINVAL; + *value = (const void *)body; + *len = pod->size; + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); +} + +SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, + uint32_t *type, const void **value) +{ + if (!spa_pod_is_pointer(pod)) + return -EINVAL; + *type = ((struct spa_pod_pointer_body*)body)->type; + *value = ((struct spa_pod_pointer_body*)body)->value; + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); +} + +SPA_API_POD_BODY int spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, + int64_t *value) +{ + if (!spa_pod_is_fd(pod)) + return -EINVAL; + *value = *((int64_t*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); +} + +SPA_API_POD_BODY int spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, + struct spa_rectangle *value) +{ + if (!spa_pod_is_rectangle(pod)) + return -EINVAL; + *value = *((struct spa_rectangle*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_fraction(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); +} +SPA_API_POD_BODY int spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, + struct spa_fraction *value) +{ + if (!spa_pod_is_fraction(pod)) + return -EINVAL; + *value = *((struct spa_fraction*)body); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_bitmap(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); +} + +SPA_API_POD_BODY int spa_pod_is_array(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); +} +SPA_API_POD_BODY int spa_pod_body_get_array(const struct spa_pod *pod, const void *body, + struct spa_pod_array *arr, const void **arr_body) +{ + if (!spa_pod_is_array(pod)) + return -EINVAL; + arr->pod = *pod; + memcpy(&arr->body, body, sizeof(struct spa_pod_array_body)); + *arr_body = SPA_PTROFF(body, sizeof(struct spa_pod_array_body), void); + return 0; +} +SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_array *arr, + const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) +{ + uint32_t child_size = arr->body.child.size; + *n_values = child_size ? (arr->pod.size - sizeof(arr->body)) / child_size : 0; + *val_size = child_size; + *val_type = arr->body.child.type; + return body; +} + +SPA_API_POD_BODY const void *spa_pod_body_get_array_values(const struct spa_pod *pod, + const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) +{ + struct spa_pod_array arr; + if (spa_pod_body_get_array(pod, body, &arr, &body) < 0) + return NULL; + return spa_pod_array_body_get_values(&arr, body, n_values, val_size, val_type); +} + +SPA_API_POD_BODY int spa_pod_is_choice(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); +} +SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, + struct spa_pod_choice *choice, const void **choice_body) +{ + if (!spa_pod_is_choice(pod)) + return -EINVAL; + choice->pod = *pod; + memcpy(&choice->body, body, sizeof(struct spa_pod_choice_body)); + if (choice->pod.size - sizeof(struct spa_pod_choice_body) < choice->body.child.size) + return -EINVAL; + *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); + return 0; +} +SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod_choice *pod, + const void *body, uint32_t *n_values, uint32_t *choice, + uint32_t *val_size, uint32_t *val_type) +{ + uint32_t child_size = pod->body.child.size; + *val_size = child_size; + *val_type = pod->body.child.type; + *n_values = child_size ? (pod->pod.size - sizeof(pod->body)) / child_size : 0; + *choice = pod->body.type; + if (*choice == SPA_CHOICE_None) + *n_values = SPA_MIN(1u, *n_values); + return body; +} + +SPA_API_POD_BODY int spa_pod_is_struct(const struct spa_pod *pod) +{ + return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Struct); +} + +SPA_API_POD_BODY int spa_pod_is_object(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); +} +SPA_API_POD_BODY int spa_pod_body_get_object(const struct spa_pod *pod, const void *body, + struct spa_pod_object *object, const void **object_body) +{ + if (!spa_pod_is_object(pod)) + return -EINVAL; + object->pod = *pod; + memcpy(&object->body, body, sizeof(struct spa_pod_object_body)); + *object_body = SPA_PTROFF(body, sizeof(struct spa_pod_object_body), void); + return 0; +} + +SPA_API_POD_BODY int spa_pod_is_sequence(const struct spa_pod *pod) +{ + return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); +} +SPA_API_POD_BODY int spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, + struct spa_pod_sequence *seq, const void **seq_body) +{ + if (!spa_pod_is_sequence(pod)) + return -EINVAL; + seq->pod = *pod; + memcpy(&seq->body, body, sizeof(struct spa_pod_sequence_body)); + *seq_body = SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), void); + return 0; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_POD_BODY_H */ diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index b30e97b9e..225ca28b1 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include #ifdef __cplusplus diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index cf65b8c62..d8a7c5b4e 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -27,66 +28,6 @@ extern "C" { * \{ */ -struct spa_pod_frame { - struct spa_pod pod; - struct spa_pod_frame *parent; - uint32_t offset; - uint32_t flags; -}; - -#define SPA_POD_IS_VALID(pod) \ - (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE) - -#define SPA_POD_CHECK_TYPE(pod,_type) \ - (SPA_POD_IS_VALID(pod) && \ - (pod)->type == (_type)) - -#define SPA_POD_CHECK(pod,_type,_size) \ - (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) - -SPA_API_POD_ITER uint32_t spa_pod_type_size(uint32_t type) -{ - switch (type) { - case SPA_TYPE_None: - case SPA_TYPE_Bytes: - case SPA_TYPE_Struct: - case SPA_TYPE_Pod: - return 0; - case SPA_TYPE_String: - return 1; - case SPA_TYPE_Bool: - case SPA_TYPE_Int: - return sizeof(int32_t); - case SPA_TYPE_Id: - return sizeof(uint32_t); - case SPA_TYPE_Long: - return sizeof(int64_t); - case SPA_TYPE_Float: - return sizeof(float); - case SPA_TYPE_Double: - return sizeof(double); - case SPA_TYPE_Rectangle: - return sizeof(struct spa_rectangle); - case SPA_TYPE_Fraction: - return sizeof(struct spa_fraction); - case SPA_TYPE_Bitmap: - return sizeof(uint8_t); - case SPA_TYPE_Array: - return sizeof(struct spa_pod_array_body); - case SPA_TYPE_Object: - return sizeof(struct spa_pod_object_body); - case SPA_TYPE_Sequence: - return sizeof(struct spa_pod_sequence_body); - case SPA_TYPE_Pointer: - return sizeof(struct spa_pod_pointer_body); - case SPA_TYPE_Fd: - return sizeof(int64_t); - case SPA_TYPE_Choice: - return sizeof(struct spa_pod_choice_body); - } - return 0; -} - SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; @@ -180,214 +121,82 @@ SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_p SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) { - void *pod; - if (offset < 0 || offset > (int64_t)UINT32_MAX) + struct spa_pod pod; + const void *body; + if (spa_pod_body_from_data(data, maxsize, offset, size, &pod, &body) < 0) return NULL; - if (size < sizeof(struct spa_pod) || - size > maxsize || - maxsize - size < (uint32_t)offset) - return NULL; - pod = SPA_PTROFF(data, offset, void); - if (!SPA_POD_IS_VALID(pod)) - return NULL; - if (SPA_POD_BODY_SIZE(pod) > size - sizeof(struct spa_pod)) - return NULL; - return pod; -} -SPA_API_POD_ITER int spa_pod_is_none(const struct spa_pod *pod) -{ - return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_None); -} - -SPA_API_POD_ITER int spa_pod_is_bool(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); + return SPA_PTROFF(data, offset, void); } SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) { - if (!spa_pod_is_bool(pod)) - return -EINVAL; - *value = !!SPA_POD_VALUE(struct spa_pod_bool, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_id(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); + return spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) { - if (!spa_pod_is_id(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_id, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_int(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); + return spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) { - if (!spa_pod_is_int(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_int, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_long(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); + return spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) { - if (!spa_pod_is_long(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_long, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_float(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); + return spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) { - if (!spa_pod_is_float(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_float, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_double(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); + return spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) { - if (!spa_pod_is_double(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_double, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_string(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); + return spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { - const char *s; - if (!spa_pod_is_string(pod)) - return -EINVAL; - s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); - if (s[pod->size-1] != '\0') - return -EINVAL; - *value = s; - return 0; + return spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { - const char *s; - if (spa_pod_get_string(pod, &s) < 0 || maxlen < 1) - return -EINVAL; - strncpy(dest, s, maxlen-1); - dest[maxlen-1]= '\0'; - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_bytes(const struct spa_pod *pod) -{ - return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); + return spa_pod_body_copy_string(pod, SPA_POD_BODY_CONST(pod), maxlen, dest); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) { - if (!spa_pod_is_bytes(pod)) - return -EINVAL; - *value = (const void *)SPA_POD_CONTENTS(struct spa_pod_bytes, pod); - *len = pod->size; - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_pointer(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); + return spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); } SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) { - if (!spa_pod_is_pointer(pod)) - return -EINVAL; - *type = ((struct spa_pod_pointer*)pod)->body.type; - *value = ((struct spa_pod_pointer*)pod)->body.value; - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_fd(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); + return spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); } SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) { - if (!spa_pod_is_fd(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_fd, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_rectangle(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); + return spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) { - if (!spa_pod_is_rectangle(pod)) - return -EINVAL; - *value = SPA_POD_VALUE(struct spa_pod_rectangle, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_fraction(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); + return spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) { - spa_return_val_if_fail(spa_pod_is_fraction(pod), -EINVAL); - *value = SPA_POD_VALUE(struct spa_pod_fraction, pod); - return 0; -} - -SPA_API_POD_ITER int spa_pod_is_bitmap(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); -} - -SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); + return spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER void *spa_pod_get_array_full(const struct spa_pod *pod, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { - spa_return_val_if_fail(spa_pod_is_array(pod), NULL); - *n_values = SPA_POD_ARRAY_N_VALUES(pod); - *val_size = SPA_POD_ARRAY_VALUE_SIZE(pod); - *val_type = SPA_POD_ARRAY_VALUE_TYPE(pod); - return SPA_POD_ARRAY_VALUES(pod); + return (void*)spa_pod_body_get_array_values(pod, SPA_POD_BODY(pod), n_values, val_size, val_type); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) { @@ -399,7 +208,7 @@ SPA_API_POD_ITER uint32_t spa_pod_copy_array_full(const struct spa_pod *pod, uin uint32_t size, void *values, uint32_t max_values) { uint32_t n_values, val_size, val_type; - void *v = spa_pod_get_array_full(pod, &n_values, &val_size, &val_type); + const void *v = spa_pod_get_array_full(pod, &n_values, &val_size, &val_type); if (v == NULL || max_values == 0 || val_type != type || val_size != size) return 0; n_values = SPA_MIN(n_values, max_values); @@ -410,20 +219,20 @@ SPA_API_POD_ITER uint32_t spa_pod_copy_array_full(const struct spa_pod *pod, uin #define spa_pod_copy_array(pod,type,values,max_values) \ spa_pod_copy_array_full(pod,type,sizeof(values[0]),values,max_values) -SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); -} - SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) { if (spa_pod_is_choice(pod)) { - *n_vals = SPA_POD_CHOICE_N_VALUES(pod); - *choice = SPA_POD_CHOICE_TYPE(pod); - if (*choice == SPA_CHOICE_None) - *n_vals = SPA_MIN(1u, *n_vals); - return (struct spa_pod*)SPA_POD_CHOICE_CHILD(pod); + const struct spa_pod_choice *p = (const struct spa_pod_choice*)pod; + uint32_t type, size; + spa_pod_choice_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, choice, &size, &type); + return (struct spa_pod*)&p->body.child; + } else if (spa_pod_is_array(pod)) { + const struct spa_pod_array *p = (const struct spa_pod_array*)pod; + uint32_t type, size; + spa_pod_array_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, &size, &type); + *choice = SPA_CHOICE_None; + return (struct spa_pod*)&p->body.child; } else { *n_vals = 1; *choice = SPA_CHOICE_None; @@ -431,16 +240,6 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, } } -SPA_API_POD_ITER int spa_pod_is_struct(const struct spa_pod *pod) -{ - return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Struct); -} - -SPA_API_POD_ITER int spa_pod_is_object(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); -} - SPA_API_POD_ITER bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type) { return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_TYPE(pod) == type); @@ -451,11 +250,6 @@ SPA_API_POD_ITER bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t i return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_ID(pod) == id); } -SPA_API_POD_ITER int spa_pod_is_sequence(const struct spa_pod *pod) -{ - return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); -} - SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod, const struct spa_pod_prop *start, uint32_t key) { @@ -535,4 +329,4 @@ SPA_API_POD_ITER int spa_pod_is_fixated(const struct spa_pod *pod) } /* extern "C" */ #endif -#endif /* SPA_POD_H */ +#endif /* SPA_POD_ITER_H */ diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index a76f2fef4..9c26e0f14 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include #ifdef __cplusplus @@ -55,6 +55,22 @@ SPA_API_POD_PARSER void spa_pod_parser_pod(struct spa_pod_parser *parser, spa_pod_parser_init(parser, pod, SPA_POD_SIZE(pod)); } +SPA_API_POD_PARSER void spa_pod_parser_init_pod_body(struct spa_pod_parser *parser, + const struct spa_pod *pod, const void *body) +{ + spa_pod_parser_init(parser, + SPA_PTROFF(body, -sizeof(struct spa_pod), const struct spa_pod), + pod->size + sizeof(struct spa_pod)); +} +SPA_API_POD_PARSER void spa_pod_parser_init_from_data(struct spa_pod_parser *parser, + const void *data, uint32_t maxsize, uint32_t offset, uint32_t size) +{ + size_t offs, sz; + offs = SPA_MIN(offset, maxsize); + sz = SPA_MIN(maxsize - offs, size); + spa_pod_parser_init(parser, SPA_PTROFF(data, offs, void), sz); +} + SPA_API_POD_PARSER void spa_pod_parser_get_state(struct spa_pod_parser *parser, struct spa_pod_parser_state *state) { @@ -67,21 +83,32 @@ spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state parser->state = *state; } +SPA_API_POD_PARSER int +spa_pod_parser_read_header(struct spa_pod_parser *parser, uint32_t offset, uint32_t size, + void *header, uint32_t header_size, uint32_t pod_offset, const void **body) +{ + /* Cast to uint64_t to avoid wraparound. */ + const uint64_t long_offset = (uint64_t)offset + header_size; + if (long_offset <= size && (offset & 7) == 0) { + memcpy(header, SPA_PTROFF(parser->data, offset, void), header_size); + struct spa_pod *pod = SPA_PTROFF(header, pod_offset, struct spa_pod); + /* Check that the size (rounded to the next multiple of 8) is in bounds. */ + if (long_offset + SPA_ROUND_UP_N((uint64_t)pod->size, SPA_POD_ALIGN) <= size) { + *body = SPA_PTROFF(parser->data, long_offset, void); + return 0; + } + } + return -EPIPE; +} + SPA_API_POD_PARSER struct spa_pod * spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size) { - /* Cast to uint64_t to avoid wraparound. Add 8 for the pod itself. */ - const uint64_t long_offset = (uint64_t)offset + 8; - if (long_offset <= size && (offset & 7) == 0) { - /* Use void* because creating a misaligned pointer is undefined. */ - void *pod = SPA_PTROFF(parser->data, offset, void); - /* - * Check that the size (rounded to the next multiple of 8) is in bounds. - */ - if (long_offset + SPA_ROUND_UP_N((uint64_t)SPA_POD_BODY_SIZE(pod), SPA_POD_ALIGN) <= size) - return (struct spa_pod *)pod; - } - return NULL; + struct spa_pod pod; + const void *body; + if (spa_pod_parser_read_header(parser, offset, size, &pod, sizeof(pod), 0, &body) < 0) + return NULL; + return SPA_PTROFF(body, -sizeof(pod), struct spa_pod); } SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) @@ -99,11 +126,28 @@ SPA_API_POD_PARSER void spa_pod_parser_push(struct spa_pod_parser *parser, parser->state.frame = frame; } -SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser) +SPA_API_POD_PARSER int spa_pod_parser_get_header(struct spa_pod_parser *parser, + void *header, uint32_t header_size, uint32_t pod_offset, const void **body) { struct spa_pod_frame *f = parser->state.frame; uint32_t size = f ? f->offset + SPA_POD_SIZE(&f->pod) : parser->size; - return spa_pod_parser_deref(parser, parser->state.offset, size); + return spa_pod_parser_read_header(parser, parser->state.offset, size, + header, header_size, pod_offset, body); +} + +SPA_API_POD_PARSER int spa_pod_parser_current_body(struct spa_pod_parser *parser, + struct spa_pod *pod, const void **body) +{ + return spa_pod_parser_get_header(parser, pod, sizeof(struct spa_pod), 0, body); +} + +SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser) +{ + struct spa_pod pod; + const void *body; + if (spa_pod_parser_current_body(parser, &pod, &body) < 0) + return NULL; + return SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); } SPA_API_POD_PARSER void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod) @@ -111,171 +155,351 @@ SPA_API_POD_PARSER void spa_pod_parser_advance(struct spa_pod_parser *parser, co parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), SPA_POD_ALIGN); } -SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) +SPA_API_POD_PARSER int spa_pod_parser_next_body(struct spa_pod_parser *parser, + struct spa_pod *pod, const void **body) { - struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod) - spa_pod_parser_advance(parser, pod); - return pod; + if (spa_pod_parser_current_body(parser, pod, body) < 0) + return -EINVAL; + spa_pod_parser_advance(parser, pod); + return 0; } -SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, +SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) +{ + struct spa_pod pod; + const void *body; + if (spa_pod_parser_current_body(parser, &pod, &body) < 0) + return NULL; + spa_pod_parser_advance(parser, &pod); + return SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); +} + +SPA_API_POD_PARSER void spa_pod_parser_restart(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { + parser->state.offset = frame->offset; +} + +SPA_API_POD_PARSER void spa_pod_parser_unpush(struct spa_pod_parser *parser, + struct spa_pod_frame *frame) +{ + spa_pod_parser_restart(parser, frame); parser->state.frame = frame->parent; - parser->state.offset = frame->offset + SPA_ROUND_UP_N(SPA_POD_SIZE(&frame->pod), SPA_POD_ALIGN); - return 0; +} + +SPA_API_POD_PARSER void spa_pod_parser_pop(struct spa_pod_parser *parser, + struct spa_pod_frame *frame) +{ + spa_pod_parser_unpush(parser, frame); + spa_pod_parser_advance(parser, &frame->pod); } SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_bool(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_bool(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_id(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_id(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_int(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_int(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_long(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_long(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_float(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_float(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_double(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_double(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_string(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_string(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_bytes(pod, value, len)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_bytes(&pod, body, value, len)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_pointer(pod, type, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_pointer(&pod, body, type, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_fd(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_fd(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_rectangle(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_rectangle(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value) { - int res = -EPIPE; - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod != NULL && (res = spa_pod_get_fraction(pod, value)) >= 0) - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_fraction(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } +SPA_API_POD_PARSER int spa_pod_parser_get_pod_body(struct spa_pod_parser *parser, + struct spa_pod *value, const void **body) +{ + int res; + if ((res = spa_pod_parser_current_body(parser, value, body)) < 0) + return res; + spa_pod_parser_advance(parser, value); + return 0; +} + SPA_API_POD_PARSER int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value) { - struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod == NULL) - return -EPIPE; - *value = pod; - spa_pod_parser_advance(parser, pod); + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_get_pod_body(parser, &pod, &body)) < 0) + return res; + *value = SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); return 0; } -SPA_API_POD_PARSER int spa_pod_parser_push_struct(struct spa_pod_parser *parser, - struct spa_pod_frame *frame) + +SPA_API_POD_PARSER int spa_pod_parser_init_struct_body(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body) { - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod == NULL) - return -EPIPE; if (!spa_pod_is_struct(pod)) return -EINVAL; + spa_pod_parser_init_pod_body(parser, pod, body); spa_pod_parser_push(parser, frame, pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_struct); return 0; } -SPA_API_POD_PARSER int spa_pod_parser_push_object(struct spa_pod_parser *parser, - struct spa_pod_frame *frame, uint32_t type, uint32_t *id) +SPA_API_POD_PARSER int spa_pod_parser_push_struct_body(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, struct spa_pod *str, const void **str_body) { - const struct spa_pod *pod = spa_pod_parser_current(parser); - if (pod == NULL) - return -EPIPE; + int res; + if ((res = spa_pod_parser_current_body(parser, str, str_body)) < 0) + return res; + if (!spa_pod_is_struct(str)) + return -EINVAL; + spa_pod_parser_push(parser, frame, str, parser->state.offset); + parser->state.offset += sizeof(struct spa_pod_struct); + return 0; +} +SPA_API_POD_PARSER int spa_pod_parser_push_struct(struct spa_pod_parser *parser, + struct spa_pod_frame *frame) +{ + struct spa_pod pod; + const void *body; + return spa_pod_parser_push_struct_body(parser, frame, &pod, &body); +} + +SPA_API_POD_PARSER int spa_pod_parser_init_object_body(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body, + struct spa_pod_object *object, const void **object_body) +{ + int res; if (!spa_pod_is_object(pod)) return -EINVAL; - if (type != SPA_POD_OBJECT_TYPE(pod)) - return -EPROTO; - if (id != NULL) - *id = SPA_POD_OBJECT_ID(pod); + spa_pod_parser_init_pod_body(parser, pod, body); + if ((res = spa_pod_body_get_object(pod, body, object, object_body)) < 0) + return res; spa_pod_parser_push(parser, frame, pod, parser->state.offset); - parser->state.offset = parser->size; + parser->state.offset += sizeof(struct spa_pod_object); return 0; } -SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type) +SPA_API_POD_PARSER int spa_pod_parser_push_object_body(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, struct spa_pod_object *object, const void **object_body) { + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_object(&pod, body, object, object_body)) < 0) + return res; + spa_pod_parser_push(parser, frame, &pod, parser->state.offset); + parser->state.offset += sizeof(struct spa_pod_object); + return 0; +} +SPA_API_POD_PARSER int spa_pod_parser_push_object(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, uint32_t type, uint32_t *id) +{ + int res; + struct spa_pod_object obj; + const void *obj_body; + if ((res = spa_pod_parser_push_object_body(parser, frame, &obj, &obj_body)) < 0) + return res; + if (type != obj.body.type) { + spa_pod_parser_unpush(parser, frame); + return -EPROTO; + } + if (id != NULL) + *id = obj.body.id; + return 0; +} +SPA_API_POD_PARSER int spa_pod_parser_get_prop_body(struct spa_pod_parser *parser, + struct spa_pod_prop *prop, const void **body) +{ + int res; + if ((res = spa_pod_parser_get_header(parser, prop, + sizeof(struct spa_pod_prop), + offsetof(struct spa_pod_prop, value), body)) >= 0) + parser->state.offset += SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(prop), SPA_POD_ALIGN); + return res; +} + +SPA_API_POD_PARSER int spa_pod_parser_push_sequence_body(struct spa_pod_parser *parser, + struct spa_pod_frame *frame, struct spa_pod_sequence *seq, const void **seq_body) +{ + int res; + struct spa_pod pod; + const void *body; + if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) + return res; + if ((res = spa_pod_body_get_sequence(&pod, body, seq, seq_body)) < 0) + return res; + spa_pod_parser_push(parser, frame, &pod, parser->state.offset); + parser->state.offset += sizeof(struct spa_pod_sequence); + return 0; +} + +SPA_API_POD_PARSER int spa_pod_parser_get_control_body(struct spa_pod_parser *parser, + struct spa_pod_control *control, const void **body) +{ + int res; + if ((res = spa_pod_parser_get_header(parser, control, + sizeof(struct spa_pod_control), + offsetof(struct spa_pod_control, value), body)) >= 0) + parser->state.offset += SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(control), SPA_POD_ALIGN); + return res; +} + +SPA_API_POD_PARSER int spa_pod_parser_object_find_prop(struct spa_pod_parser *parser, + uint32_t key, struct spa_pod_prop *prop, const void **body) +{ + uint32_t start_offset; + struct spa_pod_frame *f = parser->state.frame; + + if (f == NULL || f->pod.type != SPA_TYPE_Object) + return -EINVAL; + + start_offset = f->offset; + while (spa_pod_parser_get_prop_body(parser, prop, body) >= 0) { + if (prop->key == key) + return 0; + } + spa_pod_parser_restart(parser, f); + parser->state.offset += sizeof(struct spa_pod_object); + while (parser->state.offset != start_offset && + spa_pod_parser_get_prop_body(parser, prop, body) >= 0) { + if (prop->key == key) + return 0; + } + return -ENOENT; +} + +SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *pod, const void *body, char type) +{ + struct spa_pod_choice choice; + if (pod == NULL) return false; @@ -284,12 +508,11 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch return false; if (type == 'V') return true; - if (SPA_POD_CHOICE_TYPE(pod) != SPA_CHOICE_None) + if (spa_pod_body_get_choice(pod, body, &choice, &body) < 0) return false; - if (pod->size - sizeof(struct spa_pod_choice_body) < - SPA_POD_CHOICE_VALUE_SIZE(pod)) + if (choice.body.type != SPA_CHOICE_None) return false; - pod = SPA_POD_CHOICE_CHILD(pod); + pod = &choice.body.child; } switch (type) { @@ -334,72 +557,81 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch return false; } } +SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type) +{ + return spa_pod_parser_body_can_collect(pod, SPA_POD_BODY_CONST(pod), type); +} -#define SPA_POD_PARSER_COLLECT(pod,_type,args) \ +#define SPA_POD_PARSER_COLLECT_BODY(pod,body,_type,args) \ do { \ switch (_type) { \ case 'b': \ - *va_arg(args, bool*) = SPA_POD_VALUE(struct spa_pod_bool, pod); \ + *va_arg(args, bool*) = !!*((int32_t*)(body)); \ break; \ case 'I': \ case 'i': \ - *va_arg(args, int32_t*) = SPA_POD_VALUE(struct spa_pod_int, pod); \ + *va_arg(args, int32_t*) = *((int32_t*)(body)); \ break; \ case 'l': \ - *va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_long, pod); \ + *va_arg(args, int64_t*) = *((int64_t*)(body)); \ break; \ case 'f': \ - *va_arg(args, float*) = SPA_POD_VALUE(struct spa_pod_float, pod); \ + *va_arg(args, float*) = *((float*)(body)); \ break; \ case 'd': \ - *va_arg(args, double*) = SPA_POD_VALUE(struct spa_pod_double, pod); \ + *va_arg(args, double*) = *((double*)(body)); \ break; \ case 's': \ - *va_arg(args, char**) = \ - ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ - ? NULL \ - : (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod)); \ + *va_arg(args, char**) = ((pod)->type == SPA_TYPE_None) ? \ + NULL : (char *)(body); \ break; \ case 'S': \ { \ char *dest = va_arg(args, char*); \ uint32_t maxlen = va_arg(args, uint32_t); \ - strncpy(dest, (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod), maxlen-1); \ + maxlen = SPA_MIN(maxlen, (pod)->size); \ + strncpy(dest, (char *)(body), maxlen-1); \ dest[maxlen-1] = '\0'; \ break; \ } \ case 'y': \ - *(va_arg(args, void **)) = SPA_POD_CONTENTS(struct spa_pod_bytes, pod); \ - *(va_arg(args, uint32_t *)) = SPA_POD_BODY_SIZE(pod); \ + *(va_arg(args, void **)) = (void*)(body); \ + *(va_arg(args, uint32_t *)) = (pod)->size; \ break; \ case 'R': \ *va_arg(args, struct spa_rectangle*) = \ - SPA_POD_VALUE(struct spa_pod_rectangle, pod); \ + *((struct spa_rectangle*)(body)); \ break; \ case 'F': \ *va_arg(args, struct spa_fraction*) = \ - SPA_POD_VALUE(struct spa_pod_fraction, pod); \ + *((struct spa_fraction*)(body)); \ break; \ case 'B': \ - *va_arg(args, uint32_t **) = \ - (uint32_t *) SPA_POD_CONTENTS(struct spa_pod_bitmap, pod); \ + *va_arg(args, uint32_t **) = (uint32_t*)(body); \ break; \ case 'a': \ - *va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_SIZE(pod); \ - *va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_TYPE(pod); \ - *va_arg(args, uint32_t*) = SPA_POD_ARRAY_N_VALUES(pod); \ - *va_arg(args, void**) = SPA_POD_ARRAY_VALUES(pod); \ + { \ + struct spa_pod_array_body *b = \ + (struct spa_pod_array_body*)(body); \ + uint32_t child_size = b->child.size; \ + *va_arg(args, uint32_t*) = child_size; \ + *va_arg(args, uint32_t*) = b->child.type; \ + *va_arg(args, uint32_t*) = child_size ? \ + ((pod)->size - sizeof(struct spa_pod_array_body))/child_size : 0; \ + *va_arg(args, void**) = SPA_PTROFF(b, \ + sizeof(struct spa_pod_array_body), void); \ break; \ + } \ case 'p': \ { \ struct spa_pod_pointer_body *b = \ - (struct spa_pod_pointer_body *) SPA_POD_BODY(pod); \ + (struct spa_pod_pointer_body *)(body); \ *(va_arg(args, uint32_t *)) = b->type; \ *(va_arg(args, const void **)) = b->value; \ break; \ } \ case 'h': \ - *va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_fd, pod); \ + *va_arg(args, int64_t*) = *((int64_t*)(body)); \ break; \ case 'P': \ case 'T': \ @@ -408,8 +640,8 @@ do { \ { \ const struct spa_pod **d = va_arg(args, const struct spa_pod**); \ if (d) \ - *d = ((pod) == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None) \ - ? NULL : (pod)); \ + *d = ((pod)->type == SPA_TYPE_None) ? \ + NULL : SPA_PTROFF((body), -sizeof(struct spa_pod), const struct spa_pod); \ break; \ } \ default: \ @@ -417,6 +649,9 @@ do { \ } \ } while(false) +#define SPA_POD_PARSER_COLLECT(pod,_type,args) \ + SPA_POD_PARSER_COLLECT_BODY(pod, SPA_POD_BODY_CONST(pod),_type,args) + #define SPA_POD_PARSER_SKIP(_type,args) \ do { \ switch (_type) { \ @@ -455,49 +690,58 @@ do { \ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args) { struct spa_pod_frame *f = parser->state.frame; - uint32_t ftype = f ? f->pod.type : (uint32_t)SPA_TYPE_Struct; - const struct spa_pod_prop *prop = NULL; int count = 0; - do { - bool optional; - const struct spa_pod *pod = NULL; - const char *format; + if (f == NULL) + return -EINVAL; - if (f && ftype == SPA_TYPE_Object) { + do { + bool optional, have_pod = false; + struct spa_pod pod; + const void *body = NULL; + const char *format; + struct spa_pod_prop prop; + + if (f->pod.type == SPA_TYPE_Object) { uint32_t key = va_arg(args, uint32_t); - const struct spa_pod_object *object; if (key == 0) break; - object = (const struct spa_pod_object *)spa_pod_parser_frame(parser, f); - prop = spa_pod_object_find_prop(object, prop, key); - pod = prop ? &prop->value : NULL; + if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) { + pod = prop.value; + have_pod = true; + } } if ((format = va_arg(args, char *)) == NULL) break; - if (ftype == SPA_TYPE_Struct) - pod = spa_pod_parser_next(parser); + if (f->pod.type == SPA_TYPE_Struct) + if (spa_pod_parser_next_body(parser, &pod, &body) >= 0) + have_pod = true; if ((optional = (*format == '?'))) format++; - if (!spa_pod_parser_can_collect(pod, *format)) { + if (!have_pod || !spa_pod_parser_body_can_collect(&pod, body, *format)) { if (!optional) { - if (pod == NULL) + if (!have_pod) return -ESRCH; else return -EPROTO; } SPA_POD_PARSER_SKIP(*format, args); } else { - if (pod->type == SPA_TYPE_Choice && *format != 'V') - pod = SPA_POD_CHOICE_CHILD(pod); + struct spa_pod_choice choice; - SPA_POD_PARSER_COLLECT(pod, *format, args); + if (pod.type == SPA_TYPE_Choice && *format != 'V') { + if (spa_pod_body_get_choice(&pod, body, &choice, &body) < 0) + return -EINVAL; + pod = choice.body.child; + } + + SPA_POD_PARSER_COLLECT_BODY(&pod, body, *format, args); count++; } } while (true); diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 4f87427da..3cd1aae49 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -30,6 +30,15 @@ extern "C" { #define SPA_POD_BODY(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),void) #define SPA_POD_BODY_CONST(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),const void) +#define SPA_POD_IS_VALID(pod) \ + (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE) +#define SPA_POD_CHECK_TYPE(pod,_type) \ + (SPA_POD_IS_VALID(pod) && \ + (pod)->type == (_type)) +#define SPA_POD_CHECK(pod,_type,_size) \ + (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) + + struct spa_pod { uint32_t size; /* size of the body */ uint32_t type; /* a basic id of enum spa_type */ diff --git a/spa/lib/lib.c b/spa/lib/lib.c index 648fb82a1..165fc81ba 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -129,6 +129,7 @@ #include #include #include +#include #include #include #include diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index b9c8a7a7b..e6f7ff9d0 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -25,6 +25,7 @@ #include #include +#include #include #include diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 0917a7365..96e99f35e 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -26,7 +26,6 @@ #include #endif -#include #include #include #include diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index bef597a06..9ead5175a 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -3,7 +3,7 @@ /* SPDX-License-Identifier: MIT */ #include -#include +#include #include #include #include diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index 38e34b68d..2115a0899 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include From 37858965335991019ffab20f6d3205a6c7020775 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Jul 2025 15:28:48 +0200 Subject: [PATCH 0641/1014] mixer: rework the control mixers to use the parser Using the parser for the spa_pod_sequence in the data buffers is required in order to safely read the pods while there could be concurrent writes. See #4822 --- pipewire-jack/src/pipewire-jack.c | 111 ++++++++++++++++++------------ spa/plugins/control/mixer.c | 89 +++++++++++++----------- test/test-spa-pod.c | 2 +- 3 files changed, 117 insertions(+), 85 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 511f44606..d513d6403 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -233,6 +233,13 @@ struct buffer { uint32_t n_mem; }; +struct mix_info { + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_control control; + const void *control_body; +}; + struct mix { struct spa_list link; struct spa_list port_link; @@ -247,6 +254,8 @@ struct mix { struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; + struct mix_info mix_info; + unsigned int to_free:1; }; @@ -1492,7 +1501,8 @@ static inline int event_compare(uint8_t s1, uint8_t s2) return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; } -static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) +static inline int event_sort(struct spa_pod_control *a, const void *abody, + struct spa_pod_control *b, const void *bbody) { if (a->offset < b->offset) return -1; @@ -1503,14 +1513,14 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * switch(a->type) { case SPA_CONTROL_Midi: { - uint8_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value); + const uint8_t *sa = abody, *sb = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; return event_compare(sa[0], sb[0]); } case SPA_CONTROL_UMP: { - uint32_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value); + const uint32_t *sa = abody, *sb = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; if ((sa[0] >> 28) != 2 || (sa[0] >> 28) != 4 || @@ -1598,56 +1608,54 @@ static inline int midi_event_write(void *port_buffer, return 0; } -static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix, uint32_t type) +static void convert_to_event(struct mix_info **mix, uint32_t n_mix, void *midi, bool fix, uint32_t type) { - struct spa_pod_control *c[n_seq]; uint64_t state = 0; uint32_t i; int res = 0; bool in_sysex = false; - for (i = 0; i < n_seq; i++) - c[i] = spa_pod_control_first(&seq[i]->body); - while (true) { - struct spa_pod_control *next = NULL; + struct mix_info *next = NULL; uint32_t next_index = 0; + struct spa_pod_control *control; + size_t size; + uint8_t *data; - for (i = 0; i < n_seq; i++) { - if (!spa_pod_control_is_inside(&seq[i]->body, - SPA_POD_BODY_SIZE(seq[i]), c[i])) - continue; - - if (next == NULL || event_sort(c[i], next) <= 0) { - next = c[i]; + for (i = 0; i < n_mix; i++) { + struct mix_info *m = mix[i]; + if (next == NULL || event_sort(&m->control, m->control_body, + &next->control, next->control_body) <= 0) { + next = m; next_index = i; } } if (SPA_UNLIKELY(next == NULL)) break; - switch(next->type) { + control = &next->control; + data = (uint8_t*)next->control_body; + size = SPA_POD_BODY_SIZE(&control->value); + + switch(control->type) { case SPA_CONTROL_OSC: if (!TYPE_ID_CAN_OSC(type)) break; SPA_FALLTHROUGH; case SPA_CONTROL_Midi: { - uint8_t *data = SPA_POD_BODY(&next->value); - size_t size = SPA_POD_BODY_SIZE(&next->value); - if (type == TYPE_ID_UMP) { while (size > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; - if ((res = midi_event_write(midi, next->offset, + if ((res = midi_event_write(midi, control->offset, (uint8_t*)ump, ump_size, false)) < 0) break; } } else { - res = midi_event_write(midi, next->offset, data, size, fix); + res = midi_event_write(midi, control->offset, data, size, fix); } if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, @@ -1656,13 +1664,12 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void } case SPA_CONTROL_UMP: { - void *data = SPA_POD_BODY(&next->value); - size_t size = SPA_POD_BODY_SIZE(&next->value); uint8_t ev[32]; bool was_sysex = in_sysex; if (type == TYPE_ID_MIDI) { - int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev)); + uint32_t *d = (uint32_t*)data; + int ev_size = spa_ump_to_midi(d, size, ev, sizeof(ev)); if (ev_size <= 0) break; @@ -1680,14 +1687,18 @@ static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void if (was_sysex) res = midi_event_append(midi, data, size); else - res = midi_event_write(midi, next->offset, data, size, fix); + res = midi_event_write(midi, control->offset, data, size, fix); if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); } } - c[next_index] = spa_pod_control_next(c[next_index]); + if (spa_pod_parser_get_control_body(&next->parser, + &next->control, &next->control_body) < 0) { + spa_pod_parser_pop(&next->parser, &next->frame); + mix[next_index] = mix[--n_mix]; + } } } @@ -5790,13 +5801,15 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) struct mix *mix; void *ptr = p->emptyptr; struct midi_buffer *mb = (struct midi_buffer*)midi_scratch; - struct spa_pod_sequence *seq[MAX_MIX]; - uint32_t n_seq = 0; + struct mix_info *mix_info[MAX_MIX]; + uint32_t n_mix_info = 0; spa_list_for_each(mix, &p->mix, port_link) { struct spa_data *d; struct buffer *b; - void *pod; + struct mix_info *mi = &mix->mix_info; + struct spa_pod_sequence seq; + const void *seq_body; if (mix->id == SPA_ID_INVALID) continue; @@ -5808,21 +5821,24 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) continue; d = &b->datas[0]; + spa_pod_parser_init_from_data(&mi->parser, d->data, d->maxsize, + d->chunk->offset, d->chunk->size); + if (spa_pod_parser_push_sequence_body(&mi->parser, + &mi->frame, &seq, &seq_body) < 0) + continue; + if (spa_pod_parser_get_control_body(&mi->parser, + &mi->control, &mi->control_body) < 0) + continue; - if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL) - continue; - if (!spa_pod_is_sequence(pod)) - continue; - - seq[n_seq++] = pod; - if (n_seq == MAX_MIX) + mix_info[n_mix_info++] = mi; + if (n_mix_info == MAX_MIX) break; } midi_init_buffer(mb, MIDI_SCRATCH_FRAMES, frames); /* first convert to a thread local scratch buffer, then memcpy into * the per port buffer. This makes it possible to call this function concurrently * but also have different pointers per port */ - convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id); + convert_to_event(mix_info, n_mix_info, mb, p->client->fix_midi_events, p->object->port.type_id); memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); if (mb->write_pos > 0) { @@ -5889,21 +5905,26 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) goto done; if (TYPE_ID_IS_EVENT(o->port.type_id)) { - struct spa_pod_sequence *seq[1]; + struct mix_info *mix_info[1], mi; struct spa_data *d; - void *pod; + struct spa_pod_sequence seq; + const void *seq_body; ptr = midi_scratch; midi_init_buffer(ptr, MIDI_SCRATCH_FRAMES, frames); d = &b->datas[0]; - if ((pod = spa_pod_from_data(d->data, d->maxsize, - d->chunk->offset, d->chunk->size)) == NULL) + spa_pod_parser_init_from_data(&mi.parser, d->data, d->maxsize, + d->chunk->offset, d->chunk->size); + if (spa_pod_parser_push_sequence_body(&mi.parser, + &mi.frame, &seq, &seq_body) < 0) goto done; - if (!spa_pod_is_sequence(pod)) + if (spa_pod_parser_get_control_body(&mi.parser, + &mi.control, &mi.control_body) < 0) goto done; - seq[0] = pod; - convert_to_event(seq, 1, ptr, c->fix_midi_events, o->port.type_id); + + mix_info[0] = &mi; + convert_to_event(mix_info, 1, ptr, c->fix_midi_events, o->port.type_id); } else { ptr = get_buffer_data(b, frames); } diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index c30c13715..b3d3252df 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -61,6 +61,10 @@ struct port { struct spa_list mix_link; bool active; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_control control; + const void *control_body; }; struct impl { @@ -86,15 +90,13 @@ struct impl { struct spa_list port_list; struct spa_list free_list; - struct spa_pod_control *mix_ctrl[MAX_PORTS]; - struct spa_pod_sequence *mix_seq[MAX_PORTS]; - int n_formats; unsigned int have_format:1; unsigned int started:1; struct spa_list mix_list; + struct port *mix_ports[MAX_PORTS]; }; #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) @@ -655,7 +657,8 @@ static inline int event_compare(uint8_t s1, uint8_t s2) return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; } -static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) +static inline int event_sort(struct spa_pod_control *a, const void *abody, + struct spa_pod_control *b, const void *bbody) { if (a->offset < b->offset) return -1; @@ -666,14 +669,14 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * switch(a->type) { case SPA_CONTROL_Midi: { - uint8_t *da = SPA_POD_BODY(&a->value), *db = SPA_POD_BODY(&b->value); + const uint8_t *da = abody, *db = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; return event_compare(da[0], db[0]); } case SPA_CONTROL_UMP: { - uint32_t *da = SPA_POD_BODY(&a->value), *db = SPA_POD_BODY(&b->value); + const uint32_t *da = abody, *db = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; if ((da[0] >> 28) != 2 || (da[0] >> 28) != 4 || @@ -699,10 +702,9 @@ static int impl_node_process(void *object) struct impl *this = object; struct port *outport, *inport; struct spa_io_buffers *outio; - uint32_t n_seq; - struct spa_pod_sequence **seq; - struct spa_pod_control **ctrl; struct spa_pod_builder builder; + uint32_t n_mix_ports; + struct port **mix_ports; struct spa_pod_frame f; struct buffer *outb; struct spa_data *d; @@ -734,14 +736,14 @@ static int impl_node_process(void *object) return -EPIPE; } - ctrl = this->mix_ctrl; - seq = this->mix_seq; - n_seq = 0; + mix_ports = this->mix_ports; + n_mix_ports = 0; /* collect all sequence pod on input ports */ spa_list_for_each(inport, &this->mix_list, mix_link) { struct spa_io_buffers *inio = inport->io[cycle]; - void *pod; + struct spa_pod_sequence seq; + const void *seq_body; if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { @@ -756,22 +758,25 @@ static int impl_node_process(void *object) d = inport->buffers[inio->buffer_id].buffer->datas; - if ((pod = spa_pod_from_data(d->data, d->maxsize, - d->chunk->offset, d->chunk->size)) == NULL) { + spa_pod_parser_init_from_data(&inport->parser, d->data, d->maxsize, + d->chunk->offset, d->chunk->size); + + if (spa_pod_parser_push_sequence_body(&inport->parser, + &inport->frame, &seq, &seq_body) < 0) { spa_log_trace_fp(this->log, "%p: skip input idx:%d max:%u " "offset:%u size:%u", this, inport->id, d->maxsize, d->chunk->offset, d->chunk->size); continue; } - if (!spa_pod_is_sequence(pod)) { - spa_log_trace_fp(this->log, "%p: skip input idx:%d", this, inport->id); + if (spa_pod_parser_get_control_body(&inport->parser, + &inport->control, &inport->control_body) < 0) { + spa_log_trace_fp(this->log, "%p: skip input idx:%d %u", this, inport->id, + inport->parser.state.offset); continue; } - seq[n_seq] = pod; - ctrl[n_seq] = spa_pod_control_first(&seq[n_seq]->body); + mix_ports[n_mix_ports++] = inport; inio->status = SPA_STATUS_NEED_DATA; - n_seq++; } d = outb->buffer->datas; @@ -782,36 +787,38 @@ static int impl_node_process(void *object) /* merge sort all sequences into output buffer */ while (true) { - struct spa_pod_control *next = NULL; uint32_t i, next_index = 0; + size_t size; + uint8_t *body; + struct port *next = NULL; + struct spa_pod_control *control; - for (i = 0; i < n_seq; i++) { - if (!spa_pod_control_is_inside(&seq[i]->body, - SPA_POD_BODY_SIZE(seq[i]), ctrl[i])) - continue; - - if (next == NULL || event_sort(ctrl[i], next) <= 0) { - next = ctrl[i]; + for (i = 0; i < n_mix_ports; i++) { + struct port *p = mix_ports[i]; + if (next == NULL || event_sort(&p->control, p->control_body, + &next->control, next->control_body) <= 0) { + next = p; next_index = i; } } if (next == NULL) break; - if (control_needs_conversion(outport, next->type)) { - uint8_t *data = SPA_POD_BODY(&next->value); - size_t size = SPA_POD_BODY_SIZE(&next->value); + control = &next->control; + body = (uint8_t*)next->control_body; + size = SPA_POD_BODY_SIZE(&control->value); - switch (next->type) { + if (control_needs_conversion(outport, control->type)) { + switch (control->type) { case SPA_CONTROL_Midi: { uint32_t ump[4]; uint64_t state = 0; while (size > 0) { - int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); + int ump_size = spa_ump_from_midi(&body, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; - spa_pod_builder_control(&builder, next->offset, SPA_CONTROL_UMP); + spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&builder, ump, ump_size); } break; @@ -819,20 +826,24 @@ static int impl_node_process(void *object) case SPA_CONTROL_UMP: { uint8_t ev[8]; - int ev_size = spa_ump_to_midi((uint32_t*)data, size, ev, sizeof(ev)); + int ev_size = spa_ump_to_midi((uint32_t*)body, size, ev, sizeof(ev)); if (ev_size <= 0) break; - spa_pod_builder_control(&builder, next->offset, SPA_CONTROL_Midi); + spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_Midi); spa_pod_builder_bytes(&builder, ev, ev_size); break; } } } else { - spa_pod_builder_control(&builder, next->offset, next->type); - spa_pod_builder_primitive(&builder, &next->value); + spa_pod_builder_control(&builder, control->offset, control->type); + spa_pod_builder_primitive_body(&builder, &control->value, body, size, NULL, 0); } - ctrl[next_index] = spa_pod_control_next(ctrl[next_index]); + if (spa_pod_parser_get_control_body(&next->parser, + &next->control, &next->control_body) < 0) { + spa_pod_parser_pop(&next->parser, &next->frame); + mix_ports[next_index] = mix_ports[--n_mix_ports]; + } } spa_pod_builder_pop(&builder, &f); diff --git a/test/test-spa-pod.c b/test/test-spa-pod.c index 5c4308b46..964844f75 100644 --- a/test/test-spa-pod.c +++ b/test/test-spa-pod.c @@ -1270,7 +1270,7 @@ PWTEST(pod_parser) spa_assert_se(spa_pod_parser_push_object(&p, &f, SPA_TYPE_OBJECT_Format, NULL) == -EPROTO); spa_assert_se(p.state.offset == 0); spa_assert_se(spa_pod_parser_push_object(&p, &f, SPA_TYPE_OBJECT_Props, NULL) == 0); - spa_assert_se(p.state.offset == 392); + spa_assert_se(p.state.offset == sizeof(struct spa_pod_object)); spa_assert_se(spa_pod_parser_frame(&p, &f) == val.P); spa_zero(val); From 0cab2fcb75306297fc78fc233261aba878f3990e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 29 Jul 2025 17:01:47 +0200 Subject: [PATCH 0642/1014] pod: don't unpack array values in get_values This is only for choice, everything else stays the pod. --- spa/include/spa/pod/iter.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index d8a7c5b4e..c32a8468a 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -227,12 +227,6 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t type, size; spa_pod_choice_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, choice, &size, &type); return (struct spa_pod*)&p->body.child; - } else if (spa_pod_is_array(pod)) { - const struct spa_pod_array *p = (const struct spa_pod_array*)pod; - uint32_t type, size; - spa_pod_array_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, &size, &type); - *choice = SPA_CHOICE_None; - return (struct spa_pod*)&p->body.child; } else { *n_vals = 1; *choice = SPA_CHOICE_None; From 91ebfac75b4a430541d7f905b4ef278032e17924 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Fri, 25 Jul 2025 16:47:13 +0200 Subject: [PATCH 0643/1014] module-rtp: Clear after reading in direct timestamp mode --- src/modules/module-rtp/audio.c | 42 ++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index e262091c6..3b3de9e08 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -2,6 +2,26 @@ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +static inline void +set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, + uint32_t offset, struct iovec *iov, uint32_t len) +{ + iov[0].iov_len = SPA_MIN(len, size - offset); + iov[0].iov_base = SPA_PTROFF(buffer, offset, void); + iov[1].iov_len = len - iov[0].iov_len; + iov[1].iov_base = buffer; +} + +static void ringbuffer_clear(struct spa_ringbuffer *rbuf SPA_UNUSED, + void *buffer, uint32_t size, + uint32_t offset, uint32_t len) +{ + struct iovec iov[2]; + set_iovec(rbuf, buffer, size, offset, iov, len); + memset(iov[0].iov_base, 0, iov[0].iov_len); + memset(iov[1].iov_base, 0, iov[1].iov_len); +} + static void rtp_audio_process_playback(void *data) { struct impl *impl = data; @@ -98,6 +118,18 @@ static void rtp_audio_process_playback(void *data) (timestamp * stride) & BUFFER_MASK, d[0].data, wanted * stride); + /* Clear the bytes that were just retrieved. Since the fill level + * is not tracked in this buffer mode, it is possible that as soon + * as actual playback ends, the RTP source node re-reads old data. + * Make sure it reads silence when no actual new data is present + * and the RTP source node still runs. Do this by filling the + * region of the retrieved data with null bytes. */ + ringbuffer_clear(&impl->ring, + impl->buffer, + BUFFER_SIZE, + (timestamp * stride) & BUFFER_MASK, + wanted * stride); + if (!impl->io_position) { /* In the unlikely case that no spa_io_position pointer * was passed yet by PipeWire to this node, monotonically @@ -347,16 +379,6 @@ static void set_timer(struct impl *impl, uint64_t time, uint64_t itime) impl->timer_running = time != 0 && itime != 0; } -static inline void -set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, - uint32_t offset, struct iovec *iov, uint32_t len) -{ - iov[0].iov_len = SPA_MIN(len, size - offset); - iov[0].iov_base = SPA_PTROFF(buffer, offset, void); - iov[1].iov_len = len - iov[0].iov_len; - iov[1].iov_base = buffer; -} - static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uint64_t set_timestamp) { int32_t avail, tosend; From 84e8d5978256af63ea15fcd337d4550c8147e274 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 27 Jul 2025 12:28:14 +0300 Subject: [PATCH 0644/1014] resample: fix off-by-one in out_len calculation Fix off-by-one and add test. --- spa/plugins/audioconvert/resample-native.c | 2 +- spa/plugins/audioconvert/test-resample.c | 89 ++++++++++++++++++++-- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index a3c812c28..aab10efb9 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -190,7 +190,7 @@ static uint32_t impl_native_out_len(struct resample *r, uint32_t in_len) struct native_data *data = r->data; uint32_t out_len; - in_len = in_len - SPA_MIN(in_len, (data->n_taps - data->hist) + 1); + in_len = in_len - SPA_MIN(in_len, data->n_taps - data->hist); out_len = (uint32_t)(in_len * data->out_rate - data->phase); out_len = (out_len + data->in_rate - 1) / data->in_rate; diff --git a/spa/plugins/audioconvert/test-resample.c b/spa/plugins/audioconvert/test-resample.c index 1d77a3b23..cc97bd275 100644 --- a/spa/plugins/audioconvert/test-resample.c +++ b/spa/plugins/audioconvert/test-resample.c @@ -69,7 +69,7 @@ static void test_native(void) resample_free(&r); } -static void pull_blocks(struct resample *r, uint32_t first, uint32_t size) +static void pull_blocks(struct resample *r, uint32_t first, uint32_t size, uint32_t count) { uint32_t i; float in[SPA_MAX(size, first) * 2]; @@ -82,7 +82,7 @@ static void pull_blocks(struct resample *r, uint32_t first, uint32_t size) src[0] = in; dst[0] = out; - for (i = 0; i < 500; i++) { + for (i = 0; i < count; i++) { pout_len = out_len = i == 0 ? first : size; pin_len = in_len = resample_in_len(r, out_len); @@ -97,7 +97,46 @@ static void pull_blocks(struct resample *r, uint32_t first, uint32_t size) } } -static void test_in_len(void) +static void pull_blocks_out(struct resample *r, uint32_t first, uint32_t size, uint32_t count) +{ + uint32_t i; + float in[SPA_MAX(size, first) * 2]; + float out[SPA_MAX(size, first) * 2]; + const void *src[1]; + void *dst[1]; + uint32_t in_len, out_len; + uint32_t pin_len, pout_len; + + src[0] = in; + dst[0] = out; + + for (i = 0; i < count; i++) { + pin_len = in_len = i == 0 ? first : size; + pout_len = out_len = resample_out_len(r, in_len); + + resample_process(r, src, &pin_len, dst, &pout_len); + + fprintf(stderr, "%d: %d %d %d %d %d\n", i, + in_len, pin_len, out_len, pout_len, + resample_out_len(r, size)); + + spa_assert_se(in_len == pin_len); + spa_assert_se(out_len == pout_len); + } +} + +static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate) +{ + resample_reset(r); + resample_update_rate(r, rate); + pull_blocks(r, first, size, 500); + + resample_reset(r); + resample_update_rate(r, rate); + pull_blocks_out(r, first, size, 500); +} + +static void test_inout_len(void) { struct resample r; @@ -109,7 +148,7 @@ static void test_in_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - pull_blocks(&r, 1024, 1024); + check_inout_len(&r, 1024, 1024, 1.0); resample_free(&r); spa_zero(r); @@ -120,7 +159,7 @@ static void test_in_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - pull_blocks(&r, 1024, 1024); + check_inout_len(&r, 1024, 1024, 1.0); resample_free(&r); spa_zero(r); @@ -131,7 +170,7 @@ static void test_in_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - pull_blocks(&r, 1024, 1024); + check_inout_len(&r, 1024, 1024, 1.0); resample_free(&r); spa_zero(r); @@ -142,7 +181,41 @@ static void test_in_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - pull_blocks(&r, 513, 64); + check_inout_len(&r, 513, 64, 1.0); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 32000; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + check_inout_len(&r, 513, 64, 1.02); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 32000; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + resample_native_init(&r); + + check_inout_len(&r, 513, 64, 1.0002); + resample_free(&r); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 32000; + r.o_rate = 48000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = RESAMPLE_OPTION_PREFILL; + resample_native_init(&r); + + check_inout_len(&r, 513, 64, 1.0002); resample_free(&r); } @@ -151,7 +224,7 @@ int main(int argc, char *argv[]) logger.log.level = SPA_LOG_LEVEL_TRACE; test_native(); - test_in_len(); + test_inout_len(); return 0; } From 3cade43cf36b5bfdf176a93fcfcadf79313eed05 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 27 Jul 2025 13:18:08 +0300 Subject: [PATCH 0645/1014] test-resample: add test for floating point rounding producing bad in_len If phase is float, calculations in impl_native_in_len/out_len don't necessarily match with do_resample, because e.g. float phase0 = 7999.99; float phase = phase0; int frac = 8000, out_rate = 8000, n = 64, count = 0; for (int j = 0; j < n; ++j) { phase += frac; if (phase >= out_rate) { phase -= out_rate; count++; } } printf("count = %d\n", count); /* count = 64 */ count = (int)(phase0 + n*frac) / out_rate; printf("count = %d\n", count); /* count = 65 */ don't give the same result. Also add test where floating point multiplication rounding up to nearest in float ph = phase * pm; uint32_t offset = (uint32_t)floorf(ph); computation results to offset+1 > data->n_phases, accessing filter array beyond bounds. (The accessed value is still inside allocated memory block, but contains unrelated values; the test passes silently.) --- spa/plugins/audioconvert/test-resample.c | 53 ++++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/spa/plugins/audioconvert/test-resample.c b/spa/plugins/audioconvert/test-resample.c index cc97bd275..0848d8f80 100644 --- a/spa/plugins/audioconvert/test-resample.c +++ b/spa/plugins/audioconvert/test-resample.c @@ -15,6 +15,7 @@ SPA_LOG_IMPL(logger); #include "resample.h" +#include "resample-native-impl.h" #define N_SAMPLES 253 #define N_CHANNELS 11 @@ -125,14 +126,20 @@ static void pull_blocks_out(struct resample *r, uint32_t first, uint32_t size, u } } -static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate) +static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate, double phase) { + struct native_data *data = r->data; + resample_reset(r); resample_update_rate(r, rate); + if (phase != 0.0) + data->phase = (float)phase; pull_blocks(r, first, size, 500); resample_reset(r); resample_update_rate(r, rate); + if (phase != 0.0) + data->phase = (float)phase; pull_blocks_out(r, first, size, 500); } @@ -148,7 +155,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 1024, 1024, 1.0); + check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); @@ -159,7 +166,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 1024, 1024, 1.0); + check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); @@ -170,7 +177,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 1024, 1024, 1.0); + check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); @@ -181,7 +188,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 513, 64, 1.0); + check_inout_len(&r, 513, 64, 1.0, 0); resample_free(&r); spa_zero(r); @@ -192,7 +199,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 513, 64, 1.02); + check_inout_len(&r, 513, 64, 1.02, 0); resample_free(&r); spa_zero(r); @@ -203,7 +210,7 @@ static void test_inout_len(void) r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); - check_inout_len(&r, 513, 64, 1.0002); + check_inout_len(&r, 513, 64, 1.0002, 0); resample_free(&r); spa_zero(r); @@ -215,7 +222,37 @@ static void test_inout_len(void) r.options = RESAMPLE_OPTION_PREFILL; resample_native_init(&r); - check_inout_len(&r, 513, 64, 1.0002); + check_inout_len(&r, 513, 64, 1.0002, 0); + resample_free(&r); + + /* Test value of phase that in floating-point arithmetic produces + * inconsistent in_len + */ + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 8000; + r.o_rate = 8000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = RESAMPLE_OPTION_PREFILL; + resample_native_init(&r); + + check_inout_len(&r, 64, 64, 1.0 + 1e-10, 7999.99); + resample_free(&r); + + /* Test value of phase that overflows filter buffer due to floating point rounding + * up to nearest + */ + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 8000; + r.o_rate = 8000; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = RESAMPLE_OPTION_PREFILL; + resample_native_init(&r); + + check_inout_len(&r, 64, 64, 1.0 + 1e-10, nextafterf(8000, 0)); resample_free(&r); } From 244d5a1cc1460d36dbd4c9b76d6ae49a5a4de0fb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 26 Jul 2025 17:23:44 +0300 Subject: [PATCH 0646/1014] resample: use fixed point for resample phase and input rate If phase is float, calculations in impl_native_in_len/out_len can produce wrong results due to rounding error. It's probably better to not be in the business of predicting floating-point rounding, so replace this by fixed-point arithmetic. Also make sure `offset+1` cannot overflow data->filter array in do_resample_inter* due to float multiplication possibly rounding up. --- .../audioconvert/resample-native-impl.h | 45 ++++++++++++------- spa/plugins/audioconvert/resample-native.c | 40 +++++++++++------ spa/plugins/audioconvert/test-resample.c | 8 ++-- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index 023b350d7..534f36da6 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -12,6 +12,18 @@ typedef void (*resample_func_t)(struct resample *r, const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len); +#define FIXP_SHIFT 32 +#define FIXP_SCALE ((uint64_t)1 << FIXP_SHIFT) +#define FIXP_MASK (FIXP_SCALE - 1) +#define UINT32_TO_FIXP(v) ((struct fixp) { (uint64_t)((uint32_t)(v)) << FIXP_SHIFT }) +#define FLOAT_TO_FIXP(d) ((struct fixp) { (uint64_t)((d) * (float)FIXP_SCALE) }) +#define FIXP_TO_UINT32(f) ((f).value >> FIXP_SHIFT) +#define FIXP_TO_FLOAT(f) ((f).value / (float)FIXP_SCALE) + +struct fixp { + uint64_t value; +}; + struct resample_info { uint32_t format; resample_func_t process_copy; @@ -29,10 +41,10 @@ struct native_data { uint32_t n_phases; uint32_t in_rate; uint32_t out_rate; - float phase; + struct fixp phase; float pm; uint32_t inc; - uint32_t frac; + struct fixp frac; uint32_t filter_stride; uint32_t filter_stride_os; uint32_t gcd; @@ -86,25 +98,26 @@ DEFINE_RESAMPLER(full,arch) \ { \ struct native_data *data = r->data; \ uint32_t n_taps = data->n_taps, stride = data->filter_stride_os; \ - uint32_t index, phase, out_rate = data->out_rate; \ + uint32_t index; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ - uint32_t inc = data->inc, frac = data->frac, ch = r->channels; \ + uint32_t inc = data->inc, ch = r->channels; \ + uint64_t frac = data->frac.value, phase = data->phase.value; \ + uint64_t denom = UINT32_TO_FIXP(data->out_rate).value; \ \ index = ioffs; \ - phase = (uint32_t)data->phase; \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ - float *filter = &data->filter[phase * stride]; \ + float *filter = &data->filter[(phase >> FIXP_SHIFT) * stride]; \ for (c = 0; c < ch; c++) { \ const float *s = src[c]; \ float *d = dst[c]; \ inner_product_##arch(&d[o], &s[index], \ filter, n_taps); \ } \ - INC(index, phase, out_rate); \ + INC(index, phase, denom); \ } \ *in_len = index; \ *out_len = o; \ - data->phase = phase; \ + data->phase.value = phase; \ } #define MAKE_RESAMPLER_INTER(arch) \ @@ -112,16 +125,18 @@ DEFINE_RESAMPLER(inter,arch) \ { \ struct native_data *data = r->data; \ uint32_t index, stride = data->filter_stride; \ - uint32_t n_taps = data->n_taps, out_rate = data->out_rate; \ + uint32_t n_taps = data->n_taps; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ - uint32_t inc = data->inc, frac = data->frac, ch = r->channels; \ - float phase, pm = data->pm; \ + uint32_t inc = data->inc, ch = r->channels; \ + uint32_t ph_max = data->n_phases - 1; \ + uint64_t frac = data->frac.value, phase = data->phase.value; \ + uint64_t denom = UINT32_TO_FIXP(data->out_rate).value; \ + float pm = data->pm; \ \ index = ioffs; \ - phase = data->phase; \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ float ph = phase * pm; \ - uint32_t offset = (uint32_t)floorf(ph); \ + uint32_t offset = SPA_MIN((uint32_t)floorf(ph), ph_max); \ float *filter0 = &data->filter[(offset+0) * stride]; \ float *filter1 = &data->filter[(offset+1) * stride]; \ float pho = ph - offset; \ @@ -131,11 +146,11 @@ DEFINE_RESAMPLER(inter,arch) \ inner_product_ip_##arch(&d[o], &s[index], \ filter0, filter1, pho, n_taps); \ } \ - INC(index, phase, out_rate); \ + INC(index, phase, denom); \ } \ *in_len = index; \ *out_len = o; \ - data->phase = phase; \ + data->phase.value = phase; \ } diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index aab10efb9..d544b4cea 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -159,17 +159,31 @@ static void impl_native_update_rate(struct resample *r, double rate) data->func = data->info->process_full; } - data->in_rate = in_rate; if (data->out_rate != out_rate) { - data->phase = data->phase * out_rate / (float)data->out_rate; - data->out_rate = out_rate; + /* Cast to double to avoid overflows */ + data->phase.value = (uint64_t)(data->phase.value * (double)out_rate / data->out_rate); + if (data->phase.value >= UINT32_TO_FIXP(out_rate).value) + data->phase.value = UINT32_TO_FIXP(out_rate).value - 1; } - data->inc = data->in_rate / data->out_rate; - data->frac = data->in_rate % data->out_rate; - spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%f inc:%d frac:%d", r, - rate, r->i_rate, r->o_rate, data->phase, data->inc, data->frac); + data->in_rate = in_rate; + data->out_rate = out_rate; + data->inc = in_rate / out_rate; + data->frac = UINT32_TO_FIXP(in_rate % out_rate); + + spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%f inc:%d frac:%f", r, + rate, r->i_rate, r->o_rate, FIXP_TO_FLOAT(data->phase), + data->inc, FIXP_TO_FLOAT(data->frac)); +} + +static uint64_t fixp_floor_a_plus_bc(struct fixp a, uint32_t b, struct fixp c) +{ + /* (a + b*c) >> FIXP_SHIFT, with bigger overflow threshold */ + uint64_t hi, lo; + hi = (a.value >> FIXP_SHIFT) + b * (c.value >> FIXP_SHIFT); + lo = (a.value & FIXP_MASK) + b * (c.value & FIXP_MASK); + return hi + (lo >> FIXP_SHIFT); } static uint32_t impl_native_in_len(struct resample *r, uint32_t out_len) @@ -177,7 +191,7 @@ static uint32_t impl_native_in_len(struct resample *r, uint32_t out_len) struct native_data *data = r->data; uint32_t in_len; - in_len = (uint32_t)((data->phase + out_len * data->frac) / data->out_rate); + in_len = fixp_floor_a_plus_bc(data->phase, out_len, data->frac) / data->out_rate; in_len += out_len * data->inc + (data->n_taps - data->hist); spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, out_len, in_len); @@ -191,7 +205,7 @@ static uint32_t impl_native_out_len(struct resample *r, uint32_t in_len) uint32_t out_len; in_len = in_len - SPA_MIN(in_len, data->n_taps - data->hist); - out_len = (uint32_t)(in_len * data->out_rate - data->phase); + out_len = in_len * data->out_rate - FIXP_TO_UINT32(data->phase); out_len = (out_len + data->in_rate - 1) / data->in_rate; spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, in_len, out_len); @@ -300,7 +314,7 @@ static void impl_native_reset (struct resample *r) d->hist = d->n_taps - 1; else d->hist = d->n_taps / 2; - d->phase = 0; + d->phase.value = 0; } static uint32_t impl_native_delay (struct resample *r) @@ -315,13 +329,13 @@ static float impl_native_phase (struct resample *r) float pho = 0; if (d->func == d->info->process_full) { - pho = -(float)((int32_t)d->phase) / d->out_rate; + pho = -(float)FIXP_TO_UINT32(d->phase) / d->out_rate; /* XXX: this is how it seems to behave, but not clear why */ if (d->hist >= d->n_taps - 1) pho += 1.0f; } else if (d->func == d->info->process_inter) { - pho = -d->phase / d->out_rate; + pho = -FIXP_TO_FLOAT(d->phase) / d->out_rate; /* XXX: this is how it seems to behave, but not clear why */ if (d->hist >= d->n_taps - 1) @@ -388,7 +402,7 @@ int resample_native_init(struct resample *r) d->in_rate = in_rate; d->out_rate = out_rate; d->gcd = gcd; - d->pm = (float)n_phases / r->o_rate; + d->pm = (float)n_phases / r->o_rate / FIXP_SCALE; d->filter = SPA_PTROFF_ALIGN(d, sizeof(struct native_data), 64, float); d->hist_mem = SPA_PTROFF_ALIGN(d->filter, filter_size, 64, float); d->history = SPA_PTROFF(d->hist_mem, history_size, float*); diff --git a/spa/plugins/audioconvert/test-resample.c b/spa/plugins/audioconvert/test-resample.c index 0848d8f80..8c0a7de86 100644 --- a/spa/plugins/audioconvert/test-resample.c +++ b/spa/plugins/audioconvert/test-resample.c @@ -126,20 +126,20 @@ static void pull_blocks_out(struct resample *r, uint32_t first, uint32_t size, u } } -static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate, double phase) +static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate, float phase) { struct native_data *data = r->data; resample_reset(r); resample_update_rate(r, rate); if (phase != 0.0) - data->phase = (float)phase; + data->phase = FLOAT_TO_FIXP(phase); pull_blocks(r, first, size, 500); resample_reset(r); resample_update_rate(r, rate); if (phase != 0.0) - data->phase = (float)phase; + data->phase = FLOAT_TO_FIXP(phase); pull_blocks_out(r, first, size, 500); } @@ -237,7 +237,7 @@ static void test_inout_len(void) r.options = RESAMPLE_OPTION_PREFILL; resample_native_init(&r); - check_inout_len(&r, 64, 64, 1.0 + 1e-10, 7999.99); + check_inout_len(&r, 64, 64, 1.0 + 1e-10, 7999.99f); resample_free(&r); /* Test value of phase that overflows filter buffer due to floating point rounding From 847982eb0ed88af43332ebc0847f1624ee31402c Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 27 Jul 2025 01:49:34 +0300 Subject: [PATCH 0647/1014] resample: keep fractional part of in_rate when interpolating When interpolating with rate correction != 1.0, don't floor the resulting input rate to the nearest smallest integer. This allows rate corrections < 1/in_rate to have some effect, and reduces jumps in the response. One of the jumps is inconveniently between rate=1.0 and rate=1.0+eps and will cause rate corrections to oscillate if target rate varies close to 1.0. --- .../audioconvert/resample-native-impl.h | 2 +- spa/plugins/audioconvert/resample-native.c | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index 534f36da6..5ae2d861a 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -39,7 +39,7 @@ struct native_data { double rate; uint32_t n_taps; uint32_t n_phases; - uint32_t in_rate; + struct fixp in_rate; uint32_t out_rate; struct fixp phase; float pm; diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index d544b4cea..4996aac95 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -137,24 +137,25 @@ static inline uint32_t calc_gcd(uint32_t a, uint32_t b) static void impl_native_update_rate(struct resample *r, double rate) { struct native_data *data = r->data; - uint32_t in_rate, out_rate; + struct fixp in_rate; + uint32_t out_rate; if (SPA_LIKELY(data->rate == rate)) return; data->rate = rate; - in_rate = r->i_rate; + in_rate = UINT32_TO_FIXP(r->i_rate); out_rate = r->o_rate; if (rate != 1.0) { - in_rate = (uint32_t)(in_rate / rate); + in_rate.value = (uint64_t)round(in_rate.value / rate); data->func = data->info->process_inter; } - else if (in_rate == out_rate) { + else if (in_rate.value == UINT32_TO_FIXP(out_rate).value) { data->func = data->info->process_copy; } else { - in_rate /= data->gcd; + in_rate.value /= data->gcd; out_rate /= data->gcd; data->func = data->info->process_full; } @@ -169,8 +170,8 @@ static void impl_native_update_rate(struct resample *r, double rate) data->in_rate = in_rate; data->out_rate = out_rate; - data->inc = in_rate / out_rate; - data->frac = UINT32_TO_FIXP(in_rate % out_rate); + data->inc = in_rate.value / UINT32_TO_FIXP(out_rate).value; + data->frac.value = in_rate.value % UINT32_TO_FIXP(out_rate).value; spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%f inc:%d frac:%f", r, rate, r->i_rate, r->o_rate, FIXP_TO_FLOAT(data->phase), @@ -206,7 +207,7 @@ static uint32_t impl_native_out_len(struct resample *r, uint32_t in_len) in_len = in_len - SPA_MIN(in_len, data->n_taps - data->hist); out_len = in_len * data->out_rate - FIXP_TO_UINT32(data->phase); - out_len = (out_len + data->in_rate - 1) / data->in_rate; + out_len = (UINT32_TO_FIXP(out_len).value + data->in_rate.value - 1) / data->in_rate.value; spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, in_len, out_len); @@ -399,7 +400,7 @@ int resample_native_init(struct resample *r) r->data = d; d->n_taps = n_taps; d->n_phases = n_phases; - d->in_rate = in_rate; + d->in_rate = UINT32_TO_FIXP(in_rate); d->out_rate = out_rate; d->gcd = gcd; d->pm = (float)n_phases / r->o_rate / FIXP_SCALE; From 2c11f65701c814605a60c0f587ac5d2ec30ae099 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 11:02:00 +0200 Subject: [PATCH 0648/1014] pod: avoid child size check This will give us 0 children later and is not a problem for this function. --- spa/include/spa/pod/body.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index 9ab2ae7e3..2da4122e2 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -327,8 +327,6 @@ SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const vo return -EINVAL; choice->pod = *pod; memcpy(&choice->body, body, sizeof(struct spa_pod_choice_body)); - if (choice->pod.size - sizeof(struct spa_pod_choice_body) < choice->body.child.size) - return -EINVAL; *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); return 0; } From f7ae61cb1ea16055bc45ec036a00ae66f66f3675 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 12:56:39 +0200 Subject: [PATCH 0649/1014] pod: remove checks from spa_pod_body_get_*() The pattern is _is_foo() and then _get_foo(). This allows us to reuse some of the get code in the parser without doing checks twice. --- spa/include/spa/pod/body.h | 98 ++++++--------------- spa/include/spa/pod/iter.h | 66 ++++++++++++--- spa/include/spa/pod/parser.h | 160 ++++++++++++++++++++--------------- 3 files changed, 174 insertions(+), 150 deletions(-) diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index 2da4122e2..1678ce039 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -105,12 +105,9 @@ SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } -SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) +SPA_API_POD_BODY void spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) { - if (!spa_pod_is_bool(pod)) - return -EINVAL; *value = !!*((int32_t*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) @@ -118,12 +115,9 @@ SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); } -SPA_API_POD_BODY int spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) +SPA_API_POD_BODY void spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) { - if (!spa_pod_is_id(pod)) - return -EINVAL; *value = *((uint32_t*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) @@ -131,12 +125,9 @@ SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); } -SPA_API_POD_BODY int spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) +SPA_API_POD_BODY void spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) { - if (!spa_pod_is_int(pod)) - return -EINVAL; *value = *((int32_t*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) @@ -144,12 +135,9 @@ SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); } -SPA_API_POD_BODY int spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) +SPA_API_POD_BODY void spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) { - if (!spa_pod_is_long(pod)) - return -EINVAL; *value = *((int64_t*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) @@ -157,12 +145,9 @@ SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); } -SPA_API_POD_BODY int spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) +SPA_API_POD_BODY void spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) { - if (!spa_pod_is_float(pod)) - return -EINVAL; *value = *((float*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) @@ -170,12 +155,9 @@ SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); } -SPA_API_POD_BODY int spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) +SPA_API_POD_BODY void spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) { - if (!spa_pod_is_double(pod)) - return -EINVAL; *value = *((double*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) @@ -183,24 +165,21 @@ SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); } -SPA_API_POD_BODY int spa_pod_body_get_string(const struct spa_pod *pod, +SPA_API_POD_BODY void spa_pod_body_get_string(const struct spa_pod *pod, const void *body, const char **value) { - const char *s; - if (!spa_pod_is_string(pod)) - return -EINVAL; - s = (const char *)body; + const char *s = (const char *)body; if (s[pod->size-1] != '\0') - return -EINVAL; + s = NULL; *value = s; - return 0; } SPA_API_POD_BODY int spa_pod_body_copy_string(const struct spa_pod *pod, const void *body, - size_t maxlen, char *dest) + char *dest, size_t maxlen) { const char *s; - if (spa_pod_body_get_string(pod, body, &s) < 0 || maxlen < 1) + spa_pod_body_get_string(pod, body, &s); + if (s == NULL || maxlen < 1) return -EINVAL; strncpy(dest, s, maxlen-1); dest[maxlen-1]= '\0'; @@ -212,14 +191,11 @@ SPA_API_POD_BODY int spa_pod_is_bytes(const struct spa_pod *pod) return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); } -SPA_API_POD_BODY int spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, const void **value, uint32_t *len) { - if (!spa_pod_is_bytes(pod)) - return -EINVAL; *value = (const void *)body; *len = pod->size; - return 0; } SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) @@ -227,14 +203,11 @@ SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); } -SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, uint32_t *type, const void **value) { - if (!spa_pod_is_pointer(pod)) - return -EINVAL; *type = ((struct spa_pod_pointer_body*)body)->type; *value = ((struct spa_pod_pointer_body*)body)->value; - return 0; } SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) @@ -242,13 +215,10 @@ SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); } -SPA_API_POD_BODY int spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, int64_t *value) { - if (!spa_pod_is_fd(pod)) - return -EINVAL; *value = *((int64_t*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) @@ -256,46 +226,42 @@ SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); } -SPA_API_POD_BODY int spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, struct spa_rectangle *value) { - if (!spa_pod_is_rectangle(pod)) - return -EINVAL; *value = *((struct spa_rectangle*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_fraction(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); } -SPA_API_POD_BODY int spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, struct spa_fraction *value) { - if (!spa_pod_is_fraction(pod)) - return -EINVAL; *value = *((struct spa_fraction*)body); - return 0; } SPA_API_POD_BODY int spa_pod_is_bitmap(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); } +SPA_API_POD_BODY void spa_pod_body_get_bitmap(const struct spa_pod *pod, const void *body, + const uint8_t **value) +{ + *value = (const uint8_t *)body; +} SPA_API_POD_BODY int spa_pod_is_array(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } -SPA_API_POD_BODY int spa_pod_body_get_array(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_array(const struct spa_pod *pod, const void *body, struct spa_pod_array *arr, const void **arr_body) { - if (!spa_pod_is_array(pod)) - return -EINVAL; arr->pod = *pod; memcpy(&arr->body, body, sizeof(struct spa_pod_array_body)); *arr_body = SPA_PTROFF(body, sizeof(struct spa_pod_array_body), void); - return 0; } SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_array *arr, const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) @@ -311,8 +277,7 @@ SPA_API_POD_BODY const void *spa_pod_body_get_array_values(const struct spa_pod const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { struct spa_pod_array arr; - if (spa_pod_body_get_array(pod, body, &arr, &body) < 0) - return NULL; + spa_pod_body_get_array(pod, body, &arr, &body); return spa_pod_array_body_get_values(&arr, body, n_values, val_size, val_type); } @@ -320,15 +285,12 @@ SPA_API_POD_BODY int spa_pod_is_choice(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); } -SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, struct spa_pod_choice *choice, const void **choice_body) { - if (!spa_pod_is_choice(pod)) - return -EINVAL; choice->pod = *pod; memcpy(&choice->body, body, sizeof(struct spa_pod_choice_body)); *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); - return 0; } SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod_choice *pod, const void *body, uint32_t *n_values, uint32_t *choice, @@ -353,30 +315,24 @@ SPA_API_POD_BODY int spa_pod_is_object(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); } -SPA_API_POD_BODY int spa_pod_body_get_object(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_object(const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { - if (!spa_pod_is_object(pod)) - return -EINVAL; object->pod = *pod; memcpy(&object->body, body, sizeof(struct spa_pod_object_body)); *object_body = SPA_PTROFF(body, sizeof(struct spa_pod_object_body), void); - return 0; } SPA_API_POD_BODY int spa_pod_is_sequence(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); } -SPA_API_POD_BODY int spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY void spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, struct spa_pod_sequence *seq, const void **seq_body) { - if (!spa_pod_is_sequence(pod)) - return -EINVAL; seq->pod = *pod; memcpy(&seq->body, body, sizeof(struct spa_pod_sequence_body)); *seq_body = SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), void); - return 0; } /** diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index c32a8468a..452b447c8 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -130,72 +130,112 @@ SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offse SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) { - return spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_bool(pod)) + return -EINVAL; + spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) { - return spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_id(pod)) + return -EINVAL; + spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) { - return spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_int(pod)) + return -EINVAL; + spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) { - return spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_long(pod)) + return -EINVAL; + spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) { - return spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_float(pod)) + return -EINVAL; + spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) { - return spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_double(pod)) + return -EINVAL; + spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { - return spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_string(pod)) + return -EINVAL; + spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); + return *value ? 0 : -EINVAL; } SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { - return spa_pod_body_copy_string(pod, SPA_POD_BODY_CONST(pod), maxlen, dest); + if (!spa_pod_is_string(pod)) + return -EINVAL; + return spa_pod_body_copy_string(pod, SPA_POD_BODY_CONST(pod), dest, maxlen); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) { - return spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); + if (!spa_pod_is_bytes(pod)) + return -EINVAL; + spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); + return 0; } SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) { - return spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); + if (!spa_pod_is_pointer(pod)) + return -EINVAL; + spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); + return 0; } SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) { - return spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_fd(pod)) + return -EINVAL; + spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) { - return spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_rectangle(pod)) + return -EINVAL; + spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) { - return spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); + if (!spa_pod_is_fraction(pod)) + return -EINVAL; + spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); + return 0; } SPA_API_POD_ITER void *spa_pod_get_array_full(const struct spa_pod *pod, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { + if (!spa_pod_is_array(pod)) + return NULL; return (void*)spa_pod_body_get_array_values(pod, SPA_POD_BODY(pod), n_values, val_size, val_type); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 9c26e0f14..cb60a1d62 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -201,8 +201,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bo const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_bool(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_bool(&pod)) + return -EINVAL; + spa_pod_body_get_bool(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -213,8 +215,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_id(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_id(&pod)) + return -EINVAL; + spa_pod_body_get_id(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -225,8 +229,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_int(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_int(&pod)) + return -EINVAL; + spa_pod_body_get_int(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -237,8 +243,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_long(struct spa_pod_parser *parser, in const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_long(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_long(&pod)) + return -EINVAL; + spa_pod_body_get_long(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -249,8 +257,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_float(struct spa_pod_parser *parser, f const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_float(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_float(&pod)) + return -EINVAL; + spa_pod_body_get_float(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -261,8 +271,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_double(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_double(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_double(&pod)) + return -EINVAL; + spa_pod_body_get_double(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -273,8 +285,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_string(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_string(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_string(&pod)) + return -EINVAL; + spa_pod_body_get_string(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -285,8 +299,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, c const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_bytes(&pod, body, value, len)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_bytes(&pod)) + return -EINVAL; + spa_pod_body_get_bytes(&pod, body, value, len); + spa_pod_parser_advance(parser, &pod); return res; } @@ -297,8 +313,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_pointer(&pod, body, type, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_pointer(&pod)) + return -EINVAL; + spa_pod_body_get_pointer(&pod, body, type, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -309,8 +327,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int6 const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_fd(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_fd(&pod)) + return -EINVAL; + spa_pod_body_get_fd(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -321,8 +341,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_rectangle(struct spa_pod_parser *parse const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_rectangle(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_rectangle(&pod)) + return -EINVAL; + spa_pod_body_get_rectangle(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -333,8 +355,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get_fraction(struct spa_pod_parser *parser const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_fraction(&pod, body, value)) >= 0) - spa_pod_parser_advance(parser, &pod); + if (!spa_pod_is_fraction(&pod)) + return -EINVAL; + spa_pod_body_get_fraction(&pod, body, value); + spa_pod_parser_advance(parser, &pod); return res; } @@ -394,12 +418,10 @@ SPA_API_POD_PARSER int spa_pod_parser_init_object_body(struct spa_pod_parser *pa struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { - int res; if (!spa_pod_is_object(pod)) return -EINVAL; spa_pod_parser_init_pod_body(parser, pod, body); - if ((res = spa_pod_body_get_object(pod, body, object, object_body)) < 0) - return res; + spa_pod_body_get_object(pod, body, object, object_body); spa_pod_parser_push(parser, frame, pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; @@ -413,8 +435,9 @@ SPA_API_POD_PARSER int spa_pod_parser_push_object_body(struct spa_pod_parser *pa const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_object(&pod, body, object, object_body)) < 0) - return res; + if (!spa_pod_is_object(&pod)) + return -EINVAL; + spa_pod_body_get_object(&pod, body, object, object_body); spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; @@ -454,8 +477,9 @@ SPA_API_POD_PARSER int spa_pod_parser_push_sequence_body(struct spa_pod_parser * const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if ((res = spa_pod_body_get_sequence(&pod, body, seq, seq_body)) < 0) - return res; + if (!spa_pod_is_sequence(&pod)) + return -EINVAL; + spa_pod_body_get_sequence(&pod, body, seq, seq_body); spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_sequence); return 0; @@ -508,8 +532,7 @@ SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *po return false; if (type == 'V') return true; - if (spa_pod_body_get_choice(pod, body, &choice, &body) < 0) - return false; + spa_pod_body_get_choice(pod, body, &choice, &body); if (choice.body.type != SPA_CHOICE_None) return false; pod = &choice.body.child; @@ -566,72 +589,78 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch do { \ switch (_type) { \ case 'b': \ - *va_arg(args, bool*) = !!*((int32_t*)(body)); \ + spa_pod_body_get_bool(pod, body, va_arg(args, bool*)); \ break; \ case 'I': \ + spa_pod_body_get_id(pod, body, va_arg(args, uint32_t*)); \ + break; \ case 'i': \ - *va_arg(args, int32_t*) = *((int32_t*)(body)); \ + spa_pod_body_get_int(pod, body, va_arg(args, int32_t*)); \ break; \ case 'l': \ - *va_arg(args, int64_t*) = *((int64_t*)(body)); \ + spa_pod_body_get_long(pod, body, va_arg(args, int64_t*)); \ break; \ case 'f': \ - *va_arg(args, float*) = *((float*)(body)); \ + spa_pod_body_get_float(pod, body, va_arg(args, float*)); \ break; \ case 'd': \ - *va_arg(args, double*) = *((double*)(body)); \ + spa_pod_body_get_double(pod, body, va_arg(args, double*)); \ break; \ case 's': \ - *va_arg(args, char**) = ((pod)->type == SPA_TYPE_None) ? \ - NULL : (char *)(body); \ + { \ + const char **dest = va_arg(args, const char**); \ + if ((pod)->type == SPA_TYPE_None) \ + *dest = NULL; \ + else \ + spa_pod_body_get_string(pod, body, dest); \ break; \ + } \ case 'S': \ { \ char *dest = va_arg(args, char*); \ uint32_t maxlen = va_arg(args, uint32_t); \ - maxlen = SPA_MIN(maxlen, (pod)->size); \ - strncpy(dest, (char *)(body), maxlen-1); \ - dest[maxlen-1] = '\0'; \ + spa_pod_body_copy_string(pod, body, dest, maxlen); \ break; \ } \ case 'y': \ - *(va_arg(args, void **)) = (void*)(body); \ - *(va_arg(args, uint32_t *)) = (pod)->size; \ + { \ + const void **value = va_arg(args, const void**); \ + uint32_t *len = va_arg(args, uint32_t*); \ + spa_pod_body_get_bytes(pod, body, value, len); \ break; \ + } \ case 'R': \ - *va_arg(args, struct spa_rectangle*) = \ - *((struct spa_rectangle*)(body)); \ + spa_pod_body_get_rectangle(pod, body, \ + va_arg(args, struct spa_rectangle*)); \ break; \ case 'F': \ - *va_arg(args, struct spa_fraction*) = \ - *((struct spa_fraction*)(body)); \ + spa_pod_body_get_fraction(pod, body, \ + va_arg(args, struct spa_fraction*)); \ break; \ case 'B': \ - *va_arg(args, uint32_t **) = (uint32_t*)(body); \ + spa_pod_body_get_bitmap(pod, body, va_arg(args, const uint8_t**)); \ break; \ case 'a': \ { \ - struct spa_pod_array_body *b = \ - (struct spa_pod_array_body*)(body); \ - uint32_t child_size = b->child.size; \ - *va_arg(args, uint32_t*) = child_size; \ - *va_arg(args, uint32_t*) = b->child.type; \ - *va_arg(args, uint32_t*) = child_size ? \ - ((pod)->size - sizeof(struct spa_pod_array_body))/child_size : 0; \ - *va_arg(args, void**) = SPA_PTROFF(b, \ - sizeof(struct spa_pod_array_body), void); \ + struct spa_pod_array arr; \ + uint32_t *val_size = va_arg(args, uint32_t*); \ + uint32_t *val_type = va_arg(args, uint32_t*); \ + uint32_t *n_values = va_arg(args, uint32_t*); \ + const void **arr_body = va_arg(args, const void**); \ + spa_pod_body_get_array(pod, body, &arr, arr_body); \ + spa_pod_array_body_get_values(&arr, *arr_body, \ + n_values, val_size, val_type); \ break; \ } \ case 'p': \ { \ - struct spa_pod_pointer_body *b = \ - (struct spa_pod_pointer_body *)(body); \ - *(va_arg(args, uint32_t *)) = b->type; \ - *(va_arg(args, const void **)) = b->value; \ + uint32_t *type = va_arg(args, uint32_t*); \ + const void **value = va_arg(args, const void**); \ + spa_pod_body_get_pointer(pod, body, type, value); \ break; \ } \ case 'h': \ - *va_arg(args, int64_t*) = *((int64_t*)(body)); \ + spa_pod_body_get_fd(pod, body, va_arg(args, int64_t*)); \ break; \ case 'P': \ case 'T': \ @@ -736,8 +765,7 @@ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_lis struct spa_pod_choice choice; if (pod.type == SPA_TYPE_Choice && *format != 'V') { - if (spa_pod_body_get_choice(&pod, body, &choice, &body) < 0) - return -EINVAL; + spa_pod_body_get_choice(&pod, body, &choice, &body); pod = choice.body.child; } From 77494086c17c27d0540fca9c996b0be6d4e04fb0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 15:14:49 +0200 Subject: [PATCH 0650/1014] pod: add support for vararg building and parsing of pod+body --- spa/include/spa/pod/builder.h | 11 +++++++++++ spa/include/spa/pod/parser.h | 19 +++++++++++++++++++ spa/include/spa/pod/vararg.h | 6 ++++++ 3 files changed, 36 insertions(+) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 225ca28b1..ababd2e8d 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -595,6 +595,17 @@ do { \ spa_pod_builder_primitive(builder, pod); \ break; \ } \ + case 'Q': \ + case 'N': \ + case 'U': \ + case 'W': \ + { \ + struct spa_pod *pod = va_arg(args, struct spa_pod *); \ + const void *body = va_arg(args, const void *); \ + spa_pod_builder_primitive_body(builder, pod, \ + body, pod->size, NULL, 0); \ + break; \ + } \ } \ } while(false) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index cb60a1d62..e01bf91aa 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -540,6 +540,7 @@ SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *po switch (type) { case 'P': + case 'Q': return true; case 'b': return spa_pod_is_bool(pod); @@ -572,10 +573,13 @@ SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *po case 'h': return spa_pod_is_fd(pod); case 'T': + case 'U': return spa_pod_is_struct(pod) || spa_pod_is_none(pod); + case 'N': case 'O': return spa_pod_is_object(pod) || spa_pod_is_none(pod); case 'V': + case 'W': default: return false; } @@ -673,6 +677,17 @@ do { \ NULL : SPA_PTROFF((body), -sizeof(struct spa_pod), const struct spa_pod); \ break; \ } \ + case 'Q': \ + case 'U': \ + case 'N': \ + case 'W': \ + { \ + struct spa_pod *p = va_arg(args, struct spa_pod*); \ + const void **v = va_arg(args, const void **); \ + *p = *pod; \ + *v = body; \ + break; \ + } \ default: \ break; \ } \ @@ -807,6 +822,10 @@ SPA_API_POD_PARSER int spa_pod_parser_get(struct spa_pod_parser *parser, ...) #define SPA_POD_OPT_PodObject(val) "?" SPA_POD_PodObject(val) #define SPA_POD_OPT_PodStruct(val) "?" SPA_POD_PodStruct(val) #define SPA_POD_OPT_PodChoice(val) "?" SPA_POD_PodChoice(val) +#define SPA_POD_OPT_PodBody(val,body) "?" SPA_POD_PodBody(val,body) +#define SPA_POD_OPT_PodBodyObject(val,body) "?" SPA_POD_PodBodyObject(val,body) +#define SPA_POD_OPT_PodBodyStruct(val,body) "?" SPA_POD_PodBodyStruct(val,body) +#define SPA_POD_OPT_PodBodyChoice(val,body) "?" SPA_POD_PodBodyChoice(val,body) #define spa_pod_parser_get_object(p,type,id,...) \ ({ \ diff --git a/spa/include/spa/pod/vararg.h b/spa/include/spa/pod/vararg.h index df2efb51f..f777f953a 100644 --- a/spa/include/spa/pod/vararg.h +++ b/spa/include/spa/pod/vararg.h @@ -82,6 +82,12 @@ extern "C" { #define SPA_POD_PodStruct(val) "T", val #define SPA_POD_PodChoice(val) "V", val +#define SPA_POD_PodBody(val,body) "Q", val, body +#define SPA_POD_PodBodyObject(val,body) "N", val, body +#define SPA_POD_PodBodyStruct(val,body) "U", val, body +#define SPA_POD_PodBodyChoice(val,body) "W", val, body + + /** * \} */ From 8f45cfcbc9d381ac8bbed5726b16a9dedd5cd40b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 15:57:47 +0200 Subject: [PATCH 0651/1014] audiomixer: rework the port logic Use port lists for faster and safer updates of the mixer ports. --- spa/plugins/audiomixer/audiomixer.c | 143 +++++++++++++++------------- 1 file changed, 78 insertions(+), 65 deletions(-) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 80e514490..ff71f1442 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -59,6 +59,8 @@ struct buffer { }; struct port { + struct spa_list link; + uint32_t direction; uint32_t id; @@ -70,7 +72,6 @@ struct port { struct spa_port_info info; struct spa_param_info params[8]; - unsigned int valid:1; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; @@ -78,6 +79,9 @@ struct port { struct spa_list queue; size_t queued_bytes; + + struct spa_list mix_link; + bool active; }; struct impl { @@ -103,10 +107,10 @@ struct impl { struct spa_hook_list hooks; - uint32_t port_count; - uint32_t last_port; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; + struct spa_list port_list; + struct spa_list free_list; struct buffer *mix_buffers[MAX_PORTS]; const void *mix_datas[MAX_PORTS]; @@ -118,12 +122,13 @@ struct impl { unsigned int started:1; uint32_t stride; uint32_t blocks; + + struct spa_list mix_list; }; -#define PORT_VALID(p) ((p) != NULL && (p)->valid) #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) -#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && !PORT_VALID(this->in_ports[(p)])) -#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && PORT_VALID(this->in_ports[(p)])) +#define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) +#define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) @@ -209,7 +214,7 @@ static int impl_node_add_listener(void *object, { struct impl *this = object; struct spa_hook_list save; - uint32_t i; + struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -217,10 +222,8 @@ static int impl_node_add_listener(void *object, emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); - for (i = 0; i < this->last_port; i++) { - if (PORT_VALID(this->in_ports[i])) - emit_port_info(this, GET_IN_PORT(this, i), true); - } + spa_list_for_each(port, &this->port_list, link) + emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); @@ -235,6 +238,18 @@ impl_node_set_callbacks(void *object, return 0; } +static struct port *get_free_port(struct impl *this) +{ + struct port *port; + if (!spa_list_is_empty(&this->free_list)) { + port = spa_list_first(&this->free_list, struct port, link); + spa_list_remove(&port->link); + } else { + port = calloc(1, sizeof(struct port)); + } + return port; +} + static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { @@ -244,13 +259,9 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); - port = GET_IN_PORT(this, port_id); - if (port == NULL) { - port = calloc(1, sizeof(struct port)); - if (port == NULL) - return -errno; - this->in_ports[port_id] = port; - } + if ((port = get_free_port(this)) == NULL) + return -errno; + port->direction = SPA_DIRECTION_INPUT; port->id = port_id; @@ -272,13 +283,10 @@ static int impl_node_add_port(void *object, enum spa_direction direction, uint32 port->info.params = port->params; port->info.n_params = 5; - this->port_count++; - if (this->last_port <= port_id) - this->last_port = port_id + 1; - port->valid = true; + this->in_ports[port_id] = port; + spa_list_append(&this->port_list, &port->link); - spa_log_debug(this->log, "%p: add port %d:%d %d", this, - direction, port_id, this->last_port); + spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; @@ -294,26 +302,18 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT(this, port_id); + this->in_ports[port_id] = NULL; + spa_list_remove(&port->link); - port->valid = false; - this->port_count--; if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); + spa_list_append(&this->free_list, &port->link); - if (port_id + 1 == this->last_port) { - int i; - - for (i = this->last_port - 1; i >= 0; i--) - if (PORT_VALID(GET_IN_PORT(this, i))) - break; - - this->last_port = i + 1; - } - spa_log_debug(this->log, "%p: remove port %d:%d %d", this, - direction, port_id, this->last_port); + spa_log_debug(this->log, "%p: remove port %d:%d", this, + direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); @@ -697,6 +697,7 @@ impl_node_port_use_buffers(void *object, } struct io_info { + struct impl *impl; struct port *port; void *data; size_t size; @@ -706,17 +707,29 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; - if (info->size >= sizeof(struct spa_io_async_buffers)) { - struct spa_io_async_buffers *ab = info->data; - info->port->io[0] = &ab->buffers[info->port->direction]; - info->port->io[1] = &ab->buffers[info->port->direction^1]; - } else if (info->size >= sizeof(struct spa_io_buffers)) { - info->port->io[0] = info->data; - info->port->io[1] = info->data; - } else { - info->port->io[0] = NULL; - info->port->io[1] = NULL; - } + struct port *port = info->port; + + if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { + port->io[0] = NULL; + port->io[1] = NULL; + if (port->active) { + spa_list_remove(&port->mix_link); + port->active = false; + } + } else { + if (info->size >= sizeof(struct spa_io_async_buffers)) { + struct spa_io_async_buffers *ab = info->data; + port->io[0] = &ab->buffers[port->direction]; + port->io[1] = &ab->buffers[port->direction^1]; + } else { + port->io[0] = info->data; + port->io[1] = info->data; + } + if (!port->active) { + spa_list_append(&info->impl->mix_list, &port->mix_link); + port->active = true; + } + } return 0; } @@ -737,6 +750,7 @@ impl_node_port_set_io(void *object, spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); + info.impl = this; info.port = port; info.data = data; info.size = size; @@ -771,9 +785,9 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t static int impl_node_process(void *object) { struct impl *this = object; - struct port *outport; + struct port *outport, *inport; struct spa_io_buffers *outio; - uint32_t n_buffers, i, maxsize; + uint32_t n_buffers, maxsize; struct buffer **buffers; struct buffer *outb; const void **datas; @@ -803,24 +817,17 @@ static int impl_node_process(void *object) maxsize = UINT32_MAX; - for (i = 0; i < this->last_port; i++) { - struct port *inport = GET_IN_PORT(this, i); - struct spa_io_buffers *inio = NULL; + spa_list_for_each(inport, &this->mix_list, mix_link) { + struct spa_io_buffers *inio = inport->io[cycle]; struct buffer *inb; struct spa_data *bd; uint32_t size, offs; - if (SPA_UNLIKELY(!PORT_VALID(inport) || (inio = inport->io[cycle]) == NULL)) { - spa_log_trace_fp(this->log, "%p: skip input idx:%d valid:%d io:%p/%p/%d", - this, i, PORT_VALID(inport), - inport->io[0], inport->io[1], cycle); - continue; - } if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, - i, inio, inio->status, inio->buffer_id, inport->n_buffers); + inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } @@ -832,7 +839,7 @@ static int impl_node_process(void *object) maxsize = SPA_MIN(size, maxsize); spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d/%d", this, - i, inio, outio, inio->status, inio->buffer_id, + inport->id, inio, outio, inio->status, inio->buffer_id, offs, size, this->stride); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { @@ -912,14 +919,18 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int impl_clear(struct spa_handle *handle) { struct impl *this; - uint32_t i; + struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; - for (i = 0; i < MAX_PORTS; i++) - free(this->in_ports[i]); + spa_list_insert_list(&this->free_list, &this->port_list); + spa_list_insert_list(&this->free_list, &this->mix_list); + spa_list_consume(port, &this->free_list, link) { + spa_list_remove(&port->link); + free(port); + } mix_ops_free(&this->ops); return 0; } @@ -973,6 +984,9 @@ impl_init(const struct spa_handle_factory *factory, } spa_hook_list_init(&this->hooks); + spa_list_init(&this->port_list); + spa_list_init(&this->free_list); + spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -985,7 +999,6 @@ impl_init(const struct spa_handle_factory *factory, this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; port = GET_OUT_PORT(this, 0); - port->valid = true; port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); From 6d07eaea1fe8bce75bcf55526efac1ef780752f1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 17:32:02 +0200 Subject: [PATCH 0652/1014] seq: rework port handling Dynamically allocate ports as we need them. Use port lists to iterate active ports. --- spa/plugins/alsa/alsa-seq-bridge.c | 98 ++++++++++++++++++++---------- spa/plugins/alsa/alsa-seq.c | 59 ++++++++---------- spa/plugins/alsa/alsa-seq.h | 16 +++-- 3 files changed, 102 insertions(+), 71 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index e08d87e4a..15baf0a7f 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -294,13 +294,9 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f static void emit_stream_info(struct seq_state *this, struct seq_stream *stream, bool full) { - uint32_t i; - - for (i = 0; i < MAX_PORTS; i++) { - struct seq_port *port = &stream->ports[i]; - if (port->valid) - emit_port_info(this, port, full); - } + struct seq_port *port; + spa_list_for_each(port, &stream->port_list, link) + emit_port_info(this, port, full); } static int @@ -353,11 +349,9 @@ static int impl_node_sync(void *object, int seq) static struct seq_port *find_port(struct seq_state *state, struct seq_stream *stream, const snd_seq_addr_t *addr) { - uint32_t i; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; - if (port->valid && - port->addr.client == addr->client && + struct seq_port *port; + spa_list_for_each(port, &stream->port_list, link) { + if (port->addr.client == addr->client && port->addr.port == addr->port) return port; } @@ -366,32 +360,36 @@ static struct seq_port *find_port(struct seq_state *state, static struct seq_port *alloc_port(struct seq_state *state, struct seq_stream *stream) { + struct seq_port *port; uint32_t i; for (i = 0; i < MAX_PORTS; i++) { - struct seq_port *port = &stream->ports[i]; - if (!port->valid) { - port->id = i; - port->direction = stream->direction; - port->valid = true; - if (stream->last_port < i + 1) - stream->last_port = i + 1; - return port; - } + if (stream->ports[i] == NULL) + break; } - return NULL; + if (i == MAX_PORTS) + return NULL; + + if (!spa_list_is_empty(&state->free_list)) { + port = spa_list_first(&state->free_list, struct seq_port, link); + spa_list_remove(&port->link); + } else { + port = calloc(1, sizeof(struct seq_port)); + if (port == NULL) + return NULL; + } + port->id = i; + port->direction = stream->direction; + stream->ports[i] = port; + spa_list_append(&stream->port_list, &port->link); + + return port; } static void free_port(struct seq_state *state, struct seq_stream *stream, struct seq_port *port) { - port->valid = false; - - if (port->id + 1 == stream->last_port) { - int i; - for (i = stream->last_port - 1; i >= 0; i--) - if (stream->ports[i].valid) - break; - stream->last_port = i + 1; - } + stream->ports[port->id] = NULL; + spa_list_remove(&port->link); + spa_list_append(&state->free_list, &port->link); spa_node_emit_port_info(&state->hooks, port->direction, port->id, NULL); @@ -751,6 +749,36 @@ impl_node_port_use_buffers(void *object, return 0; } +struct io_info { + struct seq_state *state; + struct seq_port *port; + void *data; + size_t size; +}; + +static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct io_info *info = user_data; + struct seq_port *port = info->port; + struct seq_stream *stream = &info->state->streams[port->direction]; + + if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { + port->io = NULL; + if (port->mixing) { + spa_list_remove(&port->mix_link); + port->mixing = false; + } + } else { + port->io = info->data; + if (!port->mixing) { + spa_list_append(&stream->mix_list, &port->mix_link); + port->mixing = true; + } + } + return 0; + } + static int impl_node_port_set_io(void *object, enum spa_direction direction, @@ -760,19 +788,25 @@ impl_node_port_set_io(void *object, { struct seq_state *this = object; struct seq_port *port; + struct io_info info; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); + info.state = this; + info.port = port; + info.data = data; + info.size = size; spa_log_debug(this->log, "%p: io %d.%d %d %p %zd", this, direction, port_id, id, data, size); switch (id) { case SPA_IO_Buffers: - port->io = data; + spa_loop_locked(this->data_loop, + do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index df4d28766..4e85961bf 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -167,13 +167,17 @@ static int init_stream(struct seq_state *state, enum spa_direction direction) return res; } snd_midi_event_no_status(stream->codec, 1); - memset(stream->ports, 0, sizeof(stream->ports)); + + spa_list_init(&stream->port_list); + spa_list_init(&stream->mix_list); return 0; } static int uninit_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; + spa_list_insert_list(&state->free_list, &stream->port_list); + spa_list_insert_list(&state->free_list, &stream->mix_list); if (stream->codec) snd_midi_event_free(stream->codec); stream->codec = NULL; @@ -352,6 +356,7 @@ int spa_alsa_seq_open(struct seq_state *state) if (state->opened) return 0; + spa_list_init(&state->free_list); init_stream(state, SPA_DIRECTION_INPUT); init_stream(state, SPA_DIRECTION_OUTPUT); @@ -463,6 +468,7 @@ error_close: int spa_alsa_seq_close(struct seq_state *state) { int res = 0; + struct seq_port *port; if (!state->opened) return 0; @@ -475,6 +481,10 @@ int spa_alsa_seq_close(struct seq_state *state) uninit_stream(state, SPA_DIRECTION_INPUT); uninit_stream(state, SPA_DIRECTION_OUTPUT); + spa_list_consume(port, &state->free_list, link) { + spa_list_remove(&port->link); + free(port); + } spa_system_close(state->data_system, state->timerfd); state->opened = false; @@ -497,11 +507,9 @@ static int set_timeout(struct seq_state *state, uint64_t time) static struct seq_port *find_port(struct seq_state *state, struct seq_stream *stream, const snd_seq_addr_t *addr) { - uint32_t i; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; - if (port->valid && - port->addr.client == addr->client && + struct seq_port *port; + spa_list_for_each(port, &stream->mix_list, mix_link) { + if (port->addr.client == addr->client && port->addr.port == addr->port) return port; } @@ -595,15 +603,10 @@ static int prepare_buffer(struct seq_state *state, struct seq_port *port) static int process_recycle(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; - uint32_t i; + struct seq_port *port; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; + spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; - - if (!port->valid || io == NULL) - continue; - if (io->status != SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { spa_alsa_seq_recycle_buffer(state, port, io->buffer_id); @@ -620,17 +623,16 @@ static int process_read(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; const bool ump = state->ump; - uint32_t i; uint32_t *data; uint8_t midi1_data[MAX_EVENT_SIZE]; uint32_t ump_data[MAX_EVENT_SIZE]; long size; int res = -1; + struct seq_port *port; /* copy all new midi events into their port buffers */ while (1) { const snd_seq_addr_t *addr; - struct seq_port *port; uint64_t ev_time, diff; uint32_t offset; void *event; @@ -753,13 +755,9 @@ done: /* prepare a buffer on each port, some ports might have their * buffer filled above */ res = 0; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; + spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; - if (!port->valid || io == NULL) - continue; - if (prepare_buffer(state, port) >= 0) { spa_pod_builder_pop(&port->builder, &port->frame); @@ -809,11 +807,10 @@ static int process_write(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_INPUT]; const bool ump = state->ump; - uint32_t i; int err, res = 0; + struct seq_port *port; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; + spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; struct buffer *buffer; struct spa_pod_sequence *pod; @@ -823,9 +820,6 @@ static int process_write(struct seq_state *state) snd_seq_real_time_t out_rt; bool first = true; - if (!port->valid || io == NULL) - continue; - if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= port->n_buffers) continue; @@ -834,7 +828,7 @@ static int process_write(struct seq_state *state) d = &buffer->buf->datas[0]; io->status = SPA_STATUS_NEED_DATA; - spa_node_call_reuse_buffer(&state->callbacks, i, io->buffer_id); + spa_node_call_reuse_buffer(&state->callbacks, port->id, io->buffer_id); res |= SPA_STATUS_NEED_DATA; pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); @@ -1078,13 +1072,10 @@ static void reset_buffers(struct seq_state *this, struct seq_port *port) } static void reset_stream(struct seq_state *this, struct seq_stream *stream, bool active) { - uint32_t i; - for (i = 0; i < stream->last_port; i++) { - struct seq_port *port = &stream->ports[i]; - if (port->valid) { - reset_buffers(this, port); - spa_alsa_seq_activate_port(this, port, active); - } + struct seq_port *port; + spa_list_for_each(port, &stream->port_list, link) { + reset_buffers(this, port); + spa_alsa_seq_activate_port(this, port, active); } } diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 75fb1b217..354fa1d5d 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -53,6 +53,8 @@ struct buffer { }; struct seq_port { + struct spa_list link; + uint32_t id; enum spa_direction direction; snd_seq_addr_t addr; @@ -82,18 +84,21 @@ struct seq_port { struct spa_audio_info current_format; unsigned int have_format:1; - unsigned int valid:1; unsigned int active:1; struct spa_latency_info latency[2]; + + unsigned int mixing:1; + struct spa_list mix_link; }; struct seq_stream { enum spa_direction direction; unsigned int caps; snd_midi_event_t *codec; - struct seq_port ports[MAX_PORTS]; - uint32_t last_port; + struct seq_port *ports[MAX_PORTS]; + struct spa_list port_list; + struct spa_list mix_list; }; struct seq_conn { @@ -159,17 +164,18 @@ struct seq_state { unsigned int ump:1; struct seq_stream streams[2]; + struct spa_list free_list; struct spa_dll dll; }; #define VALID_DIRECTION(this,d) ((d) == SPA_DIRECTION_INPUT || (d) == SPA_DIRECTION_OUTPUT) -#define VALID_PORT(this,d,p) ((p) < MAX_PORTS && this->streams[d].ports[p].id == (p)) +#define VALID_PORT(this,d,p) ((p) < MAX_PORTS && this->streams[d].ports[p] != NULL) #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && VALID_PORT(this,d,p)) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && VALID_PORT(this,d,p)) #define CHECK_PORT(this,d,p) (VALID_DIRECTION(this,d) && VALID_PORT(this,d,p)) -#define GET_PORT(this,d,p) (&this->streams[d].ports[p]) +#define GET_PORT(this,d,p) (this->streams[d].ports[p]) int spa_alsa_seq_open(struct seq_state *state); int spa_alsa_seq_close(struct seq_state *state); From abcf70538d55b81288968f84d8d1b44fc928ce40 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 18:10:09 +0200 Subject: [PATCH 0653/1014] pod: add barrier around memcpy We need to be sure that the compiler does not perform invented loads after we checked the pod size. Otherwise we could have found that the size was ok, only to be overwritten by an invalid size. One way of avoiding this is to surround the memcpy with a barrier. See #4822 --- spa/include/spa/pod/parser.h | 5 +++++ spa/include/spa/utils/defs.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index e01bf91aa..909431e1c 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -90,7 +90,12 @@ spa_pod_parser_read_header(struct spa_pod_parser *parser, uint32_t offset, uint3 /* Cast to uint64_t to avoid wraparound. */ const uint64_t long_offset = (uint64_t)offset + header_size; if (long_offset <= size && (offset & 7) == 0) { + /* a barrier around the memcpy to make sure it is not moved around or + * duplicated after the size check below. We need to to work on shared + * memory while there could be updates happening while we read. */ + SPA_BARRIER; memcpy(header, SPA_PTROFF(parser->data, offset, void), header_size); + SPA_BARRIER; struct spa_pod *pod = SPA_PTROFF(header, pod_offset, struct spa_pod); /* Check that the size (rounded to the next multiple of 8) is in bounds. */ if (long_offset + SPA_ROUND_UP_N((uint64_t)pod->size, SPA_POD_ALIGN) <= size) { diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index 55bf9ad97..371b1e658 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -242,6 +242,7 @@ struct spa_fraction { #define SPA_UNUSED __attribute__ ((unused)) #define SPA_NORETURN __attribute__ ((noreturn)) #define SPA_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) +#define SPA_BARRIER __asm__ __volatile__("": : :"memory") #else #define SPA_PRINTF_FUNC(fmt, arg1) #define SPA_FORMAT_ARG_FUNC(arg1) @@ -252,6 +253,7 @@ struct spa_fraction { #define SPA_UNUSED #define SPA_NORETURN #define SPA_WARN_UNUSED_RESULT +#define SPA_BARRIER #endif #ifndef SPA_API_IMPL From 7f7b4be82dc805ddd836840fbf3317b122e2c9ea Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 18:43:34 +0200 Subject: [PATCH 0654/1014] alsa: use the safer pod parser for control events --- spa/plugins/alsa/alsa-seq.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 4e85961bf..902fee632 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -813,9 +813,13 @@ static int process_write(struct seq_state *state) spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; struct buffer *buffer; - struct spa_pod_sequence *pod; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body; struct spa_data *d; - struct spa_pod_control *c; + struct spa_pod_control c; + const void *c_body; uint64_t out_time; snd_seq_real_time_t out_rt; bool first = true; @@ -831,24 +835,25 @@ static int process_write(struct seq_state *state) spa_node_call_reuse_buffer(&state->callbacks, port->id, io->buffer_id); res |= SPA_STATUS_NEED_DATA; - pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); - if (pod == NULL) { + spa_pod_parser_init_from_data(&parser, d->data, d->maxsize, + d->chunk->offset, d->chunk->size); + + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) { spa_log_warn(state->log, "invalid sequence in buffer max:%u offset:%u size:%u", d->maxsize, d->chunk->offset, d->chunk->size); continue; } - - SPA_POD_SEQUENCE_FOREACH(pod, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { size_t body_size; uint8_t *body; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - body = SPA_POD_BODY(&c->value); - body_size = SPA_POD_BODY_SIZE(&c->value); + body = (uint8_t*)c_body; + body_size = c.value.size; - out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); + out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c.offset); out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; From a3da53f66e71c3b1896f47884d6c1ed98847a651 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 18:54:16 +0200 Subject: [PATCH 0655/1014] alsa: fix seq compilation --- spa/plugins/alsa/alsa-seq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 902fee632..9c693e08d 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -858,7 +858,7 @@ static int process_write(struct seq_state *state) out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; spa_log_trace_fp(state->log, "event time:%"PRIu64" offset:%d size:%zd port:%d.%d", - out_time, c->offset, body_size, port->addr.client, port->addr.port); + out_time, c.offset, body_size, port->addr.client, port->addr.port); if (ump) { #ifdef HAVE_ALSA_UMP From 5743849b507204d8fb9723791cd1855d662509f5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 30 Jul 2025 19:08:36 +0200 Subject: [PATCH 0656/1014] tools: use safer spa_pod_parser for control events --- src/tools/midifile.c | 2 +- src/tools/pw-cat.c | 27 +++++++++++++++------------ src/tools/pw-mididump.c | 29 ++++++++++++++++------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/tools/midifile.c b/src/tools/midifile.c index a58a1bc97..51720db8b 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -665,7 +665,7 @@ static const char *controller_name(uint8_t ctrl) return controller_names[ctrl]; } -static void dump_mem(FILE *out, const char *label, uint8_t *data, uint32_t size) +static void dump_mem(FILE *out, const char *label, const uint8_t *data, uint32_t size) { fprintf(out, "%s: ", label); while (size--) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index f66ea90d6..b2e534e00 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1174,28 +1174,31 @@ static int midi_play(struct data *d, void *src, unsigned int n_frames, bool *nul static int midi_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame) { - struct spa_pod *pod; - struct spa_pod_control *c; - uint32_t frame; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; + uint32_t offset; - frame = d->clock_time; + offset = d->clock_time; d->clock_time += d->position->clock.duration; - if ((pod = spa_pod_from_data(src, n_frames, 0, n_frames)) == NULL) - return 0; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, src, n_frames, 0, n_frames); + + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return 0; - SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { struct midi_event ev; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; ev.track = 0; - ev.sec = (frame + c->offset) / (float) d->position->clock.rate.denom; - ev.data = SPA_POD_BODY(&c->value), - ev.size = SPA_POD_BODY_SIZE(&c->value); + ev.sec = (offset + c.offset) / (float) d->position->clock.rate.denom; + ev.data = (uint8_t*)c_body; + ev.size = c.value.size; ev.type = MIDI_EVENT_TYPE_UMP; if (d->verbose) diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index a29bb725d..be37024fc 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -62,11 +62,14 @@ static void on_process(void *_data, struct spa_io_position *position) struct pw_buffer *b; struct spa_buffer *buf; struct spa_data *d; - struct spa_pod *pod; - struct spa_pod_control *c; - uint64_t frame; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; + uint64_t offset; - frame = data->clock_time; + offset = data->clock_time; data->clock_time += position->clock.duration; b = pw_filter_dequeue_buffer(data->in_port); @@ -79,24 +82,24 @@ static void on_process(void *_data, struct spa_io_position *position) if (d->data == NULL) goto done; - if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL) - goto done; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); + + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) goto done; - SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { struct midi_event ev; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; ev.track = 0; - ev.sec = (frame + c->offset) / (float) position->clock.rate.denom; - ev.data = SPA_POD_BODY(&c->value), - ev.size = SPA_POD_BODY_SIZE(&c->value); + ev.sec = (offset + c.offset) / (float) position->clock.rate.denom; + ev.data = (uint8_t*)c_body; + ev.size = c.value.size; ev.type = MIDI_EVENT_TYPE_UMP; - fprintf(stdout, "%4d: ", c->offset); + fprintf(stdout, "%4d: ", c.offset); midi_file_dump_event(stdout, &ev); } From 4715e36a5c811e7f4cd91100629d83b1f1b4c8a0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 10:20:59 +0200 Subject: [PATCH 0657/1014] spa: don't free the mix_list ports We can't move the mix_list ports to the free_list like that because the elements in the list use a different list to link together. Also, we don't need to free those ports at all because they will be freed when we move the port_list to the free_list. --- spa/plugins/alsa/alsa-seq.c | 1 - spa/plugins/audiomixer/audiomixer.c | 2 +- spa/plugins/audiomixer/mixer-dsp.c | 5 +---- spa/plugins/control/mixer.c | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 9c693e08d..bcbddc992 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -177,7 +177,6 @@ static int uninit_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; spa_list_insert_list(&state->free_list, &stream->port_list); - spa_list_insert_list(&state->free_list, &stream->mix_list); if (stream->codec) snd_midi_event_free(stream->codec); stream->codec = NULL; diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index ff71f1442..80d4bac02 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -926,11 +926,11 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; spa_list_insert_list(&this->free_list, &this->port_list); - spa_list_insert_list(&this->free_list, &this->mix_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); } + spa_list_init(&this->mix_list); mix_ops_free(&this->ops); return 0; } diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index a65268fb1..50b582d1d 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -863,10 +863,7 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; - spa_list_consume(port, &this->port_list, link) { - spa_list_remove(&port->link); - free(port); - } + spa_list_insert_list(&this->free_list, &this->port_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index b3d3252df..ccf0df54a 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -909,7 +909,6 @@ static int impl_clear(struct spa_handle *handle) this = (struct impl *) handle; spa_list_insert_list(&this->free_list, &this->port_list); - spa_list_insert_list(&this->free_list, &this->mix_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); From 45ed70d480f6775c65e118048947f8f7b024b328 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 10:54:09 +0200 Subject: [PATCH 0658/1014] control: mark the input as const, we don't change it --- spa/include/spa/control/ump-utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 0c0faa8ba..4525cef76 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -48,7 +48,7 @@ SPA_API_CONTROL_UMP_UTILS size_t spa_ump_message_size(uint8_t message_type) return ump_sizes[message_type & 0xf]; } -SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(uint32_t *ump, size_t ump_size, +SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(const uint32_t *ump, size_t ump_size, uint8_t *midi, size_t midi_maxsize) { int size = 0; From ede1924ded04e734943e985868019d4fdc948069 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 11:21:28 +0200 Subject: [PATCH 0659/1014] pod: add more functions for easy pod+body parsing Add functions to parse an object and struct from pod+body. --- spa/include/spa/pod/parser.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 909431e1c..ccf224c85 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -854,20 +854,25 @@ SPA_API_POD_PARSER int spa_pod_parser_get(struct spa_pod_parser *parser, ...) _res; \ }) -#define spa_pod_parse_object(pod,type,id,...) \ +#define spa_pod_body_parse_object(pod,body,type,id,...) \ ({ \ struct spa_pod_parser _p; \ - spa_pod_parser_pod(&_p, pod); \ + spa_pod_parser_init_pod_body(&_p, pod, body); \ spa_pod_parser_get_object(&_p,type,id,##__VA_ARGS__); \ }) -#define spa_pod_parse_struct(pod,...) \ +#define spa_pod_parse_object(pod,type,id,...) \ + spa_pod_body_parse_object(pod,SPA_POD_BODY_CONST(pod),type,id,##__VA_ARGS__) + +#define spa_pod_body_parse_struct(pod,body,...) \ ({ \ struct spa_pod_parser _p; \ - spa_pod_parser_pod(&_p, pod); \ + spa_pod_parser_init_pod_body(&_p, pod, body); \ spa_pod_parser_get_struct(&_p,##__VA_ARGS__); \ }) +#define spa_pod_parse_struct(pod,...) \ + spa_pod_body_parse_struct(pod,SPA_POD_BODY_CONST(pod),##__VA_ARGS__) /** * \} */ From f4ab7049489eb04a570973ad0d16e012e8e16f53 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 11:23:30 +0200 Subject: [PATCH 0660/1014] spa: use safe IO_Control parsing The IO_Control areas are in shaed memory and need to use the parser to safely extract the info. --- spa/plugins/audiotestsrc/audiotestsrc.c | 22 ++++++--- spa/plugins/libcamera/libcamera-source.cpp | 52 ++++++++++++++-------- spa/plugins/v4l2/v4l2-source.c | 35 ++++++++++----- spa/plugins/v4l2/v4l2-utils.c | 3 +- 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 6dc0db07f..a3ad10230 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -83,6 +83,7 @@ struct port { struct spa_io_buffers *io; struct spa_io_sequence *io_control; + uint32_t io_control_size; bool have_format; struct spa_audio_info current_format; @@ -872,6 +873,7 @@ impl_node_port_set_io(void *object, break; case SPA_IO_Control: port->io_control = data; + port->io_control_size = size; break; default: return -ENOENT; @@ -909,16 +911,24 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return 0; } -static int process_control(struct impl *this, struct spa_pod_sequence *sequence) +static int process_control(struct impl *this, struct spa_pod_sequence *sequence, uint32_t size) { - struct spa_pod_control *c; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; - SPA_POD_SEQUENCE_FOREACH(sequence, c) { - switch (c->type) { + spa_pod_parser_init_from_data(&parser, sequence, size, 0, size); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) + return 0; + + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { + switch (c.type) { case SPA_CONTROL_Properties: { struct props *p = &this->props; - spa_pod_parse_object(&c->value, + spa_pod_body_parse_object(&c.value, c_body, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_frequency, SPA_POD_OPT_Float(&p->freq), SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume)); @@ -946,7 +956,7 @@ static int impl_node_process(void *object) return -EIO; if (port->io_control) - process_control(this, &port->io_control->sequence); + process_control(this, &port->io_control->sequence, port->io_control_size); if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d11cd678c..d0be3aac2 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -85,6 +85,7 @@ struct port { struct spa_port_info info = SPA_PORT_INFO_INIT(); struct spa_io_buffers *io = nullptr; struct spa_io_sequence *control = nullptr; + uint32_t control_size; #define PORT_PropInfo 0 #define PORT_EnumFormat 1 #define PORT_Meta 2 @@ -895,7 +896,7 @@ int do_update_ctrls(struct spa_loop *loop, } int -spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) +spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop, const void *body) { const ControlInfoMap &info = impl->camera->controls(); const ControlId *ctrl_id; @@ -918,16 +919,19 @@ spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop) switch (d.type) { case ControlTypeBool: - if ((res = spa_pod_get_bool(&prop->value, &d.b_val)) < 0) - goto done; + if (!spa_pod_is_bool(&prop->value)) + return -EINVAL; + spa_pod_body_get_bool(&prop->value, body, &d.b_val); break; case ControlTypeFloat: - if ((res = spa_pod_get_float(&prop->value, &d.f_val)) < 0) - goto done; + if (!spa_pod_is_float(&prop->value)) + return -EINVAL; + spa_pod_body_get_float(&prop->value, body, &d.f_val); break; case ControlTypeInteger32: - if ((res = spa_pod_get_int(&prop->value, &d.i_val)) < 0) - goto done; + if (!spa_pod_is_int(&prop->value)) + return -EINVAL; + spa_pod_body_get_int(&prop->value, body, &d.i_val); break; default: res = -EINVAL; @@ -1483,7 +1487,7 @@ int impl_node_set_param(void *object, impl->device_id = device; break; default: - spa_libcamera_set_control(impl, prop); + spa_libcamera_set_control(impl, prop, SPA_POD_BODY_CONST(&prop->value)); break; } } @@ -1949,6 +1953,7 @@ int impl_node_port_set_io(void *object, break; case SPA_IO_Control: port->control = (struct spa_io_sequence*)data; + port->control_size = size; break; default: return -ENOENT; @@ -1976,20 +1981,31 @@ int impl_node_port_reuse_buffer(void *object, return res; } -int process_control(struct impl *impl, struct spa_pod_sequence *control) +int process_control(struct impl *impl, struct spa_pod_sequence *control, uint32_t size) { - struct spa_pod_control *c; + struct spa_pod_parser parser[2]; + struct spa_pod_frame frame[2]; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; - SPA_POD_SEQUENCE_FOREACH(control, c) { - switch (c->type) { + spa_pod_parser_init_from_data(&parser[0], control, size, 0, size); + if (spa_pod_parser_push_sequence_body(&parser[0], &frame[0], &seq, &seq_body) < 0) + return 0; + + while (spa_pod_parser_get_control_body(&parser[0], &c, &c_body) >= 0) { + switch (c.type) { case SPA_CONTROL_Properties: { - const auto *obj = reinterpret_cast(&c->value); - const struct spa_pod_prop *prop; + struct spa_pod_object obj; + struct spa_pod_prop prop; + const void *obj_body, *prop_body; - SPA_POD_OBJECT_FOREACH(obj, prop) { - spa_libcamera_set_control(impl, prop); - } + if (spa_pod_parser_init_object_body(&parser[1], &frame[1], + &c.value, c_body, &obj, &obj_body) < 0) + continue; + while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) + spa_libcamera_set_control(impl, &prop, prop_body); break; } default: @@ -2014,7 +2030,7 @@ int impl_node_process(void *object) return -EIO; if (port->control) - process_control(impl, &port->control->sequence); + process_control(impl, &port->control->sequence, port->control_size); spa_log_trace(impl->log, "%p; status %d", impl, io->status); diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index 6b6dcff0d..2eb66b3da 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -113,6 +113,7 @@ struct port { struct spa_port_info info; struct spa_io_buffers *io; struct spa_io_sequence *control; + uint32_t control_size; #define PORT_PropInfo 0 #define PORT_EnumFormat 1 #define PORT_Meta 2 @@ -403,7 +404,7 @@ static int impl_node_set_param(void *object, sizeof(p->device)-1); break; default: - spa_v4l2_set_control(this, prop->key, prop); + spa_v4l2_set_control(this, prop, SPA_POD_BODY_CONST(&prop->value)); break; } } @@ -863,6 +864,7 @@ static int impl_node_port_set_io(void *object, break; case SPA_IO_Control: port->control = data; + port->control_size = size; break; default: return -ENOENT; @@ -890,20 +892,31 @@ static int impl_node_port_reuse_buffer(void *object, return res; } -static int process_control(struct impl *this, struct spa_pod_sequence *control) +static int process_control(struct impl *this, struct spa_pod_sequence *control, uint32_t size) { - struct spa_pod_control *c; + struct spa_pod_parser parser[2]; + struct spa_pod_frame frame[2]; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; - SPA_POD_SEQUENCE_FOREACH(control, c) { - switch (c->type) { + spa_pod_parser_init_from_data(&parser[0], control, size, 0, size); + if (spa_pod_parser_push_sequence_body(&parser[0], &frame[0], &seq, &seq_body) < 0) + return 0; + + while (spa_pod_parser_get_control_body(&parser[0], &c, &c_body) >= 0) { + switch (c.type) { case SPA_CONTROL_Properties: { - struct spa_pod_prop *prop; - struct spa_pod_object *obj = (struct spa_pod_object *) &c->value; + struct spa_pod_object obj; + struct spa_pod_prop prop; + const void *obj_body, *prop_body; - SPA_POD_OBJECT_FOREACH(obj, prop) { - spa_v4l2_set_control(this, prop->key, prop); - } + if (spa_pod_parser_init_object_body(&parser[1], &frame[1], + &c.value, c_body, &obj, &obj_body) < 0) + continue; + while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) + spa_v4l2_set_control(this, &prop, prop_body); break; } default: @@ -928,7 +941,7 @@ static int impl_node_process(void *object) return -EIO; if (port->control) - process_control(this, &port->control->sequence); + process_control(this, &port->control->sequence, port->control_size); spa_log_trace(this->log, "%p; status %d", this, io->status); diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index e607e81d5..e5f153f4a 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1569,8 +1569,7 @@ done: } static int -spa_v4l2_set_control(struct impl *this, uint32_t id, - const struct spa_pod_prop *prop) +spa_v4l2_set_control(struct impl *this, const struct spa_pod_prop *prop, const void *body) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; From 28510f31174e298388eeb65753e78f6fa0b6d87e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 11:24:29 +0200 Subject: [PATCH 0661/1014] bluez: safely parse the control data from buffers --- spa/plugins/bluez5/midi-node.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index 9be26dd59..b2acb40f4 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -766,32 +766,35 @@ static int flush_packet(struct impl *this) static int write_data(struct impl *this, struct spa_data *d) { struct port *port = &this->ports[PORT_IN]; - struct spa_pod_sequence *pod; - struct spa_pod_control *c; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; uint64_t time; int res; - pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size); - if (pod == NULL) { + spa_pod_parser_init_from_data(&parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) { spa_log_warn(this->log, "%p: invalid sequence in buffer max:%u offset:%u size:%u", this, d->maxsize, d->chunk->offset, d->chunk->size); return -EINVAL; } + spa_bt_midi_writer_init(&this->writer, port->mtu); time = 0; - SPA_POD_SEQUENCE_FOREACH(pod, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; uint8_t event[32]; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - time = SPA_MAX(time, this->current_time + c->offset * SPA_NSEC_PER_SEC / this->rate); + time = SPA_MAX(time, this->current_time + c.offset * SPA_NSEC_PER_SEC / this->rate); - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); if (size <= 0) continue; From 8495bffee54d4d34a4995fded5ee82af8f5eed6c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 11:50:11 +0200 Subject: [PATCH 0662/1014] modules: use safer pod parsing for control sequence --- src/modules/module-ffado-driver.c | 27 +++++++++++----------- src/modules/module-jack-tunnel.c | 25 ++++++++++---------- src/modules/module-netjack2/peer.c | 28 +++++++++++----------- src/modules/module-rtp/midi.c | 37 +++++++++++++++--------------- src/modules/module-vban/midi.c | 36 ++++++++++++++--------------- 5 files changed, 73 insertions(+), 80 deletions(-) diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 761e62471..0cd2a75ff 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -317,21 +317,21 @@ static inline void fix_midi_event(uint8_t *data, size_t size) static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples) { - struct spa_pod *pod; - struct spa_pod_sequence *seq; - struct spa_pod_control *c; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + struct spa_pod_control c; + const void *seq_body, *c_body; uint32_t i, index = 0, unhandled = 0; uint32_t *dst = p->buffer; if (src == NULL) return; - if ((pod = spa_pod_from_data(src, n_samples * sizeof(float), 0, n_samples * sizeof(float))) == NULL) + spa_pod_parser_init_from_data(&parser, src, n_samples * sizeof(float), + 0, n_samples * sizeof(float)); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return; - if (!spa_pod_is_sequence(pod)) - return; - - seq = (struct spa_pod_sequence*)pod; clear_port_buffer(p, n_samples); @@ -342,20 +342,19 @@ static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples) } p->event_pos = 0; - SPA_POD_SEQUENCE_FOREACH(seq, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { uint8_t data[16]; int j, size; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + size = spa_ump_to_midi(c_body, c.value.size, data, sizeof(data)); if (size <= 0) continue; - if (index < c->offset) - index = SPA_ROUND_UP_N(c->offset, 8); + if (index < c.offset) + index = SPA_ROUND_UP_N(c.offset, 8); for (j = 0; j < size; j++) { if (index >= n_samples) { /* keep events that don't fit for the next cycle */ diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 6c7416941..098563833 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -252,9 +252,11 @@ static inline void fix_midi_event(uint8_t *data, size_t size) static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_samples) { - struct spa_pod *pod; - struct spa_pod_sequence *seq; - struct spa_pod_control *c; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + struct spa_pod_control c; + const void *seq_body, *c_body; int res; bool in_sysex = false; uint8_t tmp[n_samples * 4]; @@ -264,21 +266,18 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s if (src == NULL) return; - if ((pod = spa_pod_from_data(src, n_samples * sizeof(float), 0, n_samples * sizeof(float))) == NULL) - return; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, src, n_samples * sizeof(float), + 0, n_samples * sizeof(float)); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return; - seq = (struct spa_pod_sequence*)pod; - - SPA_POD_SEQUENCE_FOREACH(seq, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), &tmp[tmp_size], sizeof(tmp) - tmp_size); + size = spa_ump_to_midi(c_body, c.value.size, &tmp[tmp_size], sizeof(tmp) - tmp_size); if (size <= 0) continue; @@ -293,7 +292,7 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s in_sysex = false; if (!in_sysex) { - if ((res = jack.midi_event_write(dst, c->offset, tmp, tmp_size)) < 0) + if ((res = jack.midi_event_write(dst, c.offset, tmp, tmp_size)) < 0) pw_log_warn("midi %p: can't write event: %s", dst, spa_strerror(res)); tmp_size = 0; diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 3bbb8d47a..d2251e705 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -307,9 +307,11 @@ static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf, static void midi_to_netjack2(struct netjack2_peer *peer, struct nj2_midi_buffer *buf, float *src, uint32_t n_samples) { - struct spa_pod *pod; - struct spa_pod_sequence *seq; - struct spa_pod_control *c; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + struct spa_pod_control c; + const void *seq_body, *c_body; bool in_sysex = false; buf->magic = MIDI_BUFFER_MAGIC; @@ -322,28 +324,24 @@ static void midi_to_netjack2(struct netjack2_peer *peer, if (src == NULL) return; - if ((pod = spa_pod_from_data(src, n_samples * sizeof(float), - 0, n_samples * sizeof(float))) == NULL) - return; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, src, n_samples * sizeof(float), + 0, n_samples * sizeof(float)); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return; - seq = (struct spa_pod_sequence*)pod; - - SPA_POD_SEQUENCE_FOREACH(seq, c) { + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; uint8_t data[16]; bool was_sysex = in_sysex; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + size = spa_ump_to_midi(c_body, c.value.size, data, sizeof(data)); if (size <= 0) continue; - if (c->offset >= n_samples) { + if (c.offset >= n_samples) { buf->lost_events++; continue; } @@ -360,7 +358,7 @@ static void midi_to_netjack2(struct netjack2_peer *peer, if (was_sysex) n2j_midi_buffer_append(buf, data, size); else - n2j_midi_buffer_write(buf, c->offset, data, size); + n2j_midi_buffer_write(buf, c.offset, data, size); } if (buf->write_pos > 0) memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index b4d716cb3..bcecf309f 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -56,7 +56,8 @@ static void rtp_midi_process_playback(void *data) goto done; /* the ringbuffer contains series of sequences, one for each - * received packet */ + * received packet. This is not in shared mem so we can safely use + * the iterators here. */ SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { /* try to render with given delay */ uint32_t target = c->offset + impl->target_buffer; @@ -406,9 +407,10 @@ static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, void *e } static void rtp_midi_flush_packets(struct impl *impl, - struct spa_pod_sequence *sequence, uint32_t timestamp, uint32_t rate) + struct spa_pod_parser *parser, uint32_t timestamp, uint32_t rate) { - struct spa_pod_control *c; + struct spa_pod_control c; + const void *c_body; struct rtp_header header; struct rtp_midi_header midi_header; struct iovec iov[3]; @@ -431,20 +433,19 @@ static void rtp_midi_flush_packets(struct impl *impl, prev_offset = len = base = 0; max_size = impl->payload_size - sizeof(midi_header); - SPA_POD_SEQUENCE_FOREACH(sequence, c) { + while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) { uint32_t delta, offset; uint8_t event[16]; size_t size; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); if (size <= 0) continue; - offset = c->offset * impl->rate / rate; + offset = c.offset * impl->rate / rate; if (len > 0 && (len + size > max_size || offset - base > impl->psamples)) { @@ -513,9 +514,11 @@ static void rtp_midi_process_capture(void *data) struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; - uint32_t offs, size, timestamp, rate; - struct spa_pod *pod; - void *ptr; + uint32_t timestamp, rate; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_info("Out of stream buffers: %m"); @@ -523,9 +526,6 @@ static void rtp_midi_process_capture(void *data) } d = buf->buffer->datas; - offs = SPA_MIN(d[0].chunk->offset, d[0].maxsize); - size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); - if (SPA_LIKELY(impl->io_position)) { rate = impl->io_position->clock.rate.denom; timestamp = impl->io_position->clock.position * impl->rate / rate; @@ -534,11 +534,10 @@ static void rtp_midi_process_capture(void *data) timestamp = 0; } - ptr = SPA_PTROFF(d[0].data, offs, void); - if ((pod = spa_pod_from_data(ptr, size, 0, size)) == NULL) - goto done; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, d[0].data, d[0].maxsize, + d[0].chunk->offset, d[0].chunk->size); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) goto done; if (!impl->have_sync) { @@ -547,7 +546,7 @@ static void rtp_midi_process_capture(void *data) impl->have_sync = true; } - rtp_midi_flush_packets(impl, (struct spa_pod_sequence*)pod, timestamp, rate); + rtp_midi_flush_packets(impl, &parser, timestamp, rate); done: pw_stream_queue_buffer(impl->stream, buf); diff --git a/src/modules/module-vban/midi.c b/src/modules/module-vban/midi.c index f820b6839..4fbd39532 100644 --- a/src/modules/module-vban/midi.c +++ b/src/modules/module-vban/midi.c @@ -48,7 +48,8 @@ static void vban_midi_process_playback(void *data) goto done; /* the ringbuffer contains series of sequences, one for each - * received packet */ + * received packet. This is not share mem so we can use the + * iterator. */ SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { #if 0 /* try to render with given delay */ @@ -218,9 +219,10 @@ static int vban_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) } static void vban_midi_flush_packets(struct impl *impl, - struct spa_pod_sequence *sequence, uint32_t timestamp, uint32_t rate) + struct spa_pod_parser *parser, uint32_t timestamp, uint32_t rate) { - struct spa_pod_control *c; + struct spa_pod_control c; + const void *c_body; struct vban_header header; struct iovec iov[2]; uint32_t len; @@ -234,15 +236,14 @@ static void vban_midi_flush_packets(struct impl *impl, len = 0; - SPA_POD_SEQUENCE_FOREACH(sequence, c) { + while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) { int size; uint8_t event[16]; - if (c->type != SPA_CONTROL_UMP) + if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(SPA_POD_BODY(&c->value), - SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); if (size <= 0) continue; @@ -275,9 +276,11 @@ static void vban_midi_process_capture(void *data) struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; - uint32_t offs, size, timestamp, rate; - struct spa_pod *pod; - void *ptr; + uint32_t timestamp, rate; + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_debug("Out of stream buffers: %m"); @@ -285,9 +288,6 @@ static void vban_midi_process_capture(void *data) } d = buf->buffer->datas; - offs = SPA_MIN(d[0].chunk->offset, d[0].maxsize); - size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); - if (SPA_LIKELY(impl->io_position)) { rate = impl->io_position->clock.rate.denom; timestamp = impl->io_position->clock.position * impl->rate / rate; @@ -296,11 +296,9 @@ static void vban_midi_process_capture(void *data) timestamp = 0; } - ptr = SPA_PTROFF(d[0].data, offs, void); - - if ((pod = spa_pod_from_data(ptr, size, 0, size)) == NULL) - goto done; - if (!spa_pod_is_sequence(pod)) + spa_pod_parser_init_from_data(&parser, d[0].data, d[0].maxsize, + d[0].chunk->offset, d[0].chunk->size); + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) goto done; if (!impl->have_sync) { @@ -309,7 +307,7 @@ static void vban_midi_process_capture(void *data) impl->have_sync = true; } - vban_midi_flush_packets(impl, (struct spa_pod_sequence*)pod, timestamp, rate); + vban_midi_flush_packets(impl, &parser, timestamp, rate); done: pw_stream_queue_buffer(impl->stream, buf); From 121608f0405b7c52240886388dfd4771ad4d75c4 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 30 Jul 2025 20:17:49 +0300 Subject: [PATCH 0663/1014] bluez5: allow framing for BAP There's actually no reason to disable framed qos presets. --- spa/plugins/bluez5/bap-codec-lc3.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index f5bc7e111..d316da2b6 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -480,8 +480,6 @@ static bool select_bap_qos(struct bap_qos *conf, continue; if (!(get_duration_mask(c.frame_duration) & duration_mask)) continue; - if (c.framing) - continue; /* XXX: framing not supported */ if (c.framelen < framelen_min || c.framelen > framelen_max) continue; @@ -1079,7 +1077,11 @@ static int codec_get_qos(const struct media_codec *codec, return -EINVAL; } - qos->framing = false; + if (endpoint_qos->framing == 0x01) + qos->framing = true; + else + qos->framing = bap_qos.framing; + if (endpoint_qos->phy & 0x2) qos->phy = 0x2; else if (endpoint_qos->phy & 0x1) From 00dbe9cb2a1456fb4b7e7daaad957b7b61eed831 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 31 Jul 2025 14:12:21 +0200 Subject: [PATCH 0664/1014] pod: add missing parser varargs formats --- spa/include/spa/pod/parser.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index ccf224c85..8e26902dd 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -535,7 +535,7 @@ SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *po if (pod->type == SPA_TYPE_Choice) { if (!spa_pod_is_choice(pod)) return false; - if (type == 'V') + if (type == 'V' || type == 'W') return true; spa_pod_body_get_choice(pod, body, &choice, &body); if (choice.body.type != SPA_CHOICE_None) @@ -731,6 +731,10 @@ do { \ case 'P': \ case 'T': \ case 'O': \ + case 'W': \ + case 'Q': \ + case 'U': \ + case 'N': \ va_arg(args, void*); \ break; \ } \ From bef0706238b4943230215f0d506a48266b0750c1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 1 Aug 2025 10:08:06 +0200 Subject: [PATCH 0665/1014] Revert "pod: remove checks from spa_pod_body_get_*()" This partially reverts commit f7ae61cb1ea16055bc45ec036a00ae66f66f3675. We do want to do the checks in spa_pod_body_get_*() for extra safety. The reason they were removed is because then we do the checks twice in the parser. It should however be possible to fuse the can_collect and COLLECT and SKIP calls together in the future. --- spa/include/spa/pod/body.h | 96 +++++++++++++++++------ spa/include/spa/pod/iter.h | 64 +++------------ spa/include/spa/pod/parser.h | 148 ++++++++++++++++++----------------- 3 files changed, 162 insertions(+), 146 deletions(-) diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index 1678ce039..d4ed81ad5 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -105,9 +105,12 @@ SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } -SPA_API_POD_BODY void spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) +SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) { + if (!spa_pod_is_bool(pod)) + return -EINVAL; *value = !!*((int32_t*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) @@ -115,9 +118,12 @@ SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); } -SPA_API_POD_BODY void spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) +SPA_API_POD_BODY int spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) { + if (!spa_pod_is_id(pod)) + return -EINVAL; *value = *((uint32_t*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) @@ -125,9 +131,12 @@ SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); } -SPA_API_POD_BODY void spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) +SPA_API_POD_BODY int spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) { + if (!spa_pod_is_int(pod)) + return -EINVAL; *value = *((int32_t*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) @@ -135,9 +144,12 @@ SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); } -SPA_API_POD_BODY void spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) +SPA_API_POD_BODY int spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) { + if (!spa_pod_is_long(pod)) + return -EINVAL; *value = *((int64_t*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) @@ -145,9 +157,12 @@ SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); } -SPA_API_POD_BODY void spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) +SPA_API_POD_BODY int spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) { + if (!spa_pod_is_float(pod)) + return -EINVAL; *value = *((float*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) @@ -155,9 +170,12 @@ SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); } -SPA_API_POD_BODY void spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) +SPA_API_POD_BODY int spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) { + if (!spa_pod_is_double(pod)) + return -EINVAL; *value = *((double*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) @@ -165,21 +183,24 @@ SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); } -SPA_API_POD_BODY void spa_pod_body_get_string(const struct spa_pod *pod, +SPA_API_POD_BODY int spa_pod_body_get_string(const struct spa_pod *pod, const void *body, const char **value) { - const char *s = (const char *)body; + const char *s; + if (!spa_pod_is_string(pod)) + return -EINVAL; + s = (const char *)body; if (s[pod->size-1] != '\0') - s = NULL; + return -EINVAL; *value = s; + return 0; } SPA_API_POD_BODY int spa_pod_body_copy_string(const struct spa_pod *pod, const void *body, char *dest, size_t maxlen) { const char *s; - spa_pod_body_get_string(pod, body, &s); - if (s == NULL || maxlen < 1) + if (spa_pod_body_get_string(pod, body, &s) < 0 || maxlen < 1) return -EINVAL; strncpy(dest, s, maxlen-1); dest[maxlen-1]= '\0'; @@ -191,11 +212,14 @@ SPA_API_POD_BODY int spa_pod_is_bytes(const struct spa_pod *pod) return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); } -SPA_API_POD_BODY void spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, const void **value, uint32_t *len) { + if (!spa_pod_is_bytes(pod)) + return -EINVAL; *value = (const void *)body; *len = pod->size; + return 0; } SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) @@ -203,11 +227,14 @@ SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); } -SPA_API_POD_BODY void spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, uint32_t *type, const void **value) { + if (!spa_pod_is_pointer(pod)) + return -EINVAL; *type = ((struct spa_pod_pointer_body*)body)->type; *value = ((struct spa_pod_pointer_body*)body)->value; + return 0; } SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) @@ -215,10 +242,13 @@ SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); } -SPA_API_POD_BODY void spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, int64_t *value) { + if (!spa_pod_is_fd(pod)) + return -EINVAL; *value = *((int64_t*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) @@ -226,42 +256,54 @@ SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); } -SPA_API_POD_BODY void spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, struct spa_rectangle *value) { + if (!spa_pod_is_rectangle(pod)) + return -EINVAL; *value = *((struct spa_rectangle*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_fraction(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); } -SPA_API_POD_BODY void spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, struct spa_fraction *value) { + if (!spa_pod_is_fraction(pod)) + return -EINVAL; *value = *((struct spa_fraction*)body); + return 0; } SPA_API_POD_BODY int spa_pod_is_bitmap(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); } -SPA_API_POD_BODY void spa_pod_body_get_bitmap(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_bitmap(const struct spa_pod *pod, const void *body, const uint8_t **value) { + if (!spa_pod_is_bitmap(pod)) + return -EINVAL; *value = (const uint8_t *)body; + return 0; } SPA_API_POD_BODY int spa_pod_is_array(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } -SPA_API_POD_BODY void spa_pod_body_get_array(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_array(const struct spa_pod *pod, const void *body, struct spa_pod_array *arr, const void **arr_body) { + if (!spa_pod_is_array(pod)) + return -EINVAL; arr->pod = *pod; memcpy(&arr->body, body, sizeof(struct spa_pod_array_body)); *arr_body = SPA_PTROFF(body, sizeof(struct spa_pod_array_body), void); + return 0; } SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_array *arr, const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) @@ -277,7 +319,8 @@ SPA_API_POD_BODY const void *spa_pod_body_get_array_values(const struct spa_pod const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { struct spa_pod_array arr; - spa_pod_body_get_array(pod, body, &arr, &body); + if (spa_pod_body_get_array(pod, body, &arr, &body) < 0) + return NULL; return spa_pod_array_body_get_values(&arr, body, n_values, val_size, val_type); } @@ -285,12 +328,15 @@ SPA_API_POD_BODY int spa_pod_is_choice(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); } -SPA_API_POD_BODY void spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, struct spa_pod_choice *choice, const void **choice_body) { + if (!spa_pod_is_choice(pod)) + return -EINVAL; choice->pod = *pod; memcpy(&choice->body, body, sizeof(struct spa_pod_choice_body)); *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); + return 0; } SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod_choice *pod, const void *body, uint32_t *n_values, uint32_t *choice, @@ -315,24 +361,30 @@ SPA_API_POD_BODY int spa_pod_is_object(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); } -SPA_API_POD_BODY void spa_pod_body_get_object(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_object(const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { + if (!spa_pod_is_object(pod)) + return -EINVAL; object->pod = *pod; memcpy(&object->body, body, sizeof(struct spa_pod_object_body)); *object_body = SPA_PTROFF(body, sizeof(struct spa_pod_object_body), void); + return 0; } SPA_API_POD_BODY int spa_pod_is_sequence(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); } -SPA_API_POD_BODY void spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, +SPA_API_POD_BODY int spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, struct spa_pod_sequence *seq, const void **seq_body) { + if (!spa_pod_is_sequence(pod)) + return -EINVAL; seq->pod = *pod; memcpy(&seq->body, body, sizeof(struct spa_pod_sequence_body)); *seq_body = SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), void); + return 0; } /** diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 452b447c8..a0da580ea 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -130,112 +130,72 @@ SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offse SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) { - if (!spa_pod_is_bool(pod)) - return -EINVAL; - spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) { - if (!spa_pod_is_id(pod)) - return -EINVAL; - spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) { - if (!spa_pod_is_int(pod)) - return -EINVAL; - spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) { - if (!spa_pod_is_long(pod)) - return -EINVAL; - spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) { - if (!spa_pod_is_float(pod)) - return -EINVAL; - spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) { - if (!spa_pod_is_double(pod)) - return -EINVAL; - spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { - if (!spa_pod_is_string(pod)) - return -EINVAL; - spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); - return *value ? 0 : -EINVAL; + return spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { - if (!spa_pod_is_string(pod)) - return -EINVAL; return spa_pod_body_copy_string(pod, SPA_POD_BODY_CONST(pod), dest, maxlen); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) { - if (!spa_pod_is_bytes(pod)) - return -EINVAL; - spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); - return 0; + return spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); } SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) { - if (!spa_pod_is_pointer(pod)) - return -EINVAL; - spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); - return 0; + return spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); } SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) { - if (!spa_pod_is_fd(pod)) - return -EINVAL; - spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) { - if (!spa_pod_is_rectangle(pod)) - return -EINVAL; - spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) { - if (!spa_pod_is_fraction(pod)) - return -EINVAL; - spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); - return 0; + return spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER void *spa_pod_get_array_full(const struct spa_pod *pod, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { - if (!spa_pod_is_array(pod)) - return NULL; return (void*)spa_pod_body_get_array_values(pod, SPA_POD_BODY(pod), n_values, val_size, val_type); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 8e26902dd..115d20b2e 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -206,10 +206,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bo const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_bool(&pod)) - return -EINVAL; - spa_pod_body_get_bool(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_bool(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -220,10 +218,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_id(&pod)) - return -EINVAL; - spa_pod_body_get_id(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_id(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -234,10 +230,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_int(&pod)) - return -EINVAL; - spa_pod_body_get_int(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_int(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -248,10 +242,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_long(struct spa_pod_parser *parser, in const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_long(&pod)) - return -EINVAL; - spa_pod_body_get_long(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_long(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -262,10 +254,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_float(struct spa_pod_parser *parser, f const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_float(&pod)) - return -EINVAL; - spa_pod_body_get_float(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_float(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -276,10 +266,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_double(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_double(&pod)) - return -EINVAL; - spa_pod_body_get_double(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_double(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -290,10 +278,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_string(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_string(&pod)) - return -EINVAL; - spa_pod_body_get_string(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_string(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -304,10 +290,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, c const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_bytes(&pod)) - return -EINVAL; - spa_pod_body_get_bytes(&pod, body, value, len); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_bytes(&pod, body, value, len)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -318,10 +302,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_pointer(&pod)) - return -EINVAL; - spa_pod_body_get_pointer(&pod, body, type, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_pointer(&pod, body, type, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -332,10 +314,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int6 const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_fd(&pod)) - return -EINVAL; - spa_pod_body_get_fd(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_fd(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -346,10 +326,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_rectangle(struct spa_pod_parser *parse const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_rectangle(&pod)) - return -EINVAL; - spa_pod_body_get_rectangle(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_rectangle(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -360,10 +338,8 @@ SPA_API_POD_PARSER int spa_pod_parser_get_fraction(struct spa_pod_parser *parser const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_fraction(&pod)) - return -EINVAL; - spa_pod_body_get_fraction(&pod, body, value); - spa_pod_parser_advance(parser, &pod); + if ((res = spa_pod_body_get_fraction(&pod, body, value)) >= 0) + spa_pod_parser_advance(parser, &pod); return res; } @@ -423,10 +399,12 @@ SPA_API_POD_PARSER int spa_pod_parser_init_object_body(struct spa_pod_parser *pa struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { + int res; if (!spa_pod_is_object(pod)) return -EINVAL; spa_pod_parser_init_pod_body(parser, pod, body); - spa_pod_body_get_object(pod, body, object, object_body); + if ((res = spa_pod_body_get_object(pod, body, object, object_body)) < 0) + return res; spa_pod_parser_push(parser, frame, pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; @@ -440,9 +418,8 @@ SPA_API_POD_PARSER int spa_pod_parser_push_object_body(struct spa_pod_parser *pa const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_object(&pod)) - return -EINVAL; - spa_pod_body_get_object(&pod, body, object, object_body); + if ((res = spa_pod_body_get_object(&pod, body, object, object_body)) < 0) + return res; spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; @@ -482,9 +459,8 @@ SPA_API_POD_PARSER int spa_pod_parser_push_sequence_body(struct spa_pod_parser * const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; - if (!spa_pod_is_sequence(&pod)) - return -EINVAL; - spa_pod_body_get_sequence(&pod, body, seq, seq_body); + if ((res = spa_pod_body_get_sequence(&pod, body, seq, seq_body)) < 0) + return res; spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_sequence); return 0; @@ -537,7 +513,8 @@ SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *po return false; if (type == 'V' || type == 'W') return true; - spa_pod_body_get_choice(pod, body, &choice, &body); + if (spa_pod_body_get_choice(pod, body, &choice, &body) < 0) + return false; if (choice.body.type != SPA_CHOICE_None) return false; pod = &choice.body.child; @@ -598,23 +575,41 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch do { \ switch (_type) { \ case 'b': \ - spa_pod_body_get_bool(pod, body, va_arg(args, bool*)); \ + { \ + bool *val = va_arg(args, bool*); \ + spa_pod_body_get_bool(pod, body, val); \ break; \ + } \ case 'I': \ - spa_pod_body_get_id(pod, body, va_arg(args, uint32_t*)); \ + { \ + uint32_t *val = va_arg(args, uint32_t*); \ + spa_pod_body_get_id(pod, body, val); \ break; \ + } \ case 'i': \ - spa_pod_body_get_int(pod, body, va_arg(args, int32_t*)); \ + { \ + int32_t *val = va_arg(args, int32_t*); \ + spa_pod_body_get_int(pod, body, val); \ break; \ + } \ case 'l': \ - spa_pod_body_get_long(pod, body, va_arg(args, int64_t*)); \ + { \ + int64_t *val = va_arg(args, int64_t*); \ + spa_pod_body_get_long(pod, body, val); \ break; \ + } \ case 'f': \ - spa_pod_body_get_float(pod, body, va_arg(args, float*)); \ + { \ + float *val = va_arg(args, float*); \ + spa_pod_body_get_float(pod, body, val); \ break; \ + } \ case 'd': \ - spa_pod_body_get_double(pod, body, va_arg(args, double*)); \ + { \ + double *val = va_arg(args, double*); \ + spa_pod_body_get_double(pod, body, val); \ break; \ + } \ case 's': \ { \ const char **dest = va_arg(args, const char**); \ @@ -639,25 +634,30 @@ do { \ break; \ } \ case 'R': \ - spa_pod_body_get_rectangle(pod, body, \ - va_arg(args, struct spa_rectangle*)); \ + { \ + struct spa_rectangle *val = va_arg(args, struct spa_rectangle*); \ + spa_pod_body_get_rectangle(pod, body, val); \ break; \ + } \ case 'F': \ - spa_pod_body_get_fraction(pod, body, \ - va_arg(args, struct spa_fraction*)); \ + { \ + struct spa_fraction *val = va_arg(args, struct spa_fraction*); \ + spa_pod_body_get_fraction(pod, body, val); \ break; \ + } \ case 'B': \ - spa_pod_body_get_bitmap(pod, body, va_arg(args, const uint8_t**)); \ + { \ + const uint8_t **val = va_arg(args, const uint8_t**); \ + spa_pod_body_get_bitmap(pod, body, val); \ break; \ + } \ case 'a': \ { \ - struct spa_pod_array arr; \ uint32_t *val_size = va_arg(args, uint32_t*); \ uint32_t *val_type = va_arg(args, uint32_t*); \ uint32_t *n_values = va_arg(args, uint32_t*); \ const void **arr_body = va_arg(args, const void**); \ - spa_pod_body_get_array(pod, body, &arr, arr_body); \ - spa_pod_array_body_get_values(&arr, *arr_body, \ + *arr_body = spa_pod_body_get_array_values(pod, body, \ n_values, val_size, val_type); \ break; \ } \ @@ -669,8 +669,11 @@ do { \ break; \ } \ case 'h': \ - spa_pod_body_get_fd(pod, body, va_arg(args, int64_t*)); \ + { \ + int64_t *val = va_arg(args, int64_t*); \ + spa_pod_body_get_fd(pod, body, val); \ break; \ + } \ case 'P': \ case 'T': \ case 'O': \ @@ -789,7 +792,8 @@ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_lis struct spa_pod_choice choice; if (pod.type == SPA_TYPE_Choice && *format != 'V') { - spa_pod_body_get_choice(&pod, body, &choice, &body); + if (spa_pod_body_get_choice(&pod, body, &choice, &body) < 0) + return -EINVAL; pod = choice.body.child; } From 88d7d5706a19eb99e39e664724e38fb0411130d6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 1 Aug 2025 10:49:26 +0200 Subject: [PATCH 0666/1014] conf: fix priority of the overrides A config file with a higher level should override one with a lower level. Fixes #4816 --- src/pipewire/conf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c index 387a3ab9e..c29d45648 100644 --- a/src/pipewire/conf.c +++ b/src/pipewire/conf.c @@ -441,7 +441,7 @@ static bool check_override(struct pw_properties *conf, const char *name, int lev continue; if (sscanf(it->key, "override.%d.%d.config.name", &lev, &idx) != 2) continue; - if (lev < level) + if (lev > level) return false; } return true; From 0b647a9009338792a3bdf4225acb5b9f20494c98 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 1 Aug 2025 16:23:37 +0200 Subject: [PATCH 0667/1014] pod: fuse can_collect, SKIP and COLLECT Make one COLLECT function that always reads all the varargs (SKIP) and then tries to collect the pod+body with type checks (can_collect + COLLECT). This makes things nicer because we can do everything in one go and we only do one type check. --- spa/include/spa/pod/parser.h | 157 ++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 115d20b2e..340fbd699 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -498,6 +498,7 @@ SPA_API_POD_PARSER int spa_pod_parser_object_find_prop(struct spa_pod_parser *pa if (prop->key == key) return 0; } + *body = NULL; return -ENOENT; } @@ -571,84 +572,93 @@ SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, ch return spa_pod_parser_body_can_collect(pod, SPA_POD_BODY_CONST(pod), type); } -#define SPA_POD_PARSER_COLLECT_BODY(pod,body,_type,args) \ -do { \ +#define SPA_POD_PARSER_COLLECT_BODY(_pod,_body,_type,args) \ +({ \ + int res = 0; \ + struct spa_pod_choice choice; \ + const struct spa_pod *_p = _pod; \ + const void *_b = _body; \ + if (_p->type == SPA_TYPE_Choice && _type != 'V' && _type != 'W') { \ + if (spa_pod_body_get_choice(_p, _b, &choice, &_b) >= 0 && \ + choice.body.type == SPA_CHOICE_None) \ + _p = &choice.body.child; \ + } \ switch (_type) { \ case 'b': \ { \ bool *val = va_arg(args, bool*); \ - spa_pod_body_get_bool(pod, body, val); \ + res = spa_pod_body_get_bool(_p, _b, val); \ break; \ } \ case 'I': \ { \ uint32_t *val = va_arg(args, uint32_t*); \ - spa_pod_body_get_id(pod, body, val); \ + res = spa_pod_body_get_id(_p, _b, val); \ break; \ } \ case 'i': \ { \ int32_t *val = va_arg(args, int32_t*); \ - spa_pod_body_get_int(pod, body, val); \ + res = spa_pod_body_get_int(_p, _b, val); \ break; \ } \ case 'l': \ { \ int64_t *val = va_arg(args, int64_t*); \ - spa_pod_body_get_long(pod, body, val); \ + res = spa_pod_body_get_long(_p, _b, val); \ break; \ } \ case 'f': \ { \ float *val = va_arg(args, float*); \ - spa_pod_body_get_float(pod, body, val); \ + res = spa_pod_body_get_float(_p, _b, val); \ break; \ } \ case 'd': \ { \ double *val = va_arg(args, double*); \ - spa_pod_body_get_double(pod, body, val); \ + res = spa_pod_body_get_double(_p, _b, val); \ break; \ } \ case 's': \ { \ const char **dest = va_arg(args, const char**); \ - if ((pod)->type == SPA_TYPE_None) \ + if (_p->type == SPA_TYPE_None) \ *dest = NULL; \ else \ - spa_pod_body_get_string(pod, body, dest); \ + res = spa_pod_body_get_string(_p, _b, dest); \ break; \ } \ case 'S': \ { \ char *dest = va_arg(args, char*); \ uint32_t maxlen = va_arg(args, uint32_t); \ - spa_pod_body_copy_string(pod, body, dest, maxlen); \ + res = spa_pod_body_copy_string(_p, _b, dest, maxlen); \ break; \ } \ case 'y': \ { \ const void **value = va_arg(args, const void**); \ uint32_t *len = va_arg(args, uint32_t*); \ - spa_pod_body_get_bytes(pod, body, value, len); \ + res = spa_pod_body_get_bytes(_p, _b, value, len); \ break; \ } \ case 'R': \ { \ struct spa_rectangle *val = va_arg(args, struct spa_rectangle*); \ - spa_pod_body_get_rectangle(pod, body, val); \ + res = spa_pod_body_get_rectangle(_p, _b, val); \ break; \ } \ case 'F': \ { \ struct spa_fraction *val = va_arg(args, struct spa_fraction*); \ - spa_pod_body_get_fraction(pod, body, val); \ + res = spa_pod_body_get_fraction(_p, _b, val); \ break; \ } \ case 'B': \ { \ const uint8_t **val = va_arg(args, const uint8_t**); \ - spa_pod_body_get_bitmap(pod, body, val); \ + res = spa_pod_body_get_bitmap(_p, _b, val); \ break; \ } \ case 'a': \ @@ -657,49 +667,79 @@ do { \ uint32_t *val_type = va_arg(args, uint32_t*); \ uint32_t *n_values = va_arg(args, uint32_t*); \ const void **arr_body = va_arg(args, const void**); \ - *arr_body = spa_pod_body_get_array_values(pod, body, \ + *arr_body = spa_pod_body_get_array_values(_p, _b, \ n_values, val_size, val_type); \ + if (*arr_body == NULL) \ + res = -EINVAL; \ break; \ } \ case 'p': \ { \ uint32_t *type = va_arg(args, uint32_t*); \ const void **value = va_arg(args, const void**); \ - spa_pod_body_get_pointer(pod, body, type, value); \ + res = spa_pod_body_get_pointer(_p, _b, type, value); \ break; \ } \ case 'h': \ { \ int64_t *val = va_arg(args, int64_t*); \ - spa_pod_body_get_fd(pod, body, val); \ - break; \ - } \ - case 'P': \ - case 'T': \ - case 'O': \ - case 'V': \ - { \ - const struct spa_pod **d = va_arg(args, const struct spa_pod**); \ - if (d) \ - *d = ((pod)->type == SPA_TYPE_None) ? \ - NULL : SPA_PTROFF((body), -sizeof(struct spa_pod), const struct spa_pod); \ - break; \ - } \ - case 'Q': \ - case 'U': \ - case 'N': \ - case 'W': \ - { \ - struct spa_pod *p = va_arg(args, struct spa_pod*); \ - const void **v = va_arg(args, const void **); \ - *p = *pod; \ - *v = body; \ + res = spa_pod_body_get_fd(_p, _b, val); \ break; \ } \ default: \ + { \ + bool valid = false, do_body = false; \ + switch (_type) { \ + case 'Q': \ + do_body = true; \ + SPA_FALLTHROUGH; \ + case 'P': \ + valid = true; \ + break; \ + case 'U': \ + do_body = true; \ + SPA_FALLTHROUGH; \ + case 'T': \ + valid = spa_pod_is_struct(_p) || spa_pod_is_none(_p); \ + break; \ + case 'N': \ + do_body = true; \ + SPA_FALLTHROUGH; \ + case 'O': \ + valid = spa_pod_is_object(_p) || spa_pod_is_none(_p); \ + break; \ + case 'W': \ + do_body = true; \ + SPA_FALLTHROUGH; \ + case 'V': \ + valid = spa_pod_is_choice(_p) || spa_pod_is_none(_p); \ + break; \ + default: \ + res = -EINVAL; \ + break; \ + } \ + if (res >= 0 && do_body) { \ + struct spa_pod *p = va_arg(args, struct spa_pod*); \ + const void **v = va_arg(args, const void **); \ + if (valid && p && v) { \ + *p = *_p; \ + *v = _b; \ + } \ + } else if (res >= 0) { \ + const struct spa_pod **d = va_arg(args, const struct spa_pod**);\ + if (valid && d) \ + *d = (_p->type == SPA_TYPE_None) ? \ + NULL : \ + SPA_PTROFF((_b), -sizeof(struct spa_pod), \ + const struct spa_pod); \ + } \ + if (!valid) \ + res = -EINVAL; \ break; \ } \ -} while(false) + } \ + res; \ +}) #define SPA_POD_PARSER_COLLECT(pod,_type,args) \ SPA_POD_PARSER_COLLECT_BODY(pod, SPA_POD_BODY_CONST(pod),_type,args) @@ -752,8 +792,8 @@ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_lis return -EINVAL; do { - bool optional, have_pod = false; - struct spa_pod pod; + bool optional; + struct spa_pod pod = (struct spa_pod) { 0, SPA_TYPE_None }; const void *body = NULL; const char *format; struct spa_pod_prop prop; @@ -764,41 +804,26 @@ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_lis if (key == 0) break; - if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) { + if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) pod = prop.value; - have_pod = true; - } } if ((format = va_arg(args, char *)) == NULL) break; if (f->pod.type == SPA_TYPE_Struct) - if (spa_pod_parser_next_body(parser, &pod, &body) >= 0) - have_pod = true; + spa_pod_parser_next_body(parser, &pod, &body); if ((optional = (*format == '?'))) format++; - if (!have_pod || !spa_pod_parser_body_can_collect(&pod, body, *format)) { - if (!optional) { - if (!have_pod) - return -ESRCH; - else - return -EPROTO; - } - SPA_POD_PARSER_SKIP(*format, args); - } else { - struct spa_pod_choice choice; - - if (pod.type == SPA_TYPE_Choice && *format != 'V') { - if (spa_pod_body_get_choice(&pod, body, &choice, &body) < 0) - return -EINVAL; - pod = choice.body.child; - } - - SPA_POD_PARSER_COLLECT_BODY(&pod, body, *format, args); + if (SPA_POD_PARSER_COLLECT_BODY(&pod, body, *format, args) >= 0) { count++; + } else if (!optional) { + if (body == NULL) + return -ESRCH; + else + return -EPROTO; } } while (true); From e4b0f68e0b83e0c783d098d9c9bb84c4b5775b4b Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 25 Jul 2025 22:15:17 +0300 Subject: [PATCH 0668/1014] bluez5: telephony: implement asynchronous D-Bus calls This removes the need to call poll() on the rfcomm socket in order to wait for replies from the AG. Use a queue to buffer all the commands that are to be sent to the AG and match them to replies when they are received. Optionally associate each command with a DBusMessage that is assumed to be a method call from the telephony interface, which is then replied to when the rfcomm command reply is received. Also associate each command with a state, so that it is always deterministic what gets executed after the reply is received. On the telephony module, pass on the DBusMessage on the callbacks and add a method to allow the receiver to send a reply. Only send FAILED directly when the callback is not handled. Also, remove the return value from the Dial() command (it was not advertised on the introspection anyway) to make things easier. --- spa/plugins/bluez5/README-Telephony.md | 12 +- spa/plugins/bluez5/backend-native.c | 743 +++++++++---------------- spa/plugins/bluez5/telephony.c | 211 +++---- spa/plugins/bluez5/telephony.h | 31 +- 4 files changed, 369 insertions(+), 628 deletions(-) diff --git a/spa/plugins/bluez5/README-Telephony.md b/spa/plugins/bluez5/README-Telephony.md index 93910b057..994480af0 100644 --- a/spa/plugins/bluez5/README-Telephony.md +++ b/spa/plugins/bluez5/README-Telephony.md @@ -102,10 +102,10 @@ NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` interface, for compatibility. Call announcements are normally made available via the standard `org.freedesktop.DBus.ObjectManager` interface. -`object Dial(string number)` +`void Dial(string number)` -Initiates a new outgoing call. Returns the object path to the newly created -call. +Initiates a new outgoing call. If this succeeds, the new call is announced via +the signals. The number must be a string containing the following characters: `[0-9+*#,ABCD]{1,80}` In other words, it must be a non-empty string consisting @@ -176,12 +176,12 @@ Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed -`array{object} CreateMultiparty()` +`void CreateMultiparty()` Joins active and held calls together into a multi-party call. If one of the calls is already a multi-party call, then the other call is added to the -multiparty conversation. Returns the new list of calls participating in the -multiparty call. +multiparty conversation. Changes to the call objects are announced via the +signals. There can only be one subscriber controlled multi-party call according to the GSM specification. diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 707c40be6..0abfe7dea 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -61,6 +61,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #define MAX_HF_INDICATORS 16 +#define RFCOMM_MESSAGE_MAX_LENGTH 256 + enum { HFP_AG_INITIAL_CODEC_SETUP_NONE = 0, HFP_AG_INITIAL_CODEC_SETUP_SEND, @@ -133,6 +135,7 @@ struct transport_data { }; enum hfp_hf_state { + hfp_hf_idle, hfp_hf_brsf, hfp_hf_bac, hfp_hf_cind1, @@ -145,8 +148,8 @@ enum hfp_hf_state { hfp_hf_nrec, hfp_hf_clcc, hfp_hf_vgs, - hfp_hf_done, - hfp_hf_clcc_update + hfp_hf_clcc_update, + hfp_hf_chld1_hangup }; enum hsp_hs_state { @@ -168,7 +171,9 @@ struct rfcomm_call_data { struct rfcomm_cmd { struct spa_list link; - char* cmd; + int next_state; + DBusMessage *msg; + char cmd[RFCOMM_MESSAGE_MAX_LENGTH + 1]; }; struct codec_item { @@ -196,6 +201,15 @@ struct rfcomm { bool has_volume; struct rfcomm_volume volumes[SPA_BT_VOLUME_ID_TERM]; unsigned int broken_mic_hw_volume:1; + + unsigned int hfp_cmd_in_progress:1; + union { + enum hfp_hf_state hf_state; + enum hsp_hs_state hs_state; + int hf_or_hs_state; + }; + struct spa_list cmd_send_queue; // elements: struct rfcomm_cmd + #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE struct spa_list available_codec_list; struct spa_list supported_codec_list; @@ -211,16 +225,12 @@ struct rfcomm { unsigned int hfp_hf_nrec:1; unsigned int hfp_hf_clcc:1; unsigned int hfp_hf_cme:1; - unsigned int hfp_hf_cmd_in_progress:1; unsigned int hfp_hf_in_progress:1; unsigned int chld_supported:1; - enum hfp_hf_state hf_state; - enum hsp_hs_state hs_state; unsigned int codec; uint32_t cind_enabled_indicators; char *hf_indicators[MAX_HF_INDICATORS]; struct spa_bt_telephony_ag *telephony_ag; - struct spa_list hfp_hf_commands; struct spa_list updated_call_list; char *dialing_number; #endif @@ -443,37 +453,57 @@ static void rfcomm_free(struct rfcomm *rfcomm) free(rfcomm); } -#define RFCOMM_MESSAGE_MAX_LENGTH 256 - -/* from HF/HS to AG */ -SPA_PRINTF_FUNC(2, 3) -static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...) +static void rfcomm_cmd_done(struct rfcomm *rfcomm, char *reply) { struct impl *backend = rfcomm->backend; - char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; - ssize_t len; - va_list args; - va_start(args, format); - len = vsnprintf(message, RFCOMM_MESSAGE_MAX_LENGTH + 1, format, args); - va_end(args); + if (SPA_LIKELY (!spa_list_is_empty(&rfcomm->cmd_send_queue))) { + struct rfcomm_cmd *cmd = NULL; + cmd = spa_list_first(&rfcomm->cmd_send_queue, struct rfcomm_cmd, link); - if (len < 0) - return -EINVAL; + spa_log_debug(backend->log, "%s -> %s", cmd->cmd, reply); - if (len > RFCOMM_MESSAGE_MAX_LENGTH) - return -E2BIG; + if (cmd->msg) { + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; + uint8_t cme_error = 0; - if (rfcomm->hfp_hf_cmd_in_progress) { - spa_log_debug(backend->log, "Command in progress, postponing: %s", message); - struct rfcomm_cmd *cmd = calloc(1, sizeof(struct rfcomm_cmd)); - cmd->cmd = strndup(message, len); - spa_list_append(&rfcomm->hfp_hf_commands, &cmd->link); - return 0; + if (spa_strstartswith(reply, "+CME ERROR:")) { + cme_error = atoi(reply + strlen("+CME ERROR:")); + err = BT_TELEPHONY_ERROR_CME; + } else if (!spa_strstartswith(reply, "OK")) { + err = BT_TELEPHONY_ERROR_FAILED; + } + + telephony_send_dbus_method_reply(backend->telephony, cmd->msg, err, cme_error); + spa_clear_ptr(cmd->msg, dbus_message_unref); + } + + spa_list_remove(&cmd->link); + free(cmd); + } else { + spa_log_warn(backend->log, "received response but no command was sent"); } - if (rfcomm->hf_state == hfp_hf_done && spa_streq(message, "AT+CLCC")) - rfcomm->hf_state = hfp_hf_clcc_update; + rfcomm->hfp_cmd_in_progress = false; +} + +static ssize_t rfcomm_send_next_cmd(struct rfcomm *rfcomm) +{ + struct impl *backend = rfcomm->backend; + struct rfcomm_cmd *cmd = NULL; + char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; + ssize_t len = 0; + + if (rfcomm->hfp_cmd_in_progress) + return -EINPROGRESS; + + if (spa_list_is_empty(&rfcomm->cmd_send_queue)) + return -ENODATA; + + cmd = spa_list_first(&rfcomm->cmd_send_queue, struct rfcomm_cmd, link); + + strncpy(message, cmd->cmd, RFCOMM_MESSAGE_MAX_LENGTH + 1); + len = strlen(message); spa_log_debug(backend->log, "RFCOMM >> %s", message); @@ -495,11 +525,43 @@ static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...) spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); } - rfcomm->hfp_hf_cmd_in_progress = true; + rfcomm->hfp_cmd_in_progress = true; + rfcomm->hf_or_hs_state = cmd->next_state; return len; } +/* from HF/HS to AG */ +SPA_PRINTF_FUNC(4, 5) +static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, int next_state, DBusMessage *m, const char *format, ...) +{ + struct impl *backend = rfcomm->backend; + spa_autofree struct rfcomm_cmd *cmd = NULL; + ssize_t len; + va_list args; + + cmd = calloc(1, sizeof(struct rfcomm_cmd)); + + va_start(args, format); + len = vsnprintf(cmd->cmd, RFCOMM_MESSAGE_MAX_LENGTH + 1, format, args); + va_end(args); + + if (len < 0) + return -EINVAL; + + if (len > RFCOMM_MESSAGE_MAX_LENGTH) + return -E2BIG; + + spa_log_debug(backend->log, "Queueing command: %s", cmd->cmd); + + cmd->next_state = next_state; + cmd->msg = m ? dbus_message_ref(m) : NULL; + spa_list_append(&rfcomm->cmd_send_queue, &cmd->link); + cmd = NULL; + + return rfcomm_send_next_cmd(rfcomm); +} + /* from AG to HF/HS */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format, ...) @@ -633,7 +695,7 @@ static bool rfcomm_hsp_ag(struct rfcomm *rfcomm, char* buf) return true; } -static void rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) +static void rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int next_state, DBusMessage *m, int id) { struct spa_bt_transport_volume *t_volume; const char *format; @@ -660,7 +722,7 @@ static void rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int id) else spa_assert_not_reached(); - rfcomm_send_cmd(rfcomm, "%s=%d", format, hw_volume); + rfcomm_send_cmd(rfcomm, next_state, m, "%s=%d", format, hw_volume); } static bool rfcomm_hsp_hs(struct rfcomm *rfcomm, char* buf) @@ -687,14 +749,18 @@ static bool rfcomm_hsp_hs(struct rfcomm *rfcomm, char* buf) } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", buf); } - } else if (spa_streq(buf, "OK")) { - if (rfcomm->hs_state == hsp_hs_init2) { - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); - rfcomm->hs_state = hsp_hs_vgs; - } else if (rfcomm->hs_state == hsp_hs_vgs) { - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); - rfcomm->hs_state = hsp_hs_vgm; + } else if (spa_streq(buf, "OK") || spa_streq(buf, "ERROR")) { + rfcomm_cmd_done(rfcomm, buf); + + if (spa_streq(buf, "OK")) { + if (rfcomm->hs_state == hsp_hs_init2) { + rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgs, NULL, SPA_BT_VOLUME_ID_RX); + } else if (rfcomm->hs_state == hsp_hs_vgs) { + rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgm, NULL, SPA_BT_VOLUME_ID_TX); + } } + + rfcomm_send_next_cmd(rfcomm); } return true; @@ -1385,136 +1451,28 @@ next_indicator: return true; } -static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token); - -static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len) -{ - struct impl *backend = rfcomm->backend; - struct pollfd fds[1]; - bool reply_found = false; - - fds[0].fd = rfcomm->source.fd; - fds[0].events = POLLIN; - while (!reply_found) { - int ret; - char tmp_buf[512]; - ssize_t tmp_len; - char *ptr, *token; - - ret = poll(fds, 1, 2000); - if (ret < 0) { - spa_log_error(backend->log, "RFCOMM poll error: %s", strerror(errno)); - goto done; - } else if (ret == 0) { - spa_log_error(backend->log, "RFCOMM poll timeout"); - goto done; - } - - if (fds[0].revents & (POLLHUP | POLLERR)) { - spa_log_info(backend->log, "lost RFCOMM connection."); - rfcomm_free(rfcomm); - return false; - } - - if (fds[0].revents & POLLIN) { - tmp_len = read(rfcomm->source.fd, tmp_buf, sizeof(tmp_buf) - 1); - if (tmp_len < 0) { - spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno)); - goto done; - } - tmp_buf[tmp_len] = '\0'; - - /* Relaxed parsing of \r\n\r\n */ - ptr = tmp_buf; - while ((token = strsep(&ptr, "\r"))) { - size_t ptr_len; - - /* Skip leading and trailing \n */ - while (*token == '\n') - ++token; - for (ptr_len = strlen(token); ptr_len > 0 && token[ptr_len - 1] == '\n'; --ptr_len) - token[ptr_len - 1] = '\0'; - - /* Skip empty */ - if (*token == '\0' /*&& buf == NULL*/) - continue; - - spa_log_debug(backend->log, "RFCOMM event: %s", token); - if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || - spa_strstartswith(token, "+CME ERROR:")) { - spa_log_debug(backend->log, "RFCOMM reply found: %s", token); - reply_found = true; - strncpy(buf, token, len); - buf[len-1] = '\0'; - } else if (!rfcomm_hfp_hf(rfcomm, token)) { - spa_log_debug(backend->log, "RFCOMM received unsupported event: %s", token); - } - } - } - } - -done: - rfcomm->hfp_hf_cmd_in_progress = false; - if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { - struct rfcomm_cmd *cmd; - cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); - spa_list_remove(&cmd->link); - spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); - rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); - free(cmd->cmd); - free(cmd); - } - - return reply_found; -} - -static void hfp_hf_get_error_from_reply(char *reply, enum spa_bt_telephony_error *err, uint8_t *cme_error) -{ - if (spa_strstartswith(reply, "+CME ERROR:")) { - *cme_error = atoi(reply + strlen("+CME ERROR:")); - *err = BT_TELEPHONY_ERROR_CME; - } else { - *err = BT_TELEPHONY_ERROR_FAILED; - } -} - -static void hfp_hf_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_answer(void *data, DBusMessage *m) { struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; - char reply[20]; - bool res; if (call_data->call->state != CALL_STATE_INCOMING) { - *err = BT_TELEPHONY_ERROR_INVALID_STATE; + telephony_send_dbus_method_reply(backend->telephony, m, BT_TELEPHONY_ERROR_INVALID_STATE, 0); return; } - rfcomm_send_cmd(rfcomm, "ATA"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to answer call"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } - - *err = BT_TELEPHONY_ERROR_NONE; + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "ATA"); } -static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_hangup(void *data, DBusMessage *m) { struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; - struct spa_bt_telephony_call *call, *tcall; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; + struct spa_bt_telephony_call *call; bool found_held = false; - bool hfp_hf_in_progress = false; - char reply[20]; - bool res; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) @@ -1528,64 +1486,37 @@ static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t case CALL_STATE_INCOMING: if (found_held) { if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } - rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); - hfp_hf_in_progress = true; + rfcomm_send_cmd(rfcomm, hfp_hf_chld1_hangup, m, "AT+CHLD=1"); + rfcomm->hfp_hf_in_progress = true; } else { - rfcomm_send_cmd(rfcomm, "AT+CHUP"); + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHUP"); } break; case CALL_STATE_HELD: case CALL_STATE_WAITING: if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } - rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); - hfp_hf_in_progress = true; + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=0"); + rfcomm->hfp_hf_in_progress = true; break; default: - spa_log_info(backend->log, "Call invalid state: skip hangup"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; + spa_log_warn(backend->log, "Call in invalid state: skip hangup"); + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to hangup call"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } - - if (hfp_hf_in_progress) { - if (!rfcomm->hfp_hf_clcc && call_data->call->state != CALL_STATE_HELD && - call_data->call->state != CALL_STATE_WAITING) { - spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { - if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_DISCONNECTED; - telephony_call_notify_updated_props(call); - telephony_call_destroy(call); - } - } - spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { - if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_ACTIVE; - telephony_call_notify_updated_props(call); - } - } - } - rfcomm->hfp_hf_in_progress = true; - } - *err = BT_TELEPHONY_ERROR_NONE; + return; +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static const struct spa_bt_telephony_call_callbacks telephony_call_callbacks = { @@ -1615,45 +1546,33 @@ static struct spa_bt_telephony_call *hfp_hf_add_call(struct rfcomm *rfcomm, stru return call; } -static void hfp_hf_dial(void *data, const char *number, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_dial(void *data, const char *number, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; - char reply[20]; - bool res; + + /* store the number in case we need to create the Call object + via CIND notifications (if CLCC is not supported) */ + free(spa_exchange(rfcomm->dialing_number, strdup(number))); spa_log_info(backend->log, "Dialing: \"%s\"", number); - rfcomm_send_cmd(rfcomm, "ATD%s;", number); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (res && spa_strstartswith(reply, "OK")) { - if (rfcomm->dialing_number) - free(rfcomm->dialing_number); - rfcomm->dialing_number = strdup(number); - *err = BT_TELEPHONY_ERROR_NONE; - } else { - spa_log_info(backend->log, "Failed to dial: \"%s\"", number); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "ATD%s;", number); } -static void hfp_hf_swap_calls(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_swap_calls(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_held = false; - char reply[20]; - bool res; if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { @@ -1665,41 +1584,33 @@ static void hfp_hf_swap_calls(void *data, enum spa_bt_telephony_error *err, uint if (!found_held) { spa_log_debug(backend->log, "no held calls"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - - rfcomm_send_cmd(rfcomm, "AT+CHLD=2"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to swap calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=2"); rfcomm->hfp_hf_in_progress = true; - *err = BT_TELEPHONY_ERROR_NONE; + return; + +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_release_and_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_release_and_answer(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_waiting = false; - char reply[20]; - bool res; if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { @@ -1711,48 +1622,40 @@ static void hfp_hf_release_and_answer(void *data, enum spa_bt_telephony_error *e if (!found_active || !found_waiting) { spa_log_debug(backend->log, "no active and waiting calls"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - - rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to release and answer calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=1"); rfcomm->hfp_hf_in_progress = true; - *err = BT_TELEPHONY_ERROR_NONE; + return; + +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_release_and_swap(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_release_and_swap(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; - char reply[20]; - bool res; if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { spa_log_debug(backend->log, "call waiting before release and swap"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } else if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_HELD) @@ -1761,41 +1664,33 @@ static void hfp_hf_release_and_swap(void *data, enum spa_bt_telephony_error *err if (!found_active || !found_held) { spa_log_debug(backend->log, "no active and held calls"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - - rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to release and swap calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=1"); rfcomm->hfp_hf_in_progress = true; - *err = BT_TELEPHONY_ERROR_NONE; + return; + +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_hold_and_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_hold_and_answer(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_waiting = false; - char reply[20]; - bool res; if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { @@ -1807,34 +1702,24 @@ static void hfp_hf_hold_and_answer(void *data, enum spa_bt_telephony_error *err, if (!found_active || !found_waiting) { spa_log_debug(backend->log, "no active and waiting calls"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - - rfcomm_send_cmd(rfcomm, "AT+CHLD=2"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to hold and answer calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=2"); rfcomm->hfp_hf_in_progress = true; - *err = BT_TELEPHONY_ERROR_NONE; + return; + +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_hangup_all(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_hangup_all(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; - struct impl *backend = rfcomm->backend; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; - char reply[20]; - bool res; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { switch (call->state) { @@ -1853,58 +1738,39 @@ static void hfp_hf_hangup_all(void *data, enum spa_bt_telephony_error *err, uint } } - *err = BT_TELEPHONY_ERROR_NONE; - /* Hangup held calls */ if (found_held) { - rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to hangup held calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, found_active ? NULL : m, "AT+CHLD=0"); } /* Hangup active calls */ if (found_active) { - rfcomm_send_cmd(rfcomm, "AT+CHUP"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to hangup active calls"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHUP"); } } -static void hfp_hf_create_multiparty(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_create_multiparty(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; - char reply[20]; - bool res; if (!rfcomm->chld_supported) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto error; } else if (rfcomm->hfp_hf_in_progress) { - *err = BT_TELEPHONY_ERROR_IN_PROGRESS; - return; + err = BT_TELEPHONY_ERROR_IN_PROGRESS; + goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { spa_log_debug(backend->log, "call waiting before creating multiparty"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } else if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_HELD) @@ -1913,33 +1779,25 @@ static void hfp_hf_create_multiparty(void *data, enum spa_bt_telephony_error *er if (!found_active || !found_held) { spa_log_debug(backend->log, "no active and held calls"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; - } - - rfcomm_send_cmd(rfcomm, "AT+CHLD=3"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to create multiparty"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=3"); rfcomm->hfp_hf_in_progress = true; - *err = BT_TELEPHONY_ERROR_NONE; + return; + +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_send_tones(void *data, const char *tones, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_send_tones(void *data, const char *tones, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found = false; - char reply[20]; - bool res; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { @@ -1950,69 +1808,51 @@ static void hfp_hf_send_tones(void *data, const char *tones, enum spa_bt_telepho if (!found) { spa_log_debug(backend->log, "no active call"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto error; } - rfcomm_send_cmd(rfcomm, "AT+VTS=%s", tones); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to send tones: %s", tones); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+VTS=%s", tones); + return; - *err = BT_TELEPHONY_ERROR_NONE; +error: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static int sco_do_connect(struct spa_bt_transport *t); -static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_transport_activate(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; - char reply[20]; - bool res; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; if (rfcomm->transport && rfcomm->transport->fd > 0) { spa_log_debug(backend->log, "transport is already active; SCO socket exists"); - *err = BT_TELEPHONY_ERROR_INVALID_STATE; - return; + err = BT_TELEPHONY_ERROR_INVALID_STATE; + goto out; } if (rfcomm->codec_negotiation_supported) { - rfcomm_send_cmd(rfcomm, "AT+BCC"); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to send AT+BCC"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } + rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+BCC"); + return; } else { if (!rfcomm->transport || rfcomm->transport->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { - *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; - return; + err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + goto out; } sco_do_connect(rfcomm->transport); } - *err = BT_TELEPHONY_ERROR_NONE; +out: + telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } -static void hfp_hf_set_speaker_volume(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_set_speaker_volume(void *data, uint8_t volume, DBusMessage *m) { struct rfcomm *rfcomm = data; - struct impl *backend = rfcomm->backend; struct spa_bt_transport_volume *t_volume; - char reply[20]; - bool res; rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = volume; if (rfcomm_hw_volume_enabled(rfcomm)) { @@ -2024,27 +1864,13 @@ static void hfp_hf_set_speaker_volume(void *data, uint8_t volume, enum spa_bt_te } } - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to send AT+VGS"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } - - *err = BT_TELEPHONY_ERROR_NONE; + rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, m, SPA_BT_VOLUME_ID_RX); } -static void hfp_hf_set_microphone_volume(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error) +static void hfp_hf_set_microphone_volume(void *data, uint8_t volume, DBusMessage *m) { struct rfcomm *rfcomm = data; - struct impl *backend = rfcomm->backend; struct spa_bt_transport_volume *t_volume; - char reply[20]; - bool res; rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = volume; if (rfcomm_hw_volume_enabled(rfcomm)) { @@ -2056,18 +1882,7 @@ static void hfp_hf_set_microphone_volume(void *data, uint8_t volume, enum spa_bt } } - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); - res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); - if (!res || !spa_strstartswith(reply, "OK")) { - spa_log_info(backend->log, "Failed to send AT+VGM"); - if (res) - hfp_hf_get_error_from_reply(reply, err, cme_error); - else - *err = BT_TELEPHONY_ERROR_FAILED; - return; - } - - *err = BT_TELEPHONY_ERROR_NONE; + rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, m, SPA_BT_VOLUME_ID_TX); } static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { @@ -2136,7 +1951,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); /* send codec selection to AG */ - rfcomm_send_cmd(rfcomm, "AT+BCS=%u", selected_codec); + rfcomm_send_cmd(rfcomm, hfp_hf_idle, NULL, "AT+BCS=%u", selected_codec); if (!rfcomm->transport || (rfcomm->codec != selected_codec) ) { if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { @@ -2219,7 +2034,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5); } else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) { if (rfcomm->hfp_hf_clcc) { - rfcomm_send_cmd(rfcomm, "AT+CLCC"); + rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } @@ -2266,10 +2081,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_log_info(backend->log, "Dialing call"); if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, rfcomm->dialing_number) == NULL) spa_log_warn(backend->log, "failed to create dialing call"); - if (rfcomm->dialing_number) { - free(rfcomm->dialing_number); - rfcomm->dialing_number = NULL; - } + spa_clear_ptr(rfcomm->dialing_number, free); } } else if (value == CIND_CALLSETUP_ALERTING) { struct spa_bt_telephony_call *call; @@ -2284,7 +2096,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "call")) { if (rfcomm->hfp_hf_clcc) { - rfcomm_send_cmd(rfcomm, "AT+CLCC"); + rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } @@ -2311,7 +2123,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "callheld")) { if (rfcomm->hfp_hf_clcc) { - rfcomm_send_cmd(rfcomm, "AT+CLCC"); + rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } @@ -2499,6 +2311,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hfp_hf_in_progress = false; } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || spa_strstartswith(token, "+CME ERROR:")) { + + rfcomm_cmd_done(rfcomm, token); + if (spa_strstartswith(token, "OK")) { switch(rfcomm->hf_state) { case hfp_hf_brsf: @@ -2512,29 +2327,23 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(item, &rfcomm->available_codec_list, link) spa_strbuf_append(&str, ",%u", item->codec->codec_id); - rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); - rfcomm->hf_state = hfp_hf_bac; + rfcomm_send_cmd(rfcomm, hfp_hf_bac, NULL, "AT+BAC=%s", buf); } else { - rfcomm_send_cmd(rfcomm, "AT+CIND=?"); - rfcomm->hf_state = hfp_hf_cind1; + rfcomm_send_cmd(rfcomm, hfp_hf_cind1, NULL, "AT+CIND=?"); } break; case hfp_hf_bac: - rfcomm_send_cmd(rfcomm, "AT+CIND=?"); - rfcomm->hf_state = hfp_hf_cind1; + rfcomm_send_cmd(rfcomm, hfp_hf_cind1, NULL, "AT+CIND=?"); break; case hfp_hf_cind1: - rfcomm_send_cmd(rfcomm, "AT+CIND?"); - rfcomm->hf_state = hfp_hf_cind2; + rfcomm_send_cmd(rfcomm, hfp_hf_cind2, NULL, "AT+CIND?"); break; case hfp_hf_cind2: - rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); - rfcomm->hf_state = hfp_hf_cmer; + rfcomm_send_cmd(rfcomm, hfp_hf_cmer, NULL, "AT+CMER=3,0,0,1"); break; case hfp_hf_cmer: if (rfcomm->hfp_hf_3way) { - rfcomm_send_cmd(rfcomm, "AT+CHLD=?"); - rfcomm->hf_state = hfp_hf_chld; + rfcomm_send_cmd(rfcomm, hfp_hf_chld, NULL, "AT+CHLD=?"); break; } SPA_FALLTHROUGH; @@ -2561,34 +2370,29 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } telephony_ag_register(rfcomm->telephony_ag); - rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); - rfcomm->hf_state = hfp_hf_clip; + rfcomm_send_cmd(rfcomm, hfp_hf_clip, NULL, "AT+CLIP=1"); break; case hfp_hf_clip: if (rfcomm->chld_supported) { - rfcomm_send_cmd(rfcomm, "AT+CCWA=1"); - rfcomm->hf_state = hfp_hf_ccwa; + rfcomm_send_cmd(rfcomm, hfp_hf_ccwa, NULL, "AT+CCWA=1"); break; } SPA_FALLTHROUGH; case hfp_hf_ccwa: if (rfcomm->hfp_hf_cme) { - rfcomm_send_cmd(rfcomm, "AT+CMEE=1"); - rfcomm->hf_state = hfp_hf_cmee; + rfcomm_send_cmd(rfcomm, hfp_hf_cmee, NULL, "AT+CMEE=1"); break; } SPA_FALLTHROUGH; case hfp_hf_cmee: if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { - rfcomm_send_cmd(rfcomm, "AT+NREC=0"); - rfcomm->hf_state = hfp_hf_nrec; + rfcomm_send_cmd(rfcomm, hfp_hf_nrec, NULL, "AT+NREC=0"); break; } SPA_FALLTHROUGH; case hfp_hf_nrec: if (rfcomm->hfp_hf_clcc) { - rfcomm_send_cmd(rfcomm, "AT+CLCC"); - rfcomm->hf_state = hfp_hf_clcc; + rfcomm_send_cmd(rfcomm, hfp_hf_clcc, NULL, "AT+CLCC"); break; } else { // TODO: Create calls if CIND reports one during SLC setup @@ -2600,32 +2404,40 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (rfcomm->hf_state == hfp_hf_clcc) { hfp_hf_remove_disconnected_calls(rfcomm); } - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); - rfcomm->hf_state = hfp_hf_vgs; + rfcomm_send_volume_cmd(rfcomm, hfp_hf_vgs, NULL, SPA_BT_VOLUME_ID_RX); break; case hfp_hf_vgs: - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX); - rfcomm->hf_state = hfp_hf_done; + rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, NULL, SPA_BT_VOLUME_ID_TX); break; case hfp_hf_clcc_update: hfp_hf_remove_disconnected_calls(rfcomm); - rfcomm->hf_state = hfp_hf_done; break; + case hfp_hf_chld1_hangup: + /* For HFP/HF/TWC/BV-03-C - see 0e92ab9307e05758b3f70b4c0648e29c1d1e50be */ + if (!rfcomm->hfp_hf_clcc) { + struct spa_bt_telephony_call *call, *tcall; + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) { + call->state = CALL_STATE_ACTIVE; + telephony_call_notify_updated_props(call); + } + } + } + break; + case hfp_hf_idle: default: break; } } - rfcomm->hfp_hf_cmd_in_progress = false; - if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { - struct rfcomm_cmd *cmd; - cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); - spa_list_remove(&cmd->link); - spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); - rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); - free(cmd->cmd); - free(cmd); - } + rfcomm_send_next_cmd(rfcomm); } return true; @@ -2798,7 +2610,7 @@ static int sco_do_connect(struct spa_bt_transport *t) td->rfcomm->hfp_ag_switching_codec = true; rfcomm_send_reply(td->rfcomm, "+BCS: 1"); } else if (t->profile == SPA_BT_PROFILE_HFP_AG) { - rfcomm_send_cmd(td->rfcomm, "AT+BAC=1"); + rfcomm_send_cmd(td->rfcomm, hfp_hf_idle, NULL, "AT+BAC=1"); } } #endif @@ -3151,14 +2963,12 @@ static void sco_listen_event(struct spa_source *source) /* Report initial volume to remote */ if (t->profile == SPA_BT_PROFILE_HSP_AG) { - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); - rfcomm->hs_state = hsp_hs_vgs; + rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgs, NULL, SPA_BT_VOLUME_ID_RX); } else if (t->profile == SPA_BT_PROFILE_HFP_AG && rfcomm->hf_state > hfp_hf_vgs) { /* Report volume only if SLC and setup sequence has been completed * else this could break the sequence. * The volumes will be reported at the end of the setup sequence. */ - rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX); - rfcomm->hf_state = hfp_hf_vgs; + rfcomm_send_volume_cmd(rfcomm, hfp_hf_vgs, NULL, SPA_BT_VOLUME_ID_RX); } spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_PENDING); @@ -3567,7 +3377,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->source.fd = spa_steal_fd(fd); rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; - spa_list_init(&rfcomm->hfp_hf_commands); + spa_list_init(&rfcomm->cmd_send_queue); spa_list_init(&rfcomm->updated_call_list); /* By default all indicators are enabled */ rfcomm->cind_enabled_indicators = 0xFFFFFFFF; @@ -3619,9 +3429,8 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; /* send command to AG with the features supported by Hands-Free */ - rfcomm_send_cmd(rfcomm, "AT+BRSF=%u", hf_features); + rfcomm_send_cmd(rfcomm, hfp_hf_brsf, NULL, "AT+BRSF=%u", hf_features); - rfcomm->hf_state = hfp_hf_brsf; } else if (profile == SPA_BT_PROFILE_HFP_HF) { make_available_codec_list(backend, rfcomm->device, &rfcomm->available_codec_list); } diff --git a/spa/plugins/bluez5/telephony.c b/spa/plugins/bluez5/telephony.c index f5887053d..7695c0b81 100644 --- a/spa/plugins/bluez5/telephony.c +++ b/spa/plugins/bluez5/telephony.c @@ -203,9 +203,6 @@ struct agimpl { struct spa_callbacks callbacks; void *user_data; - bool dial_in_progress; - struct callimpl *dial_return; - struct { int volume[SPA_BT_VOLUME_ID_TERM]; struct spa_bt_telephony_ag_transport transport; @@ -228,22 +225,22 @@ struct callimpl { } prev; }; -#define ag_emit(ag,m,v,...) spa_callbacks_call(&ag->callbacks, struct spa_bt_telephony_ag_callbacks, m, v, ##__VA_ARGS__) -#define ag_emit_dial(s,n,e,cme) ag_emit(s,dial,0,n,e,cme) -#define ag_emit_swap_calls(s,e,cme) ag_emit(s,swap_calls,0,e,cme) -#define ag_emit_release_and_answer(s,e,cme) ag_emit(s,release_and_answer,0,e,cme) -#define ag_emit_release_and_swap(s,e,cme) ag_emit(s,release_and_swap,0,e,cme) -#define ag_emit_hold_and_answer(s,e,cme) ag_emit(s,hold_and_answer,0,e,cme) -#define ag_emit_hangup_all(s,e,cme) ag_emit(s,hangup_all,0,e,cme) -#define ag_emit_create_multiparty(s,e,cme) ag_emit(s,create_multiparty,0,e,cme) -#define ag_emit_send_tones(s,t,e,cme) ag_emit(s,send_tones,0,t,e,cme) -#define ag_emit_transport_activate(s,e,cme) ag_emit(s,transport_activate,0,e,cme) -#define ag_emit_set_speaker_volume(s,v,e,cme) ag_emit(s,set_speaker_volume,0,v,e,cme) -#define ag_emit_set_microphone_volume(s,v,e,cme) ag_emit(s,set_microphone_volume,0,v,e,cme) +#define ag_emit(ag,m,v,...) spa_callbacks_call(&ag->callbacks, struct spa_bt_telephony_ag_callbacks, m, v, ##__VA_ARGS__) +#define ag_emit_dial(s,n,m) ag_emit(s,dial,0,n,m) +#define ag_emit_swap_calls(s,m) ag_emit(s,swap_calls,0,m) +#define ag_emit_release_and_answer(s,m) ag_emit(s,release_and_answer,0,m) +#define ag_emit_release_and_swap(s,m) ag_emit(s,release_and_swap,0,m) +#define ag_emit_hold_and_answer(s,m) ag_emit(s,hold_and_answer,0,m) +#define ag_emit_hangup_all(s,m) ag_emit(s,hangup_all,0,m) +#define ag_emit_create_multiparty(s,m) ag_emit(s,create_multiparty,0,m) +#define ag_emit_send_tones(s,t,m) ag_emit(s,send_tones,0,t,m) +#define ag_emit_transport_activate(s,m) ag_emit(s,transport_activate,0,m) +#define ag_emit_set_speaker_volume(s,v,m) ag_emit(s,set_speaker_volume,0,v,m) +#define ag_emit_set_microphone_volume(s,v,m) ag_emit(s,set_microphone_volume,0,v,m) #define call_emit(c,m,v,...) spa_callbacks_call(&c->callbacks, struct spa_bt_telephony_call_callbacks, m, v, ##__VA_ARGS__) -#define call_emit_answer(s,e,cme) call_emit(s,answer,0,e,cme) -#define call_emit_hangup(s,e,cme) call_emit(s,hangup,0,e,cme) +#define call_emit_answer(s,m) call_emit(s,answer,0,m) +#define call_emit_hangup(s,m) call_emit(s,hangup,0,m) static void dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag); static void dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all); @@ -521,6 +518,21 @@ void telephony_free(struct spa_bt_telephony *telephony) free(impl); } +void telephony_send_dbus_method_reply(struct spa_bt_telephony *telephony, DBusMessage *m, + enum spa_bt_telephony_error err, uint8_t cme_error) +{ + struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); + spa_autoptr(DBusMessage) reply = NULL; + + if (err == BT_TELEPHONY_ERROR_NONE) + reply = dbus_message_new_method_return(m); + else + reply = dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); + + dbus_connection_send(impl->conn, reply, NULL); +} + static void telephony_ag_commit_properties(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); @@ -859,9 +871,6 @@ static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) return NULL; if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - if (spa_streq(name, "SpeakerVolume")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ @@ -869,12 +878,10 @@ static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) dbus_message_iter_recurse(&i, &variant); /* value */ dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_RX]); - if (ag_emit_set_speaker_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_RX], &err, &cme_error) && - err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); + return ag_emit_set_speaker_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_RX], m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); } else if (spa_streq(name, "MicrophoneVolume")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ @@ -882,12 +889,9 @@ static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) dbus_message_iter_recurse(&i, &variant); /* value */ dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_TX]); - if (ag_emit_set_microphone_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_TX], &err, &cme_error) && - err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_set_microphone_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_TX], m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { if (spa_streq(name, "RejectSCO")) { @@ -939,8 +943,6 @@ static DBusMessage *ag_dial(struct agimpl *agimpl, DBusMessage *m) { const char *number = NULL; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - spa_autoptr(DBusMessage) r = NULL; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &number, @@ -952,111 +954,60 @@ static DBusMessage *ag_dial(struct agimpl *agimpl, DBusMessage *m) goto failed; } - agimpl->dial_in_progress = true; - if (!ag_emit_dial(agimpl, number, &err, &cme_error)) { - agimpl->dial_in_progress = false; - goto failed; - } - agimpl->dial_in_progress = false; - - if (!agimpl->dial_return || !agimpl->dial_return->path) - err = BT_TELEPHONY_ERROR_FAILED; - - if (err != BT_TELEPHONY_ERROR_NONE) - goto failed; - - if ((r = dbus_message_new_method_return(m)) == NULL) + if (ag_emit_dial(agimpl, number, m)) return NULL; - if (!dbus_message_append_args(r, DBUS_TYPE_OBJECT_PATH, - &agimpl->dial_return->path, DBUS_TYPE_INVALID)) - return NULL; - - agimpl->dial_return = NULL; - - return spa_steal_ptr(r); failed: return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + telephony_error_to_description (err, 0)); } static DBusMessage *ag_swap_calls(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_swap_calls(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_swap_calls(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_release_and_answer(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_release_and_answer(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_release_and_answer(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_release_and_swap(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_release_and_swap(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_release_and_swap(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_hold_and_answer(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_hold_and_answer(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_hold_and_answer(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_hangup_all(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_hangup_all(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_hangup_all(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_create_multiparty(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_create_multiparty(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_create_multiparty(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_send_tones(struct agimpl *agimpl, DBusMessage *m) { const char *tones = NULL; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &tones, @@ -1068,24 +1019,19 @@ static DBusMessage *ag_send_tones(struct agimpl *agimpl, DBusMessage *m) goto failed; } - if (ag_emit_send_tones(agimpl, tones, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); + if (ag_emit_send_tones(agimpl, tones, m)) + return NULL; failed: return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + telephony_error_to_description (err, 0)); } static DBusMessage *ag_transport_activate(struct agimpl *agimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (ag_emit_transport_activate(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return ag_emit_transport_activate(agimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusHandlerResult ag_handler(DBusConnection *c, DBusMessage *m, void *userdata) @@ -1144,9 +1090,7 @@ static DBusHandlerResult ag_handler(DBusConnection *c, DBusMessage *m, void *use return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - if (r == NULL) - return DBUS_HANDLER_RESULT_NEED_MEMORY; - if (!dbus_connection_send(impl->conn, r, NULL)) + if (r && !dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } @@ -1386,7 +1330,6 @@ void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, struct spa_bt_telephony_call * telephony_call_new(struct spa_bt_telephony_ag *ag, size_t user_data_size) { - struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct callimpl *callimpl; spa_assert(user_data_size < SIZE_MAX - sizeof(*callimpl)); @@ -1403,10 +1346,6 @@ telephony_call_new(struct spa_bt_telephony_ag *ag, size_t user_data_size) if (user_data_size > 0) callimpl->user_data = SPA_PTROFF(callimpl, sizeof(struct callimpl), void); - /* mark this object as the return value of the Dial method */ - if (agimpl->dial_in_progress) - agimpl->dial_return = callimpl; - return &callimpl->this; } @@ -1650,26 +1589,16 @@ static DBusMessage *call_properties_set(struct callimpl *callimpl, DBusMessage * static DBusMessage *call_answer(struct callimpl *callimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (call_emit_answer(callimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return call_emit_answer(callimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *call_hangup(struct callimpl *callimpl, DBusMessage *m) { - enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; - uint8_t cme_error; - - if (call_emit_hangup(callimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) - return dbus_message_new_method_return(m); - - return dbus_message_new_error(m, telephony_error_to_dbus (err), - telephony_error_to_description (err, cme_error)); + return call_emit_hangup(callimpl, m) ? NULL : + dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), + telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusHandlerResult call_handler(DBusConnection *c, DBusMessage *m, void *userdata) @@ -1707,9 +1636,7 @@ static DBusHandlerResult call_handler(DBusConnection *c, DBusMessage *m, void *u return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } - if (r == NULL) - return DBUS_HANDLER_RESULT_NEED_MEMORY; - if (!dbus_connection_send(impl->conn, r, NULL)) + if (r && !dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } diff --git a/spa/plugins/bluez5/telephony.h b/spa/plugins/bluez5/telephony.h index 13961b7db..1f7f67362 100644 --- a/spa/plugins/bluez5/telephony.h +++ b/spa/plugins/bluez5/telephony.h @@ -67,33 +67,38 @@ struct spa_bt_telephony_ag_callbacks { #define SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS 0 uint32_t version; - void (*dial)(void *data, const char *number, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*swap_calls)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*release_and_answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*release_and_swap)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*hold_and_answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*hangup_all)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*create_multiparty)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*send_tones)(void *data, const char *tones, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*dial)(void *data, const char *number, DBusMessage *m); + void (*swap_calls)(void *data, DBusMessage *m); + void (*release_and_answer)(void *data, DBusMessage *m); + void (*release_and_swap)(void *data, DBusMessage *m); + void (*hold_and_answer)(void *data, DBusMessage *m); + void (*hangup_all)(void *data, DBusMessage *m); + void (*create_multiparty)(void *data, DBusMessage *m); + void (*send_tones)(void *data, const char *tones, DBusMessage *m); - void (*transport_activate)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*transport_activate)(void *data, DBusMessage *m); - void (*set_speaker_volume)(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*set_microphone_volume)(void *data, uint8_t volume, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*set_speaker_volume)(void *data, uint8_t volume, DBusMessage *m); + void (*set_microphone_volume)(void *data, uint8_t volume, DBusMessage *m); }; struct spa_bt_telephony_call_callbacks { #define SPA_VERSION_BT_TELEPHONY_CALL_CALLBACKS 0 uint32_t version; - void (*answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); - void (*hangup)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*answer)(void *data, DBusMessage *m); + void (*hangup)(void *data, DBusMessage *m); }; struct spa_bt_telephony *telephony_new(struct spa_log *log, struct spa_dbus *dbus, const struct spa_dict *info); void telephony_free(struct spa_bt_telephony *telephony); +/* send a reply to any of the methods (they all return void); + this is must be called from the callbacks either in sync or async */ +void telephony_send_dbus_method_reply(struct spa_bt_telephony *telephony, + DBusMessage *m, enum spa_bt_telephony_error err, uint8_t cme_error); + /* create/destroy the ag object */ struct spa_bt_telephony_ag * telephony_ag_new(struct spa_bt_telephony *telephony, From 5ccd1c5619e589dc362299e1ccfaf6103e97768c Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 30 Jul 2025 12:13:06 +0300 Subject: [PATCH 0669/1014] bluez5: backend-native: update hfp_hf_in_progress at the end of the CLCC update When we skip the CIEV event in order to update the call list with CLCC, we may receive multiple +CLCC events or even none, if the calls are disconnected. To avoid any mistakes, update the hfp_hf_in_progress flag after the CLCC update is entirely done. --- spa/plugins/bluez5/backend-native.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 0abfe7dea..51f4129c4 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2307,8 +2307,6 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } else { spa_log_warn(backend->log, "malformed +CLCC command received from AG"); } - - rfcomm->hfp_hf_in_progress = false; } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || spa_strstartswith(token, "+CME ERROR:")) { @@ -2411,6 +2409,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) break; case hfp_hf_clcc_update: hfp_hf_remove_disconnected_calls(rfcomm); + rfcomm->hfp_hf_in_progress = false; break; case hfp_hf_chld1_hangup: /* For HFP/HF/TWC/BV-03-C - see 0e92ab9307e05758b3f70b4c0648e29c1d1e50be */ From 79a069c886fef73acc32946793bac5bd67392568 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 30 Jul 2025 12:59:50 +0300 Subject: [PATCH 0670/1014] bluez5: backend-native: add debug log in hfp_hf_remove_disconnected_calls() --- spa/plugins/bluez5/backend-native.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 51f4129c4..69b25cfd6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1902,6 +1902,7 @@ static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) { + struct impl *backend = rfcomm->backend; struct spa_bt_telephony_call *call, *call_tmp; struct updated_call *updated_call; bool found; @@ -1915,6 +1916,8 @@ static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) } } + spa_log_debug(backend->log, "call %d -> %s", call->id, found ? "updated" : "disconnected"); + if (!found) { call->state = CALL_STATE_DISCONNECTED; telephony_call_notify_updated_props(call); From e8fa7929b7ee818f2ace5adb0f889b473da04560 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 13 Jul 2025 20:08:22 +0300 Subject: [PATCH 0671/1014] bluez5: use BT_PKT_SEQNUM for ISO packet sequence numbers Use kernel BT_PKT_SEQNUM (likely in Linux v6.17) to provide the ISO packets sequence numbers. Fall back to counting packets if kernel is too old to support the feature. --- spa/plugins/bluez5/decode-buffer.h | 35 +++++++++++++++++++++--------- spa/plugins/bluez5/media-source.c | 25 ++++++++++++--------- spa/plugins/bluez5/sco-io.c | 3 ++- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 91ffdac78..0f1345572 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,12 @@ #define BUFFERING_SHORT_MSEC 1000 #define BUFFERING_RATE_DIFF_MAX 0.005 +#ifndef BT_PKT_SEQNUM +#define BT_PKT_SEQNUM 22 +#endif +#ifndef BT_SCM_PKT_SEQNUM +#define BT_SCM_PKT_SEQNUM 0x05 +#endif struct spa_bt_decode_buffer { @@ -363,10 +370,11 @@ static inline void spa_bt_recvmsg_update_clock(struct spa_bt_recvmsg_data *data, data->err += (SPA_ABS(err) - data->err) / n_avg; } -static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time) +static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time, + int *seqnum) { union { - char buf[CMSG_SPACE(sizeof(struct scm_timestamping))]; + char buf[CMSG_SPACE(sizeof(struct scm_timestamping)) + CMSG_SPACE(sizeof(uint16_t))]; struct cmsghdr align; } control; struct iovec data = { @@ -383,6 +391,8 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s uint64_t t = 0, now; ssize_t res; + *seqnum = -1; + res = recvmsg(r->fd, &msg, MSG_DONTWAIT); if (res < 0 || !rx_time) return res; @@ -392,12 +402,12 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { struct scm_timestamping *tss; - if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_TIMESTAMPING) - continue; - - tss = (struct scm_timestamping *)CMSG_DATA(cmsg); - t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]); - break; + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) { + tss = (struct scm_timestamping *)CMSG_DATA(cmsg); + t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]); + } else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_PKT_SEQNUM) { + *seqnum = *((uint16_t *)CMSG_DATA(cmsg)); + } } if (!t) { @@ -411,8 +421,8 @@ static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, s if (*rx_time > now || *rx_time + 20 * SPA_NSEC_PER_MSEC < now) *rx_time = now; - spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64, - r, *rx_time, now, now - *rx_time, r->offset); + spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64" sn:%d", + r, *rx_time, now, now - *rx_time, r->offset, *seqnum); return res; } @@ -423,6 +433,7 @@ static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd, { int flags = 0; socklen_t len = sizeof(flags); + uint32_t opt; data->log = log; data->data_system = data_system; @@ -436,6 +447,10 @@ static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd, flags |= SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)) < 0) spa_log_info(log, "failed to set SO_TIMESTAMPING"); + + opt = 1; + if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0) + spa_log_info(log, "failed to set BT_PKT_SEQNUM"); } #endif diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index d2362c827..dc3e53a7b 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -428,14 +428,14 @@ static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer } } -static int32_t read_data(struct impl *this, uint64_t *rx_time) +static int32_t read_data(struct impl *this, uint64_t *rx_time, int *seqnum) { const ssize_t b_size = sizeof(this->buffer_read); int32_t size_read = 0; again: /* read data from socket */ - size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time); + size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time, seqnum); if (size_read == 0) return 0; @@ -481,7 +481,7 @@ static int produce_plc_data(struct impl *this) } static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, - uint8_t *dst, uint32_t dst_size, uint32_t *dst_out) + uint8_t *dst, uint32_t dst_size, uint32_t *dst_out, int pkt_seqnum) { ssize_t processed; size_t written, avail; @@ -494,6 +494,9 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, src, src_avail, &seqnum, NULL)) < 0) return processed; + if (pkt_seqnum >= 0) + seqnum = pkt_seqnum; + src += processed; src_avail -= processed; @@ -552,12 +555,12 @@ static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, return src_size - src_avail; } -static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now) +static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now, int pkt_seqnum) { struct port *port = &this->port; uint32_t decoded; - spa_log_trace(this->log, "read socket data size:%d", src_size); + spa_log_trace(this->log, "%p: read socket data size:%d", this, src_size); do { int32_t consumed; @@ -567,7 +570,7 @@ static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_ buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); - consumed = decode_data(this, src, src_size, buf, avail, &decoded); + consumed = decode_data(this, src, src_size, buf, avail, &decoded, pkt_seqnum); if (consumed < 0) { spa_log_debug(this->log, "%p: failed to decode data: %d", this, consumed); return; @@ -583,7 +586,8 @@ static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_ if (decoded) { dt = now - this->now; this->now = now; - spa_log_trace(this->log, "decoded socket data seq:%u size:%d frames:%d dt:%d dms", + spa_log_trace(this->log, "%p: decoded socket data seq:%u size:%d frames:%d dt:%d dms", + this, (unsigned int)this->seqnum, (int)decoded, (int)decoded/port->frame_size, (int)(dt / 100000)); } else { @@ -615,6 +619,7 @@ static void media_on_ready_read(struct spa_source *source) struct impl *this = source->data; int32_t size_read; uint64_t now = 0; + int pkt_seqnum = -1; /* make sure the source is an input */ if ((source->rmask & SPA_IO_IN) == 0) { @@ -636,7 +641,7 @@ static void media_on_ready_read(struct spa_source *source) spa_log_trace(this->log, "socket poll"); /* read */ - size_read = read_data (this, &now); + size_read = read_data (this, &now, &pkt_seqnum); if (size_read < 0) { spa_log_error(this->log, "failed to read data: %s", spa_strerror(size_read)); goto stop; @@ -648,7 +653,7 @@ static void media_on_ready_read(struct spa_source *source) this->codec_props_changed = false; } - add_data(this, this->buffer_read, size_read, now); + add_data(this, this->buffer_read, size_read, now, pkt_seqnum); return; stop: @@ -671,7 +676,7 @@ static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, u if (size_read == 0) return 0; - add_data(this, buffer_read, size_read, now); + add_data(this, buffer_read, size_read, now, -1); return 0; stop: diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 54047a51f..8195cc738 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -81,10 +81,11 @@ static void sco_io_on_ready(struct spa_source *source) if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { int res; + int dummy; uint64_t rx_time = 0; read_again: - res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time); + res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time, &dummy); if (res <= 0) { if (errno == EINTR) { /* retry if interrupted */ From 4e4086934c11daab1ed2cfee6de1460a5df62b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 13:38:35 +0200 Subject: [PATCH 0672/1014] spa: examples: local-libcamera: do not set `SPA_PARAM_Props` Setting `SPA_PROP_device` has no effect, do not do it. --- spa/examples/local-libcamera.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spa/examples/local-libcamera.c b/spa/examples/local-libcamera.c index d6e9f80d9..7e2157e9e 100644 --- a/spa/examples/local-libcamera.c +++ b/spa/examples/local-libcamera.c @@ -254,14 +254,6 @@ static int make_nodes(struct data *data, const char *device) spa_debug_pod(0, NULL, props); } - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - props = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_device, SPA_POD_String(device ? device : "/dev/media0")); - - if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) - printf("got set_props error %d\n", res); - return res; } From 6f058e6b0df8c56d45c6d076e30a346f38abb355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 15:43:15 +0200 Subject: [PATCH 0673/1014] spa: examples: local-libcamera: pass camera id to the plugin --- spa/examples/local-libcamera.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/spa/examples/local-libcamera.c b/spa/examples/local-libcamera.c index 7e2157e9e..8e8a279aa 100644 --- a/spa/examples/local-libcamera.c +++ b/spa/examples/local-libcamera.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -83,7 +84,8 @@ struct data { unsigned int n_buffers; }; -static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) +static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name, + const struct spa_dict *params) { int res; void *hnd; @@ -117,9 +119,9 @@ static int load_handle(struct data *data, struct spa_handle **handle, const char if (!spa_streq(factory->name, name)) continue; - *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + *handle = calloc(1, spa_handle_factory_get_size(factory, params)); if ((res = spa_handle_factory_init(factory, *handle, - NULL, data->support, + params, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; @@ -129,13 +131,14 @@ static int load_handle(struct data *data, struct spa_handle **handle, const char return -EBADF; } -static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) +static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name, + const struct spa_dict *params) { struct spa_handle *handle = NULL; void *iface; int res; - if ((res = load_handle(data, &handle, lib, name)) < 0) + if ((res = load_handle(data, &handle, lib, name, params)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { @@ -237,10 +240,13 @@ static int make_nodes(struct data *data, const char *device) uint8_t buffer[256]; uint32_t index; - if ((res = - make_node(data, &data->source, - "libcamera/libspa-libcamera.so", - SPA_NAME_API_LIBCAMERA_SOURCE)) < 0) { + const struct spa_dict_item items[] = { + { SPA_KEY_API_LIBCAMERA_PATH, device }, + }; + + if ((res = make_node(data, &data->source, + "libcamera/libspa-libcamera.so", SPA_NAME_API_LIBCAMERA_SOURCE, + &SPA_DICT_INIT_ARRAY(items))) < 0) { printf("can't create libcamera-source: %d\n", res); return res; } @@ -434,13 +440,18 @@ int main(int argc, char *argv[]) struct spa_handle *handle = NULL; void *iface; + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data.plugin_dir = str; if ((res = load_handle(&data, &handle, "support/libspa-support.so", - SPA_NAME_SUPPORT_SYSTEM)) < 0) + SPA_NAME_SUPPORT_SYSTEM, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { @@ -452,7 +463,7 @@ int main(int argc, char *argv[]) if ((res = load_handle(&data, &handle, "support/libspa-support.so", - SPA_NAME_SUPPORT_LOOP)) < 0) + SPA_NAME_SUPPORT_LOOP, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { From a858290e4bb1cdd8198ebf95390bbcefabd2cb82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 13:01:21 +0200 Subject: [PATCH 0674/1014] spa: libcamera: source: avoid iterator overrun when enumerating controls Do not overrun the iterator when skipping the initial couple items. --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d0be3aac2..228a12e87 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -774,7 +774,7 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, result.next = start; auto it = info.begin(); - for (skip = result.next - offset; skip; skip--) + for (skip = result.next - offset; skip && it != info.end(); skip--) it++; if (false) { From 938195b19f11cd762b14b6636903ee6da35f30b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 10:51:14 +0200 Subject: [PATCH 0675/1014] spa: libcamera: source: simplify color space conversion Instead of using an out parameter, just return the `spa_video_colorimetry` object; and do the libcamera -> spa conversion in a single place, where the data is actually added to the pod. --- spa/plugins/libcamera/libcamera-source.cpp | 42 +++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 228a12e87..76b512c97 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -444,60 +444,64 @@ int score_size(const Size &a, const Size &b) return x * x + y * y; } -void -parse_colorimetry(const ColorSpace& colorspace, - struct spa_video_colorimetry *colorimetry) +[[nodiscard]] +spa_video_colorimetry +color_space_to_colorimetry(const libcamera::ColorSpace& colorspace) { + spa_video_colorimetry res = {}; + switch (colorspace.range) { case ColorSpace::Range::Full: - colorimetry->range = SPA_VIDEO_COLOR_RANGE_0_255; + res.range = SPA_VIDEO_COLOR_RANGE_0_255; break; case ColorSpace::Range::Limited: - colorimetry->range = SPA_VIDEO_COLOR_RANGE_16_235; + res.range = SPA_VIDEO_COLOR_RANGE_16_235; break; } switch (colorspace.ycbcrEncoding) { case ColorSpace::YcbcrEncoding::None: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_RGB; + res.matrix = SPA_VIDEO_COLOR_MATRIX_RGB; break; case ColorSpace::YcbcrEncoding::Rec601: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT601; + res.matrix = SPA_VIDEO_COLOR_MATRIX_BT601; break; case ColorSpace::YcbcrEncoding::Rec709: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT709; + res.matrix = SPA_VIDEO_COLOR_MATRIX_BT709; break; case ColorSpace::YcbcrEncoding::Rec2020: - colorimetry->matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; + res.matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; break; } switch (colorspace.transferFunction) { case ColorSpace::TransferFunction::Linear: - colorimetry->transfer = SPA_VIDEO_TRANSFER_UNKNOWN; + res.transfer = SPA_VIDEO_TRANSFER_UNKNOWN; break; case ColorSpace::TransferFunction::Srgb: - colorimetry->transfer = SPA_VIDEO_TRANSFER_SRGB; + res.transfer = SPA_VIDEO_TRANSFER_SRGB; break; case ColorSpace::TransferFunction::Rec709: - colorimetry->transfer = SPA_VIDEO_TRANSFER_BT709; + res.transfer = SPA_VIDEO_TRANSFER_BT709; break; } switch (colorspace.primaries) { case ColorSpace::Primaries::Raw: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; + res.primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; break; case ColorSpace::Primaries::Smpte170m: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; + res.primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; break; case ColorSpace::Primaries::Rec709: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; + res.primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; break; case ColorSpace::Primaries::Rec2020: - colorimetry->primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; + res.primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; break; } + + return res; } int @@ -508,7 +512,6 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[2]; struct spa_result_node_params result; - struct spa_video_colorimetry colorimetry = {}; uint32_t count = 0; spa_libcamera_get_config(impl); @@ -517,9 +520,6 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, const StreamFormats &formats = streamConfig.formats(); const auto &pixel_formats = formats.pixelformats(); - if (streamConfig.colorSpace) - parse_colorimetry(*streamConfig.colorSpace, &colorimetry); - result.id = SPA_PARAM_EnumFormat; result.next = start; @@ -617,6 +617,8 @@ next_fmt: } if (streamConfig.colorSpace) { + auto colorimetry = color_space_to_colorimetry(*streamConfig.colorSpace); + spa_pod_builder_add(&b, SPA_FORMAT_VIDEO_colorRange, SPA_POD_Id(colorimetry.range), From 07a4e593bb1b0638e782042cbd69dde73e14b323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 11:54:16 +0200 Subject: [PATCH 0676/1014] spa: libcamera: source: fix mapping of `libcamera::ColorSpace::TransferFunction::Linear` `SPA_VIDEO_TRANSFER_GAMMA10` should be used to represent a linear transfer function. Fixes: 7e202a384472b5 ("spa: libcamera: add colorimetry support") --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 76b512c97..5e934790b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -476,7 +476,7 @@ color_space_to_colorimetry(const libcamera::ColorSpace& colorspace) switch (colorspace.transferFunction) { case ColorSpace::TransferFunction::Linear: - res.transfer = SPA_VIDEO_TRANSFER_UNKNOWN; + res.transfer = SPA_VIDEO_TRANSFER_GAMMA10; break; case ColorSpace::TransferFunction::Srgb: res.transfer = SPA_VIDEO_TRANSFER_SRGB; From 30ce210c2a617e2a9e6466edadb0e2c4f642666a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 09:17:22 +0200 Subject: [PATCH 0677/1014] spa: libcamera: source: prop_id_to_control(): do range check first If it is a custom spa property, it will not be found in the lookup table, so we can return immediately. --- spa/plugins/libcamera/libcamera-source.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 5e934790b..d17b634a3 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -745,14 +745,14 @@ uint32_t control_to_prop_id(uint32_t control_id) uint32_t prop_id_to_control(uint32_t prop_id) { + if (prop_id >= SPA_PROP_START_CUSTOM) + return prop_id - SPA_PROP_START_CUSTOM; + for (const auto& c : control_map) { if (c.spa_id == prop_id) return c.id; } - if (prop_id >= SPA_PROP_START_CUSTOM) - return prop_id - SPA_PROP_START_CUSTOM; - return SPA_ID_INVALID; } From c167b98ff2b0a4b3cec48151ea26a507502ed13c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 09:51:42 +0200 Subject: [PATCH 0678/1014] spa: libcamera: source: remove unused `enum_fmt` member This was introduced by b2c38a2b3b82ed ("libcamera: work on rewrite"), but it has never been used. --- spa/plugins/libcamera/libcamera-source.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d17b634a3..c989559c3 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -97,7 +97,6 @@ struct port { struct spa_param_info params[N_PORT_PARAMS]; std::size_t fmt_index = 0; - PixelFormat enum_fmt; std::size_t size_index = 0; port(struct impl *impl) From 429c0e03a3cb49631638627e4097119fd26905f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 16:27:23 +0200 Subject: [PATCH 0679/1014] spa: libcamera: source: do not close fd Currently the plugin does not support importing memory and uses `libcamera::FrameBufferAllocator` to allocate memory. Every file descriptor is managed by that object, so they must not be closed manually. --- spa/plugins/libcamera/libcamera-source.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index c989559c3..d833d756f 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -50,8 +50,7 @@ namespace { #define MASK_BUFFERS 31 #define BUFFER_FLAG_OUTSTANDING (1<<0) -#define BUFFER_FLAG_ALLOCATED (1<<1) -#define BUFFER_FLAG_MAPPED (1<<2) +#define BUFFER_FLAG_MAPPED (1<<1) struct buffer { uint32_t id; @@ -358,9 +357,7 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), d[0].maxsize - d[0].mapoffset); } - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { - close(d[0].fd); - } + d[0].type = SPA_ID_INVALID; } @@ -1125,7 +1122,6 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, d[j].fd = bufs[i]->planes()[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); d[j].data = nullptr; - SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); } else if (port->memtype == SPA_DATA_MemPtr) { d[j].fd = -1; From 8c4f60af480f3a31ff97b52a8933956bb0a3587e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 12:31:16 +0200 Subject: [PATCH 0680/1014] spa: libcamera: source: remove `SPA_PROP_device{,Name}` `SPA_PROP_deviceName` is an empty string and setting it does nothing. `SPA_PROP_device` is always the libcamera identifier of the camera, and setting it has no effect whatsoever in contrast to other plugins such as v4l2. So remove them both for now. --- spa/plugins/libcamera/libcamera-source.cpp | 67 ++++++---------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d833d756f..40b6dfceb 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -137,9 +137,6 @@ struct impl { #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; - std::string device_id; - std::string device_name; - struct spa_hook_list hooks; struct spa_callbacks callbacks = {}; @@ -169,7 +166,7 @@ struct impl { bool acquired = false; impl(spa_log *log, spa_loop *data_loop, spa_system *system, - std::shared_ptr manager, std::shared_ptr camera, std::string device_id); + std::shared_ptr manager, std::shared_ptr camera); struct spa_dll dll; }; @@ -208,7 +205,7 @@ int spa_libcamera_open(struct impl *impl) if (impl->acquired) return 0; - spa_log_info(impl->log, "open camera %s", impl->device_id.c_str()); + spa_log_info(impl->log, "open camera %s", impl->camera->id().c_str()); if (int res = impl->camera->acquire(); res < 0) return res; @@ -230,7 +227,7 @@ int spa_libcamera_close(struct impl *impl) if (impl->active || port->current_format) return 0; - spa_log_info(impl->log, "close camera %s", impl->device_id.c_str()); + spa_log_info(impl->log, "close camera %s", impl->camera->id().c_str()); delete impl->allocator; impl->allocator = nullptr; @@ -1241,7 +1238,7 @@ int spa_libcamera_stream_on(struct impl *impl) impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - spa_log_info(impl->log, "starting camera %s", impl->device_id.c_str()); + spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); if ((res = impl->camera->start(&impl->initial_controls)) < 0) goto error; @@ -1301,12 +1298,12 @@ int spa_libcamera_stream_off(struct impl *impl) } impl->active = false; - spa_log_info(impl->log, "stopping camera %s", impl->device_id.c_str()); + spa_log_info(impl->log, "stopping camera %s", impl->camera->id().c_str()); impl->pendingRequests.clear(); if ((res = impl->camera->stop()) < 0) { spa_log_warn(impl->log, "error stopping camera %s: %s", - impl->device_id.c_str(), spa_strerror(res)); + impl->camera->id().c_str(), spa_strerror(res)); } impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); @@ -1397,36 +1394,16 @@ next: case SPA_PARAM_PropInfo: { switch (result.index) { - case 0: - param = (struct spa_pod*)spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), - SPA_PROP_INFO_description, SPA_POD_String("The libcamera device"), - SPA_PROP_INFO_type, SPA_POD_String(impl->device_id.c_str())); - break; - case 1: - param = (struct spa_pod*)spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_PropInfo, id, - SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), - SPA_PROP_INFO_description, SPA_POD_String("The libcamera device name"), - SPA_PROP_INFO_type, SPA_POD_String(impl->device_name.c_str())); - break; default: return spa_libcamera_enum_controls(impl, GET_OUT_PORT(impl, 0), - seq, result.index, 2, num, filter); + seq, result.index, 0, num, filter); } break; } case SPA_PARAM_Props: { switch (result.index) { - case 0: - param = (struct spa_pod*)spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_Props, id, - SPA_PROP_device, SPA_POD_String(impl->device_id.c_str()), - SPA_PROP_deviceName, SPA_POD_String(impl->device_name.c_str())); - break; default: return 0; } @@ -1468,21 +1445,11 @@ int impl_node_set_param(void *object, const auto *obj = reinterpret_cast(param); const struct spa_pod_prop *prop; - if (param == nullptr) { - impl->device_id.clear(); - impl->device_name.clear(); + if (param == nullptr) return 0; - } - SPA_POD_OBJECT_FOREACH(obj, prop) { - char device[128]; + SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { - case SPA_PROP_device: - strncpy(device, - static_cast(SPA_POD_CONTENTS(struct spa_pod_string, &prop->value)), - sizeof(device) - 1); - impl->device_id = device; - break; default: spa_libcamera_set_control(impl, prop, SPA_POD_BODY_CONST(&prop->value)); break; @@ -2099,12 +2066,11 @@ int impl_clear(struct spa_handle *handle) } impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, - std::shared_ptr manager, std::shared_ptr camera, std::string device_id) + std::shared_ptr manager, std::shared_ptr camera) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), data_loop(data_loop), system(system), - device_id(std::move(device_id)), out_ports{{this}}, manager(std::move(manager)), camera(std::move(camera)) @@ -2146,7 +2112,6 @@ impl_init(const struct spa_handle_factory *factory, const struct spa_support *support, uint32_t n_support) { - const char *str; int res; spa_return_val_if_fail(factory != nullptr, -EINVAL); @@ -2172,18 +2137,18 @@ impl_init(const struct spa_handle_factory *factory, return res; } - std::string device_id; - if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) - device_id = str; + const char *device_id = info + ? spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH) + : nullptr; - auto camera = manager->get(device_id); + auto camera = device_id ? manager->get(device_id) : nullptr; if (!camera) { - spa_log_error(log, "unknown camera id %s", device_id.c_str()); + spa_log_error(log, "unknown camera id: %s", device_id); return -ENOENT; } new (handle) impl(log, data_loop, system, - std::move(manager), std::move(camera), std::move(device_id)); + std::move(manager), std::move(camera)); return 0; } From 49be2a1c5214c82b7c0a9ab7ab07593c5902bbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 15:52:59 +0200 Subject: [PATCH 0681/1014] spa: libcamera: source: generate camera config right away Currently the plugin uses a single configuration during its entire lifetime. So generate that configuration during initialization and hold on to it. This makes the code a bit simpler, and also fixes issues stemming from missed calls to `spa_libcamera_get_config()` as well as no error checking of `libcamera::Camera::generateConfiguration()`. --- spa/plugins/libcamera/libcamera-source.cpp | 31 +++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 40b6dfceb..2861661af 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -149,6 +149,7 @@ struct impl { std::shared_ptr manager; std::shared_ptr camera; + const std::unique_ptr config; FrameBufferAllocator *allocator = nullptr; std::vector> requestPool; @@ -156,8 +157,6 @@ struct impl { void requestComplete(libcamera::Request *request); - std::unique_ptr config; - struct spa_source source = {}; ControlList ctrls; @@ -166,7 +165,8 @@ struct impl { bool acquired = false; impl(spa_log *log, spa_loop *data_loop, spa_system *system, - std::shared_ptr manager, std::shared_ptr camera); + std::shared_ptr manager, std::shared_ptr camera, + std::unique_ptr config); struct spa_dll dll; }; @@ -237,14 +237,6 @@ int spa_libcamera_close(struct impl *impl) return 0; } -void spa_libcamera_get_config(struct impl *impl) -{ - if (impl->config) - return; - - impl->config = impl->camera->generateConfiguration({ StreamRole::VideoRecording }); -} - int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; @@ -507,8 +499,6 @@ spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, struct spa_result_node_params result; uint32_t count = 0; - spa_libcamera_get_config(impl); - const StreamConfiguration& streamConfig = impl->config->at(0); const StreamFormats &formats = streamConfig.formats(); const auto &pixel_formats = formats.pixelformats(); @@ -2066,14 +2056,16 @@ int impl_clear(struct spa_handle *handle) } impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, - std::shared_ptr manager, std::shared_ptr camera) + std::shared_ptr manager, std::shared_ptr camera, + std::unique_ptr config) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), data_loop(data_loop), system(system), out_ports{{this}}, manager(std::move(manager)), - camera(std::move(camera)) + camera(std::move(camera)), + config(std::move(config)) { libcamera_log_topic_init(log); @@ -2147,8 +2139,15 @@ impl_init(const struct spa_handle_factory *factory, return -ENOENT; } + auto config = camera->generateConfiguration({ libcamera::StreamRole::VideoRecording }); + if (!config) { + spa_log_error(log, "cannot generate configuration for camera"); + return -EINVAL; + } + new (handle) impl(log, data_loop, system, - std::move(manager), std::move(camera)); + std::move(manager), std::move(camera), + std::move(config)); return 0; } From e0e8bf083d6599ca1a2b0f8e9a2a63e643316d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 1 Aug 2025 12:44:19 +0200 Subject: [PATCH 0682/1014] spa: libcamera: source: create eventfd before starting camera An eventfd is used to signal the data loop from the libcamera request completion event handler. Previously, this eventfd was created and installed after the camera has been started and requests were queued. This is problematic because it creates a small time frame where the libcamera request completion handler will run in a state where the eventfd is not fully set up. Fix that by settup up the eventfd before the camera is started. --- spa/plugins/libcamera/libcamera-source.cpp | 67 ++++++++++++---------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 2861661af..f1656c7d4 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -1213,6 +1214,19 @@ void impl::requestComplete(libcamera::Request *request) } +int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + auto *impl = static_cast(user_data); + if (impl->source.loop) + spa_loop_remove_source(loop, &impl->source); + return 0; +} + int spa_libcamera_stream_on(struct impl *impl) { struct port *port = &impl->out_ports[0]; @@ -1226,54 +1240,45 @@ int spa_libcamera_stream_on(struct impl *impl) if (impl->active) return 0; - impl->camera->requestCompleted.connect(impl, &impl::requestComplete); + res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + if (res < 0) + return res; + + impl->source.fd = res; + impl->source.func = libcamera_on_fd_events; + impl->source.data = impl; + impl->source.mask = SPA_IO_IN | SPA_IO_ERR; + impl->source.rmask = 0; + res = spa_loop_add_source(impl->data_loop, &impl->source); + if (res < 0) + goto err_close_source; spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); if ((res = impl->camera->start(&impl->initial_controls)) < 0) - goto error; + goto err_remove_source; + + impl->camera->requestCompleted.connect(impl, &impl::requestComplete); for (Request *req : impl->pendingRequests) { if ((res = impl->camera->queueRequest(req)) < 0) - goto error_stop; + goto err_stop_camera; } impl->pendingRequests.clear(); impl->dll.bw = 0.0; - - impl->source.func = libcamera_on_fd_events; - impl->source.data = impl; - impl->source.fd = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - impl->source.mask = SPA_IO_IN | SPA_IO_ERR; - impl->source.rmask = 0; - if (impl->source.fd < 0) { - spa_log_error(impl->log, "Failed to create eventfd: %s", spa_strerror(impl->source.fd)); - res = impl->source.fd; - goto error_stop; - } - spa_loop_add_source(impl->data_loop, &impl->source); - impl->active = true; return 0; -error_stop: +err_stop_camera: impl->camera->stop(); -error: impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - return res == -EACCES ? -EBUSY : res; -} +err_remove_source: + spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); +err_close_source: + spa_system_close(impl->system, std::exchange(impl->source.fd, -1)); -int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - auto *impl = static_cast(user_data); - if (impl->source.loop) - spa_loop_remove_source(loop, &impl->source); - return 0; + return res == -EACCES ? -EBUSY : res; } int spa_libcamera_stream_off(struct impl *impl) From a36b8a273d1687d088874636b1e5878680fbfda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 29 Jul 2025 17:26:44 +0200 Subject: [PATCH 0683/1014] spa: libcamera: manager: factor out hotplug event submission The `impl::{add,remove}Camera` functions do the same thing except for one value, the type of the hotplug event. Add a private method to `impl` that implements the common parts. --- spa/plugins/libcamera/libcamera-manager.cpp | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index eda1442ee..67648105f 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -75,6 +75,17 @@ struct impl { spa_assert(std::begin(devices) <= &d && &d < std::end(devices)); return &d - std::begin(devices); } + +private: + void queue_hotplug_event(enum hotplug_event::type type, std::shared_ptr&& camera) + { + { + std::lock_guard guard(hotplug_events_lock); + hotplug_events.push({ type, std::move(camera) }); + } + + spa_loop_utils_signal_event(loop_utils, hotplug_event_source); + } }; struct device *add_device(struct impl *impl, std::shared_ptr camera) @@ -210,22 +221,12 @@ void on_hotplug_event(void *data, std::uint64_t) void impl::addCamera(std::shared_ptr camera) { - { - std::unique_lock guard(hotplug_events_lock); - hotplug_events.push({ hotplug_event::type::add, std::move(camera) }); - } - - spa_loop_utils_signal_event(loop_utils, hotplug_event_source); + queue_hotplug_event(hotplug_event::type::add, std::move(camera)); } void impl::removeCamera(std::shared_ptr camera) { - { - std::unique_lock guard(hotplug_events_lock); - hotplug_events.push({ hotplug_event::type::remove, std::move(camera) }); - } - - spa_loop_utils_signal_event(loop_utils, hotplug_event_source); + queue_hotplug_event(hotplug_event::type::remove, std::move(camera)); } void start_monitor(struct impl *impl) From 8614fc45f8f45a5abeb8ee6678c9d83c9a5d6fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 30 Jul 2025 17:26:04 +0200 Subject: [PATCH 0684/1014] spa: libcamera: manager: keep `libcamera::CameraManager` At the moment, the camera manager shared pointer is released when the last listener is removed, and recreated when the first listener is added. This is the same behaviour that the alsa and v4l2 monitors implement with their respective udev, inotify monitors. However, for `libcamera::CameraManager`, this is likely not the best way for multiple reasons: (a) it is a complex object with significant construction and starting cost, which includes starting threads and usually loading shared libraries; (b) usually one listener is added right after creating, and it is removed right before destruction, in which there are real no advantages; (c) the camera manager, being a shared resource, might very well be kept alive by some other component, in which case there is again not much real benefit. So simplify the code by getting a camera manager reference at the beginning and keeping it until the libcamera monitor is destroyed. This also fixes a race condition where a hot-plugged camera might not have been detected if the libcamera event was emitted between these two: collect_existing_devices(impl); start_monitor(impl); --- spa/plugins/libcamera/libcamera-manager.cpp | 102 ++++++-------------- 1 file changed, 29 insertions(+), 73 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-manager.cpp b/spa/plugins/libcamera/libcamera-manager.cpp index 67648105f..5c1620571 100644 --- a/spa/plugins/libcamera/libcamera-manager.cpp +++ b/spa/plugins/libcamera/libcamera-manager.cpp @@ -49,9 +49,6 @@ struct impl { struct spa_device_info info = SPA_DEVICE_INFO_INIT(); std::shared_ptr manager; - void addCamera(std::shared_ptr camera); - void removeCamera(std::shared_ptr camera); - struct device devices[max_devices]; struct hotplug_event { @@ -63,10 +60,13 @@ struct impl { std::queue hotplug_events; struct spa_source *hotplug_event_source; - impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source); + impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source, + std::shared_ptr&& manager); ~impl() { + manager->cameraAdded.disconnect(this); + manager->cameraRemoved.disconnect(this); spa_loop_utils_destroy_source(loop_utils, hotplug_event_source); } @@ -115,12 +115,6 @@ void remove_device(struct impl *impl, struct device *device) *device = {}; } -void clear_devices(struct impl *impl) -{ - for (auto& d : impl->devices) - d = {}; -} - int emit_object_info(struct impl *impl, const struct device *device) { struct spa_device_object_info info; @@ -219,40 +213,6 @@ void on_hotplug_event(void *data, std::uint64_t) } } -void impl::addCamera(std::shared_ptr camera) -{ - queue_hotplug_event(hotplug_event::type::add, std::move(camera)); -} - -void impl::removeCamera(std::shared_ptr camera) -{ - queue_hotplug_event(hotplug_event::type::remove, std::move(camera)); -} - -void start_monitor(struct impl *impl) -{ - impl->manager->cameraAdded.connect(impl, &impl::addCamera); - impl->manager->cameraRemoved.connect(impl, &impl::removeCamera); -} - -int stop_monitor(struct impl *impl) -{ - if (impl->manager) { - impl->manager->cameraAdded.disconnect(impl, &impl::addCamera); - impl->manager->cameraRemoved.disconnect(impl, &impl::removeCamera); - } - clear_devices (impl); - return 0; -} - -void collect_existing_devices(struct impl *impl) -{ - auto cameras = impl->manager->cameras(); - - for (std::shared_ptr& camera : cameras) - try_add_camera(impl, std::move(camera)); -} - const struct spa_dict_item device_info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, { SPA_KEY_DEVICE_NICK, "libcamera-manager" }, @@ -272,50 +232,27 @@ void emit_device_info(struct impl *impl, bool full) } } -void impl_hook_removed(struct spa_hook *hook) -{ - struct impl *impl = (struct impl*)hook->priv; - if (spa_hook_list_is_empty(&impl->hooks)) { - stop_monitor(impl); - impl->manager.reset(); - } -} - int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { - int res; struct impl *impl = (struct impl*) object; struct spa_hook_list save; - bool had_manager = !!impl->manager; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(events != nullptr, -EINVAL); - if (!impl->manager && !(impl->manager = libcamera_manager_acquire(res))) - return res; - spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_device_info(impl, true); - if (had_manager) { - for (const auto& d : impl->devices) { - if (d.camera) - emit_object_info(impl, &d); - } - } - else { - collect_existing_devices(impl); - start_monitor(impl); + for (const auto& d : impl->devices) { + if (d.camera) + emit_object_info(impl, &d); } spa_hook_list_join(&impl->hooks, &save); - listener->removed = impl_hook_removed; - listener->priv = impl; - return 0; } @@ -343,16 +280,17 @@ int impl_clear(struct spa_handle *handle) { auto impl = reinterpret_cast(handle); - stop_monitor(impl); std::destroy_at(impl); return 0; } -impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source) +impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source, + std::shared_ptr&& manager) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), loop_utils(loop_utils), + manager(std::move(manager)), hotplug_event_source(hotplug_event_source) { libcamera_log_topic_init(log); @@ -363,6 +301,17 @@ impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_s SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); + + this->manager->cameraAdded.connect(this, [this](std::shared_ptr camera) { + queue_hotplug_event(hotplug_event::type::add, std::move(camera)); + }); + + this->manager->cameraRemoved.connect(this, [this](std::shared_ptr camera) { + queue_hotplug_event(hotplug_event::type::remove, std::move(camera)); + }); + + for (auto&& camera : this->manager->cameras()) + try_add_camera(this, std::move(camera)); } size_t @@ -397,7 +346,14 @@ impl_init(const struct spa_handle_factory *factory, return res; } - new (handle) impl(log, loop_utils, hotplug_event_source); + int res = 0; + auto manager = libcamera_manager_acquire(res); + if (!manager) { + spa_log_error(log, "failed to start camera manager: %s", spa_strerror(res)); + return res; + } + + new (handle) impl(log, loop_utils, hotplug_event_source, std::move(manager)); return 0; } From 35b3c8c279ce6daf7ed0b09ed4b8e43081879391 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 1 Aug 2025 18:16:39 +0200 Subject: [PATCH 0685/1014] vban: truncate sess.name when too large --- src/modules/module-vban/stream.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 3630441b3..3a7a31827 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -298,7 +298,7 @@ struct vban_stream *vban_stream_new(struct pw_core *core, impl->header.format_bit = impl->format_info->format_bit; if ((str = pw_properties_get(props, "sess.name")) == NULL) str = "Stream1"; - strcpy(impl->header.stream_name, str); + snprintf(impl->header.stream_name, sizeof(impl->header.stream_name), "%s", str); break; case SPA_MEDIA_SUBTYPE_control: impl->stream_info = impl->info; @@ -319,7 +319,7 @@ struct vban_stream *vban_stream_new(struct pw_core *core, impl->header.format_bit = impl->format_info->format_bit; if ((str = pw_properties_get(props, "sess.name")) == NULL) str = "Midi1"; - strcpy(impl->header.stream_name, str); + snprintf(impl->header.stream_name, sizeof(impl->header.stream_name), "%s", str); break; default: spa_assert_not_reached(); From a346a7d42ebd0601d3f971826372906f49a99c62 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 1 Aug 2025 18:29:29 +0300 Subject: [PATCH 0686/1014] doc: document more properties Also add examples for customizing properties. --- doc/dox/config/pipewire-props.7.md | 106 +++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index e7ee8f848..b47489718 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -36,11 +36,66 @@ type. Other properties control settings of a specific kinds of device or node (ALSA, Bluetooth, ...), and have meaning only for those objects. -Usually, all the properties are configured in the session manager -configuration. For how to configure them, see the session manager -documentation. In minimal PipeWire setups without a session manager, -they can be configured via -\ref pipewire_conf__context_objects "context.objects in pipewire.conf(5)". +# CUSTOMIZING PROPERTIES @IDX@ props + +Usually, all device properties are configured in the session manager +configuration, see the session manager documentation. +Application properties are configured in +``client.conf`` (for native PipeWire and ALSA applications), and +``pipewire-pulse.conf`` (for Pulseaudio applications). + +In minimal PipeWire setups without a session manager, +the device properties can be configured via +\ref pipewire_conf__context_objects "context.objects in pipewire.conf(5)" +when creating the devices. + +\see [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) + +## Examples + +Client configuration (requires client application restart to apply) +```css +# ~/.config/pipewire/client.conf/custom-props.conf + +stream.rules = [ + { + matches = [ { application.name = "pw-play" } ] + actions = { update-props = { node.description = "Some pw-cat stream" } } + } +] +``` +\see \ref client_conf__stream_rules "pipewire-client.conf(5)", \ref client_conf__stream_rules "pipewire-pulse.conf(5)" + +Device configuration (using WirePlumber; requires WirePlumber restart to apply): +```css +# ~/.config/wireplumber/wireplumber.conf.d/custom-props.conf + +monitor.alsa.properties = { + alsa.udev.expose-busy = true +} + +monitor.alsa.rules = [ + { + matches = [ { device.name = "~alsa_card.pci-.*" } ], + actions = { update-props = { api.alsa.soft-mixer = true } ] + }, + { + matches = [ { node.name = "alsa_output.pci-0000_03_00.1.hdmi-stereo-extra3" } ] + actions = { update-props = { node.description = "Main Audio" } ] + } +] + +monitor.bluez.properties = { + bluez5.hfphsp-backend = ofono +} + +monitor.bluez.rules = [ + { + matches = [ { device.name = "~bluez_card.*" } ], + actions = { update-props = { bluez5.dummy-avrcp player = true } ] + } +] +``` \see [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) @@ -51,6 +106,9 @@ These are common properties for devices. @PAR@ device-prop device.name # string A (unique) name for the device. It can be used by command-line and other tools to identify the device. +@PAR@ device-prop device.nick # string +A short name for the device. + @PAR@ device-prop device.param.PARAM = { ... } # JSON \parblock Set value of a device \ref spa_param_type "Param" to a JSON value when the device is loaded. @@ -143,11 +201,14 @@ real or virtual devices. These contain properties to identify the node or to display the node in a GUI application. -@PAR@ node-prop node.name +@PAR@ node-prop node.name # string A (unique) name for the node. This is usually set on sink and sources to identify them as targets for linking by the session manager. -@PAR@ node-prop node.description +@PAR@ node-prop node.nick # string +A short name for the node. + +@PAR@ node-prop node.description # string A human readable description of the node or stream. @PAR@ node-prop media.name @@ -338,14 +399,14 @@ a sink or source. @PAR@ node-prop node.exclusive = false If this node wants to be linked exclusively to the sink/source. +@PAR@ node-prop target.object = +Where the node should link to, this can be a node.name or an object.serial. + @PAR@ node-prop node.target = Where this node should be linked to. This can be a node.name or an object.id of a node. This property is deprecated, the target.object property should be used instead, which uses the more unique object.serial as a possible target. -@PAR@ node-prop target.object = -Where the node should link to, this can be a node.name or an object.serial. - @PAR@ node-prop node.dont-reconnect = false \parblock When the node has a target configured and the target is destroyed, destroy the node as well. @@ -355,6 +416,13 @@ Note that if a stream should appear/disappear in sync with the target, a session should be written instead. \endparblock +@PAR@ node-prop node.dont-fallback = false +If linking this node to its specified target does not succeed, session +manager should not fall back to linking it to the default target. + +@PAR@ node-prop node.dont-move = false +Whether the node target may be changed using metadata. + @PAR@ node-prop node.passive = false \parblock This is a passive node and so it should not keep sinks/sources busy. This property makes the session manager create passive links to the sink/sources. If the node is not otherwise linked (via a non-passive link), the node and the sink it is linked to are idle (and eventually suspended). @@ -371,6 +439,13 @@ Instruct the session manager to not remix the channels of a stream. Normally the @PAR@ node-prop priority.session # integer The priority for selecting this node as the default source or sink. +@PAR@ node-prop session.suspend-timeout-seconds = 3 # float +Timeout in seconds, after which an idle node is suspended. +Value ``0`` means the node will not be suspended. + +@PAR@ node-prop state.restore-props = true +Whether session manager should save state for this node. + ## Format Properties Streams and also most device nodes can be configured in a certain format with properties. @@ -640,8 +715,9 @@ See \ref spa_param_port_config for the meaning. ## Monitor properties -@PAR@ monitor-prop alsa.use-acp # boolean +@PAR@ monitor-prop alsa.use-acp = true # boolean Use \ref monitor-prop__alsa_card_profiles "ALSA Card Profiles" (ACP) for device configuration. +This autodetects available ALSA devices and configures port and hardware mixers. @PAR@ monitor-prop alsa.udev.expose-busy # boolean Expose the ALSA card even if it is busy/in use. Default false. This can be useful when some @@ -658,7 +734,7 @@ When ACP is enabled and a UCM configuration is available for a device, by default it is used instead of the ACP profiles. This option allows you to disable this and use the ACP profiles instead. -This option does nothing if `api.alsa.use-acp` is set to `false`. +This option does nothing if `alsa.use-acp` is set to `false`. \endparblock @PAR@ device-prop api.alsa.soft-mixer = false # boolean @@ -1058,6 +1134,12 @@ this instance. Available values: - input: appear as source node. \endparblock +@PAR@ node-prop node.latency-offset-msec # string +Applies only for BLE MIDI nodes. +Latency adjustment to apply on the node. Larger values add a +constant latency, but reduces timing jitter caused by Bluetooth +transport. + # PORT PROPERTIES @IDX@ props Port properties are usually not directly configurable via PipeWire From 5ecf27681efc2f64365d552be3d695b780d533e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 8 Jun 2025 21:18:34 +0200 Subject: [PATCH 0687/1014] pipewire: module-rt: acquire_rt(): return error if thread not found Instead of implicitly acting on the current thread if the provided thread handle is unknown, return an error. This behaviour is not depended on inside pipewire, and if needed, special casing e.g. `thread == NULL` could be added, but any random `thread` value shouldn't affect the current thread. --- src/modules/module-rt.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index dcc9c31a8..2475ca6fc 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -843,16 +843,17 @@ static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked."); } - params.priority = priority; - pthread_mutex_lock(&impl->lock); - if ((thr = find_thread_by_pt(impl, pt)) != NULL) + if ((thr = find_thread_by_pt(impl, pt)) != NULL) { + params.priority = priority; params.tid = thr->tid; - else - params.tid = _gettid(); - res = pw_loop_invoke(pw_thread_loop_get_loop(impl->thread_loop), + res = pw_loop_invoke(pw_thread_loop_get_loop(impl->thread_loop), do_make_realtime, 0, ¶ms, sizeof(params), false, impl); + } + else { + res = -ESRCH; + } pthread_mutex_unlock(&impl->lock); return res; From 58d86cfb5fbcf1caa5a891ef4d932cf7cc8f4fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 1 Aug 2025 19:47:44 +0200 Subject: [PATCH 0688/1014] pw-cli: print `pw_core` errors At the moment errors are printed using `pw_log_error()`, however, that does not display them by default because `client.conf` sets `log.level=0`. Use `fprintf()` and always print the errors. --- src/tools/pw-cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index e15918c1f..88356cb8f 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -556,8 +556,8 @@ static void on_core_error(void *_data, uint32_t id, int seq, int res, const char struct remote_data *rd = _data; struct data *data = rd->data; - pw_log_error("remote %p: error id:%u seq:%d res:%d (%s): %s", rd, - id, seq, res, spa_strerror(res), message); + fprintf(stderr, "remote %" PRIu32 ": error id:%" PRIu32 " seq:%d res:%d (%s): %s\n", + rd->id, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) program_quit(data); From 02aee17cc3515bdb1c9da84cab48b294d54383ba Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 28 Jul 2025 22:05:45 +0300 Subject: [PATCH 0689/1014] test: fix test library path and fix test-functional with ASAN Set LD_LIBRARY_PATH to have processes spawned by test load the right library. Mark openal-info test skipped if ASAN is enabled. The problem is that openal-info would need LD_PRELOAD to work properly then. Just disable the test then. --- test/meson.build | 5 +++++ test/pwtest.c | 1 + test/test-functional.c | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/meson.build b/test/meson.build index 43a59a254..b4f24cf1b 100644 --- a/test/meson.build +++ b/test/meson.build @@ -135,10 +135,15 @@ endif summary({'pactl': pactl.found()}, bool_yn: true, section: 'Functional test programs') if default_sm == 'media-session' or default_sm == 'wireplumber' + test_functional_c_args = [] + if get_option('b_sanitize').contains('address') + test_functional_c_args += ['-DHAVE_ASAN'] + endif test('test-functional', executable('test-functional', 'test-functional.c', include_directories: pwtest_inc, + c_args: [test_functional_c_args], dependencies: [ spa_dep ], link_with: pwtest_lib) ) diff --git a/test/pwtest.c b/test/pwtest.c index 5726fc744..7094a59e1 100644 --- a/test/pwtest.c +++ b/test/pwtest.c @@ -788,6 +788,7 @@ static void set_test_env(struct pwtest_context *ctx, struct pwtest_test *t) replace_env(t, "ACP_PROFILES_DIR", SOURCE_ROOT "/spa/plugins/alsa/mixer/profile-sets"); replace_env(t, "PIPEWIRE_LOG_SYSTEMD", "false"); replace_env(t, "PWTEST_DATA_DIR", SOURCE_ROOT "/test/data"); + replace_env(t, "LD_LIBRARY_PATH", BUILD_ROOT "/src/pipewire:" BUILD_ROOT "pipewire-jack/src"); } static void close_pipes(int fds[_FD_LAST]) diff --git a/test/test-functional.c b/test/test-functional.c index 8c3438688..29277ffb0 100644 --- a/test/test-functional.c +++ b/test/test-functional.c @@ -10,9 +10,14 @@ PWTEST(openal_info_test) { -#ifdef OPENAL_INFO_PATH + /* openal-info tries to load libpipewire, which would need + * LD_PRELOAD=/lib64/libasan.so.XX to work when ASan is enabled. Don't try to + * figure out the right preload, but just disable the test in that case. + */ +#if defined(OPENAL_INFO_PATH) && !defined(HAVE_ASAN) int status = pwtest_spawn(OPENAL_INFO_PATH, (char *[]){ "openal-info", NULL }); pwtest_int_eq(WEXITSTATUS(status), 0); + pwtest_int_eq(WIFSIGNALED(status), 0); return PWTEST_PASS; #else return PWTEST_SKIP; @@ -24,6 +29,7 @@ PWTEST(pactl_test) #ifdef PACTL_PATH int status = pwtest_spawn(PACTL_PATH, (char *[]){ "pactl", "info", NULL }); pwtest_int_eq(WEXITSTATUS(status), 0); + pwtest_int_eq(WIFSIGNALED(status), 0); return PWTEST_PASS; #else return PWTEST_SKIP; From c9c7552fed046c4d9838cafa59fbf23079ebb199 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 4 Aug 2025 11:01:06 +0200 Subject: [PATCH 0690/1014] alsa: clear port before adding to free list If we clear the port after adding, we will destroy the links into the list and cause crashes later. --- spa/plugins/alsa/alsa-seq-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 15baf0a7f..06c5bc28f 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -389,11 +389,11 @@ static void free_port(struct seq_state *state, struct seq_stream *stream, struct { stream->ports[port->id] = NULL; spa_list_remove(&port->link); - spa_list_append(&state->free_list, &port->link); spa_node_emit_port_info(&state->hooks, port->direction, port->id, NULL); spa_zero(*port); + spa_list_append(&state->free_list, &port->link); } static void init_port(struct seq_state *state, struct seq_port *port, const snd_seq_addr_t *addr, From bac3d3128350fb95bc562e65bc80c80192fa242a Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 2 Aug 2025 22:24:16 -0400 Subject: [PATCH 0691/1014] pod: fix some data races in body code The body code didn't use atomic loads, so it had undefined behavior if the body was concurrently modified. Use atomic loads to fix this. Since the memory order is __ATOMIC_RELAXED this has no runtime overhead. Also add barriers around a strncpy call and cast to volatile before checking for a NUL terminator, though NUL-terminated strings in shared memory are unuseable. There are some places where bytewise atomic memcpy(), which doesn't currently exist, is needed. Instead, try to fake it by using two barriers around memcpy(). --- meson.build | 2 +- spa/include/spa/pod/body.h | 58 ++++++++++++++++++++++++---------- spa/include/spa/utils/atomic.h | 2 ++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/meson.build b/meson.build index 40c1e4d28..307f1155c 100644 --- a/meson.build +++ b/meson.build @@ -4,7 +4,7 @@ project('pipewire', ['c' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', 'c_std=gnu11', - 'cpp_std=c++17', + 'cpp_std=c++20', 'b_pie=true', #'b_sanitize=address,undefined', 'buildtype=debugoptimized' ]) diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index d4ed81ad5..57c3c2f6c 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -105,11 +106,14 @@ SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } +#define SPA_POD_BODY_LOAD_ONCE(a, b) (*(a) = SPA_LOAD_ONCE((const volatile __typeof__(a))(b))) +#define SPA_POD_BODY_LOAD_FIELD_ONCE(a, b, field) ((a)->field = SPA_LOAD_ONCE(&((const volatile __typeof__(a))(b))->field)) + SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) { if (!spa_pod_is_bool(pod)) return -EINVAL; - *value = !!*((int32_t*)body); + *value = !!__atomic_load_n((const int32_t *)body, __ATOMIC_RELAXED); return 0; } @@ -122,7 +126,7 @@ SPA_API_POD_BODY int spa_pod_body_get_id(const struct spa_pod *pod, const void * { if (!spa_pod_is_id(pod)) return -EINVAL; - *value = *((uint32_t*)body); + SPA_POD_BODY_LOAD_ONCE(value, body); return 0; } @@ -135,7 +139,7 @@ SPA_API_POD_BODY int spa_pod_body_get_int(const struct spa_pod *pod, const void { if (!spa_pod_is_int(pod)) return -EINVAL; - *value = *((int32_t*)body); + SPA_POD_BODY_LOAD_ONCE(value, body); return 0; } @@ -148,7 +152,10 @@ SPA_API_POD_BODY int spa_pod_body_get_long(const struct spa_pod *pod, const void { if (!spa_pod_is_long(pod)) return -EINVAL; - *value = *((int64_t*)body); + /* TODO this is wrong per C standard, but if it breaks so does the Linux kernel. */ + SPA_BARRIER; + memcpy(value, body, sizeof *value); + SPA_BARRIER; return 0; } @@ -161,7 +168,9 @@ SPA_API_POD_BODY int spa_pod_body_get_float(const struct spa_pod *pod, const voi { if (!spa_pod_is_float(pod)) return -EINVAL; - *value = *((float*)body); + SPA_BARRIER; + memcpy(value, body, sizeof *value); + SPA_BARRIER; return 0; } @@ -174,7 +183,9 @@ SPA_API_POD_BODY int spa_pod_body_get_double(const struct spa_pod *pod, const vo { if (!spa_pod_is_double(pod)) return -EINVAL; - *value = *((double*)body); + SPA_BARRIER; + memcpy(value, body, sizeof *value); + SPA_BARRIER; return 0; } @@ -190,7 +201,7 @@ SPA_API_POD_BODY int spa_pod_body_get_string(const struct spa_pod *pod, if (!spa_pod_is_string(pod)) return -EINVAL; s = (const char *)body; - if (s[pod->size-1] != '\0') + if (((const volatile char *)s)[pod->size-1] != '\0') return -EINVAL; *value = s; return 0; @@ -202,7 +213,9 @@ SPA_API_POD_BODY int spa_pod_body_copy_string(const struct spa_pod *pod, const v const char *s; if (spa_pod_body_get_string(pod, body, &s) < 0 || maxlen < 1) return -EINVAL; + SPA_BARRIER; strncpy(dest, s, maxlen-1); + SPA_BARRIER; dest[maxlen-1]= '\0'; return 0; } @@ -232,8 +245,11 @@ SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const v { if (!spa_pod_is_pointer(pod)) return -EINVAL; - *type = ((struct spa_pod_pointer_body*)body)->type; - *value = ((struct spa_pod_pointer_body*)body)->value; + struct spa_pod_pointer_body b; + SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, type); + SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, value); + *type = b.type; + *value = b.value; return 0; } @@ -247,7 +263,9 @@ SPA_API_POD_BODY int spa_pod_body_get_fd(const struct spa_pod *pod, const void * { if (!spa_pod_is_fd(pod)) return -EINVAL; - *value = *((int64_t*)body); + SPA_BARRIER; + memcpy(value, body, sizeof *value); + SPA_BARRIER; return 0; } @@ -261,7 +279,8 @@ SPA_API_POD_BODY int spa_pod_body_get_rectangle(const struct spa_pod *pod, const { if (!spa_pod_is_rectangle(pod)) return -EINVAL; - *value = *((struct spa_rectangle*)body); + SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, width); + SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, height); return 0; } @@ -274,7 +293,8 @@ SPA_API_POD_BODY int spa_pod_body_get_fraction(const struct spa_pod *pod, const { if (!spa_pod_is_fraction(pod)) return -EINVAL; - *value = *((struct spa_fraction*)body); + SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, num); + SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, denom); return 0; } @@ -301,7 +321,8 @@ SPA_API_POD_BODY int spa_pod_body_get_array(const struct spa_pod *pod, const voi if (!spa_pod_is_array(pod)) return -EINVAL; arr->pod = *pod; - memcpy(&arr->body, body, sizeof(struct spa_pod_array_body)); + SPA_POD_BODY_LOAD_FIELD_ONCE(&arr->body.child, body, type); + SPA_POD_BODY_LOAD_FIELD_ONCE(&arr->body.child, body, size); *arr_body = SPA_PTROFF(body, sizeof(struct spa_pod_array_body), void); return 0; } @@ -334,7 +355,10 @@ SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const vo if (!spa_pod_is_choice(pod)) return -EINVAL; choice->pod = *pod; - memcpy(&choice->body, body, sizeof(struct spa_pod_choice_body)); + SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, type); + SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, flags); + SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, child.size); + SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, child.type); *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); return 0; } @@ -367,7 +391,8 @@ SPA_API_POD_BODY int spa_pod_body_get_object(const struct spa_pod *pod, const vo if (!spa_pod_is_object(pod)) return -EINVAL; object->pod = *pod; - memcpy(&object->body, body, sizeof(struct spa_pod_object_body)); + SPA_POD_BODY_LOAD_FIELD_ONCE(&object->body, body, type); + SPA_POD_BODY_LOAD_FIELD_ONCE(&object->body, body, id); *object_body = SPA_PTROFF(body, sizeof(struct spa_pod_object_body), void); return 0; } @@ -382,7 +407,8 @@ SPA_API_POD_BODY int spa_pod_body_get_sequence(const struct spa_pod *pod, const if (!spa_pod_is_sequence(pod)) return -EINVAL; seq->pod = *pod; - memcpy(&seq->body, body, sizeof(struct spa_pod_sequence_body)); + SPA_POD_BODY_LOAD_FIELD_ONCE(&seq->body, body, unit); + SPA_POD_BODY_LOAD_FIELD_ONCE(&seq->body, body, pad); *seq_body = SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), void); return 0; } diff --git a/spa/include/spa/utils/atomic.h b/spa/include/spa/utils/atomic.h index 549420b89..72a18ab8d 100644 --- a/spa/include/spa/utils/atomic.h +++ b/spa/include/spa/utils/atomic.h @@ -19,6 +19,8 @@ extern "C" { #define SPA_ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST) #define SPA_ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) #define SPA_ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST) +#define SPA_LOAD_ONCE(s) __atomic_load_n((s), __ATOMIC_RELAXED) +#define SPA_STORE_ONCE(s) __atomic_store_n((s), __ATOMIC_RELAXED) #define SPA_ATOMIC_STORE(s,v) __atomic_store_n(&(s), (v), __ATOMIC_SEQ_CST) #define SPA_ATOMIC_XCHG(s,v) __atomic_exchange_n(&(s), (v), __ATOMIC_SEQ_CST) From 3c759ae2938e0931c1153c35c3fdf42017663250 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Fri, 25 Jul 2025 11:13:55 +0200 Subject: [PATCH 0692/1014] Add IWYU annotations This allows to use clang-include-cleaner tool without headers being automatically added when is included instead. See https://issues.webrtc.org/issues/422940461 --- src/pipewire/pipewire.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h index f09d65faa..b3aa58d1a 100644 --- a/src/pipewire/pipewire.h +++ b/src/pipewire/pipewire.h @@ -7,6 +7,7 @@ #include +// IWYU pragma: begin_exports #include #include #include @@ -36,6 +37,7 @@ #include #include #include +// IWYU pragma: end_exports #ifdef __cplusplus extern "C" { From cc187b035b7cc2fb7a9454237ea5b93c075fd164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 13:58:31 +0200 Subject: [PATCH 0693/1014] spa: libcamera: source: separate type info generation Add a new function `control_details_to_pod()` that builds a `SPA_TYPE_OBJECT_PropInfo` object describing a libcamera control. --- spa/plugins/libcamera/libcamera-source.cpp | 128 +++++++++++---------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f1656c7d4..ef0209f0d 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -740,6 +740,69 @@ uint32_t prop_id_to_control(uint32_t prop_id) return SPA_ID_INVALID; } +[[nodiscard]] +spa_pod *control_details_to_pod(spa_pod_builder& b, + const libcamera::ControlId& cid, const libcamera::ControlInfo& cinfo) +{ + auto id = control_to_prop_id(cid.id()); + spa_pod_frame f; + + spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add(&b, + SPA_PROP_INFO_id, SPA_POD_Id(id), + SPA_PROP_INFO_description, SPA_POD_String(cid.name().c_str()), + 0); + + switch (cid.type()) { + case ControlTypeBool: { + bool def; + if (cinfo.def().isNone()) + def = cinfo.min().get(); + else + def = cinfo.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( + def), + 0); + } break; + case ControlTypeFloat: { + float min = cinfo.min().get(); + float max = cinfo.max().get(); + float def; + + if (cinfo.def().isNone()) + def = (min + max) / 2; + else + def = cinfo.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + def, min, max), + 0); + } break; + case ControlTypeInteger32: { + int32_t min = cinfo.min().get(); + int32_t max = cinfo.max().get(); + int32_t def; + + if (cinfo.def().isNone()) + def = (min + max) / 2; + else + def = cinfo.def().get(); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + def, min, max), + 0); + } break; + default: + return nullptr; + } + + return reinterpret_cast(spa_pod_builder_pop(&b, &f)); +} + int spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t offset, uint32_t num, @@ -748,13 +811,10 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, const ControlInfoMap &info = impl->camera->controls(); uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; - struct spa_pod_frame f[2]; struct spa_result_node_params result; struct spa_pod *ctrl; - uint32_t count = 0, skip, id; + uint32_t count = 0, skip; int res; - const ControlId *ctrl_id; - ControlInfo ctrl_info; result.id = SPA_PARAM_PropInfo; result.next = start; @@ -767,70 +827,16 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, next: it++; } + result.index = result.next++; if (it == info.end()) goto enum_end; - ctrl_id = it->first; - ctrl_info = it->second; - - id = control_to_prop_id(ctrl_id->id()); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); - spa_pod_builder_add(&b, - SPA_PROP_INFO_id, SPA_POD_Id(id), - SPA_PROP_INFO_description, SPA_POD_String(ctrl_id->name().c_str()), - 0); - switch (ctrl_id->type()) { - case ControlTypeBool: { - bool def; - if (ctrl_info.def().isNone()) - def = ctrl_info.min().get(); - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - def), - 0); - } break; - case ControlTypeFloat: { - float min = ctrl_info.min().get(); - float max = ctrl_info.max().get(); - float def; - - if (ctrl_info.def().isNone()) - def = (min + max) / 2; - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - def, min, max), - 0); - } break; - case ControlTypeInteger32: { - int32_t min = ctrl_info.min().get(); - int32_t max = ctrl_info.max().get(); - int32_t def; - - if (ctrl_info.def().isNone()) - def = (min + max) / 2; - else - def = ctrl_info.def().get(); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - def, min, max), - 0); - } break; - default: + ctrl = control_details_to_pod(b, *it->first, it->second); + if (!ctrl) goto next; - } - - ctrl = (struct spa_pod*) spa_pod_builder_pop(&b, &f[0]); if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) goto next; From 44c05cfa7bd35afddc03dc8ce75df4707f49b0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:06:11 +0200 Subject: [PATCH 0694/1014] spa: libcamera: source: move control enumeration to loop Remove the `goto`s and instead move everything into a loop. --- spa/plugins/libcamera/libcamera-source.cpp | 55 +++++++++------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index ef0209f0d..f7d21a780 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -811,44 +811,35 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, const ControlInfoMap &info = impl->camera->controls(); uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; - struct spa_result_node_params result; - struct spa_pod *ctrl; - uint32_t count = 0, skip; - int res; - - result.id = SPA_PARAM_PropInfo; - result.next = start; + spa_result_node_params result = { + .id = SPA_PARAM_PropInfo, + }; auto it = info.begin(); - for (skip = result.next - offset; skip && it != info.end(); skip--) + for (auto skip = start - offset; skip && it != info.end(); skip--) it++; - if (false) { -next: - it++; + for (result.index = start; num > 0 && it != info.end(); ++it, result.index++) { + spa_log_debug(impl->log, "%p: controls[%" PRIu32 "]: %s::%s", + impl, result.index, it->first->vendor().c_str(), + it->first->name().c_str()); + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + const auto *ctrl = control_details_to_pod(b, *it->first, it->second); + if (!ctrl) + continue; + + if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) + continue; + + result.next = result.index + 1; + + spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + num -= 1; } - result.index = result.next++; - if (it == info.end()) - goto enum_end; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - ctrl = control_details_to_pod(b, *it->first, it->second); - if (!ctrl) - goto next; - - if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) - goto next; - - spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); - - if (++count != num) - goto next; - -enum_end: - res = 0; - return res; + return 0; } struct val { From 66cc01ee2d28e0cfc546f09122ad8252362281e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:12:12 +0200 Subject: [PATCH 0695/1014] spa: libcamera: source: rework bool control type info `SPA_POD_CHOICE_Bool()` assumes that both true and false are available, which may not be the case for libcamera controls, so manualy construct the enumerated choice object and only add the actually available options. --- spa/plugins/libcamera/libcamera-source.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f7d21a780..3eeecd4b6 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -755,16 +755,20 @@ spa_pod *control_details_to_pod(spa_pod_builder& b, switch (cid.type()) { case ControlTypeBool: { - bool def; - if (cinfo.def().isNone()) - def = cinfo.min().get(); - else - def = cinfo.def().get(); + auto min = cinfo.min().get(); + auto max = cinfo.max().get(); + auto def = !cinfo.def().isNone() + ? cinfo.def().get() + : min; + spa_pod_frame f; - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( - def), - 0); + spa_pod_builder_prop(&b, SPA_PROP_INFO_type, 0); + spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0); + spa_pod_builder_bool(&b, def); + spa_pod_builder_bool(&b, min); + if (max != min) + spa_pod_builder_bool(&b, max); + spa_pod_builder_pop(&b, &f); } break; case ControlTypeFloat: { float min = cinfo.min().get(); From e9367443acc959ec3146a5cd417bebb0c42c5764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:16:18 +0200 Subject: [PATCH 0696/1014] spa: libcamera: source: ignore array controls Properly ignore array controls because they are not supported for now. --- spa/plugins/libcamera/libcamera-source.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 3eeecd4b6..069468ff1 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -744,6 +744,9 @@ uint32_t prop_id_to_control(uint32_t prop_id) spa_pod *control_details_to_pod(spa_pod_builder& b, const libcamera::ControlId& cid, const libcamera::ControlInfo& cinfo) { + if (cid.isArray()) + return nullptr; + auto id = control_to_prop_id(cid.id()); spa_pod_frame f; @@ -901,6 +904,9 @@ spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop, co ctrl_id = v->second; + if (ctrl_id->isArray()) + return -EINVAL; + d.type = ctrl_id->type(); d.id = ctrl_id->id(); From 8d9e469e09ab2a6bf25a74b756eb7303a3cf2dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:23:00 +0200 Subject: [PATCH 0697/1014] spa: libcamera: source: unify control range logic If libcamera does not provide a default value, then the average of the minimum and maximum values is taken. The same logic is duplicated for `float` and `int32_t`, so move it into a function template. --- spa/plugins/libcamera/libcamera-source.cpp | 37 ++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 069468ff1..d36acaff6 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -4,10 +4,12 @@ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include #include #include #include #include +#include #include #include @@ -740,6 +742,23 @@ uint32_t prop_id_to_control(uint32_t prop_id) return SPA_ID_INVALID; } +template +[[nodiscard]] +std::array control_info_to_range(const libcamera::ControlInfo& cinfo) +{ + static_assert(std::is_arithmetic_v); + + auto min = cinfo.min().get(); + auto max = cinfo.max().get(); + spa_assert(min <= max); + + auto def = !cinfo.def().isNone() + ? cinfo.def().get() + : (min + ((max - min) / 2)); + + return {{ min, max, def }}; +} + [[nodiscard]] spa_pod *control_details_to_pod(spa_pod_builder& b, const libcamera::ControlId& cid, const libcamera::ControlInfo& cinfo) @@ -774,14 +793,7 @@ spa_pod *control_details_to_pod(spa_pod_builder& b, spa_pod_builder_pop(&b, &f); } break; case ControlTypeFloat: { - float min = cinfo.min().get(); - float max = cinfo.max().get(); - float def; - - if (cinfo.def().isNone()) - def = (min + max) / 2; - else - def = cinfo.def().get(); + auto [ min, max, def ] = control_info_to_range(cinfo); spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( @@ -789,14 +801,7 @@ spa_pod *control_details_to_pod(spa_pod_builder& b, 0); } break; case ControlTypeInteger32: { - int32_t min = cinfo.min().get(); - int32_t max = cinfo.max().get(); - int32_t def; - - if (cinfo.def().isNone()) - def = (min + max) / 2; - else - def = cinfo.def().get(); + auto [ min, max, def ] = control_info_to_range(cinfo); spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( From e379267274364ceeef8c4f94cdc768e14dd03927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:32:56 +0200 Subject: [PATCH 0698/1014] spa: libcamera: source: handle enum controls better If the `libcamera::ControlInfo` object explicitly lists the values of a control, then use those values when adding `SPA_PROP_INFO_type` to construct an enumerated choice object. --- spa/plugins/libcamera/libcamera-source.cpp | 107 +++++++++++++++------ 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d36acaff6..a983a9de2 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -742,6 +742,32 @@ uint32_t prop_id_to_control(uint32_t prop_id) return SPA_ID_INVALID; } +[[nodiscard]] +bool control_value_to_pod(spa_pod_builder& b, const libcamera::ControlValue& cv) +{ + if (cv.isArray()) + return false; + + switch (cv.type()) { + case libcamera::ControlTypeBool: { + spa_pod_builder_bool(&b, cv.get()); + break; + } + case libcamera::ControlTypeInteger32: { + spa_pod_builder_int(&b, cv.get()); + break; + } + case libcamera::ControlTypeFloat: { + spa_pod_builder_float(&b, cv.get()); + break; + } + default: + return false; + } + + return true; +} + template [[nodiscard]] std::array control_info_to_range(const libcamera::ControlInfo& cinfo) @@ -775,41 +801,62 @@ spa_pod *control_details_to_pod(spa_pod_builder& b, SPA_PROP_INFO_description, SPA_POD_String(cid.name().c_str()), 0); - switch (cid.type()) { - case ControlTypeBool: { - auto min = cinfo.min().get(); - auto max = cinfo.max().get(); - auto def = !cinfo.def().isNone() - ? cinfo.def().get() - : min; + if (cinfo.values().empty()) { + switch (cid.type()) { + case ControlTypeBool: { + auto min = cinfo.min().get(); + auto max = cinfo.max().get(); + auto def = !cinfo.def().isNone() + ? cinfo.def().get() + : min; + spa_pod_frame f; + + spa_pod_builder_prop(&b, SPA_PROP_INFO_type, 0); + spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0); + spa_pod_builder_bool(&b, def); + spa_pod_builder_bool(&b, min); + if (max != min) + spa_pod_builder_bool(&b, max); + spa_pod_builder_pop(&b, &f); + break; + } + case ControlTypeFloat: { + auto [ min, max, def ] = control_info_to_range(cinfo); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( + def, min, max), + 0); + break; + } + case ControlTypeInteger32: { + auto [ min, max, def ] = control_info_to_range(cinfo); + + spa_pod_builder_add(&b, + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( + def, min, max), + 0); + break; + } + default: + return nullptr; + } + } + else { spa_pod_frame f; spa_pod_builder_prop(&b, SPA_PROP_INFO_type, 0); spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0); - spa_pod_builder_bool(&b, def); - spa_pod_builder_bool(&b, min); - if (max != min) - spa_pod_builder_bool(&b, max); + + if (!control_value_to_pod(b, cinfo.def())) + return nullptr; + + for (const auto& cv : cinfo.values()) { + if (!control_value_to_pod(b, cv)) + return nullptr; + } + spa_pod_builder_pop(&b, &f); - } break; - case ControlTypeFloat: { - auto [ min, max, def ] = control_info_to_range(cinfo); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( - def, min, max), - 0); - } break; - case ControlTypeInteger32: { - auto [ min, max, def ] = control_info_to_range(cinfo); - - spa_pod_builder_add(&b, - SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( - def, min, max), - 0); - } break; - default: - return nullptr; } return reinterpret_cast(spa_pod_builder_pop(&b, &f)); From 8673f17c0ab8937ac304edd06348569edf999af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 14:39:46 +0200 Subject: [PATCH 0699/1014] spa: libcamera: source: provide value labels if available For enumerated controls of type `libcamera::ControlTypeInteger32`, libcamera provides the names of the enumerators, so add them to `SPA_PROP_INFO_labels`. --- spa/plugins/libcamera/libcamera-source.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index a983a9de2..0ec270111 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -857,6 +857,21 @@ spa_pod *control_details_to_pod(spa_pod_builder& b, } spa_pod_builder_pop(&b, &f); + + if (cid.type() == libcamera::ControlTypeInteger32) { + spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); + + spa_pod_builder_push_struct(&b, &f); + for (const auto& cv : cinfo.values()) { + auto it = cid.enumerators().find(cv.get()); + if (it == cid.enumerators().end()) + continue; + + spa_pod_builder_int(&b, it->first); + spa_pod_builder_string_len(&b, it->second.data(), it->second.size()); + } + spa_pod_builder_pop(&b, &f); + } } return reinterpret_cast(spa_pod_builder_pop(&b, &f)); From e6f767d41da09e50ba864bbe7b816575028a7e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 5 Aug 2025 15:08:00 +0200 Subject: [PATCH 0700/1014] spa: libcamera: source: use dynamic builder for controls Use a dynamic spa pod builder when enumerating controls since previous changes can report more information from any given control. Also increase the stack buffer size to 4096 bytes. --- spa/plugins/libcamera/libcamera-source.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 0ec270111..0fcf3cc8a 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -883,8 +884,9 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, const struct spa_pod *filter) { const ControlInfoMap &info = impl->camera->controls(); - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; + spa_auto(spa_pod_dynamic_builder) b = {}; + spa_pod_builder_state state; + uint8_t buffer[4096]; spa_result_node_params result = { .id = SPA_PARAM_PropInfo, }; @@ -893,18 +895,21 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, for (auto skip = start - offset; skip && it != info.end(); skip--) it++; + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + for (result.index = start; num > 0 && it != info.end(); ++it, result.index++) { spa_log_debug(impl->log, "%p: controls[%" PRIu32 "]: %s::%s", impl, result.index, it->first->vendor().c_str(), it->first->name().c_str()); - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_reset(&b.b, &state); - const auto *ctrl = control_details_to_pod(b, *it->first, it->second); + const auto *ctrl = control_details_to_pod(b.b, *it->first, it->second); if (!ctrl) continue; - if (spa_pod_filter(&b, &result.param, ctrl, filter) < 0) + if (spa_pod_filter(&b.b, &result.param, ctrl, filter) < 0) continue; result.next = result.index + 1; From 6c4cd0f1b2082d44615ec870bf91c4e68cffbd52 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 3 Aug 2025 19:01:11 +0200 Subject: [PATCH 0701/1014] doc: spa: Fix offset in driver documentation --- doc/dox/internals/driver.dox | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/dox/internals/driver.dox b/doc/dox/internals/driver.dox index c31982b1d..4ab0d229b 100644 --- a/doc/dox/internals/driver.dox +++ b/doc/dox/internals/driver.dox @@ -62,10 +62,10 @@ data loop assigned to the driver node. As mentioned above, the \ref spa_io_clock::position field is the _ideal_ position of the graph cycle, in samples. This contrasts with \ref spa_io_clock::nsec, which -is the moment in monotonic clock time when the cycle _actually_ happens. This is -an important distinction when driver is run by a clock that is different to the monotonic -cloc. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the pace -of that different clock (explained in the section below). In such a case, +is the moment in monotonic clock time when the cycle _actually_ happens. This is an +important distinction when driver is run by a clock that is different to the monotonic +clock. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the +pace of that different clock (explained in the section below). In such a case, \ref spa_io_clock::position still is incremented by the duration in samples. This is important, since nodes and modules may use this field as an offset within their own internal ring buffers or similar structures, using the position field as an offset within @@ -77,15 +77,15 @@ By incrementing by the duration, this requirement is met. As mentioned earlier, the driver may be run by an internal clock that is different to the monotonic clock. If that other clock can be directly used for scheduling graph cycle initiations, then it is sufficient to compute the offset between that -clock and the monotonic clock (that is, offset = other_clock_time - monotonic_clock_time) +clock and the monotonic clock (that is, offset = monotonic_clock_time - other_clock_time) at each cycle and use that offset to translate that other clock's time to the monotonic clock time. This is accomplished by adding that offset to the \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec fields. For example, when the driver uses the realtime system clock instead of the monotonic system clock, then that realtime clock can still -be used with \c timerfd to schedule callback invocations within the data loop. Then, computing -the (realtime_clock_time - monotonic_clock_time) offset is sufficient, as mentioned, -to fulfill the requirements of the \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec -fields that their timestamps must be given in monotonic clock time. +be used with \c timerfd to schedule callback invocations within the data loop. Then, +computing the (monotonic_clock_time - realtime_clock_time) offset is sufficient, as +mentioned, to be able to translate clock's time to monotonic time for \ref spa_io_clock::nsec +and \ref spa_io_clock::next_nsec (which require monotonic clock timestamps). If however that other clock cannot be used for scheduling graph cycle initiations directly (for example, because the API of that clock has no functionality to trigger callbacks), From 2a460e18e3322f079be0c4f215390910789b5d4e Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 3 Aug 2025 17:35:28 +0200 Subject: [PATCH 0702/1014] module-rtp: Rename timestamp to actual_timestamp for clarity --- src/modules/module-rtp/audio.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 3b3de9e08..5ea16d1eb 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -462,7 +462,7 @@ static void rtp_audio_process_capture(void *data) struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; - uint32_t offs, size, timestamp, expected_timestamp, stride; + uint32_t offs, size, actual_timestamp, expected_timestamp, stride; int32_t filled, wanted; uint32_t pending, num_queued; struct spa_io_position *pos; @@ -489,7 +489,7 @@ static void rtp_audio_process_capture(void *data) pos = impl->io_position; if (SPA_LIKELY(pos)) { uint32_t rate = pos->clock.rate.denom; - timestamp = pos->clock.position * impl->rate / rate; + actual_timestamp = pos->clock.position * impl->rate / rate; next_nsec = pos->clock.next_nsec; quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / (rate * pos->clock.rate_diff)); @@ -501,18 +501,18 @@ static void rtp_audio_process_capture(void *data) impl->sink_quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / rate); } } else { - timestamp = expected_timestamp; + actual_timestamp = expected_timestamp; next_nsec = 0; quantum = 0; } if (!impl->have_sync) { pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", - timestamp, impl->seq, impl->ts_offset, impl->ssrc); - impl->ring.readindex = impl->ring.writeindex = timestamp; + actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->ring.readindex = impl->ring.writeindex = actual_timestamp; memset(impl->buffer, 0, BUFFER_SIZE); impl->have_sync = true; - expected_timestamp = timestamp; + expected_timestamp = actual_timestamp; filled = 0; if (impl->separate_sender) { @@ -521,8 +521,8 @@ static void rtp_audio_process_capture(void *data) impl->refilling = true; } } else { - if (SPA_ABS((int)expected_timestamp - (int)timestamp) > (int)quantum) { - pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); + if (SPA_ABS((int)expected_timestamp - (int)actual_timestamp) > (int)quantum) { + pw_log_warn("timestamp: expected %u != actual %u", expected_timestamp, actual_timestamp); impl->have_sync = false; } else if (filled + wanted > (int32_t)SPA_MIN(impl->target_buffer * 8, BUFFER_SIZE / stride)) { pw_log_warn("sender write overrun %u + %u > %u/%u", filled, wanted, From b8d98d03fe9f6b3f9b5f6624ae62d90a8ddc058c Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 3 Aug 2025 17:58:58 +0200 Subject: [PATCH 0703/1014] module-rtp: Fix timestamp check and add discontinuity check Until now, the timestamp check was comparing the timestamp delta against the value of the "quantum" variable. However, the timestamps use clock samples as units, while the "quantum" variable uses nanoseconds. The outcome is that this check virtually never returned true. Use the spa_io_clock duration instead of that quantum nanosecond duration to make the check actually work. Also, do not just rely on vast timestamp deltas to detect discontinuities; instead, check first for the presence of the SPA_IO_CLOCK_FLAG_DISCONT flag to detect said discontinuities. --- src/modules/module-rtp/audio.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 5ea16d1eb..2c56a70b9 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -521,7 +521,17 @@ static void rtp_audio_process_capture(void *data) impl->refilling = true; } } else { - if (SPA_ABS((int)expected_timestamp - (int)actual_timestamp) > (int)quantum) { + if (SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_DISCONT)) { + pw_log_info("IO clock reports discontinuity; resynchronizing"); + impl->have_sync = false; + } else if (SPA_ABS((int64_t)expected_timestamp - (int64_t)actual_timestamp) > (int64_t)(pos->clock.duration)) { + /* Normally, expected and actual timestamp should be in sync, and deviate + * only minimally at most. If a major deviation occurs, then most likely + * the driver clock has experienced an unexpected jump. Note that the + * cycle duration in samples is used, and not the value of "quantum". + * That value is given in nanoseconds, not samples. Also, the timestamps + * themselves are not affected by rate_diff. See the documentation + * "Driver architecture and workflow" for an explanation why not. */ pw_log_warn("timestamp: expected %u != actual %u", expected_timestamp, actual_timestamp); impl->have_sync = false; } else if (filled + wanted > (int32_t)SPA_MIN(impl->target_buffer * 8, BUFFER_SIZE / stride)) { From e5be9cce4ffe7d6f7c8eeefa5ea0cc1416355744 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 3 Aug 2025 18:04:23 +0200 Subject: [PATCH 0704/1014] module-rtp: Reorder sync checks and resynchronization code This fixes the case when synchronization is established but actually not valid anymore. In such a case, the code would _first_ write to the ring buffer (at the wrong position due to the invalid sync), and _then_ detect the bogus synchronization. Reorder the code blocks to _first_ check the current sync, then resynchronize if neeeded (or perform initial sync if no sync is established yet), and _then_ write to the ring buffer. --- src/modules/module-rtp/audio.c | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 2c56a70b9..0dd6fe2d1 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -506,21 +506,9 @@ static void rtp_audio_process_capture(void *data) quantum = 0; } - if (!impl->have_sync) { - pw_log_info("sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", - actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc); - impl->ring.readindex = impl->ring.writeindex = actual_timestamp; - memset(impl->buffer, 0, BUFFER_SIZE); - impl->have_sync = true; - expected_timestamp = actual_timestamp; - filled = 0; + /* First do the synchronization checks (if the sender is in sync already.) */ - if (impl->separate_sender) { - /* the sender should know that the sync state has changed, and that it should - * refill the buffer */ - impl->refilling = true; - } - } else { + if (impl->have_sync) { if (SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_DISCONT)) { pw_log_info("IO clock reports discontinuity; resynchronizing"); impl->have_sync = false; @@ -542,6 +530,25 @@ static void rtp_audio_process_capture(void *data) } } + /* Next, (re)synchronize. If the sender was in sync, but the checks above detected + * that resynchronization is needed, then this will be done immediately below. */ + + if (!impl->have_sync) { + pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->ring.readindex = impl->ring.writeindex = actual_timestamp; + memset(impl->buffer, 0, BUFFER_SIZE); + impl->have_sync = true; + expected_timestamp = actual_timestamp; + filled = 0; + + if (impl->separate_sender) { + /* the sender should know that the sync state has changed, and that it should + * refill the buffer */ + impl->refilling = true; + } + } + pw_log_trace("writing %u samples at %u", wanted, expected_timestamp); spa_ringbuffer_write_data(&impl->ring, From c9a8b8629fb87d0f1217dc72e66bdd502e1b3c04 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 3 Aug 2025 20:10:25 +0200 Subject: [PATCH 0705/1014] module-rtp: Limit actual max buffer size to an integer multiple of stride Opus and MIDI code get TODOs added, since it is currently unclear how to implement that fix for them. --- src/modules/module-rtp/audio.c | 26 ++++++++++----------- src/modules/module-rtp/midi.c | 3 ++- src/modules/module-rtp/opus.c | 3 ++- src/modules/module-rtp/stream.c | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 0dd6fe2d1..f76d89815 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -55,7 +55,7 @@ static void rtp_audio_process_playback(void *data) * ring buffer at a position X" is mentioned. To be exact, that buffer is actually * impl->buffer. And since X can be a timestamp whose value is far higher than the * buffer size (and the fact that impl->buffer is a _ring_ buffer), reads and writes - * actually first apply BUFFER_MASK to the position to implement a ring buffer + * actually first do a modulo operation to the position to implement a ring buffer * index wrap-around. (Wrap-around when reading / writing the data bytes is * handled by the spa_ringbuffer code; this is about the wrap around of the * read or write index itself.) */ @@ -114,8 +114,8 @@ static void rtp_audio_process_playback(void *data) spa_ringbuffer_read_data(&impl->ring, impl->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, + impl->actual_max_buffer_size, + (timestamp * stride) % impl->actual_max_buffer_size, d[0].data, wanted * stride); /* Clear the bytes that were just retrieved. Since the fill level @@ -126,8 +126,8 @@ static void rtp_audio_process_playback(void *data) * region of the retrieved data with null bytes. */ ringbuffer_clear(&impl->ring, impl->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, + impl->actual_max_buffer_size, + (timestamp * stride) % impl->actual_max_buffer_size, wanted * stride); if (!impl->io_position) { @@ -215,8 +215,8 @@ static void rtp_audio_process_playback(void *data) spa_ringbuffer_read_data(&impl->ring, impl->buffer, - BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, + impl->actual_max_buffer_size, + (timestamp * stride) % impl->actual_max_buffer_size, d[0].data, wanted * stride); timestamp += wanted; @@ -314,8 +314,8 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) pw_log_trace("got samples:%u", samples); spa_ringbuffer_write_data(&impl->ring, impl->buffer, - BUFFER_SIZE, - (write * stride) & BUFFER_MASK, + impl->actual_max_buffer_size, + (write * stride) % impl->actual_max_buffer_size, &buffer[hlen], (samples * stride)); /* Only update the write index if data was actually _appended_. @@ -418,8 +418,8 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin header.timestamp = htonl(impl->ts_offset + (set_timestamp ? set_timestamp : timestamp)); set_iovec(&impl->ring, - impl->buffer, BUFFER_SIZE, - (timestamp * stride) & BUFFER_MASK, + impl->buffer, impl->actual_max_buffer_size, + (timestamp * stride) % impl->actual_max_buffer_size, &iov[1], tosend * stride); pw_log_trace("sending %d packet:%d ts_offset:%d timestamp:%d", @@ -553,8 +553,8 @@ static void rtp_audio_process_capture(void *data) spa_ringbuffer_write_data(&impl->ring, impl->buffer, - BUFFER_SIZE, - (expected_timestamp * stride) & BUFFER_MASK, + impl->actual_max_buffer_size, + (expected_timestamp * stride) % impl->actual_max_buffer_size, SPA_PTROFF(d[0].data, offs, void), wanted * stride); expected_timestamp += wanted; spa_ringbuffer_write_update(&impl->ring, expected_timestamp); diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index bcecf309f..77e348233 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -5,7 +5,8 @@ #include #include -/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. */ +/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. + * Also check out the usage of actual_max_buffer_size in audio.c. */ static void rtp_midi_process_playback(void *data) { diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index fa646f1da..6f27bd2bd 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -7,7 +7,8 @@ #include #include -/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. */ +/* TODO: Direct timestamp mode here may require a rework. See audio.c for a reference. + * Also check out the usage of actual_max_buffer_size in audio.c. */ static void rtp_opus_process_playback(void *data) { diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index e44289a7e..3e9c12645 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -62,6 +62,7 @@ struct impl { uint32_t rate; uint32_t stride; + uint32_t actual_max_buffer_size; uint8_t payload; uint32_t ssrc; uint16_t seq; @@ -423,6 +424,46 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, break; } + /* Limit the actual maximum buffer size to the maximum integer multiple + * amount of impl->stride that fits within BUFFER_SIZE. This is important + * to prevent corner cases where the read pointer wrapped around at the + * same time when the IO clock experiences a discontinuity. + * + * If the BUFFER_SIZE constant is not an integer multiple of impl->stride, + * pointer wrap-arounds will result in positions that exhibit a nonzero + * impl->stride division rest. Also, the write and read pointers are normally + * increased monotonically and contiguously. But, if a discontinuity is + * detected, these pointers may be resynchronized. Importantly, sometimes + * only one of them may be resynchronized, while the other retains its existing + * synchronization. (For example, the read and write side may use different + * discontinuity thresholds.) + * + * What then can happen is that the resynchronized pointer exhibits a _different_ + * impl->stride division than the other pointer. Once the resynchronization takes + * place, that pointer is again monotonically increased from then on, so those + * division rests will stay different. This then means that the read and write + * operations will not be aligned properly. For example, a write operation might + * write to position 20 in the ring buffer, but the read operation might read + * from position 22, and doing so with a stride value of 6. The end result is + * invalid data. + * + * One way to visualize this is to think of the ring buffer as a grid. The grid + * cell size equals impl->stride. If BUFFER_SIZE is not an integer multiple of + * impl->stride, it means that the very last grid cell will have a size that is + * smaller than impl->stride. The unaligned read/write operations mean that the + * operations will not be done at the same grid cell boundaries, so for example + * the read operation might think that a cell starts at byte 2, while the write + * operation might think that the same cell starts at byte 4. + * + * By limiting the actual maximum buffer size to the maximum integer multiple + * amount of impl->stride that fits within BUFFER_SIZE, this is avoided, since + * then, all grid cells are guaranteed to have the size impl->stride, so the + * aforementioned division rest will always be zero. + */ + impl->actual_max_buffer_size = SPA_ROUND_DOWN(BUFFER_SIZE, impl->stride); + pw_log_debug("possible / actual max buffer size: %" PRIu32 " / %" PRIu32, + (uint32_t)BUFFER_SIZE, impl->actual_max_buffer_size); + pw_properties_setf(props, "rtp.mime", "%s", impl->format_info->mime); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) From 97a1609b29fe4fbafcbdd42ea0cf72cf1f652a86 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Tue, 5 Aug 2025 11:54:52 +0200 Subject: [PATCH 0706/1014] module-rtp: Reset ring buffer contents when stream starts --- src/modules/module-rtp/audio.c | 2 +- src/modules/module-rtp/stream.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index f76d89815..68e317f1e 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -337,7 +337,7 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) * * (Note that this write index update is only important if * the constant delay mode is active, or if no spa_io_position - * was provided yet. See the rtp_audio_process_playback() + * was not provided yet. See the rtp_audio_process_playback() * code for more about this.) */ if (expected_write < (write + samples)) { write += samples; diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 3e9c12645..0ec4388e7 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -104,6 +104,13 @@ struct impl { bool timer_running; int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); + /* Used for resetting the ring buffer before the stream starts, to prevent + * reading from uninitialized memory. This can otherwise happen in direct + * timestamp mode when the read index is set to an uninitialized location. + * This is a function pointer to allow customizations in case resetting + * requires filling the ring buffer with something other than nullbytes + * (this can happen with DSD for example). */ + void (*reset_ringbuffer)(struct impl *impl); void (*flush_timeout)(struct impl *impl, uint64_t expirations); void (*deinit)(struct impl *impl, enum spa_direction direction); @@ -188,6 +195,8 @@ static int stream_start(struct impl *impl) impl->first = true; + impl->reset_ringbuffer(impl); + rtp_stream_emit_state_changed(impl, true, NULL); if (impl->separate_sender) { @@ -306,6 +315,11 @@ static void on_flush_timeout(void *d, uint64_t expirations) impl->flush_timeout(d, expirations); } +static void default_reset_ringbuffer(struct impl *impl) +{ + spa_memzero(impl->buffer, sizeof(impl->buffer)); +} + struct rtp_stream *rtp_stream_new(struct pw_core *core, enum spa_direction direction, struct pw_properties *props, const struct rtp_stream_events *events, void *data) @@ -341,6 +355,8 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, goto out; } + impl->reset_ringbuffer = default_reset_ringbuffer; + if ((str = pw_properties_get(props, "sess.media")) == NULL) str = "audio"; From 2ac708d9169faf2304eae2cd127c0c9bd04243ce Mon Sep 17 00:00:00 2001 From: Niklas Carlsson Date: Wed, 6 Aug 2025 11:52:47 +0200 Subject: [PATCH 0707/1014] filter-graph: add NULL data support per port It would be good to define NULL support per port in e.g. LV2 plugins where sidechains are usually defined optional. --- spa/plugins/filter-graph/audio-plugin.h | 10 ++++++---- spa/plugins/filter-graph/filter-graph.c | 10 ++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spa/plugins/filter-graph/audio-plugin.h b/spa/plugins/filter-graph/audio-plugin.h index d8925ea33..81d55294c 100644 --- a/spa/plugins/filter-graph/audio-plugin.h +++ b/spa/plugins/filter-graph/audio-plugin.h @@ -27,10 +27,11 @@ struct spa_fga_plugin_methods { struct spa_fga_port { uint32_t index; const char *name; -#define SPA_FGA_PORT_INPUT (1ULL << 0) -#define SPA_FGA_PORT_OUTPUT (1ULL << 1) -#define SPA_FGA_PORT_CONTROL (1ULL << 2) -#define SPA_FGA_PORT_AUDIO (1ULL << 3) +#define SPA_FGA_PORT_INPUT (1ULL << 0) +#define SPA_FGA_PORT_OUTPUT (1ULL << 1) +#define SPA_FGA_PORT_CONTROL (1ULL << 2) +#define SPA_FGA_PORT_AUDIO (1ULL << 3) +#define SPA_FGA_PORT_SUPPORTS_NULL_DATA (1ULL << 4) uint64_t flags; #define SPA_FGA_HINT_BOOLEAN (1ULL << 0) @@ -47,6 +48,7 @@ struct spa_fga_port { #define SPA_FGA_IS_PORT_OUTPUT(x) ((x) & SPA_FGA_PORT_OUTPUT) #define SPA_FGA_IS_PORT_CONTROL(x) ((x) & SPA_FGA_PORT_CONTROL) #define SPA_FGA_IS_PORT_AUDIO(x) ((x) & SPA_FGA_PORT_AUDIO) +#define SPA_FGA_SUPPORTS_NULL_DATA(x) ((x) & SPA_FGA_PORT_SUPPORTS_NULL_DATA) struct spa_fga_descriptor { const char *name; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index c9d276c5f..718bc542d 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1619,6 +1619,8 @@ static int impl_activate(void *object, const struct spa_dict *props) if ((res = port_ensure_data(link->output, i, max_samples)) < 0) goto error; data = link->output->audio_data[i]; + } else if (SPA_FGA_SUPPORTS_NULL_DATA(d->ports[port->p].flags)) { + data = NULL; } else { data = sd; } @@ -1629,9 +1631,13 @@ static int impl_activate(void *object, const struct spa_dict *props) for (j = 0; j < desc->n_output; j++) { port = &node->output_port[j]; if (port->audio_data[i] == NULL) { + if (SPA_FGA_SUPPORTS_NULL_DATA(d->ports[port->p].flags)) + data = NULL; + else + data = dd; spa_log_info(impl->log, "connect output port %s[%d]:%s %p", - node->name, i, d->ports[port->p].name, dd); - d->connect_port(node->hndl[i], port->p, dd); + node->name, i, d->ports[port->p].name, data); + d->connect_port(node->hndl[i], port->p, data); } } for (j = 0; j < desc->n_control; j++) { From 30ba62e47c4fb8f8e95c6161286b702e6fdf89dc Mon Sep 17 00:00:00 2001 From: Niklas Carlsson Date: Wed, 6 Aug 2025 12:05:35 +0200 Subject: [PATCH 0708/1014] filter-graph: set NULL data support in LV2 In LV2, a port supports NULL data if the connectionOptional property is set. --- spa/plugins/filter-graph/plugin_lv2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spa/plugins/filter-graph/plugin_lv2.c b/spa/plugins/filter-graph/plugin_lv2.c index 3928b236c..33bf38470 100644 --- a/spa/plugins/filter-graph/plugin_lv2.c +++ b/spa/plugins/filter-graph/plugin_lv2.c @@ -550,6 +550,8 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const fp->flags |= SPA_FGA_PORT_CONTROL; if (lilv_port_is_a(p->p, port, c->lv2_AudioPort)) fp->flags |= SPA_FGA_PORT_AUDIO; + if (lilv_port_has_property(p->p, port, c->lv2_Optional)) + fp->flags |= SPA_FGA_PORT_SUPPORTS_NULL_DATA; fp->hint = 0; if (latent && latency_index == i) From 9aeba05e479319aa1a241ab9a8062782fdeeb886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 26 Jul 2025 15:36:04 +0200 Subject: [PATCH 0709/1014] spa: libcamera: source: rework control setting Instead of applying each control separately, collect them into a `libcamera::ControlList`, and try to apply them at the end. Furthermore, instead of doing a blocking loop invoke, use `spa_loop_locked()` as it is sufficient to update `impl::ctrls`. After this change control setting works in a more "atomic" way since every control is parsed and checked before actually reaching the camera. It is also detected if the given control is not supported by the camera. --- spa/plugins/libcamera/libcamera-source.cpp | 190 +++++++++++---------- 1 file changed, 104 insertions(+), 86 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 0fcf3cc8a..4bdafebaa 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -743,6 +743,63 @@ uint32_t prop_id_to_control(uint32_t prop_id) return SPA_ID_INVALID; } +[[nodiscard]] +ControlValue control_value_from_pod(const libcamera::ControlId& cid, const spa_pod *value, const void *body) +{ + if (cid.isArray()) + return {}; + + switch (cid.type()) { + case libcamera::ControlTypeBool: { + bool v; + if (spa_pod_body_get_bool(value, body, &v) < 0) + return {}; + + return v; + } + case libcamera::ControlTypeInteger32: { + int32_t v; + if (spa_pod_body_get_int(value, body, &v) < 0) + return {}; + + return v; + } + case libcamera::ControlTypeFloat: { + float v; + if (spa_pod_body_get_float(value, body, &v) < 0) + return {}; + + return v; + } + default: + return {}; + } + + return {}; +} + +int control_list_update_from_prop(libcamera::ControlList& list, const spa_pod_prop *prop, const void *body) +{ + auto id = prop_id_to_control(prop->key); + if (id == SPA_ID_INVALID) + return -ENOENT; + + auto it = list.idMap()->find(id); + if (it == list.idMap()->end()) + return -ENOENT; + + if (!list.infoMap()->count(it->second)) + return -ENOENT; + + auto val = control_value_from_pod(*it->second, &prop->value, body); + if (val.isNone()) + return -EINVAL; + + list.set(id, std::move(val)); + + return 0; +} + [[nodiscard]] bool control_value_to_pod(spa_pod_builder& b, const libcamera::ControlValue& cv) { @@ -921,91 +978,31 @@ spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, return 0; } -struct val { - ControlType type; - uint32_t id; - union { - bool b_val; - int32_t i_val; - float f_val; +int spa_libcamera_apply_controls(struct impl *impl, libcamera::ControlList&& controls) +{ + if (controls.empty()) + return 0; + + struct invoke_data { + ControlList *controls; + } d = { + .controls = &controls, }; -}; -int do_update_ctrls(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - auto *impl = static_cast(user_data); - const auto *d = static_cast(data); + return spa_loop_locked( + impl->data_loop, + [](spa_loop *, bool, uint32_t, const void *data, size_t, void *user_data) + { + const auto *d = static_cast(data); + auto *impl = static_cast(user_data); - switch (d->type) { - case ControlTypeBool: - impl->ctrls.set(d->id, d->b_val); - break; - case ControlTypeFloat: - impl->ctrls.set(d->id, d->f_val); - break; - case ControlTypeInteger32: - impl->ctrls.set(d->id, d->i_val); - break; - default: - break; - } - return 0; -} + impl->ctrls.merge(std::move(*d->controls), + libcamera::ControlList::MergePolicy::OverwriteExisting); -int -spa_libcamera_set_control(struct impl *impl, const struct spa_pod_prop *prop, const void *body) -{ - const ControlInfoMap &info = impl->camera->controls(); - const ControlId *ctrl_id; - int res; - struct val d; - uint32_t control_id; - - control_id = prop_id_to_control(prop->key); - if (control_id == SPA_ID_INVALID) - return -ENOENT; - - auto v = info.idmap().find(control_id); - if (v == info.idmap().end()) - return -ENOENT; - - ctrl_id = v->second; - - if (ctrl_id->isArray()) - return -EINVAL; - - d.type = ctrl_id->type(); - d.id = ctrl_id->id(); - - switch (d.type) { - case ControlTypeBool: - if (!spa_pod_is_bool(&prop->value)) - return -EINVAL; - spa_pod_body_get_bool(&prop->value, body, &d.b_val); - break; - case ControlTypeFloat: - if (!spa_pod_is_float(&prop->value)) - return -EINVAL; - spa_pod_body_get_float(&prop->value, body, &d.f_val); - break; - case ControlTypeInteger32: - if (!spa_pod_is_int(&prop->value)) - return -EINVAL; - spa_pod_body_get_int(&prop->value, body, &d.i_val); - break; - default: - res = -EINVAL; - goto done; - } - spa_loop_invoke(impl->data_loop, do_update_ctrls, 0, &d, sizeof(d), true, impl); - res = 0; -done: - return res; + return 0; + }, + 0, &d, sizeof(d), impl + ); } @@ -1522,13 +1519,24 @@ int impl_node_set_param(void *object, if (param == nullptr) return 0; + libcamera::ControlList controls(impl->camera->controls()); + int res; + SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { default: - spa_libcamera_set_control(impl, prop, SPA_POD_BODY_CONST(&prop->value)); + res = control_list_update_from_prop(controls, prop, SPA_POD_BODY_CONST(&prop->value)); + if (res < 0) + return res; + break; } } + + res = spa_libcamera_apply_controls(impl, std::move(controls)); + if (res < 0) + return res; + break; } default: @@ -2021,11 +2029,13 @@ int impl_node_port_reuse_buffer(void *object, int process_control(struct impl *impl, struct spa_pod_sequence *control, uint32_t size) { + libcamera::ControlList controls(impl->camera->controls()); struct spa_pod_parser parser[2]; struct spa_pod_frame frame[2]; struct spa_pod_sequence seq; const void *seq_body, *c_body; struct spa_pod_control c; + int res; spa_pod_parser_init_from_data(&parser[0], control, size, 0, size); if (spa_pod_parser_push_sequence_body(&parser[0], &frame[0], &seq, &seq_body) < 0) @@ -2033,8 +2043,7 @@ int process_control(struct impl *impl, struct spa_pod_sequence *control, uint32_ while (spa_pod_parser_get_control_body(&parser[0], &c, &c_body) >= 0) { switch (c.type) { - case SPA_CONTROL_Properties: - { + case SPA_CONTROL_Properties: { struct spa_pod_object obj; struct spa_pod_prop prop; const void *obj_body, *prop_body; @@ -2042,14 +2051,23 @@ int process_control(struct impl *impl, struct spa_pod_sequence *control, uint32_ if (spa_pod_parser_init_object_body(&parser[1], &frame[1], &c.value, c_body, &obj, &obj_body) < 0) continue; - while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) - spa_libcamera_set_control(impl, &prop, prop_body); + while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) { + res = control_list_update_from_prop(controls, &prop, prop_body); + if (res < 0) + return res; + } + break; } default: break; } } + + res = spa_libcamera_apply_controls(impl, std::move(controls)); + if (res < 0) + return res; + return 0; } From 206a30d10006fd8ff08357697ce02104017881b0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 8 Aug 2025 15:08:51 +0200 Subject: [PATCH 0710/1014] examples: the example generates UMP --- src/examples/midi-src.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/midi-src.c b/src/examples/midi-src.c index 12f86d6f2..edcaa0f08 100644 --- a/src/examples/midi-src.c +++ b/src/examples/midi-src.c @@ -213,7 +213,7 @@ int main(int argc, char *argv[]) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); From a39462a6c08cd1105a35478283a193693a131965 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 31 Jul 2025 18:56:15 +0300 Subject: [PATCH 0711/1014] bluez5: support 44.1kHz rate for BAP LC3 liblc3 doesn't support 44.1kHz directly, but its authors write one should just use some nearby rate instead. Do just that. --- spa/plugins/bluez5/bap-codec-lc3.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index d316da2b6..144b8a0fd 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -32,6 +32,7 @@ struct impl { lc3_decoder_t dec[LC3_MAX_CHANNELS]; int samplerate; + int codec_samplerate; int channels; int frame_dus; int framelen; @@ -321,7 +322,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, uint8_t *data = caps; const char *str; uint16_t framelen[2]; - uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_32KHZ | \ + uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_44KHZ | LC3_FREQ_32KHZ | \ LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ; uint8_t duration_mask = LC3_DUR_ANY; uint8_t channel_counts = LC3_CHAN_1 | LC3_CHAN_2; @@ -957,6 +958,11 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } + if (conf.rate == LC3_CONFIG_FREQ_44KHZ) { + if (i++ == 0) + spa_pod_builder_int(b, 44100); + spa_pod_builder_int(b, 44100); + } if (conf.rate == LC3_CONFIG_FREQ_32KHZ) { if (i++ == 0) spa_pod_builder_int(b, 32000); @@ -1019,6 +1025,9 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags case LC3_CONFIG_FREQ_48KHZ: info->info.raw.rate = 48000U; break; + case LC3_CONFIG_FREQ_44KHZ: + info->info.raw.rate = 44100U; + break; case LC3_CONFIG_FREQ_32KHZ: info->info.raw.rate = 32000U; break; @@ -1144,6 +1153,10 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, this->channels = config_info.info.raw.channels; this->framelen = conf.framelen; + /* Google liblc3 doesn't have direct support for encoding to 44.1kHz; instead + * lc3.h suggests using a nearby samplerate, so we do just that */ + this->codec_samplerate = (this->samplerate == 44100) ? 48000 : this->samplerate; + switch (conf.frame_duration) { case LC3_CONFIG_DURATION_10: this->frame_dus = 10000; @@ -1159,7 +1172,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, spa_log_info(log_, "LC3 rate:%d frame_duration:%d channels:%d framelen:%d nblks:%d", this->samplerate, this->frame_dus, this->channels, this->framelen, conf.n_blks); - res = lc3_frame_samples(this->frame_dus, this->samplerate); + res = lc3_frame_samples(this->frame_dus, this->codec_samplerate); if (res < 0) { spa_log_error(log_, "invalid LC3 frame samples"); res = -EINVAL; @@ -1170,7 +1183,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if (!(flags & MEDIA_CODEC_FLAG_SINK)) { for (ich = 0; ich < this->channels; ich++) { - this->enc[ich] = lc3_setup_encoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_encoder_size(this->frame_dus, this->samplerate))); + this->enc[ich] = lc3_setup_encoder(this->frame_dus, this->codec_samplerate, 0, + calloc(1, lc3_encoder_size(this->frame_dus, this->codec_samplerate))); if (this->enc[ich] == NULL) { res = -EINVAL; goto error; @@ -1178,7 +1192,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, } } else { for (ich = 0; ich < this->channels; ich++) { - this->dec[ich] = lc3_setup_decoder(this->frame_dus, this->samplerate, 0, calloc(1, lc3_decoder_size(this->frame_dus, this->samplerate))); + this->dec[ich] = lc3_setup_decoder(this->frame_dus, this->codec_samplerate, 0, + calloc(1, lc3_decoder_size(this->frame_dus, this->codec_samplerate))); if (this->dec[ich] == NULL) { res = -EINVAL; goto error; @@ -1230,7 +1245,7 @@ static uint64_t codec_get_interval(void *data) { struct impl *this = data; - return (uint64_t)this->frame_dus * 1000; + return (uint64_t)this->samples * SPA_NSEC_PER_SEC / this->samplerate; } static int codec_abr_process (void *data, size_t unsent) @@ -1397,6 +1412,9 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps, case LC3_CONFIG_FREQ_48KHZ: data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_48KHZ); break; + case LC3_CONFIG_FREQ_44KHZ: + data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_44KHZ); + break; case LC3_CONFIG_FREQ_32KHZ: data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_32KHZ); break; From 22e80e228c2f967b1ae107e94b8cdf1499efa9cf Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 8 Aug 2025 12:41:48 +0300 Subject: [PATCH 0712/1014] bluez5: backend-native: reset hfp_hf_in_progress state on error reply If a AT+CHLD command fails, we may get stuck in the "in progress" state forever unless we clear the state here. --- spa/plugins/bluez5/backend-native.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 69b25cfd6..af62887a3 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -2437,6 +2437,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) default: break; } + } else { + /* reset state in case of an error reply */ + rfcomm->hfp_hf_in_progress = false; } rfcomm_send_next_cmd(rfcomm); From 618d60a1f2766e9e3a7d662b3b51adbd67496c70 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 9 Aug 2025 12:53:41 +0300 Subject: [PATCH 0713/1014] alsa: show correct value in api.alsa.period-num --- spa/plugins/alsa/alsa-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index fad4e0852..559c3fec8 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3883,7 +3883,7 @@ void spa_alsa_emit_node_info(struct state *state, bool full) snprintf(nperiods, sizeof(nperiods), "%lu", state->period_frames != 0 ? state->buffer_frames / state->period_frames : 0); else if (state->default_period_num) - snprintf(nperiods, sizeof(nperiods), "%u", state->default_period_size); + snprintf(nperiods, sizeof(nperiods), "%u", state->default_period_num); items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods[0] ? nperiods : NULL); if (state->have_format) From 1ad3fdff8a377a6e10532a8b0530765a4b472333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Mon, 11 Aug 2025 16:36:01 +0200 Subject: [PATCH 0714/1014] bluez5: backend-native: Free command list queue on RFComm free When RFComm conection is closed or lost, the pending commands should be freed, and if a DBus message is attached an error reply should be sent. --- spa/plugins/bluez5/backend-native.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index af62887a3..d4b5beef6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -411,12 +411,23 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { struct updated_call *updated_call; + struct rfcomm_cmd *cmd; spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { spa_list_remove(&updated_call->link); free(updated_call); } + spa_list_consume(cmd, &rfcomm->cmd_send_queue, link) { + if (cmd->msg) { + telephony_send_dbus_method_reply(rfcomm->backend->telephony, cmd->msg, BT_TELEPHONY_ERROR_FAILED, 0); + spa_clear_ptr(cmd->msg, dbus_message_unref); + } + + spa_list_remove(&cmd->link); + free(cmd); + } + codec_switch_stop_timer(rfcomm); if (rfcomm->telephony_ag) { telephony_ag_destroy(rfcomm->telephony_ag); From a50b66651e360241ef43540ecddee0cc76a3edb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Danis?= Date: Fri, 8 Aug 2025 16:26:25 +0200 Subject: [PATCH 0715/1014] bluez5: backend-native: Add log for call state changes This also to track each call state changes. --- spa/plugins/bluez5/backend-native.c | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index d4b5beef6..044ad5bd6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1911,6 +1911,12 @@ static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { .set_microphone_volume = hfp_hf_set_microphone_volume, }; +#define hfp_hf_set_call_state(log, obj, new_state) \ +({ \ + spa_log_debug(log, "call id: %u, %u -> %u", obj->id, obj->state, new_state); \ + obj->state = new_state; \ +}) + static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) { struct impl *backend = rfcomm->backend; @@ -1930,7 +1936,7 @@ static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) spa_log_debug(backend->log, "call %d -> %s", call->id, found ? "updated" : "disconnected"); if (!found) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2057,7 +2063,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2101,7 +2107,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING) { - call->state = CALL_STATE_ALERTING; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ALERTING); telephony_call_notify_updated_props(call); } } @@ -2118,7 +2124,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2128,7 +2134,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } @@ -2146,7 +2152,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) bool found_waiting = false; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); found_waiting = true; @@ -2156,7 +2162,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (!found_waiting) { spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } @@ -2167,10 +2173,10 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_HELD; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } else if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); changed = true; } @@ -2182,7 +2188,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE || call->state == CALL_STATE_WAITING) { - call->state = CALL_STATE_HELD; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } @@ -2431,14 +2437,14 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { - call->state = CALL_STATE_DISCONNECTED; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { - call->state = CALL_STATE_ACTIVE; + hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } From 0d0108e954124a302e959cb0f2d2200aa12319bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 22 Jul 2025 00:17:43 +0200 Subject: [PATCH 0716/1014] test: spa-utils: utils_snprintf_abort_neg_size: remove valgrind check Death tests are always skipped by the framework when running on valgrind, so there is no need for the check. --- test/test-spa-utils.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test-spa-utils.c b/test/test-spa-utils.c index 06b8f8d17..43c2ffa2c 100644 --- a/test/test-spa-utils.c +++ b/test/test-spa-utils.c @@ -853,9 +853,6 @@ PWTEST(utils_snprintf_abort_neg_size) size_t size = pwtest_get_iteration(current_test); char dest[8]; - if (RUNNING_ON_VALGRIND) - return PWTEST_SKIP; - spa_scnprintf(dest, size, "1234"); /* expected to abort() */ return PWTEST_FAIL; From fcb318b9c5158ab66ca1d5797b197237eae7d5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 23 Jul 2025 18:18:52 +0200 Subject: [PATCH 0717/1014] pwtest: is_debugger_attached(): rework test Check if the "TracerPid" field in `/proc/self/status` is non-zero. This does not need libcap, and does not depend on capabilities, and it also works under qemu user emulation. --- meson.build | 3 --- test/meson.build | 1 - test/pwtest.c | 43 +++++++++++-------------------------------- 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/meson.build b/meson.build index 307f1155c..0a01a8f37 100644 --- a/meson.build +++ b/meson.build @@ -381,9 +381,6 @@ libusb_dep = dependency('libusb-1.0', required : get_option('libusb')) summary({'libusb (Bluetooth quirks)': libusb_dep.found()}, bool_yn: true, section: 'Backend') cdata.set('HAVE_LIBUSB', libusb_dep.found()) -cap_lib = dependency('libcap', required : false) -cdata.set('HAVE_LIBCAP', cap_lib.found()) - glib2_dep = dependency('glib-2.0', required : get_option('flatpak')) summary({'GLib-2.0 (Flatpak support)': glib2_dep.found()}, bool_yn: true, section: 'Misc dependencies') flatpak_support = glib2_dep.found() diff --git a/test/meson.build b/test/meson.build index b4f24cf1b..c5671149d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -9,7 +9,6 @@ pwtest_deps = [ pipewire_dep, mathlib, dl_lib, - cap_lib, epoll_shim_dep ] diff --git a/test/pwtest.c b/test/pwtest.c index 7094a59e1..89a385f53 100644 --- a/test/pwtest.c +++ b/test/pwtest.c @@ -19,9 +19,6 @@ #ifdef HAVE_PIDFD_OPEN #include #endif -#ifdef HAVE_LIBCAP -#include -#endif #include #include #include @@ -33,6 +30,7 @@ #include #include "spa/utils/ansi.h" +#include "spa/utils/cleanup.h" #include "spa/utils/string.h" #include "spa/utils/defs.h" #include "spa/utils/list.h" @@ -1298,39 +1296,20 @@ static void list_tests(struct pwtest_context *ctx) static bool is_debugger_attached(void) { - bool rc = false; -#ifdef HAVE_LIBCAP - int status; - int pid = fork(); + spa_autofree char *line = NULL; + size_t length = 0; - if (pid == -1) - return 0; + spa_autoptr(FILE) f = fopen("/proc/self/status", "re"); + if (!f) + return false; - if (pid == 0) { - int ppid = getppid(); - cap_t caps = cap_get_pid(ppid); - cap_flag_value_t cap_val; - - if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_EFFECTIVE, &cap_val) == -1 || - cap_val != CAP_SET) - _exit(false); - - if (ptrace(PTRACE_ATTACH, ppid, NULL, 0) == 0) { - waitpid(ppid, NULL, 0); - ptrace(PTRACE_CONT, ppid, NULL, 0); - ptrace(PTRACE_DETACH, ppid, NULL, 0); - rc = false; - } else { - rc = true; - } - _exit(rc); - } else { - waitpid(pid, &status, 0); - rc = WEXITSTATUS(status); + while (getline(&line, &length, f) >= 0) { + unsigned int tracer_pid; + if (sscanf(line, "TracerPid: %u", &tracer_pid) == 1) + return tracer_pid > 0; } -#endif - return !!rc; + return false; } static void usage(FILE *fp, const char *progname) From 9438df8d30b917c54e2250ef2e53d2ab711f34b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 15 Jul 2025 19:16:28 +0200 Subject: [PATCH 0718/1014] Revert "ci: Add an x86 build" This reverts commit 2042a0483bed607c10244fe605b668239203b694. A new approach is added in subsequent commits based on debian for more architectures. --- .gitlab-ci.yml | 63 ++------------------------------------------------ cross-x86.txt | 23 ------------------ 2 files changed, 2 insertions(+), 84 deletions(-) delete mode 100644 cross-x86.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0c286ccf1..e381b7c64 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -102,36 +102,6 @@ include: # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson -# This is a pruned down container with enough dependencies for a basic i686 -# build to make sure we've not broken anything. This can be extended if we want -# to cover more of the code. -.fedora_x86: - variables: - # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-05-29.1' - FDO_DISTRIBUTION_VERSION: '42' - FDO_DISTRIBUTION_PACKAGES: >- - git - gcc - gcc-c++ - meson - glibc-devel.i686 - systemd-devel.i686 - dbus-devel.i686 - alsa-lib-devel.i686 - bluez-libs-devel.i686 - libffi-devel.i686 - pcre2-devel.i686 - sysprof-devel.i686 - zlib-ng-compat-devel.i686 - libblkid-devel.i686 - libmount-devel.i686 - libselinux-devel.i686 - glib2-devel.i686 - alsa-lib-devel - avahi-devel - bluez-libs-devel - .ubuntu: variables: # Update this tag when you want to trigger a rebuild @@ -243,14 +213,8 @@ include: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - - | - if [ -z "$MESON_SKIP_TEST" ]; then - meson test -C "$BUILD_DIR" --no-rebuild - fi - - | - if [ -z "$MESON_SKIP_INSTALL" ]; then - meson install -C "$BUILD_DIR" --no-rebuild - fi + - meson test -C "$BUILD_DIR" --no-rebuild + - meson install -C "$BUILD_DIR" --no-rebuild artifacts: name: pipewire-$CI_COMMIT_SHA when: always @@ -273,14 +237,6 @@ container_fedora: variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image -container_fedora_x86: - extends: - - .fedora_x86 - - .fdo.container-build@fedora - stage: container - variables: - GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image - container_alpine: extends: - .alpine @@ -406,21 +362,6 @@ build_on_fedora_html_docs: rules: - !reference [pages, rules] -build_on_fedora_x86: - extends: - - .fedora_x86 - - .not_coverity - - .fdo.distribution-image@fedora - - .build - stage: build - needs: - - job: container_fedora_x86 - artifacts: false - variables: - MESON_OPTIONS: "--cross-file=cross-x86.txt" - MESON_SKIP_TEST: "true" - MESON_SKIP_INSTALL: "true" - build_on_alpine: extends: - .alpine diff --git a/cross-x86.txt b/cross-x86.txt deleted file mode 100644 index 8ddd5ecc5..000000000 --- a/cross-x86.txt +++ /dev/null @@ -1,23 +0,0 @@ -[binaries] -c = 'gcc' -cpp = 'g++' -ld = 'ld' -cmake = 'cmake' -strip = 'strip' -pkg-config = 'pkg-config' - -[properties] -pkg_config_libdir = '/usr/lib/pkgconfig' -ld_args = '-m elf_i386' - -[built-in options] -c_args = '-m32 -msse' -c_link_args = '-m32 -msse' -cpp_args = '-m32 -msse' -cpp_link_args = '-m32 -msse' - -[host_machine] -system = 'linux' -cpu_family = 'x86' -cpu = 'i686' -endian = 'little' From 0fcabdbd0c618825bf2bd95758d440cc8262b9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 10 May 2025 12:49:06 +0200 Subject: [PATCH 0719/1014] ci: cross compile on debian 13 to multiple architectures Debian supports many architectures, and it is relatively easy to work with multiarch packages, and finally `meson env2mfile` supports generating cross files with the `--debarch` option. Previously only native builds were done in the CI, so use debian to build pipewire for multiple architectures. Some packages are unfortunately not multiarch compatible, so a separate container is built for every architecture. --- .gitlab-ci.yml | 77 ++++++++++++++++++++++ .gitlab/ci/setup-debian-cross-container.sh | 63 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100755 .gitlab/ci/setup-debian-cross-container.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e381b7c64..b8a703ab6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,9 @@ include: - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/alpine.yml' + - project: 'freedesktop/ci-templates' + ref: *templates_sha + file: '/templates/debian.yml' .fedora: variables: @@ -141,6 +144,30 @@ include: # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson +.debian: + variables: + # Update this tag when you want to trigger a rebuild + BASE_TAG: '2025-08-10.0' + FDO_DISTRIBUTION_VERSION: 'trixie' + FDO_DISTRIBUTION_PACKAGES: >- + build-essential + dpkg-dev + findutils + git + meson + +.debian-archictectures: + parallel: + matrix: + - ARCH: + - amd64 + - arm64 + - armhf + - i386 + - ppc64el + - riscv64 + - s390x + .alpine: variables: # Update this tag when you want to trigger a rebuild @@ -229,6 +256,18 @@ container_ubuntu: variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image +container_debian: + extends: + - .debian + - .debian-archictectures + - .fdo.container-build@debian + stage: container + variables: + GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image + FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" + FDO_DISTRIBUTION_EXEC: >- + ./.gitlab/ci/setup-debian-cross-container.sh "$ARCH" + container_fedora: extends: - .fedora @@ -267,6 +306,44 @@ build_on_ubuntu: variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=enabled" +build_on_debian: + extends: + - .debian + - .debian-archictectures + - .not_coverity + - .fdo.distribution-image@debian + - .build + stage: build + needs: + - job: container_debian + artifacts: false + # ideally + # parallel: + # matrix: + # - ARCH: "$ARCH" + # however https://gitlab.com/gitlab-org/gitlab/-/issues/423553 + # ("Expand variables in `needs:parallel:matrix`") + variables: + FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" + # see /.gitlab/ci/setup-debian-cross-container.sh for installed packages + MESON_OPTIONS: >- + --cross-file /opt/meson-$ARCH.cross + -D c_args=['-UFASTPATH'] + -D cpp_args=['-UFASTPATH'] + -D auto_features=enabled + -D session-managers=[] + -D bluez5-backend-native-mm=enabled + -D bluez5-codec-lc3plus=disabled + -D bluez5-codec-ldac=disabled + -D bluez5-codec-ldac-dec=disabled + -D libcamera=disabled + -D roc=disabled + -D snap=disabled + -D systemd-user-service=disabled + -D systemd-system-service=disabled + -D onnxruntime=disabled + -D vulkan=enabled + .build_on_fedora: extends: - .fedora diff --git a/.gitlab/ci/setup-debian-cross-container.sh b/.gitlab/ci/setup-debian-cross-container.sh new file mode 100755 index 000000000..616971803 --- /dev/null +++ b/.gitlab/ci/setup-debian-cross-container.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +set -ex + +packages=( + # libapparmor-dev + libasound2-dev + libavahi-client-dev + libavcodec-dev + libavfilter-dev + libavformat-dev + libbluetooth-dev + libcanberra-dev + libdbus-1-dev + libebur128-dev + libfdk-aac-dev + libffado-dev + libfftw3-dev + libfreeaptx-dev + libglib2.0-dev + libgstreamer1.0-dev + libgstreamer-plugins-base1.0-dev + libjack-jackd2-dev + liblc3-dev + liblilv-dev + libmysofa-dev + libopus-dev + libpulse-dev + libreadline-dev + libsbc-dev + libsdl2-dev + # libsnapd-glib-dev + libsndfile1-dev + libspandsp-dev + libssl-dev + libsystemd-dev + libudev-dev + libusb-1.0-0-dev + libvulkan-dev + libwebrtc-audio-processing-dev + libx11-dev + modemmanager-dev +) + +arch="$1" + +export DEBIAN_FRONTEND=noninteractive + +sed -i \ + 's/^Components:.*$/Components: main contrib non-free non-free-firmware/' \ + /etc/apt/sources.list.d/debian.sources + +dpkg --add-architecture "$arch" +apt update -y + +pkgs=( "crossbuild-essential-$arch" ) +for pkg in "${packages[@]}"; do + pkgs+=( "$pkg:$arch" ) +done + +apt install -y "${pkgs[@]}" + +meson env2mfile --cross --debarch "$arch" -o "/opt/meson-$arch.cross" From 3914d0cab311b9a2b0ec5f9950e848b4546340be Mon Sep 17 00:00:00 2001 From: Sertonix Date: Tue, 12 Aug 2025 12:11:35 +0000 Subject: [PATCH 0720/1014] module-jack: fix name of LIBJACK_PATH environment variable Fixes 0629647cb5de module-jack: load libjack.so.0 with dlopen --- src/modules/module-jack-tunnel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 098563833..e64535938 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -49,7 +49,7 @@ * ## Module Options * * - `jack.library`: the libjack to load, by default libjack.so.0 is searched in - * JACK_PATH directories and then some standard library paths. + * LIBJACK_PATH directories and then some standard library paths. * Can be an absolute path. * - `jack.server`: the name of the JACK server to tunnel to. * - `jack.client-name`: the name of the JACK client. From 860e4554401742074a837489b05928b9940d8bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 24 Jul 2025 12:39:44 +0200 Subject: [PATCH 0721/1014] spa: libcamera: source: support `libcamera::formats::R8` Map `libcamera::formats::R8` to `SPA_VIDEO_FORMAT_GRAY8`. --- spa/plugins/libcamera/libcamera-source.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 4bdafebaa..8ecc32c80 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -371,6 +371,7 @@ struct format_info { #define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } const struct format_info format_info[] = { /* RGB formats */ + MAKE_FMT(formats::R8, GRAY8, video, raw), MAKE_FMT(formats::RGB565, RGB16, video, raw), MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), MAKE_FMT(formats::RGB888, BGR, video, raw), From 47ee86938b88021a887f33d164f7d3e1c241473d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 10 Aug 2025 19:01:34 +0200 Subject: [PATCH 0722/1014] spa: libcamera: source: port_set_format(): remove goto Remove the `done` label by moving things into the `format != nullptr` branch. --- spa/plugins/libcamera/libcamera-source.cpp | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 8ecc32c80..524afef7b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1825,9 +1825,6 @@ next: int port_set_format(struct impl *impl, struct port *port, uint32_t flags, const struct spa_pod *format) { - struct spa_video_info info; - int res; - if (format == nullptr) { if (!port->current_format) return 0; @@ -1837,9 +1834,12 @@ int port_set_format(struct impl *impl, struct port *port, port->current_format.reset(); spa_libcamera_close(impl); - goto done; } else { + spa_video_info info; + int res; + spa_zero(info); + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; @@ -1888,21 +1888,20 @@ int port_set_format(struct impl *impl, struct port *port, default: return -EINVAL; } + + if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + spa_libcamera_use_buffers(impl, port, nullptr, 0); + port->current_format.reset(); + } + + if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) + return -EINVAL; + + if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + port->current_format = info; + } } - if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { - spa_libcamera_use_buffers(impl, port, nullptr, 0); - port->current_format.reset(); - } - - if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) - return -EINVAL; - - if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { - port->current_format = info; - } - - done: impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->current_format) { From 14e0a8f66ff009c1d486c0532a39737f4e07f017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 10:38:25 +0200 Subject: [PATCH 0723/1014] spa: libcamera: source: propagate error when setting format If `spa_libcamera_set_format()` fails, propagate its return value as is without rewriting it to `EINVAL`. --- spa/plugins/libcamera/libcamera-source.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 524afef7b..4f9816c46 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1894,8 +1894,9 @@ int port_set_format(struct impl *impl, struct port *port, port->current_format.reset(); } - if (spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) - return -EINVAL; + res = spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY); + if (res < 0) + return res; if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; From 89545946fd95ce01a7ba64d42d0be0d158a84e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 12:10:37 +0200 Subject: [PATCH 0724/1014] spa: libcamera: source: freeBuffers(): split pending request removal `freeBuffers()` should undo exactly what `allocBuffers()` does. However, it currently also clears `impl::pendingRequests`, but that is filled by `spa_libcamera_buffer_recycle()` during `spa_libcamera_alloc_buffers()`. So remove the clearing of `impl::pendingRequests` from `freeBuffers()` and move it directly into `spa_libcamera_clear_buffers()`. --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 4f9816c46..87fa2761c 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -323,7 +323,6 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) void freeBuffers(struct impl *impl, struct port *port) { - impl->pendingRequests.clear(); impl->requestPool.clear(); impl->allocator->free(port->streamConfig.stream()); } @@ -354,6 +353,7 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) d[0].type = SPA_ID_INVALID; } + impl->pendingRequests.clear(); freeBuffers(impl, port); port->n_buffers = 0; port->ring = SPA_RINGBUFFER_INIT(); From b9b7c0ab05a8ec94b4166fa85a4f33cb39d4cd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 10:06:59 +0200 Subject: [PATCH 0725/1014] spa: libcamera: source: freeBuffers(): call when format is unset At the moment the libcamera buffer allocation is completely tied to format negotiation. `freeBuffers()` undoes `allocBuffers()`. And `allocBuffers()` is called as part of `spa_libcamera_set_format()`. Therefore `freeBuffers()` should be called independent of the state of the buffers on any port. Otherwise unsetting the format while there are no buffers on the port will cause the libcamera requests and buffers not to be released correctly. Similarly, removing the buffers from a port would clear the libcamera requests and buffers, making the node unusable without setting a new format. --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 87fa2761c..3e5715b2a 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -354,7 +354,6 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) } impl->pendingRequests.clear(); - freeBuffers(impl, port); port->n_buffers = 0; port->ring = SPA_RINGBUFFER_INIT(); @@ -1831,6 +1830,7 @@ int port_set_format(struct impl *impl, struct port *port, spa_libcamera_stream_off(impl); spa_libcamera_clear_buffers(impl, port); + freeBuffers(impl, port); port->current_format.reset(); spa_libcamera_close(impl); From 348e703be0bee84371a0fe87df6b499987c1c508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 09:48:47 +0200 Subject: [PATCH 0726/1014] spa: libcamera: source: allocBuffers(): restore on failure Undo the allocation and clear the requests if a failure is encountered in order to leave things in a known state. --- spa/plugins/libcamera/libcamera-source.cpp | 61 +++++++++++++--------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 3e5715b2a..5588f9828 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -279,18 +279,42 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t return 0; } +void freeBuffers(struct impl *impl, struct port *port) +{ + impl->requestPool.clear(); + std::ignore = impl->allocator->free(port->streamConfig.stream()); +} + +[[nodiscard]] +std::size_t count_unique_fds(libcamera::Span planes) +{ + std::size_t c = 0; + int fd = -1; + + for (const auto& plane : planes) { + const int current_fd = plane.fd.get(); + if (current_fd >= 0 && current_fd != fd) { + c += 1; + fd = current_fd; + } + } + + return c; +} + int allocBuffers(struct impl *impl, struct port *port, unsigned int count) { + libcamera::Stream *stream = port->streamConfig.stream(); int res; - if ((res = impl->allocator->allocate(port->streamConfig.stream())) < 0) + if ((res = impl->allocator->allocate(stream)) < 0) return res; for (unsigned int i = 0; i < count; i++) { std::unique_ptr request = impl->camera->createRequest(i); if (!request) { - impl->requestPool.clear(); - return -ENOMEM; + res = -ENOMEM; + goto err; } impl->requestPool.push_back(std::move(request)); } @@ -300,33 +324,20 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) * video frame has to be addressed using more than one memory. * address. Therefore, need calculate the number of discontiguous * memory and allocate the specified amount of memory */ - Stream *stream = impl->config->at(0).stream(); - const std::vector> &bufs = - impl->allocator->buffers(stream); - const std::vector &planes = bufs[0]->planes(); - int fd = -1; - uint32_t buffers_blocks = 0; - - for (const FrameBuffer::Plane &plane : planes) { - const int current_fd = plane.fd.get(); - if (current_fd >= 0 && current_fd != fd) { - buffers_blocks += 1; - fd = current_fd; - } + port->buffers_blocks = count_unique_fds(impl->allocator->buffers(stream).front()->planes()); + if (port->buffers_blocks <= 0) { + res = -ENOBUFS; + goto err; } - if (buffers_blocks > 0) { - port->buffers_blocks = buffers_blocks; - } + return 0; + +err: + freeBuffers(impl, port); + return res; } -void freeBuffers(struct impl *impl, struct port *port) -{ - impl->requestPool.clear(); - impl->allocator->free(port->streamConfig.stream()); -} - int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) { uint32_t i; From 29b0c87d717266bf1dd4278b56f152e67ea22ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 09:53:13 +0200 Subject: [PATCH 0727/1014] spa: libcamera: source: allocBuffers(): more error checking First, check if the request pool is empty, and signal an error if it is not. Second, ensure that the number of allocated buffers matches. --- spa/plugins/libcamera/libcamera-source.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 5588f9828..ae3e9a9b3 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -307,10 +307,19 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) libcamera::Stream *stream = port->streamConfig.stream(); int res; + if (!impl->requestPool.empty()) + return -EBUSY; + if ((res = impl->allocator->allocate(stream)) < 0) return res; - for (unsigned int i = 0; i < count; i++) { + const auto& bufs = impl->allocator->buffers(stream); + if (bufs.empty() || bufs.size() != count) { + res = -ENOBUFS; + goto err; + } + + for (std::size_t i = 0; i < bufs.size(); i++) { std::unique_ptr request = impl->camera->createRequest(i); if (!request) { res = -ENOMEM; @@ -324,7 +333,7 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) * video frame has to be addressed using more than one memory. * address. Therefore, need calculate the number of discontiguous * memory and allocate the specified amount of memory */ - port->buffers_blocks = count_unique_fds(impl->allocator->buffers(stream).front()->planes()); + port->buffers_blocks = count_unique_fds(bufs.front()->planes()); if (port->buffers_blocks <= 0) { res = -ENOBUFS; goto err; From 475665d615eb3f139462080115f03d1484a376e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 15:22:42 +0200 Subject: [PATCH 0728/1014] spa: libcamera: source: persistent requests <-> buffer association Currently only a single stream is used. This makes it easy to associate each request with the appropriate frame buffer when it is created. In turn, this makes reusing requests a bit simpler and a bit more efficient. So do that: add buffers to requests when they are allocated in `allocBuffers()`. --- spa/plugins/libcamera/libcamera-source.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index ae3e9a9b3..9feb761ad 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -257,13 +257,6 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t return -EINVAL; } Request *request = impl->requestPool[buffer_id].get(); - Stream *stream = port->streamConfig.stream(); - FrameBuffer *buffer = impl->allocator->buffers(stream)[buffer_id].get(); - if ((res = request->addBuffer(stream, buffer)) < 0) { - spa_log_warn(impl->log, "can't add buffer %u for request: %s", - buffer_id, spa_strerror(res)); - return -ENOMEM; - } if (!impl->active) { impl->pendingRequests.push_back(request); return 0; @@ -325,6 +318,11 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) res = -ENOMEM; goto err; } + + res = request->addBuffer(stream, bufs[i].get()); + if (res < 0) + goto err; + impl->requestPool.push_back(std::move(request)); } @@ -1252,7 +1250,7 @@ void impl::requestComplete(libcamera::Request *request) if ((request->status() == Request::RequestCancelled)) { spa_log_debug(impl->log, "Request was cancelled"); - request->reuse(); + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_libcamera_buffer_recycle(impl, port, b->id); return; @@ -1299,7 +1297,7 @@ void impl::requestComplete(libcamera::Request *request) b->h->pts = fmd.timestamp; b->h->dts_offset = 0; } - request->reuse(); + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); spa_ringbuffer_get_write_index(&port->ring, &index); port->ring_ids[index & MASK_BUFFERS] = buffer_id; @@ -1384,7 +1382,7 @@ int spa_libcamera_stream_off(struct impl *impl) if (!impl->active) { for (std::unique_ptr &req : impl->requestPool) - req->reuse(); + req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); return 0; } From 68627c5563425579da2800acf423483854126c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 15:47:02 +0200 Subject: [PATCH 0729/1014] spa: libcamera: source: remove `impl::pendingRequests` The handling of `impl::pendingRequests` is a bit problematic because, for example, during startup, if `spa_libcamera_buffer_recycle()` observes that `impl::active == false`, then it will try to append to `impl::pendingRequests`, which is being iterated in the main thread in `spa_libcamera_stream_on()`. That is not allowed on an `std::deque`. So instead remove it altogether, and simply queue all requests when starting. After `libcamera::Camera::stop()` returns, every request is guaranteed not to be used by libcamera, and they can be freely reused, so this is safe to do. This also removes the need for calling `spa_libcamera_buffer_recycle()` when the buffers are set/unset on a port since that function no longer changes anything apart from updating `buffer::flags` but `spa_libcamera_alloc_buffers()` is modified appropriately to take care of that. And in the case of `spa_libcamera_clear_buffers()` clearing the flag is not required because the next `spa_libcamera_alloc_buffers()` call will reset reset the flags. --- spa/plugins/libcamera/libcamera-source.cpp | 29 ++++++---------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 9feb761ad..41fda937b 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -157,7 +156,6 @@ struct impl { FrameBufferAllocator *allocator = nullptr; std::vector> requestPool; - std::deque pendingRequests; void requestComplete(libcamera::Request *request); @@ -257,10 +255,7 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t return -EINVAL; } Request *request = impl->requestPool[buffer_id].get(); - if (!impl->active) { - impl->pendingRequests.push_back(request); - return 0; - } else { + if (impl->active) { request->controls().merge(impl->ctrls); impl->ctrls.clear(); if ((res = impl->camera->queueRequest(request)) < 0) { @@ -359,10 +354,6 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) b = &port->buffers[i]; d = b->outbuf->datas; - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { - spa_log_debug(impl->log, "queueing outstanding buffer %p", b); - spa_libcamera_buffer_recycle(impl, port, i); - } if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), d[0].maxsize - d[0].mapoffset); @@ -371,7 +362,6 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) d[0].type = SPA_ID_INVALID; } - impl->pendingRequests.clear(); port->n_buffers = 0; port->ring = SPA_RINGBUFFER_INIT(); @@ -1151,7 +1141,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; - b->flags = BUFFER_FLAG_OUTSTANDING; + b->flags = 0; b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( @@ -1224,8 +1214,6 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, return -EIO; } } - - spa_libcamera_buffer_recycle(impl, port, i); } port->n_buffers = n_buffers; @@ -1353,11 +1341,12 @@ int spa_libcamera_stream_on(struct impl *impl) impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - for (Request *req : impl->pendingRequests) { - if ((res = impl->camera->queueRequest(req)) < 0) + for (auto& req : impl->requestPool) { + req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + + if ((res = impl->camera->queueRequest(req.get())) < 0) goto err_stop_camera; } - impl->pendingRequests.clear(); impl->dll.bw = 0.0; impl->active = true; @@ -1380,15 +1369,11 @@ int spa_libcamera_stream_off(struct impl *impl) struct port *port = &impl->out_ports[0]; int res; - if (!impl->active) { - for (std::unique_ptr &req : impl->requestPool) - req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + if (!impl->active) return 0; - } impl->active = false; spa_log_info(impl->log, "stopping camera %s", impl->camera->id().c_str()); - impl->pendingRequests.clear(); if ((res = impl->camera->stop()) < 0) { spa_log_warn(impl->log, "error stopping camera %s: %s", From 099292d63d2851682740b38d5e958abff56dde6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 19:14:45 +0200 Subject: [PATCH 0730/1014] spa: libcamera: source: store the request pointer in ring buffer The request will be needed later, so store that directly in the completion ring buffer for easy access. This is fine even though `impl::requestComplete()` calls `Request::reuse()` because only the request cookie is used in `libcamera_on_fd_events()` and that remains constant during the lifetime of a request object. --- spa/plugins/libcamera/libcamera-source.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 41fda937b..f07411370 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -80,7 +80,7 @@ struct port { uint32_t n_buffers = 0; struct spa_list queue; struct spa_ringbuffer ring = SPA_RINGBUFFER_INIT(); - uint32_t ring_ids[MAX_BUFFERS]; + libcamera::Request *ring_ids[MAX_BUFFERS]; static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; @@ -1044,9 +1044,11 @@ void libcamera_on_fd_events(struct spa_source *source) spa_log_error(impl->log, "nothing is queued"); return; } - buffer_id = port->ring_ids[index & MASK_BUFFERS]; + + auto *request = port->ring_ids[index & MASK_BUFFERS]; spa_ringbuffer_read_update(&port->ring, index + 1); + buffer_id = request->cookie(); b = &port->buffers[buffer_id]; spa_list_append(&port->queue, &b->link); @@ -1288,7 +1290,7 @@ void impl::requestComplete(libcamera::Request *request) request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); spa_ringbuffer_get_write_index(&port->ring, &index); - port->ring_ids[index & MASK_BUFFERS] = buffer_id; + port->ring_ids[index & MASK_BUFFERS] = request; spa_ringbuffer_write_update(&port->ring, index + 1); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) From 72fd462090971f222383e4a8d13dd6530b3f5b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 19:20:27 +0200 Subject: [PATCH 0731/1014] spa: libcamera: source: move request completion data to `impl` A `libcamera::Request` is directly tied to the camera, not any ports ("streams") of it. So move the request completion ring buffer into the `impl` struct. This is a prerequisite for supporting multiple libcamera streams (~ exposing multiple ports on the node) in the future. --- spa/plugins/libcamera/libcamera-source.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f07411370..de4e425ef 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -79,8 +79,6 @@ struct port { struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers = 0; struct spa_list queue; - struct spa_ringbuffer ring = SPA_RINGBUFFER_INIT(); - libcamera::Request *ring_ids[MAX_BUFFERS]; static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; @@ -156,6 +154,8 @@ struct impl { FrameBufferAllocator *allocator = nullptr; std::vector> requestPool; + spa_ringbuffer completed_requests_rb = SPA_RINGBUFFER_INIT(); + std::array completed_requests; void requestComplete(libcamera::Request *request); @@ -363,7 +363,7 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) } port->n_buffers = 0; - port->ring = SPA_RINGBUFFER_INIT(); + impl->completed_requests_rb = SPA_RINGBUFFER_INIT(); return 0; } @@ -1040,13 +1040,13 @@ void libcamera_on_fd_events(struct spa_source *source) return; } - if (spa_ringbuffer_get_read_index(&port->ring, &index) < 1) { + if (spa_ringbuffer_get_read_index(&impl->completed_requests_rb, &index) < 1) { spa_log_error(impl->log, "nothing is queued"); return; } - auto *request = port->ring_ids[index & MASK_BUFFERS]; - spa_ringbuffer_read_update(&port->ring, index + 1); + auto *request = impl->completed_requests[index & MASK_BUFFERS]; + spa_ringbuffer_read_update(&impl->completed_requests_rb, index + 1); buffer_id = request->cookie(); b = &port->buffers[buffer_id]; @@ -1289,9 +1289,9 @@ void impl::requestComplete(libcamera::Request *request) } request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); - spa_ringbuffer_get_write_index(&port->ring, &index); - port->ring_ids[index & MASK_BUFFERS] = request; - spa_ringbuffer_write_update(&port->ring, index + 1); + spa_ringbuffer_get_write_index(&impl->completed_requests_rb, &index); + impl->completed_requests[index & MASK_BUFFERS] = request; + spa_ringbuffer_write_update(&impl->completed_requests_rb, index + 1); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) spa_log_error(impl->log, "Failed to write on event fd"); From c01a2977a5d531e36e10ac29e7ed69c3dcbfa999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 12:32:17 +0200 Subject: [PATCH 0732/1014] spa: libcamera: source: reset ring buffer when stopping Presently, the ring buffer of completed requests is only cleared when the buffers are removed from the port. This is not entirely correct since pause/start commands do not clear the buffers but they stop the camera. As a consequence, it is possible that some completed requests stay in the ring buffer, causing them to be mistakenly processed when the camera is started next. So reset the ring buffer after the camera is stopped, the same time as the queue of free buffers is cleared. --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index de4e425ef..75913ec90 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -363,7 +363,6 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) } port->n_buffers = 0; - impl->completed_requests_rb = SPA_RINGBUFFER_INIT(); return 0; } @@ -1390,6 +1389,7 @@ int spa_libcamera_stream_off(struct impl *impl) impl->source.fd = -1; } + impl->completed_requests_rb = SPA_RINGBUFFER_INIT(); spa_list_init(&port->queue); return 0; From 22ddb88072656209a36e99635d2f3520a44e4e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 25 Jul 2025 20:18:15 +0200 Subject: [PATCH 0733/1014] spa: libcamera: source: process all requests in the ring buffer It is possible that multiple requests complete before the data loop can run `libcamera_on_fd_events()`. However, previously only the earliest item was processed from the ring buffer, meaning that in those cases request processing could be lagging request completion by multiple requests. Fix that by getting the number of available requests and processing them all. --- spa/plugins/libcamera/libcamera-source.cpp | 74 ++++++++++++---------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 75913ec90..d2fe8043f 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1012,14 +1012,44 @@ int spa_libcamera_apply_controls(struct impl *impl, libcamera::ControlList&& con ); } +void handle_completed_request(struct impl *impl, libcamera::Request *request) +{ + const auto request_id = request->cookie(); + struct port *port = &impl->out_ports[0]; + buffer *b = &port->buffers[request_id]; + + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] process status:%u seq:%" PRIu32, + impl, request, request_id, static_cast(request->status()), + request->sequence()); + + spa_list_append(&port->queue, &b->link); + + spa_io_buffers *io = port->io; + if (io == nullptr) { + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + } else if (io->status != SPA_STATUS_HAVE_DATA) { + if (io->buffer_id < port->n_buffers) + spa_libcamera_buffer_recycle(impl, port, io->buffer_id); + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace(impl->log, "%p: now queued %" PRIu32, impl, b->id); + } + + spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); +} void libcamera_on_fd_events(struct spa_source *source) { struct impl *impl = (struct impl*) source->data; - struct spa_io_buffers *io; - struct port *port = &impl->out_ports[0]; - uint32_t index, buffer_id; - struct buffer *b; + uint32_t index; uint64_t cnt; if (source->rmask & SPA_IO_ERR) { @@ -1039,37 +1069,13 @@ void libcamera_on_fd_events(struct spa_source *source) return; } - if (spa_ringbuffer_get_read_index(&impl->completed_requests_rb, &index) < 1) { - spa_log_error(impl->log, "nothing is queued"); - return; + auto avail = spa_ringbuffer_get_read_index(&impl->completed_requests_rb, &index); + for (; avail > 0; avail--, index++) { + auto *request = impl->completed_requests[index & MASK_BUFFERS]; + + spa_ringbuffer_read_update(&impl->completed_requests_rb, index + 1); + handle_completed_request(impl, request); } - - auto *request = impl->completed_requests[index & MASK_BUFFERS]; - spa_ringbuffer_read_update(&impl->completed_requests_rb, index + 1); - - buffer_id = request->cookie(); - b = &port->buffers[buffer_id]; - spa_list_append(&port->queue, &b->link); - - io = port->io; - if (io == nullptr) { - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - } else if (io->status != SPA_STATUS_HAVE_DATA) { - if (io->buffer_id < port->n_buffers) - spa_libcamera_buffer_recycle(impl, port, io->buffer_id); - - b = spa_list_first(&port->queue, struct buffer, link); - spa_list_remove(&b->link); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - - io->buffer_id = b->id; - io->status = SPA_STATUS_HAVE_DATA; - spa_log_trace(impl->log, "libcamera %p: now queued %d", impl, b->id); - } - spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); } int spa_libcamera_use_buffers(struct impl *impl, struct port *port, From 019a5c130f486994f22caebb66d8e53f691c9a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 26 Jul 2025 15:13:26 +0200 Subject: [PATCH 0734/1014] spa: libcamera: source: process requests on data loop Since `impl::requestComplete()` runs in an internal libcamera thread, extra care would need to be taken to validate all accesses to common data structures. For example, the function might call `spa_libcamera_buffer_recycle()`, which accesses `impl::ctrls`, which would be unsafe because it could read or modified at the same time on the data thread. So move the processing of requests to the data loop. --- spa/plugins/libcamera/libcamera-source.cpp | 121 +++++++++++---------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d2fe8043f..a41f604e5 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1022,6 +1022,63 @@ void handle_completed_request(struct impl *impl, libcamera::Request *request) impl, request, request_id, static_cast(request->status()), request->sequence()); + if (request->status() == libcamera::Request::Status::RequestCancelled) { + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] cancelled", + impl, request, request_id); + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + spa_libcamera_buffer_recycle(impl, port, b->id); + return; + } + + const FrameBuffer *buffer = request->findBuffer(port->streamConfig.stream()); + if (buffer == nullptr) { + spa_log_warn(impl->log, "%p: request %p[%" PRIu64 "] has no buffer for stream %p", + impl, request, request_id, port->streamConfig.stream()); + return; + } + + const FrameMetadata &fmd = buffer->metadata(); + + if (impl->clock) { + double target = (double)port->info.rate.num / port->info.rate.denom; + double corr; + + if (impl->dll.bw == 0.0) { + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); + impl->clock->next_nsec = fmd.timestamp; + corr = 1.0; + } else { + double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; + double error = port->info.rate.denom * (diff - target); + corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); + } + /* FIXME, we should follow the driver clock and target_ values. + * for now we ignore and use our own. */ + impl->clock->target_rate = port->rate; + impl->clock->target_duration = 1; + + impl->clock->nsec = fmd.timestamp; + impl->clock->rate = port->rate; + impl->clock->position = fmd.sequence; + impl->clock->duration = 1; + impl->clock->delay = 0; + impl->clock->rate_diff = corr; + impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); + } + + if (b->h) { + b->h->flags = 0; + if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) + b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; + b->h->offset = 0; + b->h->seq = fmd.sequence; + b->h->pts = fmd.timestamp; + b->h->dts_offset = 0; + } + + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + spa_list_append(&port->queue, &b->link); spa_io_buffers *io = port->io; @@ -1233,66 +1290,12 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; - struct port *port = &impl->out_ports[0]; - Stream *stream = port->streamConfig.stream(); - uint32_t index, buffer_id; - struct buffer *b; + uint32_t index; - spa_log_debug(impl->log, "request complete"); - - buffer_id = request->cookie(); - b = &port->buffers[buffer_id]; - - if ((request->status() == Request::RequestCancelled)) { - spa_log_debug(impl->log, "Request was cancelled"); - request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); - SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); - spa_libcamera_buffer_recycle(impl, port, b->id); - return; - } - FrameBuffer *buffer = request->findBuffer(stream); - if (buffer == nullptr) { - spa_log_warn(impl->log, "unknown buffer"); - return; - } - const FrameMetadata &fmd = buffer->metadata(); - - if (impl->clock) { - double target = (double)port->info.rate.num / port->info.rate.denom; - double corr; - - if (impl->dll.bw == 0.0) { - spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); - impl->clock->next_nsec = fmd.timestamp; - corr = 1.0; - } else { - double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; - double error = port->info.rate.denom * (diff - target); - corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); - } - /* FIXME, we should follow the driver clock and target_ values. - * for now we ignore and use our own. */ - impl->clock->target_rate = port->rate; - impl->clock->target_duration = 1; - - impl->clock->nsec = fmd.timestamp; - impl->clock->rate = port->rate; - impl->clock->position = fmd.sequence; - impl->clock->duration = 1; - impl->clock->delay = 0; - impl->clock->rate_diff = corr; - impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); - } - if (b->h) { - b->h->flags = 0; - if (fmd.status != FrameMetadata::Status::FrameSuccess) - b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; - b->h->offset = 0; - b->h->seq = fmd.sequence; - b->h->pts = fmd.timestamp; - b->h->dts_offset = 0; - } - request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] completed status:%u seq:%" PRIu32, + impl, request, request->cookie(), + static_cast(request->status()), + request->sequence()); spa_ringbuffer_get_write_index(&impl->completed_requests_rb, &index); impl->completed_requests[index & MASK_BUFFERS] = request; From 25075bb3d7190e84606dce934814e27f986a56de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sat, 9 Aug 2025 01:38:18 +0200 Subject: [PATCH 0735/1014] spa: libcamera: source: set chunk flags on error Set `SPA_CHUNK_FLAG_CORRUPTED` if the frame buffer metadata signals anything other than success. --- spa/plugins/libcamera/libcamera-source.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index a41f604e5..b37fb1e8d 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1077,6 +1077,15 @@ void handle_completed_request(struct impl *impl, libcamera::Request *request) b->h->dts_offset = 0; } + for (std::size_t i = 0; i < b->outbuf->n_datas; i++) { + auto *d = &b->outbuf->datas[i]; + + d->chunk->flags = 0; + + if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) + d->chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; + } + request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); spa_list_append(&port->queue, &b->link); From 05a9e52cafe3579a4386d394cee74b8038ed2cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 18:07:02 +0200 Subject: [PATCH 0736/1014] spa: libcamera: source: remove format config shortcut Remove the code that is supposed to compare the current and the to-be-set format for returning early if they match. This is removed: * v4l2 also does not have it either; * it needs more consideration (there are not properly handled fields); * it would make later changes somewhat more complicated. --- spa/plugins/libcamera/libcamera-source.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index b37fb1e8d..1299606d6 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1878,35 +1878,16 @@ int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.raw.format == port->current_format->info.raw.format && - info.info.raw.size.width == port->current_format->info.raw.size.width && - info.info.raw.size.height == port->current_format->info.raw.size.height && - info.info.raw.flags == port->current_format->info.raw.flags && - (!(info.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER) || - (info.info.raw.modifier == port->current_format->info.raw.modifier))) - return 0; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.mjpg.size.width == port->current_format->info.mjpg.size.width && - info.info.mjpg.size.height == port->current_format->info.mjpg.size.height) - return 0; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; - if (port->current_format && info.media_type == port->current_format->media_type && - info.media_subtype == port->current_format->media_subtype && - info.info.h264.size.width == port->current_format->info.h264.size.width && - info.info.h264.size.height == port->current_format->info.h264.size.height) - return 0; break; default: return -EINVAL; From dac9e40be65ea27e4f6b32cac190e4bca0527cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 18:12:58 +0200 Subject: [PATCH 0737/1014] spa: libcamera: source: extract presence of `SPA_NODE_PARAM_FLAG_TEST_ONLY` Use a boolean named `try_only` instead of checking `flags` each time. --- spa/plugins/libcamera/libcamera-source.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 1299606d6..9a912a005 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1847,6 +1847,8 @@ next: int port_set_format(struct impl *impl, struct port *port, uint32_t flags, const struct spa_pod *format) { + const bool try_only = SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY); + if (format == nullptr) { if (!port->current_format) return 0; @@ -1893,18 +1895,17 @@ int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->current_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + if (port->current_format && !try_only) { spa_libcamera_use_buffers(impl, port, nullptr, 0); port->current_format.reset(); } - res = spa_libcamera_set_format(impl, port, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY); + res = spa_libcamera_set_format(impl, port, &info, try_only); if (res < 0) return res; - if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + if (!try_only) port->current_format = info; - } } impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; From a8a60832cd9baa99a974a3572f35816a606ba494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 18:14:07 +0200 Subject: [PATCH 0738/1014] spa: libcamera: source: do not emit param change if try-only If `SPA_NODE_PARAM_FLAG_TEST_ONLY`, then the format does not change on the node, it is only tested. So do not emit the param change events. --- spa/plugins/libcamera/libcamera-source.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 9a912a005..791d0c417 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1908,6 +1908,9 @@ int port_set_format(struct impl *impl, struct port *port, port->current_format = info; } + if (try_only) + return 0; + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->current_format) { From 31176120f512349eb59585b0b4f368b07149a05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 11 Aug 2025 17:44:48 +0200 Subject: [PATCH 0739/1014] spa: libcamera: source: handle try-only format unset Do nothing if `format == nullptr` and `SPA_NODE_PARAM_FLAG_TEST_ONLY` is present. --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 791d0c417..3e65cc2d3 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1850,7 +1850,7 @@ int port_set_format(struct impl *impl, struct port *port, const bool try_only = SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY); if (format == nullptr) { - if (!port->current_format) + if (try_only || !port->current_format) return 0; spa_libcamera_stream_off(impl); From c517e712ed3a06d7c0bbf0a7ab11a659317f9a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 7 Aug 2025 18:15:48 +0200 Subject: [PATCH 0740/1014] spa: libcamera: source: clear buffers when format is changed pipewire assumes that the buffers are removed from a port when its format is changed even without an explicit call to `port_use_buffer()`. So if the format is not just tested, clear the buffers, as well as the libcamera requests and buffers because they are also recreated when a new format is set. This matches the behaviour of the v4l2 plugin. Furthermore, this change also removes the call to `spa_libcamera_use_buffers()` because that function does nothing. And finally this change necessitates that the current format is always reset (when not testing), not just before reaching `spa_libcamera_set_format()`. --- spa/plugins/libcamera/libcamera-source.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 3e65cc2d3..485bbfb05 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1849,16 +1849,16 @@ int port_set_format(struct impl *impl, struct port *port, { const bool try_only = SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY); - if (format == nullptr) { - if (try_only || !port->current_format) - return 0; - + if (!try_only) { spa_libcamera_stream_off(impl); spa_libcamera_clear_buffers(impl, port); freeBuffers(impl, port); port->current_format.reset(); + } - spa_libcamera_close(impl); + if (format == nullptr) { + if (!try_only) + spa_libcamera_close(impl); } else { spa_video_info info; int res; @@ -1895,11 +1895,6 @@ int port_set_format(struct impl *impl, struct port *port, return -EINVAL; } - if (port->current_format && !try_only) { - spa_libcamera_use_buffers(impl, port, nullptr, 0); - port->current_format.reset(); - } - res = spa_libcamera_set_format(impl, port, &info, try_only); if (res < 0) return res; From 1f60cd291fe7882cefb2ba75f2dd045f03c737d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 8 Aug 2025 10:44:55 +0200 Subject: [PATCH 0741/1014] spa: libcamera: source: keep `libcamera::FrameBufferAllocator` Instantiate it once and keep it instead of always dynamically allocating it when the camera is acquired. --- spa/plugins/libcamera/libcamera-source.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 485bbfb05..19cd0f8ff 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -152,7 +152,7 @@ struct impl { std::shared_ptr camera; const std::unique_ptr config; - FrameBufferAllocator *allocator = nullptr; + FrameBufferAllocator allocator; std::vector> requestPool; spa_ringbuffer completed_requests_rb = SPA_RINGBUFFER_INIT(); std::array completed_requests; @@ -212,7 +212,7 @@ int spa_libcamera_open(struct impl *impl) if (int res = impl->camera->acquire(); res < 0) return res; - impl->allocator = new FrameBufferAllocator(impl->camera); + spa_assert(!impl->allocator.allocated()); const ControlInfoMap &controls = impl->camera->controls(); setup_initial_controls(controls, impl->initial_controls); @@ -230,8 +230,8 @@ int spa_libcamera_close(struct impl *impl) return 0; spa_log_info(impl->log, "close camera %s", impl->camera->id().c_str()); - delete impl->allocator; - impl->allocator = nullptr; + + spa_assert(!impl->allocator.allocated()); impl->camera->release(); @@ -270,7 +270,7 @@ int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t void freeBuffers(struct impl *impl, struct port *port) { impl->requestPool.clear(); - std::ignore = impl->allocator->free(port->streamConfig.stream()); + std::ignore = impl->allocator.free(port->streamConfig.stream()); } [[nodiscard]] @@ -298,10 +298,10 @@ int allocBuffers(struct impl *impl, struct port *port, unsigned int count) if (!impl->requestPool.empty()) return -EBUSY; - if ((res = impl->allocator->allocate(stream)) < 0) + if ((res = impl->allocator.allocate(stream)) < 0) return res; - const auto& bufs = impl->allocator->buffers(stream); + const auto& bufs = impl->allocator.buffers(stream); if (bufs.empty() || bufs.size() != count) { res = -ENOBUFS; goto err; @@ -1183,7 +1183,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, Stream *stream = impl->config->at(0).stream(); const std::vector> &bufs = - impl->allocator->buffers(stream); + impl->allocator.buffers(stream); if (n_buffers > 0) { if (bufs.size() != n_buffers) @@ -2171,7 +2171,8 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, out_ports{{this}}, manager(std::move(manager)), camera(std::move(camera)), - config(std::move(config)) + config(std::move(config)), + allocator(this->camera) { libcamera_log_topic_init(log); From 507688e6c99979b82fcffa23b3a03ebf198b2443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 10 Aug 2025 22:45:44 +0200 Subject: [PATCH 0742/1014] spa: libcamera: source: add eventfd to loop while locked While concurrent `spa_loop_add_source()` invocations work with the current main epoll-based implementation, this is not guaranteed, so lock the loop. Similarly to how it is done elsewhere and for removal already. --- spa/plugins/libcamera/libcamera-source.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 19cd0f8ff..d4da10fd7 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1350,7 +1350,16 @@ int spa_libcamera_stream_on(struct impl *impl) impl->source.data = impl; impl->source.mask = SPA_IO_IN | SPA_IO_ERR; impl->source.rmask = 0; - res = spa_loop_add_source(impl->data_loop, &impl->source); + + res = spa_loop_locked( + impl->data_loop, + [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) + { + auto *impl = static_cast(user_data); + return spa_loop_add_source(impl->data_loop, &impl->source); + }, + 0, nullptr, 0, impl + ); if (res < 0) goto err_close_source; From 3e28f3e8594efadfbd9b3d9d13d7a6ea42120e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 10 Aug 2025 22:40:12 +0200 Subject: [PATCH 0743/1014] spa: libcamera: source: rework startup sequence After e0e8bf083 ("spa: libcamera: source: create eventfd before starting camera"), things are still not entirely correct. This change ensures that if starting the camera fails, then the runtime state, most importantly the ring buffer of completed requests is restored to its initial state. Furthermore, it is also ensured that `impl::active` can never be observed by the data thread while it is being changed, which is achieved by setting it before/after adding/removing the event source. --- spa/plugins/libcamera/libcamera-source.cpp | 112 ++++++++++----------- 1 file changed, 54 insertions(+), 58 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index d4da10fd7..e931ca230 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -171,6 +171,38 @@ struct impl { std::unique_ptr config); struct spa_dll dll; + + void stop() + { + spa_loop_locked( + data_loop, + [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) { + auto *self = static_cast(user_data); + + if (self->source.loop) + spa_loop_remove_source(self->data_loop, &self->source); + + return 0; + }, + 0, nullptr, 0, this + ); + + if (source.fd >= 0) + spa_system_close(system, std::exchange(source.fd, -1)); + + camera->requestCompleted.disconnect(this, &impl::requestComplete); + + if (int res = camera->stop(); res < 0) { + spa_log_warn(log, "failed to stop camera %s: %s", + camera->id().c_str(), spa_strerror(res)); + } + + completed_requests_rb = SPA_RINGBUFFER_INIT(); + active = false; + + for (auto& p : out_ports) + spa_list_init(&p.queue); + } }; #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) @@ -1315,19 +1347,6 @@ void impl::requestComplete(libcamera::Request *request) } -int do_remove_source(struct spa_loop *loop, - bool async, - uint32_t seq, - const void *data, - size_t size, - void *user_data) -{ - auto *impl = static_cast(user_data); - if (impl->source.loop) - spa_loop_remove_source(loop, &impl->source); - return 0; -} - int spa_libcamera_stream_on(struct impl *impl) { struct port *port = &impl->out_ports[0]; @@ -1341,9 +1360,15 @@ int spa_libcamera_stream_on(struct impl *impl) if (impl->active) return 0; + spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); + if ((res = impl->camera->start(&impl->initial_controls)) < 0) + return res == -EACCES ? -EBUSY : res; + + impl->camera->requestCompleted.connect(impl, &impl::requestComplete); + res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (res < 0) - return res; + goto err_stop; impl->source.fd = res; impl->source.func = libcamera_on_fd_events; @@ -1351,6 +1376,16 @@ int spa_libcamera_stream_on(struct impl *impl) impl->source.mask = SPA_IO_IN | SPA_IO_ERR; impl->source.rmask = 0; + for (auto& req : impl->requestPool) { + req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); + + if ((res = impl->camera->queueRequest(req.get())) < 0) + goto err_stop; + } + + impl->dll.bw = 0.0; + impl->active = true; + res = spa_loop_locked( impl->data_loop, [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) @@ -1361,63 +1396,24 @@ int spa_libcamera_stream_on(struct impl *impl) 0, nullptr, 0, impl ); if (res < 0) - goto err_close_source; - - spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); - if ((res = impl->camera->start(&impl->initial_controls)) < 0) - goto err_remove_source; - - impl->camera->requestCompleted.connect(impl, &impl::requestComplete); - - for (auto& req : impl->requestPool) { - req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); - - if ((res = impl->camera->queueRequest(req.get())) < 0) - goto err_stop_camera; - } - - impl->dll.bw = 0.0; - impl->active = true; + goto err_stop; return 0; -err_stop_camera: - impl->camera->stop(); - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); -err_remove_source: - spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); -err_close_source: - spa_system_close(impl->system, std::exchange(impl->source.fd, -1)); +err_stop: + impl->stop(); - return res == -EACCES ? -EBUSY : res; + return res; } int spa_libcamera_stream_off(struct impl *impl) { - struct port *port = &impl->out_ports[0]; - int res; - if (!impl->active) return 0; - impl->active = false; spa_log_info(impl->log, "stopping camera %s", impl->camera->id().c_str()); - if ((res = impl->camera->stop()) < 0) { - spa_log_warn(impl->log, "error stopping camera %s: %s", - impl->camera->id().c_str(), spa_strerror(res)); - } - - impl->camera->requestCompleted.disconnect(impl, &impl::requestComplete); - - spa_loop_locked(impl->data_loop, do_remove_source, 0, nullptr, 0, impl); - if (impl->source.fd >= 0) { - spa_system_close(impl->system, impl->source.fd); - impl->source.fd = -1; - } - - impl->completed_requests_rb = SPA_RINGBUFFER_INIT(); - spa_list_init(&port->queue); + impl->stop(); return 0; } From b948ffdb2509c664b25374767006914dc133a033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 12 Aug 2025 23:36:49 +0200 Subject: [PATCH 0744/1014] spa: libcamera: source: remove `SPA_DATA_MemPtr` support The current handling of `SPA_DATA_MemPtr` is not entirely correct because its handling of multi-planar buffers is not appropriate: it leaks memory mappings because it overwrites `buffer::ptr` for each plane. Since this data type should not really be in use in normal deployments, let's remove it for now. --- spa/plugins/libcamera/libcamera-source.cpp | 25 +--------------------- 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index e931ca230..a16ce7971 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -53,7 +53,6 @@ namespace { #define MASK_BUFFERS 31 #define BUFFER_FLAG_OUTSTANDING (1<<0) -#define BUFFER_FLAG_MAPPED (1<<1) struct buffer { uint32_t id; @@ -386,11 +385,6 @@ int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) b = &port->buffers[i]; d = b->outbuf->datas; - if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { - munmap(SPA_PTROFF(b->ptr, -d[0].mapoffset, void), - d[0].maxsize - d[0].mapoffset); - } - d[0].type = SPA_ID_INVALID; } @@ -1225,10 +1219,8 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_DmaBuf)) { port->memtype = SPA_DATA_DmaBuf; - } else if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_MemFd)) { + } else if (d[0].type & (1u << SPA_DATA_MemFd)) { port->memtype = SPA_DATA_MemFd; - } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { - port->memtype = SPA_DATA_MemPtr; } else { spa_log_error(impl->log, "can't use buffers of type %d", d[0].type); return -EINVAL; @@ -1299,21 +1291,6 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, d[j].fd = bufs[i]->planes()[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); d[j].data = nullptr; - } - else if (port->memtype == SPA_DATA_MemPtr) { - d[j].fd = -1; - d[j].data = mmap(nullptr, - d[j].maxsize + d[j].mapoffset, - PROT_READ, MAP_SHARED, - bufs[i]->planes()[j].fd.get(), - 0); - if (d[j].data == MAP_FAILED) { - spa_log_error(impl->log, "mmap: %m"); - continue; - } - b->ptr = d[j].data; - SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); - spa_log_debug(impl->log, "mmap ptr:%p", d[j].data); } else { spa_log_error(impl->log, "invalid buffer type"); return -EIO; From bf327d3dfbbee6642d5a3486c874c9e15d0b25ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 13 Aug 2025 15:35:04 +0200 Subject: [PATCH 0745/1014] spa: libcamera: source: simplify `spa_libcamera_clear_buffers()` Remove the unused `impl` parameter and the unnecessary early return. --- spa/plugins/libcamera/libcamera-source.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index a16ce7971..18cca35ea 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -371,14 +371,9 @@ err: return res; } -int spa_libcamera_clear_buffers(struct impl *impl, struct port *port) +int spa_libcamera_clear_buffers(struct port *port) { - uint32_t i; - - if (port->n_buffers == 0) - return 0; - - for (i = 0; i < port->n_buffers; i++) { + for (std::size_t i = 0; i < port->n_buffers; i++) { struct buffer *b; struct spa_data *d; @@ -1833,7 +1828,7 @@ int port_set_format(struct impl *impl, struct port *port, if (!try_only) { spa_libcamera_stream_off(impl); - spa_libcamera_clear_buffers(impl, port); + spa_libcamera_clear_buffers(port); freeBuffers(impl, port); port->current_format.reset(); } @@ -1947,7 +1942,7 @@ int impl_node_port_use_buffers(void *object, if (port->n_buffers) { spa_libcamera_stream_off(impl); - if ((res = spa_libcamera_clear_buffers(impl, port)) < 0) + if ((res = spa_libcamera_clear_buffers(port)) < 0) return res; } if (n_buffers > 0 && !port->current_format) From b82160c2e71648b1bfc2be1991aeb1ef1e37a716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 13 Aug 2025 15:37:51 +0200 Subject: [PATCH 0746/1014] spa: libcamera: source: remove stale data from buffers When clearing the buffers, remove the stale pointers and file descriptors as accidental reuse of those is problematic and potentially difficult to diagnose. Do this for every data plane. Also clear the node's `buffer` structures to remove any references to the provided `spa_buffer` objects and related metadata. --- spa/plugins/libcamera/libcamera-source.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index 18cca35ea..a469ed377 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -374,13 +374,18 @@ err: int spa_libcamera_clear_buffers(struct port *port) { for (std::size_t i = 0; i < port->n_buffers; i++) { - struct buffer *b; - struct spa_data *d; + buffer *b = &port->buffers[i]; + spa_buffer *sb = b->outbuf; - b = &port->buffers[i]; - d = b->outbuf->datas; + for (std::size_t j = 0; j < sb->n_datas; j++) { + auto *d = &sb->datas[j]; - d[0].type = SPA_ID_INVALID; + d->type = SPA_ID_INVALID; + d->data = nullptr; + d->fd = -1; + } + + *b = {}; } port->n_buffers = 0; From e8ae244b2b656b32eeac257ed4c2e87e43435c44 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Wed, 13 Aug 2025 13:20:31 +0200 Subject: [PATCH 0747/1014] gst: src: Promote 'set format' log to info Follow various other elements like glupload and gtk4paintablesink and print the negotiated caps at a higher priority than debug. A small quality of life improvement to facilitate debugging. --- src/gst/gstpipewiresrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index e6f7ff9d0..e5c0bc7ee 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1206,7 +1206,7 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsrc->stream->clock), 0); - GST_DEBUG_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, negotiated_caps); + GST_INFO_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, negotiated_caps); result = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), negotiated_caps); if (!result) goto no_caps; From 1d437dfb8e7c7d63b23ac660087129ff819a271b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 18 Aug 2025 11:25:50 +0200 Subject: [PATCH 0748/1014] global: allow property updated for registered globals The idea of fd2db174c1e866e70833ec358ba6cef668441eb5 was to allow for property updates after the global was registered but this check was not removed to actually make this happen. Fixes #4851 --- src/pipewire/global.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pipewire/global.c b/src/pipewire/global.c index c9975f9b5..1973f88ca 100644 --- a/src/pipewire/global.c +++ b/src/pipewire/global.c @@ -237,8 +237,6 @@ SPA_EXPORT int pw_global_update_keys(struct pw_global *global, const struct spa_dict *dict, const char * const keys[]) { - if (global->registered) - return -EINVAL; return pw_properties_update_keys(global->properties, dict, keys); } From 3643c468e485e385fe8195d5e006db4e6d0e7a20 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 18 Aug 2025 11:28:15 +0200 Subject: [PATCH 0749/1014] impl-port: keep auto-generated port.alias in sync When the port.alias was auto-generated from the port.name, make sure we update it when the port.name changes. See #4851 --- src/pipewire/impl-port.c | 27 ++++++++++++++++++++++++++- src/pipewire/private.h | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index f5fb70997..89d652d89 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -463,6 +463,29 @@ static int update_properties(struct pw_impl_port *port, const struct spa_dict *d int changed; changed = pw_properties_update_ignore(port->properties, dict, filter ? ignored : NULL); + + if (changed) { + const char *name, *alias; + name = spa_dict_lookup(dict, PW_KEY_PORT_NAME); + alias = spa_dict_lookup(dict, PW_KEY_PORT_ALIAS); + + if (alias != NULL) { + /* alias was explicitly updated, don't generate one from + * the port.name */ + port->alias_port_name = false; + } + else if (name != NULL && port->alias_port_name) { + const struct pw_properties *nprops = pw_impl_node_get_properties(port->node); + const char *node_desc = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION); + const char *node_nick = pw_properties_get(nprops, PW_KEY_NODE_NICK); + const char *node_name = pw_properties_get(nprops, PW_KEY_NODE_NAME); + const char *str; + + if ((str = node_nick) == NULL && (str = node_desc) == NULL && (str = node_name) == NULL) + str = "node"; + pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", str, name); + } + } port->info.props = &port->properties->dict; if (changed) { @@ -1315,9 +1338,11 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) if (is_control) pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", str, prefix); - else + else { pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", str, pw_properties_get(port->properties, PW_KEY_PORT_NAME)); + port->alias_port_name = true; + } } port->info.props = &port->properties->dict; diff --git a/src/pipewire/private.h b/src/pipewire/private.h index a98201587..c610def55 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -957,6 +957,7 @@ struct pw_impl_port { } rt; /**< data only accessed from the data thread */ unsigned int destroying:1; unsigned int passive:1; + unsigned int alias_port_name:1; int busy_count; struct spa_latency_info latency[2]; /**< latencies */ From 5153dc3362348f40ed14a531c09e9e88d033764a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 18 Aug 2025 13:40:01 +0200 Subject: [PATCH 0750/1014] stream: emit RUNNING state from Start cd68819febdf27edfa2f8d4cda20858c5cf6c713 added code to follow the state change of the node because at the time it the Start code was async and it was better to complete it before emitting the state. 9b808558219f6dd37a1733c9720dcbd33178606e however made the Start command sync again and so we can safely emit the running state in the Start. The effect is that the Running state change is emitted first and then the node eventfd is added to the data loop that can then call the process callback. Having the process callback happen before the RUNNING state change is unexpected and racy. --- src/pipewire/stream.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index ddd53ebd0..4c279fe7b 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -719,6 +719,7 @@ static int impl_send_command(void *object, const struct spa_command *command) impl->io->status = SPA_STATUS_NEED_DATA; } copy_position(impl, impl->queued.incount); + stream_set_state(stream, PW_STREAM_STATE_STREAMING, 0, NULL); } break; default: @@ -1451,10 +1452,6 @@ static void node_state_changed(void *data, enum pw_node_state old, struct pw_stream *stream = data; switch (state) { - case PW_NODE_STATE_RUNNING: - if (stream->state == PW_STREAM_STATE_PAUSED) - stream_set_state(stream, PW_STREAM_STATE_STREAMING, 0, NULL); - break; case PW_NODE_STATE_ERROR: stream_set_state(stream, PW_STREAM_STATE_ERROR, -EIO, error); break; From 0aff6e0ef0b634b7a643d9d2d9eadc5d4c7f051b Mon Sep 17 00:00:00 2001 From: Jordi Mas Date: Sat, 16 Aug 2025 11:23:06 +0200 Subject: [PATCH 0751/1014] Update Catalan translation --- po/ca.po | 390 +++++++++++++++++++++++++------------------------------ 1 file changed, 176 insertions(+), 214 deletions(-) diff --git a/po/ca.po b/po/ca.po index 1b2423002..50240f3ed 100644 --- a/po/ca.po +++ b/po/ca.po @@ -5,7 +5,7 @@ # Xavier Conde Rueda , 2008. # Agustí Grau , 2009. # Judith Pintó Subirada -# Jordi Mas i Herǹandez, , 2022-2023 +# Jordi Mas i Herǹandez, , 2022-2025 # # This file is translated according to the glossary and style guide of # Softcatalà. If you plan to modify this file, please read first the page @@ -16,7 +16,7 @@ # Aquest fitxer s'ha de traduir d'acord amb el recull de termes i la guia # d'estil de Softcatalà. Si voleu modificar aquest fitxer, llegiu si # us plau la pàgina de catalanització del projecte Fedora a: -# http://www.softcatala.org/projectes/fedora/ +# https://www.softcatala.org/projectes/fedora/ # i contacteu l'anterior traductor/a. # Josep Torné Llavall , 2009, 2012. # Robert Antoni Buj Gelonch , 2016. #zanata @@ -27,10 +27,9 @@ msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" -"issues\n" -"POT-Creation-Date: 2023-06-06 15:28+0000\n" -"PO-Revision-Date: 2023-06-06 22:39+0200\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues\n" +"POT-Creation-Date: 2025-07-20 15:32+0000\n" +"PO-Revision-Date: 2025-06-20 21:46+0200\n" "Last-Translator: Jordi Mas i Herǹandez, ,\n" "Language-Team: Catalan \n" "Language: ca\n" @@ -38,60 +37,64 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.4.2\n" +"X-Generator: Poedit 3.2.2\n" -#: src/daemon/pipewire.c:26 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [opcions]\n" " -h, --help Mostra aquesta ajuda\n" +" -v, --verbose Augmenta la verbositat en un nivell\n" " --version Mostra la versió\n" -" -c, --config Carrega la configuració " -"(predeterminada %s)\n" +" -c, --config Carrega la configuració (predeterminada %s)\n" +" -P --properties Estableix les propietats del context\n" +"\n" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "Sistema multimèdia PipeWire" -#: src/daemon/pipewire.desktop.in:5 +#: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Inicia el sistema multimèdia PipeWire" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Túnel cap a %s%s%s" -#: src/modules/module-fallback-sink.c:31 +#: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Sortida fictícia" -#: src/modules/module-pulse-tunnel.c:844 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel per a %s@%s" -#: src/modules/module-zeroconf-discover.c:315 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Dispositiu desconegut" -#: src/modules/module-zeroconf-discover.c:327 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s en %s@%s" -#: src/modules/module-zeroconf-discover.c:331 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s en %s" -#: src/tools/pw-cat.c:974 +#: src/tools/pw-cat.c:1044 #, c-format msgid "" "%s [options] [|-]\n" @@ -105,86 +108,68 @@ msgstr "" " --version Mostra la versió\n" " -v, --verbose Habilita les operacions detallades\n" -#: src/tools/pw-cat.c:981 +#: src/tools/pw-cat.c:1051 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target serial or name " -"(default %s)\n" +" --target Set node target serial or name (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" -" the rate is the one of the source " -"file\n" +" the rate is the one of the source file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nom del dimoni remot\n" -" --media-type Estableix el tipus de mitjà (per " -"defecte %s)\n" -" --media-category Estableix la categoria del " -"mitjà (per defecte %s)\n" -" --media-role Estableix el rol del mitjà (per " -"defecte %s)\n" -" --target Estableix l'objectiu sèrie o el nom " -"del node (per defecte %s)\n" +" --media-type Estableix el tipus de mitjà (per defecte %s)\n" +" --media-category Estableix la categoria del mitjà (per defecte %s)\n" +" --media-role Estableix el rol del mitjà (per defecte %s)\n" +" --target Estableix l'objectiu sèrie o el nom del node (per defecte %s)\n" " 0 vol dir que no enllaça\n" -" --latency Estableix latència del node (per " -"defecte %s)\n" +" --latency Estableix latència del node (per defecte %s)\n" " Xunit (unitat = s, ms, us, ns)\n" " o mostres directes (256)\n" " la taxa és la del fitxer d'origen\n" -" -P --properties Estableix les propietats del " -"node\n" +" -P --properties Estableix les propietats del node\n" "\n" -#: src/tools/pw-cat.c:999 +#: src/tools/pw-cat.c:1069 #, c-format msgid "" -" --rate Sample rate (req. for rec) (default " -"%u)\n" -" --channels Number of channels (req. for rec) " -"(default %u)\n" +" --rate Sample rate (req. for rec) (default %u)\n" +" --channels Number of channels (req. for rec) (default %u)\n" " --channel-map Channel map\n" -" one of: \"stereo\", " -"\"surround-51\",... or\n" -" comma separated list of channel " -"names: eg. \"FL,FR\"\n" -" --format Sample format %s (req. for rec) " -"(default %s)\n" +" one of: \"stereo\", \"surround-51\",... or\n" +" comma separated list of channel names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) (default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" -" -q --quality Resampler quality (0 - 15) (default " -"%d)\n" +" -q --quality Resampler quality (0 - 15) (default %d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" -" --rate Freqüència de mostreig (req. per a " -"rec) (predeterminat %u)\n" -" --channels Nombre de canals (req. per a rec) " -"(predeterminat %u)\n" +" --rate Freqüència de mostreig (req. per a rec) (predeterminat %u)\n" +" --channels Nombre de canals (req. per a rec) (predeterminat %u)\n" " --channel-map Mapa de canals\n" -" un dels següents: \"stereo\", " -"\"surround-51\",... o\n" -" llista separada per comes dels " -"noms dels canals: per exemple. «FL,FR»\n" -" --format Format de mostra %s (req. per a rec) " -"(predeterminat %s)\n" -" --volume Volum del flux 0-1.0 (predeterminat " -"%.3f)\n" -" -q --quality Qualitat de remostrador (0 - 15) " -"(predeterminal %d)\n" +" un dels següents: \"stereo\", \"surround-51\",... o\n" +" llista separada per comes dels noms dels canals: per exemple. «FL,FR»\n" +" --format Format de mostra %s (req. per a rec) (predeterminat %s)\n" +" --volume Volum del flux 0-1.0 (predeterminat %.3f)\n" +" -q --quality Qualitat de remostrador (0 - 15) (predeterminat %d)\n" +" -a, --raw Mode RAW\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:1087 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" +" -s, --sysex SysEx mode\n" "\n" msgstr "" " -p, --playback Mode de reproducció\n" @@ -192,9 +177,10 @@ msgstr "" " -m, --midi Mode MIDI\n" " -d, --dsd Mode DSD\n" " -o, --encoded Mode codificat\n" +" -s, --sysex Mode SysEx\n" "\n" -#: src/tools/pw-cli.c:2220 +#: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" @@ -208,21 +194,25 @@ msgstr "" "%s [opcions] [ordre]\n" " -h, --help Mostra aquesta ajuda\n" " --version Mostra la versió\n" -" -d, --daemon Inicia com a dimoni (fals per " -"defecte)\n" +" -d, --daemon Inicia com a dimoni (fals per defecte)\n" " -r, --remote Nom del dimoni remot\n" " -m, --monitor Monitor d'activitat\n" "\n" -#: spa/plugins/alsa/acp/acp.c:303 +#: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1586 +#: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Inactiu" +#: spa/plugins/alsa/acp/acp.c:603 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [Error d'ALSA UCM]" + #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" @@ -246,7 +236,7 @@ msgstr "Entrada de línia" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1831 +#: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "Micròfon" @@ -312,12 +302,15 @@ msgid "No Bass Boost" msgstr "Sense accentuació dels baixos" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1837 +#: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "Altaveu" +#. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 +#: spa/plugins/bluez5/bluez5-device.c:2384 +#: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "Auriculars" @@ -394,15 +387,15 @@ msgstr "Entrada del xat" msgid "Virtual Surround 7.1" msgstr "Envoltant virtual 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Mono analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Mono analògic (esquerra)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Mono analògic (dreta)" @@ -411,338 +404,307 @@ msgstr "Mono analògic (dreta)" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4474 -#: spa/plugins/alsa/acp/alsa-mixer.c:4482 -#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Estèreo analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Estèreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 -#: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1819 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "Auriculars" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 -#: spa/plugins/alsa/acp/alsa-mixer.c:4643 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Altaveu del telèfon" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Envoltant analògic 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Envoltant analògic 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Envoltant analògic 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Envoltant analògic 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4492 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Envoltant analògic 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4493 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Envoltant analògic 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4494 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Envoltant analògic 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4495 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Envoltant analògic 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4496 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Envoltant analògic 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4497 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Envoltant analògic 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4498 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Envoltant analògic 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4499 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Estèreo digital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4500 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envoltant digital 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4501 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envoltant digital 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4502 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envoltant digital 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4503 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Estèreo digital (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4504 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envoltant digital 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4505 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Xat" -#: spa/plugins/alsa/acp/alsa-mixer.c:4506 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Joc" -#: spa/plugins/alsa/acp/alsa-mixer.c:4640 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Dúplex mono analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4641 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Dúplex estèreo analògic" -#: spa/plugins/alsa/acp/alsa-mixer.c:4644 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dúplex estèreo digital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4645 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Dúplex Multicanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4646 +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Dúplex estèreo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4647 +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Xat mono + 7.1 envoltant" -#: spa/plugins/alsa/acp/alsa-mixer.c:4748 +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "Sortida %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4756 +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "Entrada %s" -#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " -"ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " -"ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu byte (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu byte (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1253 +#: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte " -"(%s%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " -"(%s%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li byte (%s%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li byte (%s%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1300 +#: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" -"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " -"%lu.\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr "" -"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu " -"ms).\n" -"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu " -"d'aquest problema als desenvolupadors d'ALSA." +"snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu d'aquest problema als desenvolupadors d'ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1343 +#: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " -"(%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" -"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " -"(%lu ms).\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " -"to the ALSA developers." +"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu byte " -"(%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu byte (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" -"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes " -"(%lu ms).\n" -"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu " -"d'aquest incident als desenvolupadors de l'ALSA." +"snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" +"Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(incorrecte)" -#: spa/plugins/alsa/acp/compat.c:189 +#: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Àudio intern" -#: spa/plugins/alsa/acp/compat.c:194 +#: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Mòdem" -#: spa/plugins/bluez5/bluez5-device.c:1597 +#: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Passarel·la d'àudio (A2DP Source & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1622 +#: spa/plugins/bluez5/bluez5-device.c:2014 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "Retransmissió d'àudio per als audiòfons (ASHA Sink)" + +#: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reproducció d'alta fidelitat (Sink A2DP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1625 +#: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1633 +#: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reproducció d'alta fidelitat (A2DP Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1635 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1677 +#: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Reproducció d'alta fidelitat (sortida BAP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1681 +#: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Entrada d'alta fidelitat (font A2DP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1685 +#: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dúplex d'alta fidelitat (BAP Source/Sink, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1693 +#: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "Reproducció d'alta fidelitat (Sortida BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1696 +#: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "Entrada d'alta fidelitat (Font BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1699 +#: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dúplex d'alta fidelitat (BAP Source/Sink)" -#: spa/plugins/bluez5/bluez5-device.c:1735 +#: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Unitat d'ariculars pel cap (HSP/HFP, còdec %s)" +msgstr "Unitat d'auriculars pel cap (HSP/HFP, còdec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1740 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Unitat d'ariculars pel cap (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1820 -#: spa/plugins/bluez5/bluez5-device.c:1825 -#: spa/plugins/bluez5/bluez5-device.c:1832 -#: spa/plugins/bluez5/bluez5-device.c:1838 -#: spa/plugins/bluez5/bluez5-device.c:1844 -#: spa/plugins/bluez5/bluez5-device.c:1850 -#: spa/plugins/bluez5/bluez5-device.c:1856 -#: spa/plugins/bluez5/bluez5-device.c:1862 -#: spa/plugins/bluez5/bluez5-device.c:1868 +#: spa/plugins/bluez5/bluez5-device.c:2361 +#: spa/plugins/bluez5/bluez5-device.c:2366 +#: spa/plugins/bluez5/bluez5-device.c:2373 +#: spa/plugins/bluez5/bluez5-device.c:2379 +#: spa/plugins/bluez5/bluez5-device.c:2385 +#: spa/plugins/bluez5/bluez5-device.c:2391 +#: spa/plugins/bluez5/bluez5-device.c:2397 +#: spa/plugins/bluez5/bluez5-device.c:2403 +#: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "Mans lliures" -#: spa/plugins/bluez5/bluez5-device.c:1826 +#: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "Mans lliures (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1843 -msgid "Headphone" -msgstr "Auricular" - -#: spa/plugins/bluez5/bluez5-device.c:1849 +#: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "Portable" -#: spa/plugins/bluez5/bluez5-device.c:1855 +#: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "Cotxe" -#: spa/plugins/bluez5/bluez5-device.c:1861 +#: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1867 +#: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "Telèfon" -#: spa/plugins/bluez5/bluez5-device.c:1874 +#: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1875 +#: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" + From f562394596ec3d025b332059840be6b9e8ededa2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 15:28:22 +0200 Subject: [PATCH 0752/1014] alsa-seq: improve debug Make it easier to change the event debug. Also add some more context to the event debug. --- spa/plugins/alsa/alsa-seq.c | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index bcbddc992..efd1d3f24 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -210,53 +210,51 @@ static void init_ports(struct seq_state *state) } } -static void debug_event(struct seq_state *state, snd_seq_event_t *ev) +static void debug_event(struct seq_state *state, const char *prefix, snd_seq_event_t *ev) { - if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) + enum spa_log_level lev = SPA_LOG_LEVEL_TRACE; + + if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, lev))) return; - spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags); + spa_log_lev(state->log, lev, "%s: event type:%d flags:0x%x", prefix, ev->type, ev->flags); switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { case SND_SEQ_TIME_STAMP_TICK: - spa_log_trace(state->log, " time: %d ticks", ev->time.tick); + spa_log_lev(state->log, lev, "%s: time: %d ticks", prefix, ev->time.tick); break; case SND_SEQ_TIME_STAMP_REAL: - spa_log_trace(state->log, " time = %d.%09d", + spa_log_lev(state->log, lev, "%s: time = %d.%09d", prefix, (int)ev->time.time.tv_sec, (int)ev->time.time.tv_nsec); break; } - spa_log_trace(state->log, " source:%d.%d dest:%d.%d queue:%d", - ev->source.client, - ev->source.port, - ev->dest.client, - ev->dest.port, - ev->queue); + spa_log_lev(state->log, lev, "%s: source:%d.%d dest:%d.%d queue:%d", prefix, + ev->source.client, ev->source.port, ev->dest.client, + ev->dest.port, ev->queue); } #ifdef HAVE_ALSA_UMP -static void debug_ump_event(struct seq_state *state, snd_seq_ump_event_t *ev) +static void debug_ump_event(struct seq_state *state, const char *prefix, snd_seq_ump_event_t *ev) { - if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) + enum spa_log_level lev = SPA_LOG_LEVEL_TRACE; + + if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, lev))) return; - spa_log_trace(state->log, "event type:%d flags:0x%x", ev->type, ev->flags); + spa_log_lev(state->log, lev, "%s: event type:%d flags:0x%x", prefix, ev->type, ev->flags); switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { case SND_SEQ_TIME_STAMP_TICK: - spa_log_trace(state->log, " time: %d ticks", ev->time.tick); + spa_log_lev(state->log, lev, "%s: time: %d ticks", prefix, ev->time.tick); break; case SND_SEQ_TIME_STAMP_REAL: - spa_log_trace(state->log, " time = %d.%09d", + spa_log_lev(state->log, lev, "%s: time = %d.%09d", prefix, (int)ev->time.time.tv_sec, (int)ev->time.time.tv_nsec); break; } - spa_log_trace(state->log, " source:%d.%d dest:%d.%d queue:%d", - ev->source.client, - ev->source.port, - ev->dest.client, - ev->dest.port, - ev->queue); + spa_log_lev(state->log, lev, "%s: source:%d.%d dest:%d.%d queue:%d %08x", + prefix, ev->source.client, ev->source.port, ev->dest.client, + ev->dest.port, ev->queue, ev->ump[0]); } #endif @@ -278,7 +276,7 @@ static void alsa_seq_on_sys(struct spa_source *source) if (res <= 0) break; - debug_ump_event(state, ev); + debug_ump_event(state, "sys", ev); addr = &ev->data.addr; type = ev->type; @@ -292,7 +290,7 @@ static void alsa_seq_on_sys(struct spa_source *source) if (res <= 0) break; - debug_event(state, ev); + debug_event(state, "sys", ev); addr = &ev->data.addr; type = ev->type; @@ -648,7 +646,7 @@ static int process_read(struct seq_state *state) if (res <= 0) break; - debug_ump_event(state, ev); + debug_ump_event(state, "read", ev); event = ev; addr = &ev->source; @@ -664,7 +662,7 @@ static int process_read(struct seq_state *state) if (res <= 0) break; - debug_event(state, ev); + debug_event(state, "read", ev); event = ev; addr = &ev->source; @@ -869,6 +867,8 @@ static int process_write(struct seq_state *state) snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + debug_ump_event(state, "send", &ev); + if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); From c0a7c01a35e1582101603bcafb60a9ec87904180 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 15:32:13 +0200 Subject: [PATCH 0753/1014] midifile: fix seeking in midifile When we perform a seek, we need to update the current position in the file as well or we calculate wrong offsets. --- src/tools/midifile.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 51720db8b..d7503da92 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -58,6 +58,17 @@ static inline uint32_t parse_be32(const uint8_t *in) return (in[0] << 24) | (in[1] << 16) | (in[2] << 8) | in[3]; } +static inline int mf_seek(struct midi_file *mf, long offs) +{ + int res; + if (mf->pos == offs) + return 0; + if ((res = fseek(mf->file, offs, SEEK_SET)) != 0) + return -errno; + mf->pos = offs; + return 0; +} + static inline int mf_read(struct midi_file *mf, void *data, size_t size) { if (fread(data, size, 1, mf->file) != 1) @@ -178,10 +189,8 @@ static int open_read(struct midi_file *mf, const char *filename, struct midi_fil tr->id = i; if (i + 1 < mf->info.ntracks && - fseek(mf->file, tr->start + tr->size, SEEK_SET) != 0) { - res = -errno; + (res = mf_seek(mf, tr->start + tr->size)) < 0) goto exit_close; - } } mf->mode = 1; *info = mf->info; @@ -218,7 +227,7 @@ static int write_headers(struct midi_file *mf) struct midi_track *tr = &mf->tracks[0]; int res; - fseek(mf->file, 0, SEEK_SET); + mf_seek(mf, 0); mf->length = 6; CHECK_RES(write_n(mf->file, "MThd", 4)); @@ -351,7 +360,6 @@ int midi_file_read_event(struct midi_file *mf, struct midi_event *event) uint32_t size; uint8_t status, meta; int res, running; - long offs; event->data = NULL; @@ -360,11 +368,8 @@ int midi_file_read_event(struct midi_file *mf, struct midi_event *event) tr = &mf->tracks[event->track]; - offs = tr->pos; - if (offs != mf->pos) { - if (fseek(mf->file, offs, SEEK_SET) != 0) - return -errno; - } + if ((res = mf_seek(mf, tr->pos)) < 0) + return res; mf_read(mf, &status, 1); From 15f3b1f450b99ef21b82b7fe9717a5e98006c1f9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 16:33:31 +0200 Subject: [PATCH 0754/1014] tools: add -m options to mididump The -m option forces conversion to MIDI 1.0 and will then dump the midi 1 events. --- src/tools/pw-mididump.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index be37024fc..0b04f7daf 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -32,6 +32,7 @@ struct data { struct pw_filter *filter; struct port *in_port; int64_t clock_time; + bool opt_midi1; }; static int dump_file(const char *filename) @@ -90,14 +91,20 @@ static void on_process(void *_data, struct spa_io_position *position) while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { struct midi_event ev; - if (c.type != SPA_CONTROL_UMP) + switch (c.type) { + case SPA_CONTROL_UMP: + ev.type = MIDI_EVENT_TYPE_UMP; + break; + case SPA_CONTROL_Midi: + ev.type = MIDI_EVENT_TYPE_MIDI1; + break; + default: continue; - + } ev.track = 0; ev.sec = (offset + c.offset) / (float) position->clock.rate.denom; ev.data = (uint8_t*)c_body; ev.size = c.value.size; - ev.type = MIDI_EVENT_TYPE_UMP; fprintf(stdout, "%4d: ", c.offset); midi_file_dump_event(stdout, &ev); @@ -144,7 +151,7 @@ static int dump_filter(struct data *data) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_FORMAT_DSP, data->opt_midi1 ? "8 bit raw midi" : "32 bit raw UMP", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); @@ -167,7 +174,8 @@ static void show_help(const char *name, bool error) fprintf(error ? stderr : stdout, "%s [options] [FILE]\n" " -h, --help Show this help\n" " --version Show version\n" - " -r, --remote Remote daemon name\n", + " -r, --remote Remote daemon name\n" + " -m, --midi1 Dump as MIDI 1.0\n", name); } @@ -179,6 +187,7 @@ int main(int argc, char *argv[]) { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "remote", required_argument, NULL, 'r' }, + { "midi1", no_argument, NULL, 'm' }, { NULL, 0, NULL, 0} }; @@ -187,7 +196,7 @@ int main(int argc, char *argv[]) setlinebuf(stdout); - while ((c = getopt_long(argc, argv, "hVr:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:m", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); @@ -203,6 +212,9 @@ int main(int argc, char *argv[]) case 'r': data.opt_remote = optarg; break; + case 'm': + data.opt_midi1 = true; + break; default: show_help(argv[0], true); return -1; From bf10458604276c457e28ef36030a547672db4b24 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 17:37:40 +0200 Subject: [PATCH 0755/1014] tools: handle both Midi and UMP when recording The midifile can handle both UMP and Midi formats when saving so allow this here. --- src/tools/pw-cat.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index b2e534e00..db96b8c2e 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1192,14 +1192,20 @@ static int midi_record(struct data *d, void *src, unsigned int n_frames, bool *n while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { struct midi_event ev; - if (c.type != SPA_CONTROL_UMP) + switch (c.type) { + case SPA_CONTROL_UMP: + ev.type = MIDI_EVENT_TYPE_UMP; + break; + case SPA_CONTROL_Midi: + ev.type = MIDI_EVENT_TYPE_MIDI1; + break; + default: continue; - + } ev.track = 0; ev.sec = (offset + c.offset) / (float) d->position->clock.rate.denom; ev.data = (uint8_t*)c_body; ev.size = c.value.size; - ev.type = MIDI_EVENT_TYPE_UMP; if (d->verbose) midi_file_dump_event(stderr, &ev); From e35a8554f860dbd1a3919307ac798ab31a026890 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 17:41:03 +0200 Subject: [PATCH 0756/1014] control: improve UMP to Midi conversiom Improve the spa_ump_to_midi function so that it can consume multiple UMP messages and produce multiple midi messages. Some UMP messages (like program changes) need to be translated into up to 3 midi messages. Do this byt adding a state to the function and by making it consume the input bytes, just like the spa_ump_from_midi function. Adapt code to this new world. This is a little API break.. --- pipewire-jack/src/pipewire-jack.c | 42 ++++++------ spa/include/spa/control/ump-utils.h | 80 ++++++++++++++-------- spa/plugins/alsa/alsa-seq.c | 53 ++++++++------- spa/plugins/audioconvert/audioconvert.c | 16 +++-- spa/plugins/bluez5/midi-node.c | 37 +++++----- spa/plugins/control/mixer.c | 14 ++-- src/modules/module-ffado-driver.c | 31 +++++---- src/modules/module-jack-tunnel.c | 35 +++++----- src/modules/module-netjack2/peer.c | 44 ++++++------ src/modules/module-rtp/midi.c | 90 +++++++++++++------------ src/modules/module-vban/midi.c | 33 +++++---- src/tools/midifile.c | 47 +++++++------ test/test-spa-control.c | 13 ++-- 13 files changed, 307 insertions(+), 228 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index d513d6403..c8b4a149d 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1610,7 +1610,6 @@ static inline int midi_event_write(void *port_buffer, static void convert_to_event(struct mix_info **mix, uint32_t n_mix, void *midi, bool fix, uint32_t type) { - uint64_t state = 0; uint32_t i; int res = 0; bool in_sysex = false; @@ -1621,6 +1620,7 @@ static void convert_to_event(struct mix_info **mix, uint32_t n_mix, void *midi, struct spa_pod_control *control; size_t size; uint8_t *data; + uint64_t state = 0; for (i = 0; i < n_mix; i++) { struct mix_info *m = mix[i]; @@ -1664,34 +1664,36 @@ static void convert_to_event(struct mix_info **mix, uint32_t n_mix, void *midi, } case SPA_CONTROL_UMP: { - uint8_t ev[32]; - bool was_sysex = in_sysex; - if (type == TYPE_ID_MIDI) { - uint32_t *d = (uint32_t*)data; - int ev_size = spa_ump_to_midi(d, size, ev, sizeof(ev)); - if (ev_size <= 0) - break; + uint8_t ev[32]; + const uint32_t *d = (uint32_t*)data; - size = ev_size; - data = ev; + while (size > 0) { + bool was_sysex = in_sysex; + int ev_size = spa_ump_to_midi(&d, &size, ev, sizeof(ev), &state); + if (ev_size <= 0) + break; - if (!in_sysex && ev[0] == 0xf0) - in_sysex = true; - if (in_sysex && ev[ev_size-1] == 0xf7) - in_sysex = false; + if (!in_sysex && ev[0] == 0xf0) + in_sysex = true; + if (in_sysex && ev[ev_size-1] == 0xf7) + in_sysex = false; - } else if (type != TYPE_ID_UMP) - break; + if (was_sysex) + res = midi_event_append(midi, ev, ev_size); + else + res = midi_event_write(midi, control->offset, ev, ev_size, fix); + if (res < 0) + break; - if (was_sysex) - res = midi_event_append(midi, data, size); - else + } + } else if (type == TYPE_ID_UMP) { res = midi_event_write(midi, control->offset, data, size, fix); - + } if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); + break; } } if (spa_pod_parser_get_control_body(&next->parser, diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 4525cef76..52738897f 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -48,72 +48,98 @@ SPA_API_CONTROL_UMP_UTILS size_t spa_ump_message_size(uint8_t message_type) return ump_sizes[message_type & 0xf]; } -SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(const uint32_t *ump, size_t ump_size, - uint8_t *midi, size_t midi_maxsize) +SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(const uint32_t **ump, size_t *ump_size, + uint8_t *midi, size_t midi_maxsize, uint64_t *state) { int size = 0; + uint32_t to_consume = 0; + const uint32_t *u = *ump; - if (ump_size < 4) - return 0; + if (*ump_size < 4 || + (to_consume = (spa_ump_message_size(u[0]>>28) * 4)) > *ump_size) { + to_consume = *ump_size; + goto done; + } if (midi_maxsize < 8) return -ENOSPC; - switch (ump[0] >> 28) { + switch (u[0] >> 28) { case 0x1: /* System Real Time and System Common Messages (except System Exclusive) */ - midi[size++] = (ump[0] >> 16) & 0xff; + midi[size++] = (u[0] >> 16) & 0xff; if (midi[0] >= 0xf1 && midi[0] <= 0xf3) { - midi[size++] = (ump[0] >> 8) & 0x7f; + midi[size++] = (u[0] >> 8) & 0x7f; if (midi[0] == 0xf2) - midi[size++] = ump[0] & 0x7f; + midi[size++] = u[0] & 0x7f; } break; case 0x2: /* MIDI 1.0 Channel Voice Messages */ - midi[size++] = (ump[0] >> 16); - midi[size++] = (ump[0] >> 8); + midi[size++] = (u[0] >> 16); + midi[size++] = (u[0] >> 8); if (midi[0] < 0xc0 || midi[0] > 0xdf) - midi[size++] = (ump[0]); + midi[size++] = (u[0]); break; case 0x3: /* Data Messages (including System Exclusive) */ { uint8_t status, i, bytes; - if (ump_size < 8) - return 0; - - status = (ump[0] >> 20) & 0xf; - bytes = SPA_CLAMP((ump[0] >> 16) & 0xf, 0u, 6u); + status = (u[0] >> 20) & 0xf; + bytes = SPA_CLAMP((u[0] >> 16) & 0xf, 0u, 6u); if (status == 0 || status == 1) midi[size++] = 0xf0; for (i = 0 ; i < bytes; i++) - /* ump[0] >> 8 | ump[0] | ump[1] >> 24 | ump[1] >>16 ... */ - midi[size++] = ump[(i+2)/4] >> ((5-i)%4 * 8); + /* u[0] >> 8 | u[0] | u[1] >> 24 | u[1] >>16 ... */ + midi[size++] = u[(i+2)/4] >> ((5-i)%4 * 8); if (status == 0 || status == 3) midi[size++] = 0xf7; break; } case 0x4: /* MIDI 2.0 Channel Voice Messages */ - if (ump_size < 8) - return 0; - midi[size++] = (ump[0] >> 16) | 0x80; - switch (midi[0] & 0xf0) { + { + uint8_t status = (u[0] >> 16) | 0x80; + switch (status & 0xf0) { case 0xc0: - midi[size++] = (ump[1] >> 24); + /* program/bank change */ + if (!(u[0] & 1)) + *state = 2; + if (*state == 0) { + midi[size++] = (status & 0xf) | 0xb0; + midi[size++] = 0; + midi[size++] = (u[1] >> 8); + to_consume = 0; + *state = 1; + } + else if (*state == 1) { + midi[size++] = (status & 0xf) | 0xb0; + midi[size++] = 32; + midi[size++] = u[1]; + to_consume = 0; + *state = 2; + } + else if (*state == 2) { + midi[size++] = status; + midi[size++] = (u[1] >> 24); + *state = 0; + } break; default: - midi[size++] = (ump[0] >> 8) & 0x7f; + midi[size++] = status; + midi[size++] = (u[0] >> 8) & 0x7f; SPA_FALLTHROUGH; case 0xd0: - midi[size++] = (ump[1] >> 25); + midi[size++] = (u[1] >> 25); break; } break; - + } case 0x0: /* Utility Messages */ case 0x5: /* Data Messages */ default: - return 0; + break; } +done: + (*ump_size) -= to_consume; + (*ump) = SPA_PTROFF(*ump, to_consume, uint32_t); return size; } diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index efd1d3f24..8a4ba369c 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -880,34 +880,41 @@ static int process_write(struct seq_state *state) snd_seq_event_t ev; uint8_t data[MAX_EVENT_SIZE]; int size; + uint64_t st = 0; - if ((size = spa_ump_to_midi((uint32_t *)body, body_size, data, sizeof(data))) <= 0) - continue; + while (body_size > 0) { + if ((size = spa_ump_to_midi((const uint32_t **)&body, &body_size, + data, sizeof(data), &st)) <= 0) + break; - if (first) - snd_seq_ev_clear(&ev); + if (first) + snd_seq_ev_clear(&ev); - if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) { - spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); - snd_midi_event_reset_encode(stream->codec); + if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) { + spa_log_warn(state->log, "failed to encode event: %s", + snd_strerror(size)); + snd_midi_event_reset_encode(stream->codec); + first = true; + continue; + } + first = false; + if (ev.type == SND_SEQ_EVENT_NONE) + /* this can happen when the event is not complete yet, like + * a sysex message and we need to encode some more data. */ + continue; + + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + debug_event(state, "send", &ev); + + if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); + } first = true; - continue; } - first = false; - if (ev.type == SND_SEQ_EVENT_NONE) - /* this can happen when the event is not complete yet, like - * a sysex message and we need to encode some more data. */ - continue; - - snd_seq_ev_set_source(&ev, state->event.addr.port); - snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); - snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); - - if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { - spa_log_warn(state->log, "failed to output event: %s", - snd_strerror(err)); - } - first = true; } } } diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 5c411efcf..adb4b92bb 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1824,18 +1824,20 @@ static int apply_props(struct impl *this, const struct spa_pod *param) static int apply_midi(struct impl *this, const struct spa_pod *value) { struct props *p = &this->props; - uint8_t data[8]; - int size; + uint8_t ev[8]; + int ev_size; + const uint32_t *body = SPA_POD_BODY_CONST(value); + size_t size = SPA_POD_BODY_SIZE(value); + uint64_t state = 0; - size = spa_ump_to_midi(SPA_POD_BODY(value), SPA_POD_BODY_SIZE(value), - data, sizeof(data)); - if (size < 3) + ev_size = spa_ump_to_midi(&body, &size, ev, sizeof(ev), &state); + if (ev_size < 3) return -EINVAL; - if ((data[0] & 0xf0) != 0xb0 || data[1] != 7) + if ((ev[0] & 0xf0) != 0xb0 || ev[1] != 7) return 0; - p->volume = data[2] / 127.0f; + p->volume = ev[2] / 127.0f; set_volume(this); return 1; } diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index b2acb40f4..7146d6f8a 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -788,30 +788,35 @@ static int write_data(struct impl *this, struct spa_data *d) while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; uint8_t event[32]; + const uint32_t *ump = c_body; + size_t ump_size = c.value.size; + uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; time = SPA_MAX(time, this->current_time + c.offset * SPA_NSEC_PER_SEC / this->rate); - size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); - if (size <= 0) - continue; + while (ump_size > 0) { + size = spa_ump_to_midi(&ump, &ump_size, event, sizeof(event), &state); + if (size <= 0) + break; - spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this, - (size > 0) ? event[0] : 0, time); + spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this, + (size > 0) ? event[0] : 0, time); - do { - res = spa_bt_midi_writer_write(&this->writer, - time, event, size); - if (res < 0) { - return res; - } else if (res) { - int res2; - if ((res2 = flush_packet(this)) < 0) - return res2; - } - } while (res); + do { + res = spa_bt_midi_writer_write(&this->writer, + time, event, size); + if (res < 0) { + return res; + } else if (res) { + int res2; + if ((res2 = flush_packet(this)) < 0) + return res2; + } + } while (res); + } } if ((res = flush_packet(this)) < 0) diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index ccf0df54a..d71a1e16d 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -826,11 +826,15 @@ static int impl_node_process(void *object) case SPA_CONTROL_UMP: { uint8_t ev[8]; - int ev_size = spa_ump_to_midi((uint32_t*)body, size, ev, sizeof(ev)); - if (ev_size <= 0) - break; - spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&builder, ev, ev_size); + const uint32_t *ump = (const uint32_t*)body; + uint64_t state = 0; + while (size > 0) { + int ev_size = spa_ump_to_midi(&ump, &size, ev, sizeof(ev), &state); + if (ev_size <= 0) + break; + spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&builder, ev, ev_size); + } break; } } diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 0cd2a75ff..8c94edcdd 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -345,27 +345,32 @@ static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples) while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { uint8_t data[16]; int j, size; + size_t c_size = c.value.size; + uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(c_body, c.value.size, data, sizeof(data)); - if (size <= 0) - continue; - if (index < c.offset) index = SPA_ROUND_UP_N(c.offset, 8); - for (j = 0; j < size; j++) { - if (index >= n_samples) { - /* keep events that don't fit for the next cycle */ - if (p->event_pos < sizeof(p->event_buffer)) - p->event_buffer[p->event_pos++] = data[j]; + + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, data, sizeof(data), &state); + if (size <= 0) + break; + + for (j = 0; j < size; j++) { + if (index >= n_samples) { + /* keep events that don't fit for the next cycle */ + if (p->event_pos < sizeof(p->event_buffer)) + p->event_buffer[p->event_pos++] = data[j]; + else + unhandled++; + } else - unhandled++; + dst[index] = 0x01000000 | (uint32_t) data[j]; + index += 8; } - else - dst[index] = 0x01000000 | (uint32_t) data[j]; - index += 8; } } if (unhandled > 0) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index e64535938..226caf6fe 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -273,29 +273,34 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; + size_t c_size = c.value.size; + uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(c_body, c.value.size, &tmp[tmp_size], sizeof(tmp) - tmp_size); - if (size <= 0) - continue; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, + &tmp[tmp_size], sizeof(tmp) - tmp_size, &state); + if (size <= 0) + break; - if (impl->fix_midi) - fix_midi_event(&tmp[tmp_size], size); + if (impl->fix_midi) + fix_midi_event(&tmp[tmp_size], size); - if (!in_sysex && tmp[tmp_size] == 0xf0) - in_sysex = true; + if (!in_sysex && tmp[tmp_size] == 0xf0) + in_sysex = true; - tmp_size += size; - if (in_sysex && tmp[tmp_size-1] == 0xf7) - in_sysex = false; + tmp_size += size; + if (in_sysex && tmp[tmp_size-1] == 0xf7) + in_sysex = false; - if (!in_sysex) { - if ((res = jack.midi_event_write(dst, c.offset, tmp, tmp_size)) < 0) - pw_log_warn("midi %p: can't write event: %s", dst, - spa_strerror(res)); - tmp_size = 0; + if (!in_sysex) { + if ((res = jack.midi_event_write(dst, c.offset, tmp, tmp_size)) < 0) + pw_log_warn("midi %p: can't write event: %s", dst, + spa_strerror(res)); + tmp_size = 0; + } } } } diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index d2251e705..37f7854db 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -333,32 +333,36 @@ static void midi_to_netjack2(struct netjack2_peer *peer, int size; uint8_t data[16]; bool was_sysex = in_sysex; + size_t c_size = c.value.size; + uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(c_body, c.value.size, data, sizeof(data)); - if (size <= 0) - continue; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, data, sizeof(data), &state); + if (size <= 0) + break; - if (c.offset >= n_samples) { - buf->lost_events++; - continue; + if (c.offset >= n_samples) { + buf->lost_events++; + continue; + } + + if (!in_sysex && data[0] == 0xf0) + in_sysex = true; + + if (!in_sysex && peer->fix_midi) + fix_midi_event(data, size); + + if (in_sysex && data[size-1] == 0xf7) + in_sysex = false; + + if (was_sysex) + n2j_midi_buffer_append(buf, data, size); + else + n2j_midi_buffer_write(buf, c.offset, data, size); } - - if (!in_sysex && data[0] == 0xf0) - in_sysex = true; - - if (!in_sysex && peer->fix_midi) - fix_midi_event(data, size); - - if (in_sysex && data[size-1] == 0xf7) - in_sysex = false; - - if (was_sysex) - n2j_midi_buffer_append(buf, data, size); - else - n2j_midi_buffer_write(buf, c.offset, data, size); } if (buf->write_pos > 0) memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index 77e348233..498c6e6a9 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -437,57 +437,61 @@ static void rtp_midi_flush_packets(struct impl *impl, while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) { uint32_t delta, offset; uint8_t event[16]; - size_t size; + int size; + size_t c_size = c.value.size; + uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); - if (size <= 0) - continue; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t **)&c_body, &c_size, event, sizeof(event), &state); + if (size <= 0) + break; - offset = c.offset * impl->rate / rate; + offset = c.offset * impl->rate / rate; - if (len > 0 && (len + size > max_size || - offset - base > impl->psamples)) { - /* flush packet when we have one and when it's either - * too large or has too much data. */ - if (len < 16) { - midi_header.b = 0; - midi_header.len = len; - iov[1].iov_len = sizeof(midi_header) - 1; - } else { - midi_header.b = 1; - midi_header.len = (len >> 8) & 0xf; - midi_header.len_b = len & 0xff; - iov[1].iov_len = sizeof(midi_header); + if (len > 0 && (len + size > max_size || + offset - base > impl->psamples)) { + /* flush packet when we have one and when it's either + * too large or has too much data. */ + if (len < 16) { + midi_header.b = 0; + midi_header.len = len; + iov[1].iov_len = sizeof(midi_header) - 1; + } else { + midi_header.b = 1; + midi_header.len = (len >> 8) & 0xf; + midi_header.len_b = len & 0xff; + iov[1].iov_len = sizeof(midi_header); + } + iov[2].iov_len = len; + + pw_log_trace("sending %d timestamp:%d %u %u", + len, timestamp + base, + offset, impl->psamples); + rtp_stream_emit_send_packet(impl, iov, 3); + + impl->seq++; + len = 0; } - iov[2].iov_len = len; + if ((unsigned int)size > BUFFER_SIZE || len > BUFFER_SIZE - size) { + pw_log_error("Buffer overflow prevented!"); + return; // FIXME: what to do instead? + } + if (len == 0) { + /* start new packet */ + base = prev_offset = offset; + header.sequence_number = htons(impl->seq); + header.timestamp = htonl(impl->ts_offset + timestamp + base); - pw_log_trace("sending %d timestamp:%d %u %u", - len, timestamp + base, - offset, impl->psamples); - rtp_stream_emit_send_packet(impl, iov, 3); - - impl->seq++; - len = 0; - } - if (size > BUFFER_SIZE || len > BUFFER_SIZE - size) { - pw_log_error("Buffer overflow prevented!"); - return; // FIXME: what to do instead? - } - if (len == 0) { - /* start new packet */ - base = prev_offset = offset; - header.sequence_number = htons(impl->seq); - header.timestamp = htonl(impl->ts_offset + timestamp + base); - - memcpy(&impl->buffer[len], event, size); - len += size; - } else { - delta = offset - prev_offset; - prev_offset = offset; - len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, event, size); + memcpy(&impl->buffer[len], event, size); + len += size; + } else { + delta = offset - prev_offset; + prev_offset = offset; + len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, event, size); + } } } if (len > 0) { diff --git a/src/modules/module-vban/midi.c b/src/modules/module-vban/midi.c index 4fbd39532..f460bd274 100644 --- a/src/modules/module-vban/midi.c +++ b/src/modules/module-vban/midi.c @@ -239,27 +239,32 @@ static void vban_midi_flush_packets(struct impl *impl, while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) { int size; uint8_t event[16]; + uint64_t state = 0; + size_t c_size = c.value.size; if (c.type != SPA_CONTROL_UMP) continue; - size = spa_ump_to_midi(c_body, c.value.size, event, sizeof(event)); - if (size <= 0) - continue; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t**)&c_body, + &c_size, event, sizeof(event), &state); + if (size <= 0) + break; - if (len == 0) { - /* start new packet */ - header.n_frames++; - } else if (len + size > impl->mtu) { - /* flush packet when we have one and when it's too large */ - iov[1].iov_len = len; + if (len == 0) { + /* start new packet */ + header.n_frames++; + } else if (len + size > impl->mtu) { + /* flush packet when we have one and when it's too large */ + iov[1].iov_len = len; - pw_log_debug("sending %d", len); - vban_stream_emit_send_packet(impl, iov, 2); - len = 0; + pw_log_debug("sending %d", len); + vban_stream_emit_send_packet(impl, iov, 2); + len = 0; + } + memcpy(&impl->buffer[len], event, size); + len += size; } - memcpy(&impl->buffer[len], event, size); - len += size; } if (len > 0) { /* flush last packet */ diff --git a/src/tools/midifile.c b/src/tools/midifile.c index d7503da92..0aee51638 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -496,41 +496,46 @@ int midi_file_write_event(struct midi_file *mf, const struct midi_event *event) { struct midi_track *tr; uint32_t tick; - void *data; + void *data, *ev_data; size_t size; - int res; + int res, ev_size; uint8_t ev[32]; + uint64_t state = 0; spa_return_val_if_fail(event != NULL, -EINVAL); spa_return_val_if_fail(mf != NULL, -EINVAL); spa_return_val_if_fail(event->track == 0, -EINVAL); spa_return_val_if_fail(event->size > 1, -EINVAL); - switch (event->type) { - case MIDI_EVENT_TYPE_MIDI1: - data = event->data; - size = event->size; - break; - case MIDI_EVENT_TYPE_UMP: - data = ev; - size = spa_ump_to_midi((uint32_t*)event->data, event->size, ev, sizeof(ev)); - if (size == 0) - return 0; - break; - default: - return -EINVAL; - } + data = event->data; + size = event->size; tr = &mf->tracks[event->track]; - tick = (uint32_t)(event->sec * (1000000.0 * mf->info.division) / (double)mf->tempo); - CHECK_RES(write_varlen(mf, tr, tick - tr->tick)); - tr->tick = tick; + while (size > 0) { + switch (event->type) { + case MIDI_EVENT_TYPE_MIDI1: + ev_data = data; + ev_size = size; + size = 0; + break; + case MIDI_EVENT_TYPE_UMP: + ev_size = spa_ump_to_midi((const uint32_t**)&data, &size, ev, sizeof(ev), &state); + if (ev_size <= 0) + return ev_size; + ev_data = ev; + break; + default: + return -EINVAL; + } - CHECK_RES(write_n(mf->file, data, size)); - tr->size += size; + CHECK_RES(write_varlen(mf, tr, tick - tr->tick)); + tr->tick = tick; + CHECK_RES(write_n(mf->file, ev_data, ev_size)); + tr->size += ev_size; + } return 0; } diff --git a/test/test-spa-control.c b/test/test-spa-control.c index d493b2934..97589efd7 100644 --- a/test/test-spa-control.c +++ b/test/test-spa-control.c @@ -123,6 +123,7 @@ static int do_ump_to_midi_test(char *ump, char *midi) size_t m_size, u_size, m_offs = 0; uint8_t *m_data = alloca(strlen(midi) / 2); uint32_t *u_data = alloca(strlen(ump) / 2); + uint64_t state = 0; u_size = parse_ump(ump, u_data, sizeof(u_data)); m_size = parse_midi(midi, m_data, sizeof(m_data)); @@ -133,8 +134,9 @@ static int do_ump_to_midi_test(char *ump, char *midi) while (u_size > 0) { uint8_t midi[32]; fprintf(stdout, "%zd %08x\n", u_size, *u_data); - int midi_size = spa_ump_to_midi(u_data, u_size, - midi, sizeof(midi)); + + int midi_size = spa_ump_to_midi((const uint32_t**)&u_data, &u_size, + midi, sizeof(midi), &state); if (midi_size <= 0) return midi_size; @@ -145,8 +147,6 @@ static int do_ump_to_midi_test(char *ump, char *midi) fprintf(stdout, "%08x %08x\n", m_data[m_offs], midi[i]); spa_assert(m_data[m_offs++] == midi[i]); } - u_size -= spa_ump_message_size(*u_data >> 28) * 4; - u_data += spa_ump_message_size(*u_data >> 28); } return 0; } @@ -160,6 +160,11 @@ PWTEST(control_ump_to_midi) spa_assert(do_ump_to_midi_test("30160102 03040506 30260708 09101112 30311300 00000000", "f0 01 02 03 04 05 06 07 08 09 10 11 12 13 f7") >= 0); + + spa_assert(do_ump_to_midi_test("40cf0000 11000000", "cf 11") >= 0); + + spa_assert(do_ump_to_midi_test("40cf0001 11002233", "bf 00 22 bf 20 33 cf 11") >= 0); + return PWTEST_PASS; } From 00bb29de0faf9aebec4427ab2861ba067d6da1bc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Aug 2025 17:47:29 +0200 Subject: [PATCH 0757/1014] tools: add -M option to pw-cat and friends It forces conversion to or from UMP. By default, the events will be converted to UMP before injecting them into the graph. --- src/tools/pw-cat.c | 76 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index db96b8c2e..bb70c321d 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -142,6 +142,10 @@ struct data { struct { struct midi_file *file; struct midi_file_info info; +#define MIDI_FORCE_NONE 0 +#define MIDI_FORCE_UMP 1 +#define MIDI_FORCE_MIDI1 2 + int force_type; } midi; struct { struct dsf_file *file; @@ -1030,6 +1034,7 @@ static const struct option long_options[] = { { "volume", required_argument, NULL, OPT_VOLUME }, { "quality", required_argument, NULL, 'q' }, { "raw", no_argument, NULL, 'a' }, + { "force-midi", required_argument, NULL, 'M' }, { NULL, 0, NULL, 0 } }; @@ -1075,6 +1080,7 @@ static void show_usage(const char *name, bool is_error) " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default %d)\n" " -a, --raw RAW mode\n" + " -M, --force-midi Force midi format, one of \"midi\" or \"ump\", (default ump)\n" "\n"), DEFAULT_RATE, DEFAULT_CHANNELS, @@ -1116,6 +1122,8 @@ static int midi_play(struct data *d, void *src, unsigned int n_frames, bool *nul while (1) { uint32_t frame; struct midi_event ev; + uint64_t state = 0; + size_t size; res = midi_file_next_time(d->midi.file, &ev.sec); if (res <= 0) { @@ -1137,30 +1145,46 @@ static int midi_play(struct data *d, void *src, unsigned int n_frames, bool *nul if (d->verbose) midi_file_dump_event(stderr, &ev); - if (ev.type == MIDI_EVENT_TYPE_MIDI1) { - size_t size; - uint8_t *data; - uint64_t state = 0; + size = ev.size; - if (ev.data[0] == 0xff) + if (ev.type == MIDI_EVENT_TYPE_MIDI1) { + + if (size < 1 || ev.data[0] == 0xff) continue; - data = ev.data; - size = ev.size; + if (d->midi.force_type == MIDI_FORCE_UMP) { + uint8_t *data = ev.data; + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; - while (size > 0) { - uint32_t ump[4]; - int ump_size = spa_ump_from_midi(&data, &size, - ump, sizeof(ump), 0, &state); - if (ump_size <= 0) - break; - - spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); - spa_pod_builder_bytes(&b, ump, ump_size); + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } + } else { + spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, ev.data, ev.size); } } else if (ev.type == MIDI_EVENT_TYPE_UMP) { - spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); - spa_pod_builder_bytes(&b, ev.data, ev.size); + if (d->midi.force_type == MIDI_FORCE_MIDI1) { + const uint32_t *data = (const uint32_t*)ev.data; + while (size > 0) { + uint8_t ev[16]; + int ev_size = spa_ump_to_midi(&data, &size, + ev, sizeof(ev), &state); + if (ev_size <= 0) + break; + + spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, ev, ev_size); + } + } else { + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ev.data, ev.size); + } } else continue; @@ -1811,6 +1835,7 @@ int main(int argc, char *argv[]) /* negative means no volume adjustment */ data.volume = -1.0; data.quality = -1; + data.midi.force_type = MIDI_FORCE_UMP; data.props = pw_properties_new( PW_KEY_APP_NAME, prog, PW_KEY_NODE_NAME, prog, @@ -1822,9 +1847,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:a", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:", long_options, NULL)) != -1) { #endif switch (c) { @@ -1883,6 +1908,17 @@ int main(int argc, char *argv[]) data.raw = true; break; + case 'M': + if (spa_streq(optarg, "midi")) + data.midi.force_type = MIDI_FORCE_MIDI1; + else if (spa_streq(optarg, "ump")) + data.midi.force_type = MIDI_FORCE_UMP; + else { + fprintf(stderr, "error: bad force-midi %s\n", optarg); + goto error_usage; + } + break; + case OPT_MEDIA_TYPE: data.media_type = optarg; break; From b7412169f5e8f30b201812ece6a258c20ed64660 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 20 Aug 2025 10:00:07 +0200 Subject: [PATCH 0758/1014] tools: Use the same -M option as pw-cat to force midi --- src/tools/pw-mididump.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index 0b04f7daf..2a17ef05c 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -175,7 +175,7 @@ static void show_help(const char *name, bool error) " -h, --help Show this help\n" " --version Show version\n" " -r, --remote Remote daemon name\n" - " -m, --midi1 Dump as MIDI 1.0\n", + " -M, --force-midi Force midi format, one of \"midi\" or \"ump\",(default ump)\n", name); } @@ -187,7 +187,7 @@ int main(int argc, char *argv[]) { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "remote", required_argument, NULL, 'r' }, - { "midi1", no_argument, NULL, 'm' }, + { "force-midi", required_argument, NULL, 'M' }, { NULL, 0, NULL, 0} }; @@ -196,7 +196,7 @@ int main(int argc, char *argv[]) setlinebuf(stdout); - while ((c = getopt_long(argc, argv, "hVr:m", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:M:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); @@ -212,9 +212,19 @@ int main(int argc, char *argv[]) case 'r': data.opt_remote = optarg; break; - case 'm': - data.opt_midi1 = true; + + case 'M': + if (spa_streq(optarg, "midi")) + data.opt_midi1 = true; + else if (spa_streq(optarg, "ump")) + data.opt_midi1 = false; + else { + fprintf(stderr, "error: bad force-midi %s\n", optarg); + show_help(argv[0], true); + return 0; + } break; + default: show_help(argv[0], true); return -1; From f60638a3aaad1be780afe37bd72814366e2c2707 Mon Sep 17 00:00:00 2001 From: Dimitris Papaioannou Date: Tue, 19 Aug 2025 20:10:02 +0300 Subject: [PATCH 0759/1014] spa: Make spa_pod_parser_pop return int again This fixes an API break that couldn't let the Rust bindings build. See https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/e317edcfb9da360a01aeb706923f0041e960ab6f#caedda3d4b09299412c1c1ea2f100ac54cfc7b6d_128_190 --- spa/include/spa/pod/parser.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 340fbd699..6598c3060 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -192,11 +192,12 @@ SPA_API_POD_PARSER void spa_pod_parser_unpush(struct spa_pod_parser *parser, parser->state.frame = frame->parent; } -SPA_API_POD_PARSER void spa_pod_parser_pop(struct spa_pod_parser *parser, +SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { spa_pod_parser_unpush(parser, frame); spa_pod_parser_advance(parser, &frame->pod); + return 0; } SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value) From b801527f882fa2572fa3c2bc6c1e1c0504b3237d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 20 Aug 2025 11:39:30 +0200 Subject: [PATCH 0760/1014] jack: emit port_rename callbacks When the port is renamed, we queue a PORT_RENAME callback with an arg1 as 1. Before emitting the event we check the registered state of the port but we want to suppress the event if the port was *not* registered, ie. when the registered state != 1 (arg1). --- pipewire-jack/src/pipewire-jack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index c8b4a149d..92d777f7e 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1095,7 +1095,7 @@ static void on_notify_event(void *data, uint64_t count) c->portregistration_arg); break; case NOTIFY_TYPE_PORT_RENAME: - if (o->registered == notify->arg1) + if (o->registered != notify->arg1) break; pw_log_debug("%p: port rename %u %s->%s", c, o->serial, o->port.old_name, o->port.name); From 07f36ab580ff0265456b719b6d9f67a5941e1ebf Mon Sep 17 00:00:00 2001 From: filmsi Date: Wed, 20 Aug 2025 14:03:35 +0000 Subject: [PATCH 0761/1014] Updated Slovenian (sl.po) --- po/sl.po | 121 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 56 deletions(-) diff --git a/po/sl.po b/po/sl.po index de3f94528..bb0c0c53e 100644 --- a/po/sl.po +++ b/po/sl.po @@ -9,8 +9,8 @@ msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2025-01-09 15:25+0000\n" -"PO-Revision-Date: 2025-01-23 09:23+0100\n" +"POT-Creation-Date: 2025-08-20 03:34+0000\n" +"PO-Revision-Date: 2025-08-20 16:01+0200\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" "Language: sl\n" @@ -37,13 +37,13 @@ msgstr "" " --version Pokaži različico\n" " -c, --config Naloži prilagoditev config (privzeto " "%s)\n" -" -P —properties Določi lastnosti konteksta\n" +" -P --properties Določi lastnosti konteksta\n" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "Medijski sistem PipeWire" -#: src/daemon/pipewire.desktop.in:5 +#: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Zaženite medijski sistem PipeWire" @@ -76,7 +76,7 @@ msgstr "%s na %s@%s" msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:973 +#: src/tools/pw-cat.c:1049 #, c-format msgid "" "%s [options] [|-]\n" @@ -92,7 +92,7 @@ msgstr "" "\n" "\n" -#: src/tools/pw-cat.c:980 +#: src/tools/pw-cat.c:1056 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -129,7 +129,7 @@ msgstr "" " -P --properties Nastavi lastnosti vozlišča\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:1074 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -147,14 +147,16 @@ msgid "" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" +" -M, --force-midi Force midi format, one of \"midi\" " +"or \"ump\", (default ump)\n" "\n" msgstr "" -" --rate Mera vzorčenja (zahtevano za rec) " +" --rate Mera vzorčenja (zaht. za rec) " +"(privzeto %u)\n" +" --channels Število kanalov (zaht. za snemanje) " "(privzeto %u)\n" -" --channels Število kanalov (zahteva za " -"snemanje) (privzeto %u)\n" " --channel-map Preslikava kanalov\n" -" Ena izmed: \"Stereo\", " +" Ena izmed: \"stereo\", " "\"surround-51\",... ali\n" " seznam imen kanalov, ločen z " "vejico: npr. \"FL,FR\"\n" @@ -164,16 +166,18 @@ msgstr "" " -q --quality Kakovost prevzorčenja (0 - 15) " "(privzeto %d)\n" " -a, --raw neobdelan način (RAW)\n" -"\n" +" -M, --force-midi Vsili zapis midi, eden izmed \"midi" +"\" ali \"ump\" (privzeto ump)\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:1093 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" +" -s, --sysex SysEx mode\n" "\n" msgstr "" " -p, --playback Način predvajanja\n" @@ -181,9 +185,10 @@ msgstr "" " -m, --midi Midi način\n" " -d, --dsd Način DSD\n" " -o, --encoded Kodiran način\n" +" -s, --sysex Način SysEx\n" "\n" -#: src/tools/pw-cli.c:2306 +#: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" @@ -203,15 +208,20 @@ msgstr "" " -m, --monitor Spremljaj dejavnosti\n" "\n" -#: spa/plugins/alsa/acp/acp.c:347 +#: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "Profesionalni zvok" -#: spa/plugins/alsa/acp/acp.c:507 spa/plugins/alsa/acp/alsa-mixer.c:4635 -#: spa/plugins/bluez5/bluez5-device.c:1795 +#: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Izklopljeno" +#: spa/plugins/alsa/acp/acp.c:603 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [napaka ALSA UCM]" + #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Vhod" @@ -235,7 +245,7 @@ msgstr "Linijski vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2139 +#: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "Mikrofon" @@ -301,12 +311,15 @@ msgid "No Bass Boost" msgstr "Brez ojačitve nizkih tonov" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:2145 +#: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "Zvočnik" +#. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 +#: spa/plugins/bluez5/bluez5-device.c:2384 +#: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "Slušalke" @@ -416,7 +429,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4629 -#: spa/plugins/bluez5/bluez5-device.c:2127 +#: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "Slušalka" @@ -655,112 +668,108 @@ msgstr[3] "" msgid "(invalid)" msgstr "(neveljavno)" -#: spa/plugins/alsa/acp/compat.c:193 +#: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Vgrajen zvok" -#: spa/plugins/alsa/acp/compat.c:198 +#: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1806 +#: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1834 +#: spa/plugins/bluez5/bluez5-device.c:2014 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)" -#: spa/plugins/bluez5/bluez5-device.c:1874 +#: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1877 +#: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1885 +#: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1887 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1937 +#: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1942 +#: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Vhod visoke ločljivosti (vir BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1946 +#: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1955 +#: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1959 +#: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "Vhod visoke ločljivosti (vir BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1962 +#: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)" -#: spa/plugins/bluez5/bluez5-device.c:2008 +#: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Naglavna enota slušalk (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2128 -#: spa/plugins/bluez5/bluez5-device.c:2133 -#: spa/plugins/bluez5/bluez5-device.c:2140 -#: spa/plugins/bluez5/bluez5-device.c:2146 -#: spa/plugins/bluez5/bluez5-device.c:2152 -#: spa/plugins/bluez5/bluez5-device.c:2158 -#: spa/plugins/bluez5/bluez5-device.c:2164 -#: spa/plugins/bluez5/bluez5-device.c:2170 -#: spa/plugins/bluez5/bluez5-device.c:2176 +#: spa/plugins/bluez5/bluez5-device.c:2361 +#: spa/plugins/bluez5/bluez5-device.c:2366 +#: spa/plugins/bluez5/bluez5-device.c:2373 +#: spa/plugins/bluez5/bluez5-device.c:2379 +#: spa/plugins/bluez5/bluez5-device.c:2385 +#: spa/plugins/bluez5/bluez5-device.c:2391 +#: spa/plugins/bluez5/bluez5-device.c:2397 +#: spa/plugins/bluez5/bluez5-device.c:2403 +#: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "Prostoročno telefoniranje" -#: spa/plugins/bluez5/bluez5-device.c:2134 +#: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "Prostoročno telefoniranje (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2151 -msgid "Headphone" -msgstr "Slušalke" - -#: spa/plugins/bluez5/bluez5-device.c:2157 +#: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "Prenosna naprava" -#: spa/plugins/bluez5/bluez5-device.c:2163 +#: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "Avtomobil" -#: spa/plugins/bluez5/bluez5-device.c:2169 +#: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2175 +#: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2182 +#: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2183 +#: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" From 882737b077e1bd3757595de92716cc8a8ce53efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 20 Aug 2025 23:12:13 +0200 Subject: [PATCH 0762/1014] pipewire: module-link-factory: cancel async work in link's destroy event When a link enters the "ERROR" state, it is scheduled for destruction in `module-link-factory.c:link_state_changed()`, which queues `destroy_link()` to be executed on the context's work queue. However, if the link is destroyed by means of `pw_impl_link_destroy()` directly after that, then `link_destroy()` unregisters the associated `pw_global`'s event hook, resulting in `global_destroy()` not being called when `pw_impl_link_destroy()` proceeds to call `pw_global_destroy()` some time later. This causes the scheduled async work to not be cancelled. When it runs later, it will trigger a use-after-free since the `link_data` object is directly tied to the `pw_impl_link` object. For example, if the link is destroyed when the client disconnects: ==259313==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ce753028af0 at pc 0x7f475354a565 bp 0x7ffd71501930 sp 0x7ffd71501920 READ of size 8 at 0x7ce753028af0 thread T0 #0 0x7f475354a564 in destroy_link ../src/modules/module-link-factory.c:253 #1 0x7f475575a234 in process_work_queue ../src/pipewire/work-queue.c:67 #2 0x7b47504e7f24 in source_event_func ../spa/plugins/support/loop.c:1011 [...] 0x7ce753028af0 is located 1136 bytes inside of 1208-byte region [0x7ce753028680,0x7ce753028b38) freed by thread T0 here: #0 0x7f475631f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x7f4755594a44 in pw_impl_link_destroy ../src/pipewire/impl-link.c:1742 #2 0x7f475569dc11 in do_destroy_link ../src/pipewire/impl-port.c:1386 #3 0x7f47556a428b in pw_impl_port_for_each_link ../src/pipewire/impl-port.c:1673 #4 0x7f475569dc3e in pw_impl_port_unlink ../src/pipewire/impl-port.c:1392 #5 0x7f47556a02d8 in pw_impl_port_destroy ../src/pipewire/impl-port.c:1453 #6 0x7f4755634f79 in pw_impl_node_destroy ../src/pipewire/impl-node.c:2447 #7 0x7b474f722ba8 in client_node_resource_destroy ../src/modules/module-client-node/client-node.c:1253 #8 0x7f47556d7c6c in pw_resource_destroy ../src/pipewire/resource.c:325 #9 0x7f475545f07d in destroy_resource ../src/pipewire/impl-client.c:627 #10 0x7f47554550cd in pw_map_for_each ../src/pipewire/map.h:222 #11 0x7f4755460aa4 in pw_impl_client_destroy ../src/pipewire/impl-client.c:681 #12 0x7b474fb0658b in handle_client_error ../src/modules/module-protocol-native.c:471 [...] Fix this by cancelling the work queue item in `link_destroy()`, which should always run, regardless of the ordering of events. Fixes #4691 --- src/modules/module-link-factory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index 039ed16ff..8daa3b11c 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -193,8 +193,7 @@ static const struct pw_resource_events resource_events = { static void global_destroy(void *data) { struct link_data *ld = data; - struct factory_data *d = ld->data; - pw_work_queue_cancel(d->work, ld, SPA_ID_INVALID); + spa_hook_remove(&ld->global_listener); ld->global = NULL; } @@ -213,6 +212,7 @@ static void link_destroy(void *data) spa_hook_remove(&ld->global_listener); if (ld->resource) spa_hook_remove(&ld->resource_listener); + pw_work_queue_cancel(ld->data->work, ld, SPA_ID_INVALID); } static void link_initialized(void *data) From 2958692eca34ed2deb015ed56eff845c9d6bd523 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 22 Aug 2025 21:57:15 -0400 Subject: [PATCH 0763/1014] doc: Fix some trailing whitespace in protocol.dox --- doc/dox/internals/protocol.dox | 54 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/doc/dox/internals/protocol.dox b/doc/dox/internals/protocol.dox index e2ec1c042..37a99c8f8 100644 --- a/doc/dox/internals/protocol.dox +++ b/doc/dox/internals/protocol.dox @@ -179,7 +179,7 @@ A client requests to bind to the registry object and list the available objects on the server. Like with all bindings, first the client allocates a new proxy id and puts this -as the new_id field. Methods and Events can then be sent and received on the +as the new_id field. Methods and Events can then be sent and received on the new_id (in the message Id field). ``` @@ -237,7 +237,7 @@ Core::Done event. Create a new object from a factory of a certain type. The client allocates a new_id for the proxy. The server will allocate a new -resource with the same new_id and from then on, Methods and Events will be +resource with the same new_id and from then on, Methods and Events will be exchanged between the new object of the given type. ``` @@ -290,7 +290,7 @@ server. String: version String: name Long: change_mask - Struct( + Struct( Int: n_items (String: key String: value)* @@ -434,7 +434,7 @@ registry. Struct( Int: id Int: global_id - Struct( + Struct( Int: n_items (String: key String: value)* @@ -498,7 +498,7 @@ Notify a client about a new global object. Int: permissions String: type Int: version - Struct( + Struct( Int: n_items (String: key String: value)* @@ -556,7 +556,7 @@ Is used to update the properties of a client. ``` Struct( - Struct( + Struct( Int: n_items (String: key String: value)* @@ -610,7 +610,7 @@ when the client info is updated later. Struct( Int: id Long: change_mask - Struct( + Struct( Int: n_items (String: key String: value)* @@ -628,7 +628,7 @@ Emitted as the reply of the GetPermissions method. ``` Struct( Int: index - Struct( + Struct( Int: n_permissions (Int: id Int: permission)* @@ -705,15 +705,15 @@ The info event is emitted when binding or when the device information changed. Struct( Int: id Long: change_mask - Struct( + Struct( Int: n_items ( String: key String: value )* ): props - Struct( + Struct( Int: n_params ( Int: id - Int: flags )* + Int: flags )* ): param_info ) ``` @@ -741,7 +741,7 @@ Emitted as a result of EnumParams or SubscribeParams. ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. -- id: the param id that is reported, see enum spa_param_type +- id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter - param: the parameter. The object type depends on the id @@ -768,7 +768,7 @@ Info is emitted when binding to the factory global or when the information chang String: type Int: version Long: change_mask - Struct( + Struct( Int: n_items ( String: key String: value )* @@ -808,7 +808,7 @@ Info is emitted when binding to the link global or when the information changed. Int: state String: error Pod: format - Struct( + Struct( Int: n_items ( String: key String: value )* @@ -850,7 +850,7 @@ Info is emitted when binding to the module global or when the information change String: filename String: args Long: change_mask - Struct( + Struct( Int: n_items ( String: key String: value )* @@ -926,7 +926,7 @@ Send a Command to the node. Pod: command ) ``` -- command: the command to send. See enum spa_node_command +- command: the command to send. See enum spa_node_command ## Node events @@ -944,15 +944,15 @@ The info event is emitted when binding or when the node information changed. Int: n_output_ports Id: state String: error - Struct( + Struct( Int: n_items ( String: key String: value )* ): props - Struct( + Struct( Int: n_params ( Int: id - Int: flags )* + Int: flags )* ): param_info ) ``` @@ -987,7 +987,7 @@ Emitted as a result of EnumParams or SubscribeParams. ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. -- id: the param id that is reported, see enum spa_param_type +- id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter - param: the parameter. The object type depends on the id @@ -1040,15 +1040,15 @@ The info event is emitted when binding or when the port information changed. Int: id Int: direction Long: change_mask - Struct( + Struct( Int: n_items ( String: key String: value )* ): props - Struct( + Struct( Int: n_params ( Int: id - Int: flags )* + Int: flags )* ): param_info ) ``` @@ -1077,7 +1077,7 @@ Emitted as a result of EnumParams or SubscribeParams. ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. -- id: the param id that is reported, see enum spa_param_type +- id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter @@ -1404,7 +1404,7 @@ Add a new port to the node Struct( Int: direction Int: port_id - Struct( + Struct( Int: n_items ( String: key String: value )* @@ -1557,7 +1557,7 @@ ports of a node. Int: port_id Int: mix_id Int: peer_id - Struct( + Struct( Int: n_items ( String: key String: value )* @@ -1641,7 +1641,7 @@ The profiler has no methods Pod: object ) ``` -- object: a SPA_TYPE_OBJECT_Profiler object. See enum spa_profiler +- object: a SPA_TYPE_OBJECT_Profiler object. See enum spa_profiler # Footer {#native-protocol-footer} From d991be6a26a9e5c1644829b59758b0fea5305092 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Fri, 22 Aug 2025 21:57:57 -0400 Subject: [PATCH 0764/1014] doc: Fix Device::Info id field type in protocol documentation --- doc/dox/internals/protocol.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/dox/internals/protocol.dox b/doc/dox/internals/protocol.dox index 37a99c8f8..7a62247a8 100644 --- a/doc/dox/internals/protocol.dox +++ b/doc/dox/internals/protocol.dox @@ -712,7 +712,7 @@ The info event is emitted when binding or when the device information changed. ): props Struct( Int: n_params - ( Int: id + ( Id: id Int: flags )* ): param_info ) From 95497f8777e1b62638736197634a8e312fe3c2b0 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sun, 24 Aug 2025 00:11:02 +0200 Subject: [PATCH 0765/1014] gst: pool: Remove last g_slice remnants It has long been deprecated and just makes the code harder to read. --- src/gst/gstpipewirepool.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 0b4f30309..2e4da6e65 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -57,7 +57,7 @@ pool_data_destroy (gpointer user_data) GstPipeWirePoolData *data = user_data; gst_object_unref (data->pool); - g_slice_free (GstPipeWirePoolData, data); + g_free (data); } void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) @@ -73,7 +73,7 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) GST_DEBUG_OBJECT (pool, "wrap buffer, datas:%d", b->buffer->n_datas); - data = g_slice_new (GstPipeWirePoolData); + data = g_new0 (GstPipeWirePoolData, 1); buf = gst_buffer_new (); From 5f3ae4376ebdc1d778de389711103395b9d34a10 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Sat, 23 Aug 2025 14:58:18 +0200 Subject: [PATCH 0766/1014] gst: pool: Keep dmabufs mapped Downstream elements accessing dmabufs from the CPU currently need to map and unmap buffers on every frame. While the kernel's page cache avoids most overhead, this still requires a round-trip through the kernel and possibly non-negligible work in `dma_buf_mmap()`. Keeping the buffers mapped avoids that without causing additional syncronization work, as the later should only happen when `DMA_BUF_IOCTL_SYNC` is triggered in `gst_dmabuf_mem_map()`. A common scenario where this matters is clients using cameras. The downstream elements in question may not be aware of dmabufs - e.g. `videoconvert` - or fail to import the dmabuf and fall back to import from memory - e.g. `glupload`. Notes: - GstShmAllocator implicitly does this already. - We could also do this in the MemFd case, however I'm less convinced about the trade-offs. --- src/gst/gstpipewirepool.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 2e4da6e65..5581c64ac 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -158,9 +158,28 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) gst_memory_resize (gmem, d->mapoffset, d->maxsize); } else if(d->type == SPA_DATA_DmaBuf) { + GstMapInfo info = { 0 }; + GstFdMemoryFlags fd_flags = GST_FD_MEMORY_FLAG_NONE; + + if (d->flags & SPA_DATA_FLAG_MAPPABLE && d->flags & SPA_DATA_FLAG_READABLE) + fd_flags |= GST_FD_MEMORY_FLAG_KEEP_MAPPED; + gmem = gst_fd_allocator_alloc (pool->dmabuf_allocator, dup(d->fd), - d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE); + d->mapoffset + d->maxsize, fd_flags); gst_memory_resize (gmem, d->mapoffset, d->maxsize); + + if (fd_flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED) { + GstMapFlags map_flags = GST_MAP_READ; + + if (d->flags & SPA_DATA_FLAG_WRITABLE) + map_flags |= GST_MAP_WRITE; + + if (gst_memory_map (gmem, &info, map_flags)) { + gst_memory_unmap (gmem, &info); + } else { + GST_ERROR_OBJECT (pool, "mmaping buffer failed"); + } + } } else if (d->type == SPA_DATA_MemPtr) { gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, 0, From 2f22c1d595b267ef2f3ace77f1a7863a534d0dd2 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 6 Aug 2025 13:39:07 +0200 Subject: [PATCH 0767/1014] module-rtp: Stop any ongoing timer when starting stream --- src/modules/module-rtp/audio.c | 6 ++++++ src/modules/module-rtp/stream.c | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 68e317f1e..99be8ee5e 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -450,6 +450,11 @@ done: } } +static void rtp_audio_stop_timer(struct impl *impl) +{ + set_timer(impl, 0, 0); +} + static void rtp_audio_flush_timeout(struct impl *impl, uint64_t expirations) { if (expirations > 1) @@ -785,6 +790,7 @@ static int rtp_audio_init(struct impl *impl, struct pw_core *core, enum spa_dire impl->stream_events.process = rtp_audio_process_playback; impl->receive_rtp = rtp_audio_receive; + impl->stop_timer = rtp_audio_stop_timer; impl->flush_timeout = rtp_audio_flush_timeout; setup_ptp_sender(impl, core, direction, ptp_driver); diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 0ec4388e7..426a2d9ca 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -111,6 +111,7 @@ struct impl { * requires filling the ring buffer with something other than nullbytes * (this can happen with DSD for example). */ void (*reset_ringbuffer)(struct impl *impl); + void (*stop_timer)(struct impl *impl); void (*flush_timeout)(struct impl *impl, uint64_t expirations); void (*deinit)(struct impl *impl, enum spa_direction direction); @@ -195,6 +196,14 @@ static int stream_start(struct impl *impl) impl->first = true; + /* Stop the timer now (if the timer is used). Any lingering timer + * will try to send data that is stale at this point, especially + * after the ring buffer contents get reset. Worse, the timer might + * emit a "stopped" state change after a "started" state change + * is emitted here, causing undefined behavior. */ + if (impl->stop_timer) + impl->stop_timer(impl); + impl->reset_ringbuffer(impl); rtp_stream_emit_state_changed(impl, true, NULL); From 3476e777141077029fb703924a7346609d707516 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 21 Aug 2025 13:30:11 +0200 Subject: [PATCH 0768/1014] module-rtp: Replace state_changed callbacks The state_changed callbacks fulfill multiple roles, which is both a problem regarding separation of concerns and regarding code clarity. De facto, these callbacks cover error reporting, opening connections, and closing connection, all in one, depending on a state that is arguably an internal stream detail. The code in these callbacks tie these internal states to assumptions that opening/closing callbacks is directly tied to specific state changes in a common way, which is not always true. For example, stopping the stream may not _actually_ stop it if a background send timer is still running. The notion of a "state_changed" callback is also problematic because the pw_streams that are used in rtp-sink and rtp-source also have a callback for state changes, causing confusion. Solve this by replacing state_changed with three new callbacks: 1. report_error : Used for reporting nonrecoverable errors to the caller. Note that currently, no one does such error reporting, but the feature does exist, so this callback is introduced to preserve said feature. 2. open_connection : Used for opening a connection. Its optional return value informs about success or failure. 3. close_connection : Used for opening a connection. Its optional return value informs about success or failure. Importantly, these callbacks do not export any internal stream state. This improves encapsulation, and also makes it possible to invoke these callbacks in situations that may not neatly map to a state change. One example could be to close the connection as part of a stream_start call to close any connection(s) left over from a previous run. (Followup commits will in fact introduce such measures.) --- src/modules/module-raop-sink.c | 6 +-- src/modules/module-rtp-session.c | 54 ++++++++++++++--------- src/modules/module-rtp-sink.c | 57 +++++++++++++++++------- src/modules/module-rtp-source.c | 74 ++++++++++++++++++-------------- src/modules/module-rtp/stream.c | 31 ++++++++++--- src/modules/module-rtp/stream.h | 12 +++++- 6 files changed, 156 insertions(+), 78 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 8cfcf06db..1d9a7963b 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1545,14 +1545,12 @@ static void stream_destroy(void *d) impl->stream = NULL; } -static void stream_state_changed(void *data, bool started, const char *error) +static void stream_report_error(void *data, const char *error) { struct impl *impl = data; - if (error) { pw_log_error("stream error: %s", error); pw_impl_module_schedule_destroy(impl->module); - return; } } @@ -1726,7 +1724,7 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * static const struct rtp_stream_events stream_events = { RTP_VERSION_STREAM_EVENTS, .destroy = stream_destroy, - .state_changed = stream_state_changed, + .report_error = stream_report_error, .param_changed = stream_param_changed, .send_packet = stream_send_packet }; diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index 91809e6ae..4e9919f93 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -478,18 +478,23 @@ static void send_destroy(void *data) { } -static void send_state_changed(void *data, bool started, const char *error) +static void send_open_connection(void *data, int *result) { struct session *sess = data; + sess->sending = true; + if (result) + *result = 1; + session_establish(sess); +} - if (started) { - sess->sending = true; - session_establish(sess); - } else { - sess->sending = false; - if (!sess->receiving) - session_stop(sess); - } +static void send_close_connection(void *data, int *result) +{ + struct session *sess = data; + sess->sending = false; + if (result) + *result = 1; + if (!sess->receiving) + session_stop(sess); } static void send_send_packet(void *data, struct iovec *iov, size_t iovlen) @@ -516,17 +521,24 @@ static void send_send_packet(void *data, struct iovec *iov, size_t iovlen) static void recv_destroy(void *data) { } -static void recv_state_changed(void *data, bool started, const char *error) + +static void recv_open_connection(void *data, int *result) { struct session *sess = data; - if (started) { - sess->receiving = true; - session_establish(sess); - } else { - sess->receiving = false; - if (!sess->sending) - session_stop(sess); - } + sess->receiving = true; + if (result) + *result = 1; + session_establish(sess); +} + +static void recv_close_connection(void *data, int *result) +{ + struct session *sess = data; + sess->receiving = false; + if (result) + *result = 1; + if (!sess->sending) + session_stop(sess); } static void recv_send_feedback(void *data, uint32_t seqnum) @@ -560,14 +572,16 @@ static void recv_send_feedback(void *data, uint32_t seqnum) static const struct rtp_stream_events send_stream_events = { RTP_VERSION_STREAM_EVENTS, .destroy = send_destroy, - .state_changed = send_state_changed, + .open_connection = send_open_connection, + .close_connection = send_close_connection, .send_packet = send_send_packet, }; static const struct rtp_stream_events recv_stream_events = { RTP_VERSION_STREAM_EVENTS, .destroy = recv_destroy, - .state_changed = recv_state_changed, + .open_connection = recv_open_connection, + .close_connection = recv_close_connection, .send_feedback = recv_send_feedback, }; diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index fb16d7793..cb21cb79a 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -310,28 +310,49 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen) } } -static void stream_state_changed(void *data, bool started, const char *error) +static void stream_report_error(void *data, const char *error) { struct impl *impl = data; - if (error) { pw_log_error("stream error: %s", error); pw_impl_module_schedule_destroy(impl->module); - } else if (started) { - int res; + } +} - if ((res = make_socket(&impl->src_addr, impl->src_len, - &impl->dst_addr, impl->dst_len, - impl->mcast_loop, impl->ttl, impl->dscp, - impl->ifname)) < 0) { - pw_log_error("can't make socket: %s", spa_strerror(res)); - rtp_stream_set_error(impl->stream, res, "Can't make socket"); - return; - } - impl->rtp_fd = res; - } else { +static void stream_open_connection(void *data, int *result) +{ + int res; + struct impl *impl = data; + + if ((res = make_socket(&impl->src_addr, impl->src_len, + &impl->dst_addr, impl->dst_len, + impl->mcast_loop, impl->ttl, impl->dscp, + impl->ifname)) < 0) { + pw_log_error("can't make socket: %s", spa_strerror(res)); + rtp_stream_set_error(impl->stream, res, "Can't make socket"); + if (result) + *result = res; + return; + } + + if (result) + *result = 1; + + impl->rtp_fd = res; +} + +static void stream_close_connection(void *data, int *result) +{ + struct impl *impl = data; + + if (impl->rtp_fd > 0) { + if (result) + *result = 1; close(impl->rtp_fd); impl->rtp_fd = -1; + } else { + if (result) + *result = 0; } } @@ -417,7 +438,9 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * static const struct rtp_stream_events stream_events = { RTP_VERSION_STREAM_EVENTS, .destroy = stream_destroy, - .state_changed = stream_state_changed, + .report_error = stream_report_error, + .open_connection = stream_open_connection, + .close_connection = stream_close_connection, .param_changed = stream_param_changed, .send_packet = stream_send_packet, }; @@ -442,8 +465,10 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->rtp_fd != -1) + if (impl->rtp_fd != -1) { + pw_log_info("closing socket with FD %d as part of shutdown", impl->rtp_fd); close(impl->rtp_fd); + } pw_properties_free(impl->stream_props); pw_properties_free(impl->props); diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 7bdaa1122..cf497e354 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -385,13 +385,13 @@ error: return res; } -static int stream_start(struct impl *impl); +static void stream_open_connection(void *data, int *result); -static void on_stream_start_retry_timer_event(void *data, uint64_t expirations) +static void on_open_connection_retry_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; - pw_log_debug("trying again to start RTP listener after previous attempt failed with ENODEV"); - stream_start(impl); + pw_log_debug("trying again to open connection after previous attempt failed with ENODEV"); + stream_open_connection(impl, NULL); } static void destroy_stream_start_retry_timer(struct impl *impl) @@ -402,12 +402,23 @@ static void destroy_stream_start_retry_timer(struct impl *impl) } } -static int stream_start(struct impl *impl) +static void stream_report_error(void *data, const char *error) { + struct impl *impl = data; + if (error) { + pw_log_error("stream error: %s", error); + pw_impl_module_schedule_destroy(impl->module); + } +} + +static void stream_open_connection(void *data, int *result) +{ + int res = 0; int fd; + struct impl *impl = data; if (impl->source != NULL) - return 0; + goto finish; pw_log_info("starting RTP listener"); @@ -431,7 +442,7 @@ static int stream_start(struct impl *impl) struct timespec value, interval; impl->stream_start_retry_timer = pw_loop_add_timer(impl->main_loop, - on_stream_start_retry_timer_event, impl); + on_open_connection_retry_timer_event, impl); /* Use a 1-second retry interval. The network interfaces * are likely to be up and running then. */ value.tv_sec = 1; @@ -445,14 +456,16 @@ static int stream_start(struct impl *impl) /* It is important to return 0 in this case. Otherwise, the nonzero return * value will later be propagated through the core as an error. */ - return 0; + res = 0; + goto finish; } else { pw_log_error("failed to create socket: %m"); /* If ENODEV was returned earlier, and the stream_start_retry_timer * was consequently created, but then a non-ENODEV error occurred, * the timer must be stopped and removed. */ destroy_stream_start_retry_timer(impl); - return -errno; + res = -errno; + goto finish; } } @@ -465,13 +478,27 @@ static int stream_start(struct impl *impl) if (impl->source == NULL) { pw_log_error("can't create io source: %m"); close(fd); - return -errno; + res = -errno; + goto finish; } - return 0; + +finish: + if (res != 0) { + pw_log_error("failed to start RTP stream: %s", spa_strerror(res)); + rtp_stream_set_error(impl->stream, res, "Can't start RTP stream"); + } + + if (result) + *result = res; } -static void stream_stop(struct impl *impl) +static void stream_close_connection(void *data, int *result) { + struct impl *impl = data; + + if (result) + *result = 0; + if (!impl->source) return; @@ -489,25 +516,6 @@ static void stream_destroy(void *d) impl->stream = NULL; } -static void stream_state_changed(void *data, bool started, const char *error) -{ - struct impl *impl = data; - int res; - - if (error) { - pw_log_error("stream error: %s", error); - pw_impl_module_schedule_destroy(impl->module); - } else if (started) { - if ((res = stream_start(impl)) < 0) { - pw_log_error("failed to start RTP stream: %s", spa_strerror(res)); - rtp_stream_set_error(impl->stream, res, "Can't start RTP stream"); - } - } else { - if (!impl->always_process && !impl->standby) - stream_stop(impl); - } -} - static void stream_props_changed(struct impl *impl, uint32_t id, const struct spa_pod *param) { struct spa_pod_object *obj = (struct spa_pod_object *)param; @@ -572,7 +580,9 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * static const struct rtp_stream_events stream_events = { RTP_VERSION_STREAM_EVENTS, .destroy = stream_destroy, - .state_changed = stream_state_changed, + .report_error = stream_report_error, + .open_connection = stream_open_connection, + .close_connection = stream_close_connection, .param_changed = stream_param_changed, }; diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 426a2d9ca..96c06e2ee 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -34,10 +34,17 @@ PW_LOG_TOPIC_EXTERN(mod_topic); #define BUFFER_SIZE2 (BUFFER_SIZE>>1) #define BUFFER_MASK2 (BUFFER_SIZE2-1) +/* IMPORTANT: When using calls that have return values, like + * rtp_stream_emit_open_connection, callers must set the variables + * that receive the return values to a default value, because in + * cases where the callback is not actually set, no call is made, + * and thus, uninitialized return variables remain uninitialized.*/ #define rtp_stream_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, \ struct rtp_stream_events, m, v, ##__VA_ARGS__) #define rtp_stream_emit_destroy(s) rtp_stream_emit(s, destroy, 0) -#define rtp_stream_emit_state_changed(s,n,e) rtp_stream_emit(s, state_changed,0,n,e) +#define rtp_stream_emit_report_error(s,e) rtp_stream_emit(s, report_error, 0,e) +#define rtp_stream_emit_open_connection(s,r) rtp_stream_emit(s, open_connection, 0,r) +#define rtp_stream_emit_close_connection(s,r) rtp_stream_emit(s, close_connection, 0,r) #define rtp_stream_emit_param_changed(s,i,p) rtp_stream_emit(s, param_changed,0,i,p) #define rtp_stream_emit_send_packet(s,i,l) rtp_stream_emit(s, send_packet,0,i,l) #define rtp_stream_emit_send_feedback(s,seq) rtp_stream_emit(s, send_feedback,0,seq) @@ -140,9 +147,14 @@ struct impl { static int do_emit_state_changed(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = user_data; - bool *started = (bool *)data; + bool started = *((bool *)data); + + if (started) { + rtp_stream_emit_open_connection(impl, NULL); + } else { + rtp_stream_emit_close_connection(impl, NULL); + } - rtp_stream_emit_state_changed(impl, *started, NULL); return 0; } @@ -191,6 +203,8 @@ static void stream_destroy(void *d) static int stream_start(struct impl *impl) { + int res; + if (impl->started) return 0; @@ -206,7 +220,14 @@ static int stream_start(struct impl *impl) impl->reset_ringbuffer(impl); - rtp_stream_emit_state_changed(impl, true, NULL); + res = 0; + rtp_stream_emit_open_connection(impl, &res); + if (res > 0) { + pw_log_debug("opened new connection"); + } else if (res < 0) { + pw_log_error("could not open connection: %s", spa_strerror(res)); + return res; + } if (impl->separate_sender) { struct spa_dict_item items[1]; @@ -230,7 +251,7 @@ static int stream_stop(struct impl *impl) /* if timer is running, the state changed event must be emitted by the timer after all packets have been sent */ if (!impl->timer_running) - rtp_stream_emit_state_changed(impl, false, NULL); + rtp_stream_emit_close_connection(impl, NULL); if (impl->separate_sender) { struct spa_dict_item items[1]; diff --git a/src/modules/module-rtp/stream.h b/src/modules/module-rtp/stream.h index 83cf0da34..67908d706 100644 --- a/src/modules/module-rtp/stream.h +++ b/src/modules/module-rtp/stream.h @@ -35,7 +35,17 @@ struct rtp_stream_events { void (*destroy) (void *data); - void (*state_changed) (void *data, bool started, const char *error); + void (*report_error) (void *data, const char *error); + + /* Requests the network connection to be opened. If result is non-NULL, + * the call sets it to >0 in case of success, and a negative errno error + * code in case of failure. (Result value 0 is unused.) */ + void (*open_connection) (void *data, int *result); + + /* Requests the network connection to be closed. If result is non-NULL, + * the call sets it to >0 in case of success, 0 if the connection was + * already closed, and a negative errno error code in case of failure. */ + void (*close_connection) (void *data, int *result); void (*param_changed) (void *data, uint32_t id, const struct spa_pod *param); From 37e597ff0a80bc0d1393f59661c258936a434ec7 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Fri, 22 Aug 2025 12:05:16 +0200 Subject: [PATCH 0769/1014] module-rtp: Replace "started" boolean with internal state enum The started boolean is insufficient to fully cover the possible internal states. For this reason, it needs to be replaced by an enum that covers these states. Also, due to potential access by both the dataloop and the mainloop, access to that internal state needs to be synchronized. Finally, a variable "internal_state" makes for code that is easier to read, since it emphasizes that this is state that is fully internal inside the stream (and is not visible to the rtp-sink and rtp-source modules for example). --- src/modules/module-rtp/audio.c | 46 ++++++-- src/modules/module-rtp/stream.c | 198 +++++++++++++++++++++++++++++--- 2 files changed, 216 insertions(+), 28 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 99be8ee5e..18dd9d0ee 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -385,19 +385,31 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin uint32_t stride, timestamp; struct iovec iov[3]; struct rtp_header header; + bool insufficient_data; avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); tosend = impl->psamples; - if (avail < tosend) - if (impl->started) + insufficient_data = (avail < tosend); + if (insufficient_data) { + /* There is insufficient data for even a single full packet. + * Handle this depending on the current state. */ + + if (get_internal_stream_state(impl) == RTP_STREAM_INTERNAL_STATE_STARTED) { + /* If the stream is started, just try again later, + * when more data comes in. Enough data for covering + * the psamples amount might be available by then. */ goto done; - else { - /* send last packet before emitting state_changed */ + } else { + /* There is not enough data for a full packet, but the + * stream is no longer in the started state, so the + * remaining data needs to be flushed out now. */ tosend = avail; num_packets = 1; } - else + } else { + /* There is sufficient data for one or more full packets. */ num_packets = SPA_MIN(num_packets, (uint32_t)(avail / tosend)); + } stride = impl->stride; @@ -434,18 +446,30 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin num_packets--; } spa_ringbuffer_read_update(&impl->ring, timestamp); + done: if (impl->timer_running) { - if (impl->started) { - if (avail < tosend) { + if (get_internal_stream_state(impl) != RTP_STREAM_INTERNAL_STATE_STOPPING) { + /* If the stream isn't being stopped, and instead is running, + * keep the timer running if there was sufficient data to + * produce at least one packet. That's because by the time + * the next timer expiration happens, there might be enough + * data available for even more packets. However, if there + * wasn't sufficient data for even one packet, stop the + * timer, since it is likely then that input has ceased + * (at least for now). */ + if (insufficient_data) { set_timer(impl, 0, 0); } } else if (avail <= 0) { - bool started = false; - - /* the stream has been stopped and all packets have been sent */ + /* All packets were sent, and the stream is in the stopping + * state. This means that stream_stop() was called while this + * timer was still sending out remaining packets, and thus, + * stream_stop() could not immediately change the stream to the + * stopping state. Now that all packets have gone out, finish + * the stopping state change. */ set_timer(impl, 0, 0); - pw_loop_invoke(impl->main_loop, do_emit_state_changed, SPA_ID_INVALID, &started, sizeof started, false, impl); + pw_loop_invoke(impl->main_loop, do_finish_stopping_state, SPA_ID_INVALID, NULL, 0, false, impl); } } } diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 96c06e2ee..f83019a3e 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -49,6 +50,30 @@ PW_LOG_TOPIC_EXTERN(mod_topic); #define rtp_stream_emit_send_packet(s,i,l) rtp_stream_emit(s, send_packet,0,i,l) #define rtp_stream_emit_send_feedback(s,seq) rtp_stream_emit(s, send_feedback,0,seq) +enum rtp_stream_internal_state { + /* The state when the stream is idle / stopped. The background + * timer that may be used for sending out buffered data + * must not be running in this state. If the separate PTP sender + * is being used, then that one must be inactive in this state. + * Set at the end of stream_stop() and when the stream is created. */ + RTP_STREAM_INTERNAL_STATE_STOPPED, + /* Temporary state that is set at the beginning of stream_stop(). + * If a full stop is possible, stream_stop() immediately moves on + * to the STOPPED state. However, if the timer is running (because it + * is still sending out buffered data), the state remains set to + * STOPPING until the timer has sent out all data, at which point + * the timer finishes the change to the STOPPED state. */ + RTP_STREAM_INTERNAL_STATE_STOPPING, + /* Temporary state that is set at the beginning of stream_start(). + * It is mainly used for preventing do_finish_stopping_state() + * from setting a stopped state. See do_finish_stopping_state() + * for details. */ + RTP_STREAM_INTERNAL_STATE_STARTING, + /* The state when the stream has been started. It is set at the + * end of stream_start(). */ + RTP_STREAM_INTERNAL_STATE_STARTED +}; + struct impl { struct spa_audio_info info; struct spa_audio_info stream_info; @@ -100,11 +125,19 @@ struct impl { unsigned direct_timestamp:1; unsigned always_process:1; - unsigned started:1; unsigned have_sync:1; unsigned receiving:1; unsigned first:1; + /* IMPORTANT: Do NOT access this value directly. Use the atomic + * set_internal_stream_state() / get_internal_stream_state() accessors, + * since the state is accessed by both the dataloop and mainloop. To + * prevent memory visibility issues, atomic accessors need to be used. + * + * Also, its type here is uint32_t. See the explanation about atomic + * access below for the reason why. */ + uint32_t internal_state; + struct pw_loop *main_loop; struct pw_loop *data_loop; struct spa_source *timer; @@ -118,6 +151,9 @@ struct impl { * requires filling the ring buffer with something other than nullbytes * (this can happen with DSD for example). */ void (*reset_ringbuffer)(struct impl *impl); + /* Called by stream_start() to stop any running timer before continuing to + * start the stream. This is necessary, because by that point, any remaining + * buffered data is stale, and the timer would keep sending it out. */ void (*stop_timer)(struct impl *impl); void (*flush_timeout)(struct impl *impl, uint64_t expirations); void (*deinit)(struct impl *impl, enum spa_direction direction); @@ -144,17 +180,83 @@ struct impl { uint32_t rtp_last_ts; }; -static int do_emit_state_changed(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) -{ - struct impl *impl = user_data; - bool started = *((bool *)data); +/* Atomic internal_state accessors. + * + * These are necessary because internal_state may be accessed by both + * the dataloop (in the flush_timeout and do_finish_stopping_state()) + * and the mainloop (in stream_start() and stream_stop()). Even though + * stream_start() and stream_stop() may not necessarily run at the + * same time when the dataloop is active, there is still a potential + * memory visibility issue if the state is set in one loop but that + * change is not yet propagated to other CPU cores, causing the other + * loop (which runs in a separate thread) to still see the old state. + * + * Also, since GCC __atomic built-ins (which the SPA macros use) are + * designed to work with integral scalar or pointer type that is 1, + * 2, 4, or 8 bytes in length, impl->internal_state is of type uint33_t. + * This guarantee a correct size for the built-ins. The accessors take + * care of casting from/to rtp_stream_internal_state . The relevant + * GCC manual page for this is: + * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html + */ - if (started) { - rtp_stream_emit_open_connection(impl, NULL); - } else { - rtp_stream_emit_close_connection(impl, NULL); +static inline enum rtp_stream_internal_state get_internal_stream_state(struct impl *impl) { + return (enum rtp_stream_internal_state)SPA_ATOMIC_LOAD(impl->internal_state); +} + +static inline void set_internal_stream_state(struct impl *impl, enum rtp_stream_internal_state state) { + SPA_ATOMIC_STORE(impl->internal_state, (uint32_t)state); +} + +static int do_finish_stopping_state(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + int res = 0; + struct impl *impl = user_data; + enum rtp_stream_internal_state cur_state = get_internal_stream_state(impl); + + /* The checks here cover a corner case that can happen when the + * following conditions are met (in order): + * + * 1. Stream is stopped via stream_stop(), but the timer is still + * running, meaning that internal_state stays at STOPPING. + * 2. The timer manages to invoke do_finish_stopping_state() + * asynchronously, meaning that the invocation is queued. + * 3. Immediately afterwards, the state is started again via + * stream_start(). That call stops the timer, but does not + * undo the do_finish_stopping_state() invocation. + * The internal_state is set to STARTED. + * 4. The queued do_finish_stopping_state() invocation takes + * place, and it tries to set the internal_state to STOPPED. + * + * In such a case, the STARTED state would be set again to STOPPED, + * even though the stream has been started and is running. + * + * To fix this, check if the current internal state is STOPPING. + * This is the only case where setting the state to STOPPED makes + * sense, since that is why this do_finish_stopping_state() exists - + * to finish a stopping procedure that could not be finished in + * stream_stop() immediately. If the stream is restarted, then this + * delayed stop is no longer needed. Canceling the queued invocation + * is not possible (PipeWire has no cancellation API for this), + * so this approach needs to be used instead. */ + + switch (cur_state) { + case RTP_STREAM_INTERNAL_STATE_STOPPING: + pw_log_debug("setting \"stopped\" state after timer expired"); + break; + default: + pw_log_debug("\"stopped\" state change event emission was scheduled, " + "but the current state is not \"stopping\"; ignoring " + "scheduled request"); + return 0; } + rtp_stream_emit_close_connection(impl, &res); + if (res > 0) + pw_log_debug("closed connection"); + else if (res < 0) + pw_log_error("error while closing connection: %s", spa_strerror(res)); + return 0; } @@ -204,12 +306,17 @@ static void stream_destroy(void *d) static int stream_start(struct impl *impl) { int res; + enum rtp_stream_internal_state cur_state; - if (impl->started) + cur_state = get_internal_stream_state(impl); + + if (cur_state == RTP_STREAM_INTERNAL_STATE_STARTED) return 0; impl->first = true; + set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STARTING); + /* Stop the timer now (if the timer is used). Any lingering timer * will try to send data that is stale at this point, especially * after the ring buffer contents get reset. Worse, the timer might @@ -218,6 +325,33 @@ static int stream_start(struct impl *impl) if (impl->stop_timer) impl->stop_timer(impl); + res = 0; + rtp_stream_emit_close_connection(impl, &res); + + /* A leftover connection only makes sense if the stream was in the + * STOPPING state prior to this stream_start() call, because then, + * the previous stream_stop() call could not finish stopping the + * stream, and had to leave the connection open so the timer can + * finish sending out packets. If stream_start() was called before + * the timer finished, then the stream is still in the STOPPING + * state, was thus not properly stopped, and the connection is still + * there. This is not an error, but a consequence of restarting the + * stream early enough. + * If however the state prior to this stream_start() call was + * anything other than STOPPING, then something is wrong. */ + if (res > 0) { + if (cur_state != RTP_STREAM_INTERNAL_STATE_STOPPING) { + pw_log_warn("there was already an open connection, " + "even though none was expected"); + } else { + pw_log_debug("closed leftover connection since a scheduled " + "\"stopped\" state change was cancelled " + "and we are still in the \"stopping\" state"); + } + } else if (res < 0) { + pw_log_error("error while closing leftover connection: %s", spa_strerror(res)); + } + impl->reset_ringbuffer(impl); res = 0; @@ -239,20 +373,42 @@ static int stream_start(struct impl *impl) pw_log_info("activated pw_filter for separate sender"); } - impl->started = true; + set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STARTED); + pw_log_info("stream started"); return 0; } static int stream_stop(struct impl *impl) { - if (!impl->started) - return 0; + switch (get_internal_stream_state(impl)) { + case RTP_STREAM_INTERNAL_STATE_STOPPING: + case RTP_STREAM_INTERNAL_STATE_STOPPED: + return 0; + default: + break; + } - /* if timer is running, the state changed event must be emitted by the timer after all packets have been sent */ - if (!impl->timer_running) - rtp_stream_emit_close_connection(impl, NULL); + set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPING); + /* Proper stop is only possible if the timer is currently not running, + * because a stop involves closing the connection. If the timer is still + * running, it needs an open connection for sending out remaining packets. */ + if (!impl->timer_running) { + int res; + pw_log_info("closing connection as part of stopping the stream"); + rtp_stream_emit_close_connection(impl, &res); + if (res > 0) { + pw_log_debug("closed connection"); + } else if (res < 0) { + pw_log_error("error while closing connection: %s", spa_strerror(res)); + } + } else { + pw_log_info("cannot close connection yet - timer is still running"); + } + + /* Stopping the separate sender can be done even if the timer is still + * running because it has no dependency on said timer. */ if (impl->separate_sender) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_ALWAYS_PROCESS, "false"); @@ -263,7 +419,14 @@ static int stream_stop(struct impl *impl) pw_filter_set_active(impl->ptp_sender, false); } - impl->started = false; + /* Only switch to STOPPED if the stream could _actually_ be stopped, + * meaning that the timer was no longer running, and the connection + * could be closed. */ + if (!impl->timer_running) { + set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPED); + pw_log_info("stream stopped"); + } + return 0; } @@ -372,6 +535,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, goto out; } impl->first = true; + set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPED); spa_hook_list_init(&impl->listener_list); impl->direction = direction; impl->stream_events = stream_events; From caf72fd9bced60a0462cc861cc4d79f347c29fd8 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Fri, 22 Aug 2025 17:39:34 +0200 Subject: [PATCH 0770/1014] module-rtp: Synchronize access to timer_running flag --- src/modules/module-rtp/audio.c | 4 ++-- src/modules/module-rtp/stream.c | 32 +++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 18dd9d0ee..1563e1917 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -376,7 +376,7 @@ static void set_timer(struct impl *impl, uint64_t time, uint64_t itime) ts.it_interval.tv_nsec = itime % SPA_NSEC_PER_SEC; spa_system_timerfd_settime(impl->data_loop->system, impl->timer->fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); - impl->timer_running = time != 0 && itime != 0; + set_timer_running(impl, time != 0 && itime != 0); } static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uint64_t set_timestamp) @@ -448,7 +448,7 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin spa_ringbuffer_read_update(&impl->ring, timestamp); done: - if (impl->timer_running) { + if (is_timer_running(impl)) { if (get_internal_stream_state(impl) != RTP_STREAM_INTERNAL_STATE_STOPPING) { /* If the stream isn't being stopped, and instead is running, * keep the timer running if there was sufficient data to diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index f83019a3e..bb9da4995 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -141,7 +141,14 @@ struct impl { struct pw_loop *main_loop; struct pw_loop *data_loop; struct spa_source *timer; - bool timer_running; + /* IMPORTANT: Do NOT access this value directly. Use the atomic + * set_timer_running() / is_timer_running() accessors, since the + * flag is accessed by both the dataloop and mainloop. To prevent + * memory visibility issues, atomic accessors need to be used. + * + * Also, its type here is uint8_t. See the explanation about atomic + * access below for the reason why. */ + uint8_t timer_running; int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); /* Used for resetting the ring buffer before the stream starts, to prevent @@ -208,6 +215,21 @@ static inline void set_internal_stream_state(struct impl *impl, enum rtp_stream_ SPA_ATOMIC_STORE(impl->internal_state, (uint32_t)state); } +/* Similar to the atomic internal_state accessors, these safeguard + * the timer_running flag, which can be accessed both by stream_stop() + * and the flush_timeout, which are called in separate threads. + * Since timer_running and internal_state are accessed independently, + * they are treated as two independent atomic variables instead of two + * resources under a common mutex. */ + +static inline bool is_timer_running(struct impl *impl) { + return (bool)SPA_ATOMIC_LOAD(impl->timer_running); +} + +static inline void set_timer_running(struct impl *impl, bool running) { + SPA_ATOMIC_STORE(impl->timer_running, (uint8_t)(running ? 1 : 0)); +} + static int do_finish_stopping_state(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { int res = 0; @@ -381,6 +403,8 @@ static int stream_start(struct impl *impl) static int stream_stop(struct impl *impl) { + bool timer_running; + switch (get_internal_stream_state(impl)) { case RTP_STREAM_INTERNAL_STATE_STOPPING: case RTP_STREAM_INTERNAL_STATE_STOPPED: @@ -391,10 +415,12 @@ static int stream_stop(struct impl *impl) set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPING); + timer_running = is_timer_running(impl); + /* Proper stop is only possible if the timer is currently not running, * because a stop involves closing the connection. If the timer is still * running, it needs an open connection for sending out remaining packets. */ - if (!impl->timer_running) { + if (!timer_running) { int res; pw_log_info("closing connection as part of stopping the stream"); rtp_stream_emit_close_connection(impl, &res); @@ -422,7 +448,7 @@ static int stream_stop(struct impl *impl) /* Only switch to STOPPED if the stream could _actually_ be stopped, * meaning that the timer was no longer running, and the connection * could be closed. */ - if (!impl->timer_running) { + if (!timer_running) { set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPED); pw_log_info("stream stopped"); } From 28ed09b155f9fc2ec012c18b58f97998b2094b44 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 25 Aug 2025 09:48:19 -0400 Subject: [PATCH 0771/1014] tools: Fix -C handling for pw-dump The short form needs to have a :: to signal an optional argument so that something like -Calways can work. --- src/tools/pw-dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index 6b4e7b5ef..e72e7d257 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -1600,7 +1600,7 @@ int main(int argc, char *argv[]) data.keysep_char = ":"; data.indent = INDENT; - while ((c = getopt_long(argc, argv, "hVr:mNCRi:s", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:mNC::Ri:s", long_options, NULL)) != -1) { switch (c) { case 'h' : show_help(&data, argv[0], false); From 7359491b9793f0a4523623636142337986d9b3aa Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 26 Aug 2025 10:59:11 +0200 Subject: [PATCH 0772/1014] tools: dump sndfile loginfo on error when verbose --- src/tools/pw-cat.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index bb70c321d..0215555a9 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1631,6 +1631,11 @@ static int setup_sndfile(struct data *data) if (!data->file) { fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n", data->filename, sf_strerror(NULL)); + if (data->verbose) { + char loginfo[4096]; + if (sf_command(NULL, SFC_GET_LOG_INFO, &loginfo, sizeof(loginfo)) > 0) + fprintf(stderr, "%s\n", loginfo); + } return -EIO; } From e3cc44966b45b8a67de751223bcaa50b9cd1999f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 26 Aug 2025 13:13:30 +0200 Subject: [PATCH 0773/1014] filter-graph: pass void * to connect_port This makes it a bit more generic and allows us to connect other things that float arrays. Add a SPA_FGA_PORT_SEQUENCE as a new port type. The data to connect to this port is a Sequence type with the size field set to the max/size of the data. --- spa/plugins/filter-graph/audio-plugin.h | 5 +++-- spa/plugins/filter-graph/plugin_builtin.c | 12 ++++++------ spa/plugins/filter-graph/plugin_ebur128.c | 4 ++-- spa/plugins/filter-graph/plugin_ffmpeg.c | 2 +- spa/plugins/filter-graph/plugin_ladspa.c | 2 +- spa/plugins/filter-graph/plugin_lv2.c | 2 +- spa/plugins/filter-graph/plugin_onnx.c | 2 +- spa/plugins/filter-graph/plugin_sofa.c | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/spa/plugins/filter-graph/audio-plugin.h b/spa/plugins/filter-graph/audio-plugin.h index 81d55294c..9f9d1bce6 100644 --- a/spa/plugins/filter-graph/audio-plugin.h +++ b/spa/plugins/filter-graph/audio-plugin.h @@ -9,7 +9,6 @@ #include #include -#include #include #define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioPlugin" @@ -32,6 +31,7 @@ struct spa_fga_port { #define SPA_FGA_PORT_CONTROL (1ULL << 2) #define SPA_FGA_PORT_AUDIO (1ULL << 3) #define SPA_FGA_PORT_SUPPORTS_NULL_DATA (1ULL << 4) +#define SPA_FGA_PORT_SEQUENCE (1ULL << 5) uint64_t flags; #define SPA_FGA_HINT_BOOLEAN (1ULL << 0) @@ -49,6 +49,7 @@ struct spa_fga_port { #define SPA_FGA_IS_PORT_CONTROL(x) ((x) & SPA_FGA_PORT_CONTROL) #define SPA_FGA_IS_PORT_AUDIO(x) ((x) & SPA_FGA_PORT_AUDIO) #define SPA_FGA_SUPPORTS_NULL_DATA(x) ((x) & SPA_FGA_PORT_SUPPORTS_NULL_DATA) +#define SPA_FGA_IS_PORT_SEQUENCE(x) ((x) & SPA_FGA_PORT_SEQUENCE) struct spa_fga_descriptor { const char *name; @@ -66,7 +67,7 @@ struct spa_fga_descriptor { void (*cleanup) (void *instance); - void (*connect_port) (void *instance, unsigned long port, float *data); + void (*connect_port) (void *instance, unsigned long port, void *data); void (*control_changed) (void *instance); void (*activate) (void *instance); diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index bdc2e71de..955c6442d 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -83,7 +83,7 @@ static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const stru return impl; } -static void builtin_connect_port(void *Instance, unsigned long Port, float * DataLocation) +static void builtin_connect_port(void *Instance, unsigned long Port, void * DataLocation) { struct builtin *impl = Instance; impl->port[Port] = DataLocation; @@ -1103,7 +1103,7 @@ error: } static void convolver_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct convolver_impl *impl = Instance; impl->port[Port] = DataLocation; @@ -1259,7 +1259,7 @@ static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct } static void delay_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct delay_impl *impl = Instance; impl->port[Port] = DataLocation; @@ -2057,7 +2057,7 @@ error: } static void param_eq_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct param_eq_impl *impl = Instance; impl->port[Port] = DataLocation; @@ -2276,7 +2276,7 @@ static void dcblock_run(void * Instance, unsigned long SampleCount) } static void dcblock_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct dcblock_impl *impl = Instance; impl->port[Port] = DataLocation; @@ -2736,7 +2736,7 @@ static void *pipe_instantiate(const struct spa_fga_plugin *plugin, const struct return impl; } -static void pipe_connect_port(void *Instance, unsigned long Port, float * DataLocation) +static void pipe_connect_port(void *Instance, unsigned long Port, void * DataLocation) { struct pipe_impl *impl = Instance; impl->port[Port] = DataLocation; diff --git a/spa/plugins/filter-graph/plugin_ebur128.c b/spa/plugins/filter-graph/plugin_ebur128.c index dfd2b3add..fb79de590 100644 --- a/spa/plugins/filter-graph/plugin_ebur128.c +++ b/spa/plugins/filter-graph/plugin_ebur128.c @@ -219,7 +219,7 @@ static void ebur128_run(void * Instance, unsigned long SampleCount) } static void ebur128_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct ebur128_impl *impl = Instance; impl->port[Port] = DataLocation; @@ -442,7 +442,7 @@ static void * lufs2gain_instantiate(const struct spa_fga_plugin *plugin, const s } static void lufs2gain_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct lufs2gain_impl *impl = Instance; impl->port[Port] = DataLocation; diff --git a/spa/plugins/filter-graph/plugin_ffmpeg.c b/spa/plugins/filter-graph/plugin_ffmpeg.c index ba9c525d5..4e4b9f4a0 100644 --- a/spa/plugins/filter-graph/plugin_ffmpeg.c +++ b/spa/plugins/filter-graph/plugin_ffmpeg.c @@ -188,7 +188,7 @@ static void ffmpeg_free(const struct spa_fga_descriptor *desc) free(d); } -static void ffmpeg_connect_port(void *instance, unsigned long port, float *data) +static void ffmpeg_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; i->data[port] = data; diff --git a/spa/plugins/filter-graph/plugin_ladspa.c b/spa/plugins/filter-graph/plugin_ladspa.c index bad13e89f..d5c8ef488 100644 --- a/spa/plugins/filter-graph/plugin_ladspa.c +++ b/spa/plugins/filter-graph/plugin_ladspa.c @@ -153,7 +153,7 @@ static const struct spa_fga_descriptor *ladspa_plugin_make_desc(void *plugin, co desc->desc.instantiate = ladspa_instantiate; desc->desc.cleanup = d->cleanup; - desc->desc.connect_port = d->connect_port; + desc->desc.connect_port = (__typeof__(desc->desc.connect_port))d->connect_port; desc->desc.activate = d->activate; desc->desc.deactivate = d->deactivate; desc->desc.run = d->run; diff --git a/spa/plugins/filter-graph/plugin_lv2.c b/spa/plugins/filter-graph/plugin_lv2.c index 33bf38470..712b728e2 100644 --- a/spa/plugins/filter-graph/plugin_lv2.c +++ b/spa/plugins/filter-graph/plugin_lv2.c @@ -457,7 +457,7 @@ static void lv2_cleanup(void *instance) free(i); } -static void lv2_connect_port(void *instance, unsigned long port, float *data) +static void lv2_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; lilv_instance_connect_port(i->instance, port, data); diff --git a/spa/plugins/filter-graph/plugin_onnx.c b/spa/plugins/filter-graph/plugin_onnx.c index 73a41268e..3ef12e963 100644 --- a/spa/plugins/filter-graph/plugin_onnx.c +++ b/spa/plugins/filter-graph/plugin_onnx.c @@ -341,7 +341,7 @@ static void onnx_free(const struct spa_fga_descriptor *desc) free(d); } -static void onnx_connect_port(void *instance, unsigned long port, float *data) +static void onnx_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; i->data[port] = data; diff --git a/spa/plugins/filter-graph/plugin_sofa.c b/spa/plugins/filter-graph/plugin_sofa.c index cd1d3e49f..7ec73ea2b 100644 --- a/spa/plugins/filter-graph/plugin_sofa.c +++ b/spa/plugins/filter-graph/plugin_sofa.c @@ -316,7 +316,7 @@ static void spatializer_run(void * Instance, unsigned long SampleCount) } static void spatializer_connect_port(void * Instance, unsigned long Port, - float * DataLocation) + void * DataLocation) { struct spatializer_impl *impl = Instance; impl->port[Port] = DataLocation; From 335d891ee960aebb0a168af56be7028aa6989568 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 26 Aug 2025 14:54:23 +0200 Subject: [PATCH 0774/1014] spa: avoid warnings when compiling cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I don't think those qualifiers are needed when doing the atomic operations. ../spa/include/spa/pod/body.h:250:9: note: in expansion of macro ‘SPA_POD_BODY_LOAD_FIELD_ONCE’ 250 | SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, value); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../spa/include/spa/pod/body.h: In function ‘int spa_pod_body_get_rectangle(const spa_pod*, const void*, spa_rectangle*)’: ../spa/include/spa/pod/body.h:110:81: warning: type qualifiers ignored on cast result type [-Wignored-qualifiers] 110 | #define SPA_POD_BODY_LOAD_FIELD_ONCE(a, b, field) ((a)->field = SPA_LOAD_ONCE(&((volatile __typeof__(a))(b))->field)) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ ../spa/include/spa/utils/atomic.h:22:58: note: in definition of macro ‘SPA_LOAD_ONCE’ 22 | #define SPA_LOAD_ONCE(s) __atomic_load_n((s), __ATOMIC_RELAXED) | ^ --- spa/include/spa/pod/body.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index 57c3c2f6c..07758c7e6 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -106,8 +106,8 @@ SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } -#define SPA_POD_BODY_LOAD_ONCE(a, b) (*(a) = SPA_LOAD_ONCE((const volatile __typeof__(a))(b))) -#define SPA_POD_BODY_LOAD_FIELD_ONCE(a, b, field) ((a)->field = SPA_LOAD_ONCE(&((const volatile __typeof__(a))(b))->field)) +#define SPA_POD_BODY_LOAD_ONCE(a, b) (*(a) = SPA_LOAD_ONCE((__typeof__(a))(b))) +#define SPA_POD_BODY_LOAD_FIELD_ONCE(a, b, field) ((a)->field = SPA_LOAD_ONCE(&((__typeof__(a))(b))->field)) SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) { From b0b6b1fcc58890dacdba4182a4914c35f56f7342 Mon Sep 17 00:00:00 2001 From: alexdlm Date: Tue, 26 Aug 2025 17:17:40 +1000 Subject: [PATCH 0775/1014] Map Razer BlackShark v3 ACP --- spa/plugins/alsa/90-pipewire-alsa.rules | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 631c24852..b2e1f6886 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -138,8 +138,13 @@ ATTRS{idVendor}=="9886", ATTRS{idProduct}=="002c", ENV{ACP_PROFILE_SET}="usb-gam ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0038", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 9886:0045 is for the Astro A20 Gen2 ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0045", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" + # ID 1532:0520 is for the Razer Kraken Tournament Edition ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0520", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" +# ID 1532:0579 is Razer BlackShark v3 (usb direct to headset) +ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0579", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" +# ID 1532:057a is Razer BlackShark v3 (2.4GHz dongle) +ATTRS{idVendor}=="1532", ATTRS{idProduct}=="057a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:1250 is for the Arctis 5 From 984c44b044a5845ad58d7032ad3bd20f4435c6a1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 27 Aug 2025 18:42:20 +0300 Subject: [PATCH 0776/1014] bluez5: fix BIS source presentation delay The value comes from QoS preset, not configurable otherwise right now. Patch from @michael-kong754 --- spa/plugins/bluez5/bluez5-dbus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 6796bf49c..1beddebd4 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -191,7 +191,6 @@ struct spa_bt_big { struct spa_list link; char broadcast_code[BROADCAST_CODE_LEN]; bool encryption; - int presentation_delay; struct spa_list bis_list; int big_id; int sync_factor; @@ -5781,7 +5780,6 @@ static void configure_bis(struct spa_bt_monitor *monitor, uint8_t metadata [METADATA_MAX_LEN]; uint8_t caps_size, metadata_size = 0; struct bap_codec_qos qos; - int presentation_delay; struct spa_bt_metadata *metadata_entry; struct spa_dict settings; struct spa_dict_item setting_items[2]; @@ -5863,7 +5861,7 @@ static void configure_bis(struct spa_bt_monitor *monitor, append_basic_variant_dict_entry(&qos_dict, "SDU", DBUS_TYPE_UINT16, "q", &qos.sdu); append_basic_variant_dict_entry(&qos_dict, "Retransmissions", DBUS_TYPE_BYTE, "y", &qos.retransmission); append_basic_variant_dict_entry(&qos_dict, "Latency", DBUS_TYPE_UINT16, "q", &qos.latency); - append_basic_variant_dict_entry(&qos_dict, "PresentationDelay", DBUS_TYPE_UINT32, "u", &presentation_delay); + append_basic_variant_dict_entry(&qos_dict, "PresentationDelay", DBUS_TYPE_UINT32, "u", &qos.delay); dbus_message_iter_close_container(&variant, &qos_dict); dbus_message_iter_close_container(&entry, &variant); From 2385aa18e13aff744ec52414d7d5516b0b0a983a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 27 Aug 2025 17:46:11 +0200 Subject: [PATCH 0777/1014] audioconvert: handle filter-graph setup better Force filter graph reconfiguration in setup_convert. When adding/removing filter-graphs, only perform setup when we were already setup, otherwise we will do this in setup_convert later. Don't do channelmix_init when we were not setup. Deactivate the filter-graphs when we suspend. Fixes #4866 --- spa/plugins/audioconvert/audioconvert.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index adb4b92bb..1f315bf5a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1155,7 +1155,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, char rate_str[64], in_ports[64]; struct dir *dir; - if (g == NULL || g->graph == NULL || g->setup) + if (g->graph == NULL || g->setup) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; @@ -1259,7 +1259,7 @@ static int ensure_tmp(struct impl *this) } -static int setup_filter_graphs(struct impl *impl) +static int setup_filter_graphs(struct impl *impl, bool force) { int res; uint32_t channels, *position; @@ -1276,6 +1276,8 @@ static int setup_filter_graphs(struct impl *impl) spa_list_for_each_safe(g, t, &impl->active_graphs, link) { if (g->removing) continue; + if (force) + g->setup = false; if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { g->removing = true; spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, @@ -1393,7 +1395,8 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order) spa_log_info(impl->log, "loading filter-graph order:%d", order); } - res = setup_filter_graphs(impl); + if (impl->setup) + res = setup_filter_graphs(impl, false); spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl); @@ -1517,7 +1520,8 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) } if (changed) { this->props.filter_graph_disabled = filter_graph_disabled; - channelmix_init(&this->mix); + if (this->setup) + channelmix_init(&this->mix); } return changed; } @@ -2490,7 +2494,7 @@ static int setup_convert(struct impl *this) if ((res = setup_in_convert(this)) < 0) return res; - if ((res = setup_filter_graphs(this)) < 0) + if ((res = setup_filter_graphs(this, true)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; @@ -2549,6 +2553,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman this->started = true; break; case SPA_NODE_COMMAND_Suspend: + reset_node(this); this->setup = false; SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: From a0e27314c6a493d1bc345383eb141b036a8fff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Dr=C4=85g?= Date: Wed, 27 Aug 2025 18:01:47 +0200 Subject: [PATCH 0778/1014] Update Polish translation --- po/pl.po | 111 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/po/pl.po b/po/pl.po index 6d6572a3e..0bb385bae 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2025-01-09 15:25+0000\n" -"PO-Revision-Date: 2025-02-09 14:55+0100\n" +"POT-Creation-Date: 2025-08-27 03:33+0000\n" +"PO-Revision-Date: 2025-08-27 14:12+0200\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" @@ -38,11 +38,11 @@ msgstr "" "%s)\n" " -P --properties Ustawia właściwości kontekstu\n" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "System multimediów PipeWire" -#: src/daemon/pipewire.desktop.in:5 +#: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Uruchomienie systemu multimediów PipeWire" @@ -75,7 +75,7 @@ msgstr "%s na %s@%s" msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:973 +#: src/tools/pw-cat.c:1049 #, c-format msgid "" "%s [options] [|-]\n" @@ -90,7 +90,7 @@ msgstr "" " -v, --verbose Wyświetla więcej komunikatów\n" "\n" -#: src/tools/pw-cat.c:980 +#: src/tools/pw-cat.c:1056 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -128,7 +128,7 @@ msgstr "" " -P --properties Ustawia właściwości węzła\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:1074 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -146,6 +146,8 @@ msgid "" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" +" -M, --force-midi Force midi format, one of \"midi\" " +"or \"ump\", (default ump)\n" "\n" msgstr "" " --rate Częstotliwość próbki (wymagane do " @@ -164,15 +166,19 @@ msgstr "" " -q --quality Jakość resamplera od 0 do 15 " "(domyślnie %d)\n" " -a, --raw Tryb RAW\n" +" -M, --force-midi Wymusza format MIDI, można użyć " +"„midi”\n" +" albo „ump” (domyślnie ump)\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:1093 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" +" -s, --sysex SysEx mode\n" "\n" msgstr "" " -p, --playback Tryb odtwarzania\n" @@ -180,9 +186,10 @@ msgstr "" " -m, --midi Tryb MIDI\n" " -d, --dsd Tryb DSD\n" " -o, --encoded Tryb zakodowany\n" +" -s, --sysex Tryb SysEx\n" "\n" -#: src/tools/pw-cli.c:2306 +#: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" @@ -202,15 +209,20 @@ msgstr "" " -m, --monitor Monitoruje aktywność\n" "\n" -#: spa/plugins/alsa/acp/acp.c:347 +#: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "Dźwięk w zastosowaniach profesjonalnych" -#: spa/plugins/alsa/acp/acp.c:507 spa/plugins/alsa/acp/alsa-mixer.c:4635 -#: spa/plugins/bluez5/bluez5-device.c:1795 +#: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Wyłączone" +#: spa/plugins/alsa/acp/acp.c:603 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [błąd UCM biblioteki ALSA]" + #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Wejście" @@ -234,7 +246,7 @@ msgstr "Wejście liniowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2139 +#: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "Mikrofon" @@ -300,12 +312,15 @@ msgid "No Bass Boost" msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:2145 +#: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "Głośnik" +#. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 +#: spa/plugins/bluez5/bluez5-device.c:2384 +#: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "Słuchawki" @@ -415,7 +430,7 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4629 -#: spa/plugins/bluez5/bluez5-device.c:2127 +#: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "Słuchawki z mikrofonem" @@ -631,112 +646,108 @@ msgstr[2] "" msgid "(invalid)" msgstr "(nieprawidłowe)" -#: spa/plugins/alsa/acp/compat.c:193 +#: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Wbudowany dźwięk" -#: spa/plugins/alsa/acp/compat.c:198 +#: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1806 +#: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Bramka dźwięku (źródło A2DP i AG HSP/HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1834 +#: spa/plugins/bluez5/bluez5-device.c:2014 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Przesyłanie dźwięku do aparatów słuchowych (odpływ ASHA)" -#: spa/plugins/bluez5/bluez5-device.c:1874 +#: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1877 +#: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1885 +#: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1887 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1937 +#: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1942 +#: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Wejście o wysokiej dokładności (źródło BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1946 +#: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1955 +#: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1959 +#: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "Wejście o wysokiej dokładności (źródło BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1962 +#: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP)" -#: spa/plugins/bluez5/bluez5-device.c:2008 +#: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednostka główna słuchawek z mikrofonem (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2128 -#: spa/plugins/bluez5/bluez5-device.c:2133 -#: spa/plugins/bluez5/bluez5-device.c:2140 -#: spa/plugins/bluez5/bluez5-device.c:2146 -#: spa/plugins/bluez5/bluez5-device.c:2152 -#: spa/plugins/bluez5/bluez5-device.c:2158 -#: spa/plugins/bluez5/bluez5-device.c:2164 -#: spa/plugins/bluez5/bluez5-device.c:2170 -#: spa/plugins/bluez5/bluez5-device.c:2176 +#: spa/plugins/bluez5/bluez5-device.c:2361 +#: spa/plugins/bluez5/bluez5-device.c:2366 +#: spa/plugins/bluez5/bluez5-device.c:2373 +#: spa/plugins/bluez5/bluez5-device.c:2379 +#: spa/plugins/bluez5/bluez5-device.c:2385 +#: spa/plugins/bluez5/bluez5-device.c:2391 +#: spa/plugins/bluez5/bluez5-device.c:2397 +#: spa/plugins/bluez5/bluez5-device.c:2403 +#: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "Zestaw głośnomówiący" -#: spa/plugins/bluez5/bluez5-device.c:2134 +#: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "Zestaw głośnomówiący (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2151 -msgid "Headphone" -msgstr "Słuchawki" - -#: spa/plugins/bluez5/bluez5-device.c:2157 +#: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "Przenośne" -#: spa/plugins/bluez5/bluez5-device.c:2163 +#: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:2169 +#: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2175 +#: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2182 +#: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2183 +#: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" From c99311e822c5ad61508b5d3fa88b03d145611567 Mon Sep 17 00:00:00 2001 From: Martin Geier Date: Wed, 27 Aug 2025 09:44:54 +0200 Subject: [PATCH 0779/1014] filter-graph: clear external field in unsetup_graph Without this change the playback with different number of channels fails with `input port %s[%d]:%s already used as input %d, use mixer` on the first port. Signed-off-by: Martin Geier Fixes #4866 --- spa/plugins/filter-graph/filter-graph.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 718bc542d..36d0c1ec1 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -1744,6 +1744,9 @@ error: static void unsetup_graph(struct graph *graph) { + struct node *node; + uint32_t i; + free(graph->input); graph->input = NULL; free(graph->output); @@ -1751,7 +1754,19 @@ static void unsetup_graph(struct graph *graph) free(graph->hndl); graph->hndl = NULL; + spa_list_for_each(node, &graph->node_list, link) { + struct descriptor *desc = node->desc; + for (i = 0; i < desc->n_input; i++) { + struct port *port = &node->input_port[i]; + port->external = SPA_ID_INVALID; + } + for (i = 0; i < desc->n_output; i++) { + struct port *port = &node->output_port[i]; + port->external = SPA_ID_INVALID; + } + } } + static int setup_graph(struct graph *graph) { struct impl *impl = graph->impl; From 6c110a3b186ba0047303bff06a65654f85fe231d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 28 Aug 2025 12:17:52 +0200 Subject: [PATCH 0780/1014] raop: write ALAC end tag Fixes #4853 --- src/modules/module-raop-sink.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 1d9a7963b..eb2a339e7 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -443,6 +443,7 @@ static int write_codec_pcm(void *dst, void *frames, uint32_t n_frames) bit_writer(&bp, &bpos, *(d + 2), 8); d += 4; } + bit_writer(&bp, &bpos, 7, 3); /* end tag */ return bp - b + 1; } From 1ce85ee2aea545c10cee45f89a7df602b7ca9056 Mon Sep 17 00:00:00 2001 From: Torkel Niklasson Date: Wed, 27 Aug 2025 21:26:00 +0200 Subject: [PATCH 0781/1014] tools: add -n option to pw-cat Add sample limit switch -n to pw-cat to stop the recording or playback after a set number of samples received. Change-Id: Iaa551db9849acd6acdb6897dbfaa92a21afa1312 --- src/tools/pw-cat.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 0215555a9..e433c0e55 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -170,6 +170,9 @@ struct data { struct { FILE *file; } sysex; + + uint64_t sample_limit; /* 0 means unlimited */ + uint64_t samples_processed; }; #define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)" @@ -905,8 +908,20 @@ static void on_process(void *userdata) * fill callback actually returns number of bytes, not frames, since * this is encoded data. However, the calculations below still work * out because the stride is set to 1 in setup_encodedfile(). */ + if (data->sample_limit > 0) { + uint64_t samples_left = data->sample_limit - data->samples_processed; + if (samples_left == 0) { + pw_main_loop_quit(data->loop); + return; + } + n_frames = SPA_MIN(n_frames, (int)samples_left); + } + n_fill_frames = data->fill(data, p, n_frames, &null_frame); + if (data->sample_limit > 0 && n_fill_frames > 0) + data->samples_processed += n_fill_frames; + if (null_frame) { /* A null frame is not to be confused with the drain scenario. * In this case, we want to continue streaming, but in this @@ -938,8 +953,20 @@ static void on_process(void *userdata) n_frames = size / data->stride; + if (data->sample_limit > 0) { + uint64_t samples_left = data->sample_limit - data->samples_processed; + if (samples_left == 0) { + pw_main_loop_quit(data->loop); + return; + } + n_frames = SPA_MIN(n_frames, (int)samples_left); + } + n_fill_frames = data->fill(data, p, n_frames, &null_frame); + if (data->sample_limit > 0 && n_fill_frames > 0) + data->samples_processed += n_fill_frames; + have_data = true; } @@ -1035,6 +1062,7 @@ static const struct option long_options[] = { { "quality", required_argument, NULL, 'q' }, { "raw", no_argument, NULL, 'a' }, { "force-midi", required_argument, NULL, 'M' }, + { "sample-count", required_argument, NULL, 'n' }, { NULL, 0, NULL, 0 } }; @@ -1081,6 +1109,7 @@ static void show_usage(const char *name, bool is_error) " -q --quality Resampler quality (0 - 15) (default %d)\n" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" or \"ump\", (default ump)\n" + " -n, --sample-count COUNT Stop after COUNT samples\n" "\n"), DEFAULT_RATE, DEFAULT_CHANNELS, @@ -1852,9 +1881,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:n:", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:n:", long_options, NULL)) != -1) { #endif switch (c) { @@ -1987,6 +2016,9 @@ int main(int argc, char *argv[]) if (!spa_atof(optarg, &data.volume)) data.volume = (float)atof(optarg); break; + case 'n': + data.sample_limit = strtoull(optarg, NULL, 10); + break; default: goto error_usage; } From ddc5c17163c0e3b228fbd15d533ace7939c31831 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 28 Aug 2025 15:45:06 +0200 Subject: [PATCH 0782/1014] tools: move midievent to separate file --- src/tools/meson.build | 3 +- src/tools/midievent.c | 535 ++++++++++++++++++++++++++++++++++++++++ src/tools/midievent.h | 28 +++ src/tools/midifile.c | 520 -------------------------------------- src/tools/midifile.h | 23 +- src/tools/pw-cat.c | 4 +- src/tools/pw-mididump.c | 4 +- 7 files changed, 571 insertions(+), 546 deletions(-) create mode 100644 src/tools/midievent.c create mode 100644 src/tools/midievent.h diff --git a/src/tools/meson.build b/src/tools/meson.build index 103b48adc..8651bd3b1 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -5,7 +5,7 @@ tools_sources = [ [ 'pw-dot', [ 'pw-dot.c' ] ], [ 'pw-dump', [ 'pw-dump.c' ] ], [ 'pw-profiler', [ 'pw-profiler.c' ] ], - [ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c' ] ], + [ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c', 'midievent.c' ] ], [ 'pw-metadata', [ 'pw-metadata.c' ] ], [ 'pw-loopback', [ 'pw-loopback.c' ] ], [ 'pw-link', [ 'pw-link.c' ] ], @@ -48,6 +48,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found() pwcat_sources = [ 'pw-cat.c', 'midifile.c', + 'midievent.c', 'dfffile.c', 'dsffile.c', ] diff --git a/src/tools/midievent.c b/src/tools/midievent.c new file mode 100644 index 000000000..e9f6a9e82 --- /dev/null +++ b/src/tools/midievent.c @@ -0,0 +1,535 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "midievent.h" + +static const char * const event_names[] = { + "Text", "Copyright", "Sequence/Track Name", + "Instrument", "Lyric", "Marker", "Cue Point", + "Program Name", "Device (Port) Name" +}; + +static const char * const note_names[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" +}; + +static const char * const controller_names[128] = { + [0] = "Bank Select (coarse)", + [1] = "Modulation Wheel (coarse)", + [2] = "Breath controller (coarse)", + [4] = "Foot Pedal (coarse)", + [5] = "Portamento Time (coarse)", + [6] = "Data Entry (coarse)", + [7] = "Volume (coarse)", + [8] = "Balance (coarse)", + [10] = "Pan position (coarse)", + [11] = "Expression (coarse)", + [12] = "Effect Control 1 (coarse)", + [13] = "Effect Control 2 (coarse)", + [16] = "General Purpose Slider 1", + [17] = "General Purpose Slider 2", + [18] = "General Purpose Slider 3", + [19] = "General Purpose Slider 4", + [32] = "Bank Select (fine)", + [33] = "Modulation Wheel (fine)", + [34] = "Breath (fine)", + [36] = "Foot Pedal (fine)", + [37] = "Portamento Time (fine)", + [38] = "Data Entry (fine)", + [39] = "Volume (fine)", + [40] = "Balance (fine)", + [42] = "Pan position (fine)", + [43] = "Expression (fine)", + [44] = "Effect Control 1 (fine)", + [45] = "Effect Control 2 (fine)", + [64] = "Hold Pedal (on/off)", + [65] = "Portamento (on/off)", + [66] = "Sustenuto Pedal (on/off)", + [67] = "Soft Pedal (on/off)", + [68] = "Legato Pedal (on/off)", + [69] = "Hold 2 Pedal (on/off)", + [70] = "Sound Variation", + [71] = "Sound Timbre", + [72] = "Sound Release Time", + [73] = "Sound Attack Time", + [74] = "Sound Brightness", + [75] = "Sound Control 6", + [76] = "Sound Control 7", + [77] = "Sound Control 8", + [78] = "Sound Control 9", + [79] = "Sound Control 10", + [80] = "General Purpose Button 1 (on/off)", + [81] = "General Purpose Button 2 (on/off)", + [82] = "General Purpose Button 3 (on/off)", + [83] = "General Purpose Button 4 (on/off)", + [91] = "Effects Level", + [92] = "Tremulo Level", + [93] = "Chorus Level", + [94] = "Celeste Level", + [95] = "Phaser Level", + [96] = "Data Button increment", + [97] = "Data Button decrement", + [98] = "Non-registered Parameter (fine)", + [99] = "Non-registered Parameter (coarse)", + [100] = "Registered Parameter (fine)", + [101] = "Registered Parameter (coarse)", + [120] = "All Sound Off", + [121] = "All Controllers Off", + [122] = "Local Keyboard (on/off)", + [123] = "All Notes Off", + [124] = "Omni Mode Off", + [125] = "Omni Mode On", + [126] = "Mono Operation", + [127] = "Poly Operation", +}; + +static const char * const program_names[] = { + "Acoustic Grand", "Bright Acoustic", "Electric Grand", "Honky-Tonk", + "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", + "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", + "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", + "Rock Organ", "Church Organ", "Reed Organ", "Accoridan", "Harmonica", + "Tango Accordion", "Nylon String Guitar", "Steel String Guitar", + "Electric Jazz Guitar", "Electric Clean Guitar", "Electric Muted Guitar", + "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", + "Acoustic Bass", "Electric Bass (fingered)", "Electric Bass (picked)", + "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", + "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", + "Orchestral Strings", "Timpani", "String Ensemble 1", "String Ensemble 2", + "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", + "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", + "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", + "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", + "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Skakuhachi", + "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", + "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", + "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", + "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", + "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", + "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", + "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", + "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", + "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", + "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", + "Applause", "Gunshot" +}; + +static const char * const smpte_rates[] = { + "24 fps", + "25 fps", + "30 fps (drop frame)", + "30 fps (non drop frame)" +}; + +static const char * const major_keys[] = { + "Unknown major", "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", + "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "Unknown major" +}; + +static const char * const minor_keys[] = { + "Unknown minor", "Dbm", "Abm", "Ebm", "Bbm", "Fm", "Cm", "Gm", "Dm", + "Am", "Em", "Bm", "F#m", "C#m", "G#m", "D#m", "A#m", "E#m", "Unknown minor" +}; + +static const char *controller_name(uint8_t ctrl) +{ + if (ctrl > 127 || + controller_names[ctrl] == NULL) + return "Unknown"; + return controller_names[ctrl]; +} + +static void dump_mem(FILE *out, const char *label, const uint8_t *data, uint32_t size) +{ + fprintf(out, "%s: ", label); + while (size--) + fprintf(out, "%02x ", *data++); +} + +static int dump_event_midi1(FILE *out, const struct midi_event *ev) +{ + fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); + + switch (ev->data[0]) { + case 0x80 ... 0x8f: + fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %3d", + (ev->data[0] & 0x0f) + 1, + note_names[ev->data[1] % 12], ev->data[1] / 12 -1, + ev->data[2]); + break; + case 0x90 ... 0x9f: + fprintf(out, "Note On (channel %2d): note %3s%d, velocity %3d", + (ev->data[0] & 0x0f) + 1, + note_names[ev->data[1] % 12], ev->data[1] / 12 -1, + ev->data[2]); + break; + case 0xa0 ... 0xaf: + fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %3d", + (ev->data[0] & 0x0f) + 1, + note_names[ev->data[1] % 12], ev->data[1] / 12 -1, + ev->data[2]); + break; + case 0xb0 ... 0xbf: + fprintf(out, "Controller (channel %2d): controller %3d (%s), value %3d", + (ev->data[0] & 0x0f) + 1, ev->data[1], + controller_name(ev->data[1]), ev->data[2]); + break; + case 0xc0 ... 0xcf: + fprintf(out, "Program (channel %2d): program %3d (%s)", + (ev->data[0] & 0x0f) + 1, ev->data[1], + program_names[ev->data[1]]); + break; + case 0xd0 ... 0xdf: + fprintf(out, "Channel Pressure (channel %2d): pressure %3d", + (ev->data[0] & 0x0f) + 1, ev->data[1]); + break; + case 0xe0 ... 0xef: + fprintf(out, "Pitch Bend (channel %2d): value %d", (ev->data[0] & 0x0f) + 1, + ((int)ev->data[2] << 7 | ev->data[1]) - 0x2000); + break; + case 0xf0: + case 0xf7: + dump_mem(out, "SysEx", ev->data, ev->size); + break; + case 0xf1: + fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", + ev->data[0] >> 4, ev->data[0] & 0xf); + break; + case 0xf2: + fprintf(out, "Song Position Pointer: value %d", + ((int)ev->data[1] << 7 | ev->data[0])); + break; + case 0xf3: + fprintf(out, "Song Select: value %d", (ev->data[0] & 0x7f)); + break; + case 0xf6: + fprintf(out, "Tune Request"); + break; + case 0xf8: + fprintf(out, "Timing Clock"); + break; + case 0xfa: + fprintf(out, "Start Sequence"); + break; + case 0xfb: + fprintf(out, "Continue Sequence"); + break; + case 0xfc: + fprintf(out, "Stop Sequence"); + break; + case 0xfe: + fprintf(out, "Active Sensing"); + break; + case 0xff: + { + uint8_t *meta = &ev->data[ev->meta.offset]; + fprintf(out, "Meta: "); + switch (ev->data[1]) { + case 0x00: + fprintf(out, "Sequence Number %3d %3d", meta[0], meta[1]); + break; + case 0x01 ... 0x09: + fprintf(out, "%s: %s", event_names[ev->data[1] - 1], meta); + break; + case 0x20: + fprintf(out, "Channel Prefix: %03d", meta[0]); + break; + case 0x21: + fprintf(out, "Midi Port: %03d", meta[0]); + break; + case 0x2f: + fprintf(out, "End Of Track"); + break; + case 0x51: + fprintf(out, "Tempo: %d microseconds per quarter note, %.2f BPM", + ev->meta.parsed.tempo.uspqn, + 60000000.0 / (double)ev->meta.parsed.tempo.uspqn); + break; + case 0x54: + fprintf(out, "SMPTE Offset: %s %02d:%02d:%02d:%02d.%03d", + smpte_rates[(meta[0] & 0x60) >> 5], + meta[0] & 0x1f, meta[1], meta[2], + meta[3], meta[4]); + break; + case 0x58: + fprintf(out, "Time Signature: %d/%d, %d clocks per click, %d notated 32nd notes per quarter note", + meta[0], (int)pow(2, meta[1]), meta[2], meta[3]); + break; + case 0x59: + { + int sf = meta[0]; + fprintf(out, "Key Signature: %d %s: %s", abs(sf), + sf > 0 ? "sharps" : "flats", + meta[1] == 0 ? + major_keys[SPA_CLAMP(sf + 9, 0, 18)] : + minor_keys[SPA_CLAMP(sf + 9, 0, 18)]); + break; + } + case 0x7f: + dump_mem(out, "Sequencer", ev->data, ev->size); + break; + default: + dump_mem(out, "Invalid", ev->data, ev->size); + } + break; + } + default: + dump_mem(out, "Unknown", ev->data, ev->size); + break; + } + return 0; +} + +static int dump_event_midi2_channel(FILE *out, const struct midi_event *ev) +{ + uint32_t *d = (uint32_t*)ev->data; + uint8_t status = d[0] >> 16; + + fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); + + switch (status) { + case 0x00 ... 0x0f: + case 0x10 ... 0x1f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t index = d[0] & 0xff; + fprintf(out, "%s Per-Note controller (channel %2d): note %3s%d, index %u, value %u", + (status & 0xf0) == 0x00 ? "Registered" : "Assignable", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, index, d[1]); + break; + } + case 0x20 ... 0x2f: + case 0x30 ... 0x3f: + { + uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); + fprintf(out, "%s controller (channel %2d): index %u, value %u", + (status & 0xf0) == 0x20 ? "Registered" : "Assignable", + (status & 0x0f) + 1, index, d[1]); + break; + } + case 0x40 ... 0x4f: + case 0x50 ... 0x5f: + { + uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); + fprintf(out, "Relative %s controller (channel %2d): index %u, value %u", + (status & 0xf0) == 0x20 ? "Registered" : "Assignable", + (status & 0x0f) + 1, index, d[1]); + break; + } + case 0x60 ... 0x6f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + fprintf(out, "Per-Note Pitch Bend (channel %2d): note %3s%d, pitch %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, d[1]); + break; + } + case 0x80 ... 0x8f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t attr_type = d[0] & 0xff; + uint16_t velocity = (d[1] >> 16) & 0xffff; + uint16_t attr_data = (d[1]) & 0xffff; + fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, + velocity, attr_type, attr_data); + break; + } + case 0x90 ... 0x9f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t attr_type = d[0] & 0xff; + uint16_t velocity = (d[1] >> 16) & 0xffff; + uint16_t attr_data = (d[1]) & 0xffff; + fprintf(out, "Note On (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, + velocity, attr_type, attr_data); + break; + } + case 0xa0 ... 0xaf: + { + uint8_t note = (d[0] >> 8) & 0x7f; + fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, d[1]); + break; + } + case 0xb0 ... 0xbf: + { + uint8_t index = (d[0] >> 8) & 0x7f; + fprintf(out, "Controller (channel %2d): controller %3d (%s), value %u", + (status & 0x0f) + 1, index, + controller_name(index), d[1]); + break; + } + case 0xc0 ... 0xcf: + { + uint8_t flags = (d[0] & 0xff); + uint8_t program = (d[1] >> 24) & 0x7f; + uint16_t bank = (d[1] & 0x7f) | ((d[1] & 0x7f00) >> 1); + fprintf(out, "Program (channel %2d): flags %u program %3d (%s), bank %u", + (status & 0x0f) + 1, flags, program, + program_names[program], bank); + break; + } + case 0xd0 ... 0xdf: + fprintf(out, "Channel Pressure (channel %2d): pressure %u", + (status & 0x0f) + 1, d[1]); + break; + case 0xe0 ... 0xef: + fprintf(out, "Pitch Bend (channel %2d): value %u", + (status & 0x0f) + 1, d[1]); + break; + case 0xf0 ... 0xff: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t flags = d[0] & 0xff; + fprintf(out, "Per-Note management (channel %2d): note %3s%d, flags %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, flags); + break; + } + default: + dump_mem(out, "Unknown", ev->data, ev->size); + break; + } + + return 0; +} + +static int dump_event_ump(FILE *out, const struct midi_event *ev) +{ + uint32_t *d = (uint32_t*)ev->data; + uint8_t group = (d[0] >> 24) & 0xf; + uint8_t mt = (d[0] >> 28) & 0xf; + int res = 0; + + fprintf(out, "group:%2d ", group); + + switch (mt) { + case 0x0: + dump_mem(out, "Utility", ev->data, ev->size); + break; + case 0x1: + { + uint8_t b[3] = { (d[0] >> 16) & 0x7f, (d[0] >> 8) & 0x7f, d[0] & 0x7f }; + switch (b[0]) { + case 0xf1: + fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", + b[1] >> 4, b[1] & 0xf); + break; + case 0xf2: + fprintf(out, "Song Position Pointer: value %d", + ((int)b[2] << 7 | b[1])); + break; + case 0xf3: + fprintf(out, "Song Select: value %d", b[1]); + break; + case 0xf6: + fprintf(out, "Tune Request"); + break; + case 0xf8: + fprintf(out, "Timing Clock"); + break; + case 0xfa: + fprintf(out, "Start Sequence"); + break; + case 0xfb: + fprintf(out, "Continue Sequence"); + break; + case 0xfc: + fprintf(out, "Stop Sequence"); + break; + case 0xfe: + fprintf(out, "Active Sensing"); + break; + case 0xff: + fprintf(out, "System Reset"); + break; + default: + dump_mem(out, "SysRT", ev->data, ev->size); + break; + } + break; + } + case 0x2: + { + struct midi_event ev1; + uint8_t b[3] = { d[0] >> 16, d[0] >> 8, d[0] }; + + ev1 = *ev; + if (b[0] >= 0xc0 && b[0] <= 0xdf) + ev1.size = 2; + else + ev1.size = 3; + ev1.data = b; + dump_event_midi1(out, &ev1); + break; + } + case 0x3: + { + uint8_t status = (d[0] >> 20) & 0xf; + uint8_t bytes = SPA_CLAMP((d[0] >> 16) & 0xf, 0u, 6u); + uint8_t b[6] = { d[0] >> 8, d[0], d[1] >> 24, d[1] >> 16, d[1] >> 8, d[1] }; + switch (status) { + case 0x0: + dump_mem(out, "SysEx7 (Complete) ", b, bytes); + break; + case 0x1: + dump_mem(out, "SysEx7 (Start) ", b, bytes); + break; + case 0x2: + dump_mem(out, "SysEx7 (Continue) ", b, bytes); + break; + case 0x3: + dump_mem(out, "SysEx7 (End) ", b, bytes); + break; + default: + dump_mem(out, "SysEx7 (invalid)", ev->data, ev->size); + break; + } + break; + } + case 0x4: + res = dump_event_midi2_channel(out, ev); + break; + case 0x5: + dump_mem(out, "Data128", ev->data, ev->size); + break; + default: + dump_mem(out, "Reserved", ev->data, ev->size); + break; + } + return res; +} + +int midi_event_dump(FILE *out, const struct midi_event *ev) +{ + int res; + switch (ev->type) { + case MIDI_EVENT_TYPE_MIDI1: + res = dump_event_midi1(out, ev); + break; + case MIDI_EVENT_TYPE_UMP: + res = dump_event_ump(out, ev); + break; + default: + return -EINVAL; + } + fprintf(out, "\n"); + return res; +} diff --git a/src/tools/midievent.h b/src/tools/midievent.h new file mode 100644 index 000000000..232375772 --- /dev/null +++ b/src/tools/midievent.h @@ -0,0 +1,28 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include + +#include + +struct midi_event { +#define MIDI_EVENT_TYPE_MIDI1 0 +#define MIDI_EVENT_TYPE_UMP 1 + uint32_t type; + uint32_t track; + double sec; + uint8_t *data; + uint32_t size; + struct { + uint32_t offset; + uint32_t size; + union { + struct { + uint32_t uspqn; /* microseconds per quarter note */ + } tempo; + } parsed; + } meta; +}; + +int midi_event_dump(FILE *out, const struct midi_event *event); diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 0aee51638..cc05ee78f 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -538,523 +538,3 @@ int midi_file_write_event(struct midi_file *mf, const struct midi_event *event) } return 0; } - -static const char * const event_names[] = { - "Text", "Copyright", "Sequence/Track Name", - "Instrument", "Lyric", "Marker", "Cue Point", - "Program Name", "Device (Port) Name" -}; - -static const char * const note_names[] = { - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" -}; - -static const char * const controller_names[128] = { - [0] = "Bank Select (coarse)", - [1] = "Modulation Wheel (coarse)", - [2] = "Breath controller (coarse)", - [4] = "Foot Pedal (coarse)", - [5] = "Portamento Time (coarse)", - [6] = "Data Entry (coarse)", - [7] = "Volume (coarse)", - [8] = "Balance (coarse)", - [10] = "Pan position (coarse)", - [11] = "Expression (coarse)", - [12] = "Effect Control 1 (coarse)", - [13] = "Effect Control 2 (coarse)", - [16] = "General Purpose Slider 1", - [17] = "General Purpose Slider 2", - [18] = "General Purpose Slider 3", - [19] = "General Purpose Slider 4", - [32] = "Bank Select (fine)", - [33] = "Modulation Wheel (fine)", - [34] = "Breath (fine)", - [36] = "Foot Pedal (fine)", - [37] = "Portamento Time (fine)", - [38] = "Data Entry (fine)", - [39] = "Volume (fine)", - [40] = "Balance (fine)", - [42] = "Pan position (fine)", - [43] = "Expression (fine)", - [44] = "Effect Control 1 (fine)", - [45] = "Effect Control 2 (fine)", - [64] = "Hold Pedal (on/off)", - [65] = "Portamento (on/off)", - [66] = "Sustenuto Pedal (on/off)", - [67] = "Soft Pedal (on/off)", - [68] = "Legato Pedal (on/off)", - [69] = "Hold 2 Pedal (on/off)", - [70] = "Sound Variation", - [71] = "Sound Timbre", - [72] = "Sound Release Time", - [73] = "Sound Attack Time", - [74] = "Sound Brightness", - [75] = "Sound Control 6", - [76] = "Sound Control 7", - [77] = "Sound Control 8", - [78] = "Sound Control 9", - [79] = "Sound Control 10", - [80] = "General Purpose Button 1 (on/off)", - [81] = "General Purpose Button 2 (on/off)", - [82] = "General Purpose Button 3 (on/off)", - [83] = "General Purpose Button 4 (on/off)", - [91] = "Effects Level", - [92] = "Tremulo Level", - [93] = "Chorus Level", - [94] = "Celeste Level", - [95] = "Phaser Level", - [96] = "Data Button increment", - [97] = "Data Button decrement", - [98] = "Non-registered Parameter (fine)", - [99] = "Non-registered Parameter (coarse)", - [100] = "Registered Parameter (fine)", - [101] = "Registered Parameter (coarse)", - [120] = "All Sound Off", - [121] = "All Controllers Off", - [122] = "Local Keyboard (on/off)", - [123] = "All Notes Off", - [124] = "Omni Mode Off", - [125] = "Omni Mode On", - [126] = "Mono Operation", - [127] = "Poly Operation", -}; - -static const char * const program_names[] = { - "Acoustic Grand", "Bright Acoustic", "Electric Grand", "Honky-Tonk", - "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet", - "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", - "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", - "Rock Organ", "Church Organ", "Reed Organ", "Accoridan", "Harmonica", - "Tango Accordion", "Nylon String Guitar", "Steel String Guitar", - "Electric Jazz Guitar", "Electric Clean Guitar", "Electric Muted Guitar", - "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", - "Acoustic Bass", "Electric Bass (fingered)", "Electric Bass (picked)", - "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", - "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", - "Orchestral Strings", "Timpani", "String Ensemble 1", "String Ensemble 2", - "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", - "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", - "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", - "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", - "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Skakuhachi", - "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", - "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", - "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", - "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", - "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", - "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", - "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", - "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", - "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", - "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", - "Applause", "Gunshot" -}; - -static const char * const smpte_rates[] = { - "24 fps", - "25 fps", - "30 fps (drop frame)", - "30 fps (non drop frame)" -}; - -static const char * const major_keys[] = { - "Unknown major", "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F", - "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "Unknown major" -}; - -static const char * const minor_keys[] = { - "Unknown minor", "Dbm", "Abm", "Ebm", "Bbm", "Fm", "Cm", "Gm", "Dm", - "Am", "Em", "Bm", "F#m", "C#m", "G#m", "D#m", "A#m", "E#m", "Unknown minor" -}; - -static const char *controller_name(uint8_t ctrl) -{ - if (ctrl > 127 || - controller_names[ctrl] == NULL) - return "Unknown"; - return controller_names[ctrl]; -} - -static void dump_mem(FILE *out, const char *label, const uint8_t *data, uint32_t size) -{ - fprintf(out, "%s: ", label); - while (size--) - fprintf(out, "%02x ", *data++); -} - -static int dump_event_midi1(FILE *out, const struct midi_event *ev) -{ - fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); - - switch (ev->data[0]) { - case 0x80 ... 0x8f: - fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %3d", - (ev->data[0] & 0x0f) + 1, - note_names[ev->data[1] % 12], ev->data[1] / 12 -1, - ev->data[2]); - break; - case 0x90 ... 0x9f: - fprintf(out, "Note On (channel %2d): note %3s%d, velocity %3d", - (ev->data[0] & 0x0f) + 1, - note_names[ev->data[1] % 12], ev->data[1] / 12 -1, - ev->data[2]); - break; - case 0xa0 ... 0xaf: - fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %3d", - (ev->data[0] & 0x0f) + 1, - note_names[ev->data[1] % 12], ev->data[1] / 12 -1, - ev->data[2]); - break; - case 0xb0 ... 0xbf: - fprintf(out, "Controller (channel %2d): controller %3d (%s), value %3d", - (ev->data[0] & 0x0f) + 1, ev->data[1], - controller_name(ev->data[1]), ev->data[2]); - break; - case 0xc0 ... 0xcf: - fprintf(out, "Program (channel %2d): program %3d (%s)", - (ev->data[0] & 0x0f) + 1, ev->data[1], - program_names[ev->data[1]]); - break; - case 0xd0 ... 0xdf: - fprintf(out, "Channel Pressure (channel %2d): pressure %3d", - (ev->data[0] & 0x0f) + 1, ev->data[1]); - break; - case 0xe0 ... 0xef: - fprintf(out, "Pitch Bend (channel %2d): value %d", (ev->data[0] & 0x0f) + 1, - ((int)ev->data[2] << 7 | ev->data[1]) - 0x2000); - break; - case 0xf0: - case 0xf7: - dump_mem(out, "SysEx", ev->data, ev->size); - break; - case 0xf1: - fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", - ev->data[0] >> 4, ev->data[0] & 0xf); - break; - case 0xf2: - fprintf(out, "Song Position Pointer: value %d", - ((int)ev->data[1] << 7 | ev->data[0])); - break; - case 0xf3: - fprintf(out, "Song Select: value %d", (ev->data[0] & 0x7f)); - break; - case 0xf6: - fprintf(out, "Tune Request"); - break; - case 0xf8: - fprintf(out, "Timing Clock"); - break; - case 0xfa: - fprintf(out, "Start Sequence"); - break; - case 0xfb: - fprintf(out, "Continue Sequence"); - break; - case 0xfc: - fprintf(out, "Stop Sequence"); - break; - case 0xfe: - fprintf(out, "Active Sensing"); - break; - case 0xff: - { - uint8_t *meta = &ev->data[ev->meta.offset]; - fprintf(out, "Meta: "); - switch (ev->data[1]) { - case 0x00: - fprintf(out, "Sequence Number %3d %3d", meta[0], meta[1]); - break; - case 0x01 ... 0x09: - fprintf(out, "%s: %s", event_names[ev->data[1] - 1], meta); - break; - case 0x20: - fprintf(out, "Channel Prefix: %03d", meta[0]); - break; - case 0x21: - fprintf(out, "Midi Port: %03d", meta[0]); - break; - case 0x2f: - fprintf(out, "End Of Track"); - break; - case 0x51: - fprintf(out, "Tempo: %d microseconds per quarter note, %.2f BPM", - ev->meta.parsed.tempo.uspqn, - 60000000.0 / (double)ev->meta.parsed.tempo.uspqn); - break; - case 0x54: - fprintf(out, "SMPTE Offset: %s %02d:%02d:%02d:%02d.%03d", - smpte_rates[(meta[0] & 0x60) >> 5], - meta[0] & 0x1f, meta[1], meta[2], - meta[3], meta[4]); - break; - case 0x58: - fprintf(out, "Time Signature: %d/%d, %d clocks per click, %d notated 32nd notes per quarter note", - meta[0], (int)pow(2, meta[1]), meta[2], meta[3]); - break; - case 0x59: - { - int sf = meta[0]; - fprintf(out, "Key Signature: %d %s: %s", abs(sf), - sf > 0 ? "sharps" : "flats", - meta[1] == 0 ? - major_keys[SPA_CLAMP(sf + 9, 0, 18)] : - minor_keys[SPA_CLAMP(sf + 9, 0, 18)]); - break; - } - case 0x7f: - dump_mem(out, "Sequencer", ev->data, ev->size); - break; - default: - dump_mem(out, "Invalid", ev->data, ev->size); - } - break; - } - default: - dump_mem(out, "Unknown", ev->data, ev->size); - break; - } - return 0; -} - -static int dump_event_midi2_channel(FILE *out, const struct midi_event *ev) -{ - uint32_t *d = (uint32_t*)ev->data; - uint8_t status = d[0] >> 16; - - fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); - - switch (status) { - case 0x00 ... 0x0f: - case 0x10 ... 0x1f: - { - uint8_t note = (d[0] >> 8) & 0x7f; - uint8_t index = d[0] & 0xff; - fprintf(out, "%s Per-Note controller (channel %2d): note %3s%d, index %u, value %u", - (status & 0xf0) == 0x00 ? "Registered" : "Assignable", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, index, d[1]); - break; - } - case 0x20 ... 0x2f: - case 0x30 ... 0x3f: - { - uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); - fprintf(out, "%s controller (channel %2d): index %u, value %u", - (status & 0xf0) == 0x20 ? "Registered" : "Assignable", - (status & 0x0f) + 1, index, d[1]); - break; - } - case 0x40 ... 0x4f: - case 0x50 ... 0x5f: - { - uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); - fprintf(out, "Relative %s controller (channel %2d): index %u, value %u", - (status & 0xf0) == 0x20 ? "Registered" : "Assignable", - (status & 0x0f) + 1, index, d[1]); - break; - } - case 0x60 ... 0x6f: - { - uint8_t note = (d[0] >> 8) & 0x7f; - fprintf(out, "Per-Note Pitch Bend (channel %2d): note %3s%d, pitch %u", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, d[1]); - break; - } - case 0x80 ... 0x8f: - { - uint8_t note = (d[0] >> 8) & 0x7f; - uint8_t attr_type = d[0] & 0xff; - uint16_t velocity = (d[1] >> 16) & 0xffff; - uint16_t attr_data = (d[1]) & 0xffff; - fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, - velocity, attr_type, attr_data); - break; - } - case 0x90 ... 0x9f: - { - uint8_t note = (d[0] >> 8) & 0x7f; - uint8_t attr_type = d[0] & 0xff; - uint16_t velocity = (d[1] >> 16) & 0xffff; - uint16_t attr_data = (d[1]) & 0xffff; - fprintf(out, "Note On (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, - velocity, attr_type, attr_data); - break; - } - case 0xa0 ... 0xaf: - { - uint8_t note = (d[0] >> 8) & 0x7f; - fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %u", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, d[1]); - break; - } - case 0xb0 ... 0xbf: - { - uint8_t index = (d[0] >> 8) & 0x7f; - fprintf(out, "Controller (channel %2d): controller %3d (%s), value %u", - (status & 0x0f) + 1, index, - controller_name(index), d[1]); - break; - } - case 0xc0 ... 0xcf: - { - uint8_t flags = (d[0] & 0xff); - uint8_t program = (d[1] >> 24) & 0x7f; - uint16_t bank = (d[1] & 0x7f) | ((d[1] & 0x7f00) >> 1); - fprintf(out, "Program (channel %2d): flags %u program %3d (%s), bank %u", - (status & 0x0f) + 1, flags, program, - program_names[program], bank); - break; - } - case 0xd0 ... 0xdf: - fprintf(out, "Channel Pressure (channel %2d): pressure %u", - (status & 0x0f) + 1, d[1]); - break; - case 0xe0 ... 0xef: - fprintf(out, "Pitch Bend (channel %2d): value %u", - (status & 0x0f) + 1, d[1]); - break; - case 0xf0 ... 0xff: - { - uint8_t note = (d[0] >> 8) & 0x7f; - uint8_t flags = d[0] & 0xff; - fprintf(out, "Per-Note management (channel %2d): note %3s%d, flags %u", - (status & 0x0f) + 1, - note_names[note % 12], note / 12 -1, flags); - break; - } - default: - dump_mem(out, "Unknown", ev->data, ev->size); - break; - } - - return 0; -} - -static int dump_event_ump(FILE *out, const struct midi_event *ev) -{ - uint32_t *d = (uint32_t*)ev->data; - uint8_t group = (d[0] >> 24) & 0xf; - uint8_t mt = (d[0] >> 28) & 0xf; - int res = 0; - - fprintf(out, "group:%2d ", group); - - switch (mt) { - case 0x0: - dump_mem(out, "Utility", ev->data, ev->size); - break; - case 0x1: - { - uint8_t b[3] = { (d[0] >> 16) & 0x7f, (d[0] >> 8) & 0x7f, d[0] & 0x7f }; - switch (b[0]) { - case 0xf1: - fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d", - b[1] >> 4, b[1] & 0xf); - break; - case 0xf2: - fprintf(out, "Song Position Pointer: value %d", - ((int)b[2] << 7 | b[1])); - break; - case 0xf3: - fprintf(out, "Song Select: value %d", b[1]); - break; - case 0xf6: - fprintf(out, "Tune Request"); - break; - case 0xf8: - fprintf(out, "Timing Clock"); - break; - case 0xfa: - fprintf(out, "Start Sequence"); - break; - case 0xfb: - fprintf(out, "Continue Sequence"); - break; - case 0xfc: - fprintf(out, "Stop Sequence"); - break; - case 0xfe: - fprintf(out, "Active Sensing"); - break; - case 0xff: - fprintf(out, "System Reset"); - break; - default: - dump_mem(out, "SysRT", ev->data, ev->size); - break; - } - break; - } - case 0x2: - { - struct midi_event ev1; - uint8_t b[3] = { d[0] >> 16, d[0] >> 8, d[0] }; - - ev1 = *ev; - if (b[0] >= 0xc0 && b[0] <= 0xdf) - ev1.size = 2; - else - ev1.size = 3; - ev1.data = b; - dump_event_midi1(out, &ev1); - break; - } - case 0x3: - { - uint8_t status = (d[0] >> 20) & 0xf; - uint8_t bytes = SPA_CLAMP((d[0] >> 16) & 0xf, 0u, 6u); - uint8_t b[6] = { d[0] >> 8, d[0], d[1] >> 24, d[1] >> 16, d[1] >> 8, d[1] }; - switch (status) { - case 0x0: - dump_mem(out, "SysEx7 (Complete) ", b, bytes); - break; - case 0x1: - dump_mem(out, "SysEx7 (Start) ", b, bytes); - break; - case 0x2: - dump_mem(out, "SysEx7 (Continue) ", b, bytes); - break; - case 0x3: - dump_mem(out, "SysEx7 (End) ", b, bytes); - break; - default: - dump_mem(out, "SysEx7 (invalid)", ev->data, ev->size); - break; - } - break; - } - case 0x4: - res = dump_event_midi2_channel(out, ev); - break; - case 0x5: - dump_mem(out, "Data128", ev->data, ev->size); - break; - default: - dump_mem(out, "Reserved", ev->data, ev->size); - break; - } - return res; -} - -int midi_file_dump_event(FILE *out, const struct midi_event *ev) -{ - int res; - switch (ev->type) { - case MIDI_EVENT_TYPE_MIDI1: - res = dump_event_midi1(out, ev); - break; - case MIDI_EVENT_TYPE_UMP: - res = dump_event_ump(out, ev); - break; - default: - return -EINVAL; - } - fprintf(out, "\n"); - return res; -} diff --git a/src/tools/midifile.h b/src/tools/midifile.h index 6b3c23b2a..375877311 100644 --- a/src/tools/midifile.h +++ b/src/tools/midifile.h @@ -6,26 +6,9 @@ #include -struct midi_file; +#include "midievent.h" -struct midi_event { -#define MIDI_EVENT_TYPE_MIDI1 0 -#define MIDI_EVENT_TYPE_UMP 1 - uint32_t type; - uint32_t track; - double sec; - uint8_t *data; - uint32_t size; - struct { - uint32_t offset; - uint32_t size; - union { - struct { - uint32_t uspqn; /* microseconds per quarter note */ - } tempo; - } parsed; - } meta; -}; +struct midi_file; struct midi_file_info { uint16_t format; @@ -43,5 +26,3 @@ int midi_file_next_time(struct midi_file *mf, double *sec); int midi_file_read_event(struct midi_file *mf, struct midi_event *event); int midi_file_write_event(struct midi_file *mf, const struct midi_event *event); - -int midi_file_dump_event(FILE *out, const struct midi_event *event); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index e433c0e55..d017b2316 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1172,7 +1172,7 @@ static int midi_play(struct data *d, void *src, unsigned int n_frames, bool *nul midi_file_read_event(d->midi.file, &ev); if (d->verbose) - midi_file_dump_event(stderr, &ev); + midi_event_dump(stderr, &ev); size = ev.size; @@ -1261,7 +1261,7 @@ static int midi_record(struct data *d, void *src, unsigned int n_frames, bool *n ev.size = c.value.size; if (d->verbose) - midi_file_dump_event(stderr, &ev); + midi_event_dump(stderr, &ev); midi_file_write_event(d->midi.file, &ev); } diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index 2a17ef05c..f7ba5fcba 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -50,7 +50,7 @@ static int dump_file(const char *filename) printf("opened %s format:%u ntracks:%u division:%u\n", filename, info.format, info.ntracks, info.division); while (midi_file_read_event(file, &ev) == 1) { - midi_file_dump_event(stdout, &ev); + midi_event_dump(stdout, &ev); } midi_file_close(file); @@ -107,7 +107,7 @@ static void on_process(void *_data, struct spa_io_position *position) ev.size = c.value.size; fprintf(stdout, "%4d: ", c.offset); - midi_file_dump_event(stdout, &ev); + midi_event_dump(stdout, &ev); } done: From e2ac91b8601bcf1ed2cbc83bd2530412e3b49a19 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 16:59:38 +0200 Subject: [PATCH 0783/1014] ump: make sure we set the group correctly --- spa/include/spa/control/ump-utils.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h index 52738897f..582c44e2d 100644 --- a/spa/include/spa/control/ump-utils.h +++ b/spa/include/spa/control/ump-utils.h @@ -223,15 +223,15 @@ SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_siz break; case 0xf2: to_consume = 3; - prefix = 0x10000000; + prefix |= 0x10000000; break; case 0xf1: case 0xf3: to_consume = 2; - prefix = 0x10000000; + prefix |= 0x10000000; break; case 0xf4 ... 0xff: to_consume = 1; - prefix = 0x10000000; + prefix |= 0x10000000; break; default: return -EIO; From 70ec3aec64fcfcdeeb64e93a9d2cfe92eed4572e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:00:06 +0200 Subject: [PATCH 0784/1014] tools: debug utility messages better --- src/tools/midievent.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tools/midievent.c b/src/tools/midievent.c index e9f6a9e82..e7f08660b 100644 --- a/src/tools/midievent.c +++ b/src/tools/midievent.c @@ -422,7 +422,22 @@ static int dump_event_ump(FILE *out, const struct midi_event *ev) switch (mt) { case 0x0: - dump_mem(out, "Utility", ev->data, ev->size); + switch ((d[0] >> 20) & 0xf) { + case 0x1: + fprintf(out, "JR clock: value %d", d[0] & 0xffff); + break; + case 0x2: + fprintf(out, "JR timestamp: value %d", d[0] & 0xffff); + break; + case 0x3: + fprintf(out, "DCTPQ: value %d", d[0] & 0xffff); + break; + case 0x4: + fprintf(out, "DC: value %d", d[0] & 0xfffff); + break; + default: + dump_mem(out, "Utility unkown", ev->data, ev->size); + } break; case 0x1: { From 4b177f4557c7cc5b23819bb0332cea70ac25a6df Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:00:41 +0200 Subject: [PATCH 0785/1014] tools: add guards around header include --- src/tools/midievent.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/midievent.h b/src/tools/midievent.h index 232375772..c2e1d978d 100644 --- a/src/tools/midievent.h +++ b/src/tools/midievent.h @@ -2,6 +2,9 @@ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#ifndef MIDI_EVENT_H +#define MIDI_EVENT_H + #include #include @@ -26,3 +29,5 @@ struct midi_event { }; int midi_event_dump(FILE *out, const struct midi_event *event); + +#endif /* MIDI_EVENT_H */ From b19209935365948a6b8e8ce8c1b84bb2a714f2e4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:01:29 +0200 Subject: [PATCH 0786/1014] tools: don't generate error when closing read midi file --- src/tools/midifile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/midifile.c b/src/tools/midifile.c index cc05ee78f..974cec6e5 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -311,7 +311,7 @@ int midi_file_close(struct midi_file *mf) CHECK_RES(write_n(mf->file, buf, 4)); mf->tracks[0].size += 4; CHECK_RES(write_headers(mf)); - } else + } else if (mf->mode != 1) return -EINVAL; if (mf->close) From eda32908830ceff272920b96dfc4f02c9a05bdb9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:34:34 +0200 Subject: [PATCH 0787/1014] tools: add midi clip support The SMF2 CLIP format is the official container for storing MIDI 2.0 messages. Add support in mididump and pw-cat. --- src/tools/meson.build | 3 +- src/tools/midiclip.c | 327 ++++++++++++++++++++++++++++++++++++++++ src/tools/midiclip.h | 27 ++++ src/tools/pw-cat.c | 159 ++++++++++++++++++- src/tools/pw-mididump.c | 31 +++- 5 files changed, 540 insertions(+), 7 deletions(-) create mode 100644 src/tools/midiclip.c create mode 100644 src/tools/midiclip.h diff --git a/src/tools/meson.build b/src/tools/meson.build index 8651bd3b1..300a9cb4c 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -5,7 +5,7 @@ tools_sources = [ [ 'pw-dot', [ 'pw-dot.c' ] ], [ 'pw-dump', [ 'pw-dump.c' ] ], [ 'pw-profiler', [ 'pw-profiler.c' ] ], - [ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c', 'midievent.c' ] ], + [ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c', 'midievent.c', 'midiclip.c' ] ], [ 'pw-metadata', [ 'pw-metadata.c' ] ], [ 'pw-loopback', [ 'pw-loopback.c' ] ], [ 'pw-link', [ 'pw-link.c' ] ], @@ -48,6 +48,7 @@ if get_option('pw-cat').allowed() and sndfile_dep.found() pwcat_sources = [ 'pw-cat.c', 'midifile.c', + 'midiclip.c', 'midievent.c', 'dfffile.c', 'dsffile.c', diff --git a/src/tools/midiclip.c b/src/tools/midiclip.c new file mode 100644 index 000000000..4d7741d44 --- /dev/null +++ b/src/tools/midiclip.c @@ -0,0 +1,327 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "midiclip.h" + +#define DEFAULT_BPM 120 +#define SEC_AS_10NS 100000000.0 +#define MINUTE_10NS 6000000000 /* in 10ns units */ +#define DEFAULT_TEMPO MINUTE_10NS/DEFAULT_BPM + +struct midi_clip { + int mode; + FILE *file; + bool close; + int64_t count; + + uint8_t data[16]; + + uint32_t next[4]; + int num; + + bool pass_all; + struct midi_clip_info info; + uint32_t tempo; + + int64_t tick; + int64_t tick_start; + double tick_sec; +}; + +static int read_header(struct midi_clip *mc) +{ + uint8_t data[8]; + + if (fread(data, sizeof(data), 1, mc->file) != 1 || + memcmp(data, "SMF2CLIP", 4) != 0) + return -EINVAL; + return 0; +} + +static inline int read_word(struct midi_clip *mc, uint32_t *val) +{ + uint32_t v; + if (fread(&v, 4, 1, mc->file) != 1) + return 0; + *val = be32toh(v); + return 1; +} + +static inline int read_ump(struct midi_clip *mc) +{ + int i, num; + mc->num = 0; + if (read_word(mc, &mc->next[0]) != 1) + return 0; + num = spa_ump_message_size(mc->next[0]>>28); + for (i = 1; i < num; i++) { + if (read_word(mc, &mc->next[i]) != 1) + return 0; + } + return mc->num = num; +} + +static int next_packet(struct midi_clip *mc) +{ + while (read_ump(mc) > 0) { + uint8_t type = mc->next[0] >> 28; + + switch (type) { + case 0x0: /* utility */ + switch ((mc->next[0] >> 20) & 0xf) { + case 0x3: /* DCTPQ */ + mc->info.division = (mc->next[0] & 0xffff); + break; + case 0x4: /* DC */ + mc->tick += (mc->next[0] & 0xfffff); + break; + } + break; + case 0x2: /* midi 1.0 */ + case 0x3: /* sysex 7bits */ + case 0x4: /* midi 2.0 */ + return mc->num; + case 0xd: /* flex data */ + if (((mc->next[0] >> 8) & 0xff) == 0 && + (mc->next[0] & 0xff) == 0) + mc->tempo = mc->next[1]; + break; + case 0xf: /* stream */ + break; + default: + break; + } + if (mc->pass_all) + return mc->num; + } + return 0; +} + +static int open_read(struct midi_clip *mc, const char *filename, struct midi_clip_info *info) +{ + int res; + + if (strcmp(filename, "-") != 0) { + if ((mc->file = fopen(filename, "r")) == NULL) { + res = -errno; + goto exit; + } + mc->close = true; + } else { + mc->file = stdin; + mc->close = false; + } + + if ((res = read_header(mc)) < 0) + goto exit_close; + + mc->tempo = DEFAULT_TEMPO; + mc->tick = 0; + mc->mode = 1; + + next_packet(mc); + *info = mc->info; + return 0; + +exit_close: + if (mc->close) + fclose(mc->file); +exit: + return res; +} + +static inline int write_n(FILE *file, const void *buf, int count) +{ + return fwrite(buf, 1, count, file) == (size_t)count ? count : -errno; +} + +static inline int write_be32(FILE *file, uint32_t val) +{ + uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val }; + return write_n(file, buf, 4); +} + +#define CHECK_RES(expr) if ((res = (expr)) < 0) return res + +static int write_headers(struct midi_clip *mc) +{ + int res; + CHECK_RES(write_n(mc->file, "SMF2CLIP", 8)); + + /* DC 0 */ + CHECK_RES(write_be32(mc->file, 0x00400000)); + /* DCTPQ division */ + CHECK_RES(write_be32(mc->file, 0x00300000 | mc->info.division)); + /* tempo */ + CHECK_RES(write_be32(mc->file, 0xd0100000)); + CHECK_RES(write_be32(mc->file, mc->tempo)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + /* start */ + CHECK_RES(write_be32(mc->file, 0xf0200000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + + return 0; +} + +static int open_write(struct midi_clip *mc, const char *filename, struct midi_clip_info *info) +{ + int res; + + if (info->format != 0) + return -EINVAL; + if (info->division == 0) + info->division = 96; + + if (strcmp(filename, "-") != 0) { + if ((mc->file = fopen(filename, "w")) == NULL) { + res = -errno; + goto exit; + } + mc->close = true; + } else { + mc->file = stdout; + mc->close = false; + } + mc->mode = 2; + mc->tempo = DEFAULT_TEMPO; + mc->info = *info; + + res = write_headers(mc); +exit: + return res; +} + +struct midi_clip * +midi_clip_open(const char *filename, const char *mode, struct midi_clip_info *info) +{ + int res; + struct midi_clip *mc; + + mc = calloc(1, sizeof(struct midi_clip)); + if (mc == NULL) + return NULL; + + if (spa_streq(mode, "r")) { + if ((res = open_read(mc, filename, info)) < 0) + goto exit_free; + } else if (spa_streq(mode, "w")) { + if ((res = open_write(mc, filename, info)) < 0) + goto exit_free; + } else { + res = -EINVAL; + goto exit_free; + } + return mc; + +exit_free: + free(mc); + errno = -res; + return NULL; +} + +int midi_clip_close(struct midi_clip *mc) +{ + int res; + + if (mc->mode == 2) { + CHECK_RES(write_be32(mc->file, 0xf0210000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + CHECK_RES(write_be32(mc->file, 0x00000000)); + } else if (mc->mode != 1) + return -EINVAL; + + if (mc->close) + fclose(mc->file); + free(mc); + return 0; +} + +int midi_clip_next_time(struct midi_clip *mc, double *sec) +{ + if (mc->num <= 0) + return 0; + + if (mc->info.division == 0) + *sec = 0.0; + else + *sec = mc->tick_sec + ((mc->tick - mc->tick_start) * (double)mc->tempo) / + (SEC_AS_10NS * mc->info.division); + return 1; +} + +int midi_clip_read_event(struct midi_clip *mc, struct midi_event *event) +{ + if (midi_clip_next_time(mc, &event->sec) != 1) + return 0; + event->track = 0; + event->type = MIDI_EVENT_TYPE_UMP; + event->data = mc->data; + event->size = mc->num * 4; + memcpy(mc->data, mc->next, event->size); + + next_packet(mc); + return 1; +} + +int midi_clip_write_event(struct midi_clip *mc, const struct midi_event *event) +{ + uint32_t tick; + void *data; + size_t size; + int res, i, ump_size; + int32_t diff; + uint32_t ump[4], *ump_data; + uint64_t state = 0; + + spa_return_val_if_fail(event != NULL, -EINVAL); + spa_return_val_if_fail(mc != NULL, -EINVAL); + spa_return_val_if_fail(event->track == 0, -EINVAL); + spa_return_val_if_fail(event->size > 1, -EINVAL); + + data = event->data; + size = event->size; + + tick = (uint32_t)(event->sec * (SEC_AS_10NS * mc->info.division) / (double)mc->tempo); + + diff = mc->count++ == 0 ? 0 : tick - mc->tick; + if (diff > 0 || mc->count == 1) + CHECK_RES(write_be32(mc->file, 0x00400000 | diff)); + mc->tick = tick; + + while (size > 0) { + switch (event->type) { + case MIDI_EVENT_TYPE_UMP: + ump_data = data; + ump_size = size; + size = 0; + break; + case MIDI_EVENT_TYPE_MIDI1: + ump_size = spa_ump_from_midi((uint8_t**)&data, &size, + ump, sizeof(ump), event->track, &state); + if (ump_size <= 0) + return ump_size; + ump_data = ump; + break; + default: + return -EINVAL; + } + for (i = 0; i < ump_size/4; i++) + CHECK_RES(write_be32(mc->file, ump_data[i])); + } + return 0; +} diff --git a/src/tools/midiclip.h b/src/tools/midiclip.h new file mode 100644 index 000000000..8c02a0b00 --- /dev/null +++ b/src/tools/midiclip.h @@ -0,0 +1,27 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include + +#include + +#include "midievent.h" + +struct midi_clip; + +struct midi_clip_info { + uint16_t format; + uint16_t division; +}; + +struct midi_clip * +midi_clip_open(const char *filename, const char *mode, struct midi_clip_info *info); + +int midi_clip_close(struct midi_clip *mc); + +int midi_clip_next_time(struct midi_clip *mc, double *sec); + +int midi_clip_read_event(struct midi_clip *mc, struct midi_event *event); + +int midi_clip_write_event(struct midi_clip *mc, const struct midi_event *event); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index d017b2316..a9cd4460f 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -43,6 +43,7 @@ #endif #include "midifile.h" +#include "midiclip.h" #include "dfffile.h" #include "dsffile.h" @@ -104,6 +105,7 @@ struct data { #define TYPE_ENCODED 3 #endif #define TYPE_SYSEX 4 +#define TYPE_MIDI2 5 int data_type; bool raw; const char *remote_name; @@ -147,6 +149,10 @@ struct data { #define MIDI_FORCE_MIDI1 2 int force_type; } midi; + struct { + struct midi_clip *file; + struct midi_clip_info info; + } clip; struct { struct dsf_file *file; struct dsf_file_info info; @@ -1063,6 +1069,7 @@ static const struct option long_options[] = { { "raw", no_argument, NULL, 'a' }, { "force-midi", required_argument, NULL, 'M' }, { "sample-count", required_argument, NULL, 'n' }, + { "midi-clip", no_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } }; @@ -1127,6 +1134,7 @@ static void show_usage(const char *name, bool is_error) " -o, --encoded Encoded mode\n" #endif " -s, --sysex SysEx mode\n" + " -c, --midi-clip MIDI clip mode\n" "\n"), fp); } } @@ -1297,6 +1305,143 @@ static int setup_midifile(struct data *data) return 0; } +static int clip_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame) +{ + int res; + struct spa_pod_builder b; + struct spa_pod_frame f; + uint32_t first_frame, last_frame; + bool have_data = false; + + spa_zero(b); + spa_pod_builder_init(&b, src, n_frames); + + spa_pod_builder_push_sequence(&b, &f, 0); + + first_frame = d->clock_time; + last_frame = first_frame + d->position->clock.duration; + d->clock_time = last_frame; + + while (1) { + uint32_t frame; + struct midi_event ev; + uint64_t state = 0; + size_t size; + + res = midi_clip_next_time(d->clip.file, &ev.sec); + if (res <= 0) { + if (have_data) + break; + return res; + } + + frame = (uint32_t)(ev.sec * d->position->clock.rate.denom); + if (frame < first_frame) + frame = 0; + else if (frame < last_frame) + frame -= first_frame; + else + break; + + midi_clip_read_event(d->clip.file, &ev); + + if (d->verbose) + midi_event_dump(stderr, &ev); + + size = ev.size; + + if (d->midi.force_type == MIDI_FORCE_MIDI1) { + const uint32_t *data = (const uint32_t*)ev.data; + while (size > 0) { + uint8_t ev[16]; + int ev_size = spa_ump_to_midi(&data, &size, + ev, sizeof(ev), &state); + if (ev_size <= 0) + break; + + spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&b, ev, ev_size); + } + } else { + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ev.data, ev.size); + } + have_data = true; + } + spa_pod_builder_pop(&b, &f); + + return b.state.offset; +} + +static int clip_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame) +{ + struct spa_pod_parser parser; + struct spa_pod_frame frame; + struct spa_pod_sequence seq; + const void *seq_body, *c_body; + struct spa_pod_control c; + uint32_t offset; + + offset = d->clock_time; + d->clock_time += d->position->clock.duration; + + spa_pod_parser_init_from_data(&parser, src, n_frames, 0, n_frames); + + if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) + return 0; + + while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { + struct midi_event ev; + + switch (c.type) { + case SPA_CONTROL_UMP: + ev.type = MIDI_EVENT_TYPE_UMP; + break; + case SPA_CONTROL_Midi: + ev.type = MIDI_EVENT_TYPE_MIDI1; + break; + default: + continue; + } + ev.track = 0; + ev.sec = (offset + c.offset) / (float) d->position->clock.rate.denom; + ev.data = (uint8_t*)c_body; + ev.size = c.value.size; + + if (d->verbose) + midi_event_dump(stderr, &ev); + + midi_clip_write_event(d->clip.file, &ev); + } + return 0; +} + +static int setup_midiclip(struct data *data) +{ + if (data->mode == mode_record) { + spa_zero(data->clip.info); + data->clip.info.format = 0; + } + + data->clip.file = midi_clip_open(data->filename, + data->mode == mode_playback ? "r" : "w", + &data->clip.info); + if (data->clip.file == NULL) { + fprintf(stderr, "midiclip: can't read midi file '%s': %m\n", data->filename); + return -errno; + } + + if (data->verbose) + fprintf(stderr, "midifile: opened file \"%s\" format %08x div:%d\n", + data->filename, + data->clip.info.format, data->clip.info.division); + + data->fill = data->mode == mode_playback ? clip_play : clip_record; + data->stride = 1; + + return 0; +} + static int sysex_play(struct data *d, void *dst, unsigned int n_frames, bool *null_frame) { struct spa_pod_builder b; @@ -1881,9 +2026,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:n:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdosR:q:P:aM:n:c", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:n:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdsR:q:P:aM:n:c", long_options, NULL)) != -1) { #endif switch (c) { @@ -2019,6 +2164,9 @@ int main(int argc, char *argv[]) case 'n': data.sample_limit = strtoull(optarg, NULL, 10); break; + case 'c': + data.data_type = TYPE_MIDI2; + break; default: goto error_usage; } @@ -2032,6 +2180,7 @@ int main(int argc, char *argv[]) if (!data.media_type) { switch (data.data_type) { case TYPE_MIDI: + case TYPE_MIDI2: case TYPE_SYSEX: data.media_type = DEFAULT_MIDI_MEDIA_TYPE; break; @@ -2112,6 +2261,9 @@ int main(int argc, char *argv[]) case TYPE_MIDI: ret = setup_midifile(&data); break; + case TYPE_MIDI2: + ret = setup_midiclip(&data); + break; case TYPE_DSD: ret = setup_dsdfile(&data); break; @@ -2185,6 +2337,7 @@ int main(int argc, char *argv[]) break; } case TYPE_MIDI: + case TYPE_MIDI2: case TYPE_SYSEX: params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, @@ -2307,6 +2460,8 @@ error_no_main_loop: sf_close(data.file); if (data.midi.file) midi_file_close(data.midi.file); + if (data.clip.file) + midi_clip_close(data.clip.file); if (data.dsf.file) dsf_file_close(data.dsf.file); if (data.dff.file) diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index f7ba5fcba..277784bb1 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -19,6 +19,7 @@ #include #include "midifile.h" +#include "midiclip.h" struct data; @@ -35,6 +36,29 @@ struct data { bool opt_midi1; }; + +static int dump_clip(const char *filename) +{ + struct midi_clip *file; + struct midi_clip_info info; + struct midi_event ev; + + file = midi_clip_open(filename, "r", &info); + if (file == NULL) { + fprintf(stderr, "error opening %s: %m\n", filename); + return -1; + } + + printf("opened %s format:%u division:%u\n", filename, info.format, info.division); + + while (midi_clip_read_event(file, &ev) == 1) + midi_event_dump(stdout, &ev); + + midi_clip_close(file); + + return 0; +} + static int dump_file(const char *filename) { struct midi_file *file; @@ -43,15 +67,14 @@ static int dump_file(const char *filename) file = midi_file_open(filename, "r", &info); if (file == NULL) { - fprintf(stderr, "error opening %s: %m\n", filename); - return -1; + return dump_clip(filename); } printf("opened %s format:%u ntracks:%u division:%u\n", filename, info.format, info.ntracks, info.division); - while (midi_file_read_event(file, &ev) == 1) { + while (midi_file_read_event(file, &ev) == 1) midi_event_dump(stdout, &ev); - } + midi_file_close(file); return 0; From 9eb6cda2456c83d2bb900490e2ef5334fd153fd4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:43:13 +0200 Subject: [PATCH 0788/1014] tools: add pw-midi2play and pw-midi2record aliases And update the documentation. --- doc/dox/programs/pw-cat.1.md | 28 ++++++++++++++++++++-------- src/tools/meson.build | 2 ++ src/tools/pw-cat.c | 6 ++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/doc/dox/programs/pw-cat.1.md b/doc/dox/programs/pw-cat.1.md index 4860c427f..b6e259a6f 100644 --- a/doc/dox/programs/pw-cat.1.md +++ b/doc/dox/programs/pw-cat.1.md @@ -14,6 +14,10 @@ Play and record media with PipeWire **pw-midirecord** \[*options*\] \[*FILE* \| -\] +**pw-midi2play** \[*options*\] \[*FILE* \| -\] + +**pw-midi2record** \[*options*\] \[*FILE* \| -\] + **pw-dsdplay** \[*options*\] \[*FILE* \| -\] # DESCRIPTION @@ -24,10 +28,10 @@ supported by `libsndfile` for PCM capture and playback. When capturing PCM, the filename extension is used to guess the file format with the WAV file format as the default. -It understands standard MIDI files for playback and recording. This tool -will not render MIDI files, it will simply make the MIDI events -available to the graph. You need a MIDI renderer such as qsynth, -timidity or a hardware MIDI rendered to hear the MIDI. +It understands standard MIDI files and MIDI 2.0 clip files for playback +and recording. This tool will not render MIDI files, it will simply make +the MIDI events available to the graph. You need a MIDI renderer such as +qsynth, timidity or a hardware MIDI renderer to hear the MIDI. DSD playback is supported with the DSF file format. This tool will only work with native DSD capable hardware and will produce an error when no @@ -53,13 +57,13 @@ connection is made to the default PipeWire instance. \par -p | \--playback Playback mode. Read data from the specified file, and play it back. If -the tool is called under the name **pw-play** or **pw-midiplay** this is -the default. +the tool is called under the name **pw-play**, **pw-midiplay** or +**pw-midi2play** this is the default. \par -r | \--record Recording mode. Capture data and write it to the specified file. If the -tool is called under the name **pw-record** or **pw-midirecord** this is -the default. +tool is called under the name **pw-record**, **pw-midirecord** or +**pw-midi2record** this is the default. \par -m | \--midi MIDI mode. *FILE* is a MIDI file. If the tool is called under the name @@ -69,6 +73,14 @@ simply provide the MIDI events in the graph. You need a separate MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. +\par -c | \--midi-clip +MIDI 2.0 clip mode. *FILE* is a MIDI 2.0 clip file. If the tool is called +under the name **pw-midi2play** or **pw-midi2record** this is the default. +Note that this program will *not* render the MIDI events into audible +samples, it will simply provide the MIDI events in the graph. You need a +separate MIDI renderer such as qsynth, timidity or a hardware renderer to +hear the MIDI. + \par -d | \--dsd DSD mode. *FILE* is a DSF file. If the tool is called under the name **pw-dsdplay** this is the default. Note that this program will *not* diff --git a/src/tools/meson.build b/src/tools/meson.build index 300a9cb4c..8147906fb 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -59,6 +59,8 @@ if get_option('pw-cat').allowed() and sndfile_dep.found() 'pw-record', 'pw-midiplay', 'pw-midirecord', + 'pw-midi2play', + 'pw-midi2record', 'pw-dsdplay', 'pw-encplay', 'pw-sysex', diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index a9cd4460f..d179d0a67 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -1997,6 +1997,12 @@ int main(int argc, char *argv[]) } else if (spa_streq(prog, "pw-midirecord")) { data.mode = mode_record; data.data_type = TYPE_MIDI; + } else if (spa_streq(prog, "pw-midi2play")) { + data.mode = mode_playback; + data.data_type = TYPE_MIDI2; + } else if (spa_streq(prog, "pw-midi2record")) { + data.mode = mode_record; + data.data_type = TYPE_MIDI2; } else if (spa_streq(prog, "pw-sysex")) { data.mode = mode_playback; data.data_type = TYPE_SYSEX; From 8425307ca13df816549f1c204b57dfd90d7633e5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 29 Aug 2025 17:59:00 +0200 Subject: [PATCH 0789/1014] tools: fix compilation on alpine --- src/tools/midiclip.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tools/midiclip.c b/src/tools/midiclip.c index 4d7741d44..2d2449c9b 100644 --- a/src/tools/midiclip.c +++ b/src/tools/midiclip.c @@ -9,6 +9,8 @@ #include #include +#include + #include #include @@ -54,7 +56,7 @@ static inline int read_word(struct midi_clip *mc, uint32_t *val) uint32_t v; if (fread(&v, 4, 1, mc->file) != 1) return 0; - *val = be32toh(v); + *val = ntohl(v); return 1; } @@ -148,8 +150,8 @@ static inline int write_n(FILE *file, const void *buf, int count) static inline int write_be32(FILE *file, uint32_t val) { - uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val }; - return write_n(file, buf, 4); + uint32_t v = htonl(val); + return write_n(file, &v, 4); } #define CHECK_RES(expr) if ((res = (expr)) < 0) return res From 4796b3fb9524c20ac0f5006143b6a13ee50c01ec Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Fri, 29 Aug 2025 08:29:23 +0200 Subject: [PATCH 0790/1014] systemd: Allow mincore syscal for Mesa/EGL This is required in order to allow plugins to use GL as mincore is used in Mesas `_eglPointerIsDereferenceable()`. One example for a client wanting to do so is the in-development libcamera GPUISP, see https://patchwork.libcamera.org/cover/24183/ --- src/daemon/systemd/user/pipewire.service.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in index f0c8d9cd5..27818b4b9 100644 --- a/src/daemon/systemd/user/pipewire.service.in +++ b/src/daemon/systemd/user/pipewire.service.in @@ -22,7 +22,7 @@ MemoryDenyWriteExecute=yes NoNewPrivileges=yes RestrictNamespaces=yes SystemCallArchitectures=native -SystemCallFilter=@system-service +SystemCallFilter=@system-service mincore Type=simple ExecStart=@PW_BINARY@ Restart=on-failure From 0310bb5c5c43933c6c02ad6ed57c527e8cc7ed81 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 1 Sep 2025 12:39:08 +0200 Subject: [PATCH 0791/1014] audiommixer: only clear mix_ops when initialized It's possible that the mix_ops was not initialized and then the free pointer is NULL, so check this instead of segfaulting. --- spa/plugins/audiomixer/audiomixer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 80d4bac02..4e9333dca 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -931,7 +931,8 @@ static int impl_clear(struct spa_handle *handle) free(port); } spa_list_init(&this->mix_list); - mix_ops_free(&this->ops); + if (this->ops.free) + mix_ops_free(&this->ops); return 0; } From 370d19057259abb7e322ae328c0fc8042bc2cb63 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 1 Sep 2025 12:55:51 +0200 Subject: [PATCH 0792/1014] pulse: improve stream suspended state handling Only send out SUSPENDED event when there is a change in the suspended state. This avoids sending out unsuspend events when we simply uncork. Implement the fail_on_suspend flag for capture and playback streams. Instead of suspending those streams, we need to kill them. --- .../module-protocol-pulse/pulse-server.c | 20 +++++++++++++++---- src/modules/module-protocol-pulse/stream.c | 4 ++-- src/modules/module-protocol-pulse/stream.h | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 5f5cd558f..944066852 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1130,12 +1130,22 @@ static void stream_state_changed(void *data, enum pw_stream_state old, /* Don't emit suspended if we are creating a corked stream, as that will have a quick * RUNNING/SUSPENDED transition for initial negotiation */ if (stream->create_tag == SPA_ID_INVALID && !stream->corked) { - if (old == PW_STREAM_STATE_PAUSED && state == PW_STREAM_STATE_STREAMING) + if (old == PW_STREAM_STATE_PAUSED && state == PW_STREAM_STATE_STREAMING && + stream->is_suspended) { stream_send_suspended(stream, false); - if (old == PW_STREAM_STATE_STREAMING && state == PW_STREAM_STATE_PAUSED) - stream_send_suspended(stream, true); + stream->is_suspended = false; + } + if (old == PW_STREAM_STATE_STREAMING && state == PW_STREAM_STATE_PAUSED && + !stream->is_suspended) { + if (stream->fail_on_suspend) { + stream->killed = true; + destroy_stream = true; + } else { + stream_send_suspended(stream, true); + } + stream->is_suspended = true; + } } - if (destroy_stream) { pw_work_queue_add(impl->work_queue, stream, 0, do_destroy_stream, NULL); @@ -1742,6 +1752,7 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui stream->muted_set = muted_set; stream->is_underrun = true; stream->underrun_for = -1; + stream->fail_on_suspend = fail_on_suspend; pw_properties_set(props, "pulse.corked", corked ? "true" : "false"); @@ -2017,6 +2028,7 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint stream->volume_set = volume_set; stream->muted = muted; stream->muted_set = muted_set; + stream->fail_on_suspend = fail_on_suspend; if (client->quirks & QUIRK_REMOVE_CAPTURE_DONT_MOVE) no_move = false; diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 7d0ac0cf4..7b7f6edc1 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -321,8 +321,8 @@ int stream_send_suspended(struct stream *stream, bool suspended) struct message *reply; uint32_t command; - pw_log_debug("client %p [%s]: stream %p SUSPENDED channel:%u", - client, client->name, stream, stream->channel); + pw_log_debug("client %p [%s]: stream %p SUSPENDED %d channel:%u", + client, client->name, stream, suspended, stream->channel); command = stream->direction == PW_DIRECTION_OUTPUT ? COMMAND_PLAYBACK_STREAM_SUSPENDED : diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index 3d017f391..7807fd491 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -98,6 +98,8 @@ struct stream { unsigned int pending:1; unsigned int is_idle:1; unsigned int is_paused:1; + unsigned int fail_on_suspend:1; + unsigned int is_suspended:1; }; struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag, From d46a4686a8f9488662ecb2e9b5a7b69d0b020b01 Mon Sep 17 00:00:00 2001 From: James Seo Date: Mon, 1 Sep 2025 09:55:33 +0000 Subject: [PATCH 0793/1014] stream: fix comment for requested field of pw_buffer The requested field was added in 0.3.50, not 0.3.49. --- src/pipewire/stream.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index c636497d7..28b4eba1f 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -269,7 +269,7 @@ struct pw_buffer { * suggested amount of data to provide. For audio * streams this will be the amount of frames * required by the resampler. This field is 0 - * when no suggestion is provided. Since 0.3.49 */ + * when no suggestion is provided. Since 0.3.50 */ uint64_t time; /**< For capture streams, this field contains the * cycle time in nanoseconds when this buffer was * queued in the stream. It can be compared against From 0d42f11b8760a71ea9816b2b8a41a1eb8c5ec8b7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 1 Sep 2025 13:31:47 +0200 Subject: [PATCH 0794/1014] v4l2: delay pipewire init until first openat call Initialization of PipeWire could happen too early and deadlock in some cases. Instead, initialize pipewire right before we're going to actually use it for the first time. Fixes #4859 --- pipewire-v4l2/src/pipewire-v4l2.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 779c52f7d..a3397ef64 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -772,6 +772,19 @@ static int v4l2_dup(int oldfd) return do_dup(oldfd, FD_MAP_DUP); } +/* Deferred PipeWire init (called on first device access) */ +static void pipewire_init_func(void) +{ + pw_init(NULL, NULL); + PW_LOG_TOPIC_INIT(v4l2_log_topic); +} + +static void ensure_pipewire_init(void) +{ + static pthread_once_t pipewire_once = PTHREAD_ONCE_INIT; + pthread_once(&pipewire_once, pipewire_init_func); +} + static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) { int res, flags; @@ -795,6 +808,8 @@ static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) if (passthrough) return globals.old_fops.openat(dirfd, path, oflag, mode); + ensure_pipewire_init(); + pw_log_info("path:%s oflag:%d mode:%d", path, oflag, mode); if ((file = find_file_by_dev(dev_id)) != NULL) { @@ -2565,10 +2580,14 @@ static void initialize(void) globals.old_fops.ioctl = dlsym(RTLD_NEXT, "ioctl"); globals.old_fops.mmap = dlsym(RTLD_NEXT, "mmap64"); globals.old_fops.munmap = dlsym(RTLD_NEXT, "munmap"); - - pw_init(NULL, NULL); - PW_LOG_TOPIC_INIT(v4l2_log_topic); - + /* NOTE: + * We avoid calling pw_init() here (constructor/early init path) because + * that can deadlock in certain host processes (e.g. Zoom >= 5.0) when + * the preload causes PipeWire initialisation to run too early. + * + * PipeWire initialisation (pw_init + PW_LOG_TOPIC_INIT) is deferred + * to ensure it runs on-demand in the first actual V4L2 open call. + */ pthread_mutex_init(&globals.lock, NULL); pw_array_init(&globals.file_maps, 1024); pw_array_init(&globals.fd_maps, 256); From 0095d79ef8ffd0d737d2b640cbb4e9d6efd8ea1f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 2 Sep 2025 14:13:19 +0200 Subject: [PATCH 0795/1014] alsa: use 2 (or 3 for batch) periods in IRQ mode Some drivers (Firewire) have a latency depending on the ALSA buffer size instead of the period size. In IRQ mode, we can safely use 2 (or 3 for batch devices) periods because we always need to reconfigure the hardware when we want to change the period and so we don't need to keep some headroom like we do for timer based scheduling. See #4785 --- spa/plugins/alsa/alsa-pcm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 559c3fec8..7e9efeae4 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2337,8 +2337,13 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->period_frames = period_size; - if (state->default_period_num != 0) { - periods = state->default_period_num; + if (state->default_period_num != 0 || state->disable_tsched) { + if (state->default_period_num != 0) + /* period number given use that */ + periods = state->default_period_num; + else + /* IRQ mode, use 2 periods or 3 for batch */ + periods = state->is_batch ? 3 : 2; CHECK(snd_pcm_hw_params_set_periods_near(hndl, params, &periods, &dir), "set_periods"); state->buffer_frames = period_size * periods; } else { From 220bdf8a00de16fdd949f0b050aa45afc9e05f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 2 Sep 2025 13:18:19 +0200 Subject: [PATCH 0796/1014] pipewire/stream: Don't queue back cleared buffers When renegotiating stream parameters (e.g. size), the buffers are cleared should no longer be queued back. Add a flag to detect this, while logging a warning and erroring out when the user tries to queue such a buffer. --- src/pipewire/stream.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 4c279fe7b..cacb48b59 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -40,7 +40,8 @@ struct buffer { uint32_t id; #define BUFFER_FLAG_MAPPED (1 << 0) #define BUFFER_FLAG_QUEUED (1 << 1) -#define BUFFER_FLAG_ADDED (1 << 2) +#define BUFFER_FLAG_DEQUEUED (1 << 2) +#define BUFFER_FLAG_ADDED (1 << 3) uint32_t flags; struct spa_meta_busy *busy; }; @@ -2513,6 +2514,9 @@ struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream) return NULL; } } + + SPA_FLAG_SET(b->flags, BUFFER_FLAG_DEQUEUED); + return &b->this; } @@ -2523,6 +2527,13 @@ int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer) struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this); int res; + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_DEQUEUED)) { + pw_log_warn("%p: tried to queue cleared buffer %d", stream, b->id); + return -EINVAL; + } + + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_DEQUEUED); + if (b->busy) SPA_ATOMIC_DEC(b->busy->count); From b6ce585da6ea540ef12894c0fa8d30c5051a002b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 2 Sep 2025 16:37:53 +0200 Subject: [PATCH 0797/1014] stream: remove QUEUED buffer flag The flag was used to see if a buffer was in a queue or not but that doesn't really matter much and with the DEQUEUED flag we can only move buffers from dequeued to queued. --- src/pipewire/stream.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index cacb48b59..75ba9eaee 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -39,9 +39,8 @@ struct buffer { struct pw_buffer this; uint32_t id; #define BUFFER_FLAG_MAPPED (1 << 0) -#define BUFFER_FLAG_QUEUED (1 << 1) -#define BUFFER_FLAG_DEQUEUED (1 << 2) -#define BUFFER_FLAG_ADDED (1 << 3) +#define BUFFER_FLAG_DEQUEUED (1 << 1) +#define BUFFER_FLAG_ADDED (1 << 2) uint32_t flags; struct spa_meta_busy *busy; }; @@ -358,11 +357,9 @@ static inline int queue_push(struct stream *stream, struct queue *queue, struct { uint32_t index; - if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED) || - buffer->id >= stream->n_buffers) + if (buffer->id >= stream->n_buffers) return -EINVAL; - SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); queue->incount += buffer->this.size; spa_ringbuffer_get_write_index(&queue->ring, &index); @@ -393,7 +390,6 @@ static inline struct buffer *queue_pop(struct stream *stream, struct queue *queu buffer = &stream->buffers[id]; queue->outcount += buffer->this.size; - SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED); return buffer; } @@ -2563,7 +2559,6 @@ static inline int queue_push_front(struct stream *stream, struct queue *queue, s index -= 1; queue->ids[index & MASK_BUFFERS] = buffer->id; queue->outcount -= buffer->this.size; - SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); spa_ringbuffer_read_update(&queue->ring, index); return ret; From 00bb4a936a3dd0ab2668b5c9f2b3ef009a93a38e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 2 Sep 2025 16:46:03 +0200 Subject: [PATCH 0798/1014] filter: removed QUEUED flag and add DEQUEUED flag Remove the QUEUED flags to check if a buffer is in some queue. Add a new flag to check if a buffer was dequeued by the application. Check if the application only queues buffers with the DEQUEUED flag set. --- src/pipewire/filter.c | 24 +++++++++++++----------- src/pipewire/stream.c | 3 +-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index a92819710..b78b827ed 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -36,7 +36,7 @@ struct buffer { struct pw_buffer this; uint32_t id; #define BUFFER_FLAG_MAPPED (1 << 0) -#define BUFFER_FLAG_QUEUED (1 << 1) +#define BUFFER_FLAG_DEQUEUED (1 << 1) #define BUFFER_FLAG_ADDED (1 << 2) uint32_t flags; }; @@ -356,11 +356,9 @@ static inline int push_queue(struct port *port, struct queue *queue, struct buff { uint32_t index; - if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED)) + if (buffer->id >= port->n_buffers) return -EINVAL; - SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); - spa_ringbuffer_get_write_index(&queue->ring, &index); queue->ids[index & MASK_BUFFERS] = buffer->id; spa_ringbuffer_write_update(&queue->ring, index + 1); @@ -382,7 +380,6 @@ static inline struct buffer *pop_queue(struct port *port, struct queue *queue) spa_ringbuffer_read_update(&queue->ring, index + 1); buffer = &port->buffers[id]; - SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED); return buffer; } @@ -941,6 +938,7 @@ static int impl_port_use_buffers(void *object, pw_log_debug("%p: got buffer id:%d datas:%d mapped size %d", filter, i, buffers[i]->n_datas, size); } + port->n_buffers = n_buffers; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; @@ -953,11 +951,9 @@ static int impl_port_use_buffers(void *object, } SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED); + pw_filter_emit_add_buffer(filter, port->user_data, &b->this); } - - port->n_buffers = n_buffers; - return 0; } @@ -970,9 +966,7 @@ static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffe return -EINVAL; pw_log_trace("%p: recycle buffer %d", impl, buffer_id); - if (buffer_id < port->n_buffers) - push_queue(port, &port->queued, &port->buffers[buffer_id]); - + push_queue(port, &port->queued, &port->buffers[buffer_id]); return 0; } @@ -2014,6 +2008,7 @@ struct pw_buffer *pw_filter_dequeue_buffer(void *port_data) return NULL; } pw_log_trace_fp("%p: dequeue buffer %d", p->filter, b->id); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_DEQUEUED); return &b->this; } @@ -2023,6 +2018,13 @@ int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer) { struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data); struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this); + + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_DEQUEUED)) { + pw_log_warn("%p: tried to queue cleared buffer %d", p->filter, b->id); + return -EINVAL; + } + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_DEQUEUED); + pw_log_trace_fp("%p: queue buffer %d", p->filter, b->id); return push_queue(p, &p->queued, b); } diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 75ba9eaee..904e44a67 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1045,8 +1045,7 @@ static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffe { struct stream *d = object; pw_log_trace("%p: recycle buffer %d", d, buffer_id); - if (buffer_id < d->n_buffers) - queue_push(d, &d->queued, &d->buffers[buffer_id]); + queue_push(d, &d->queued, &d->buffers[buffer_id]); return 0; } From 9606b377769c8a4a9c9d5a6db5c824606308699c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 2 Sep 2025 17:29:26 +0200 Subject: [PATCH 0799/1014] alsa: use 3 periods in IRQ mode by default 3 seems to work better as a default for Firewire. It does not actually add latency because we only keep 1 period filled with data at all times. --- spa/plugins/alsa/alsa-pcm.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 7e9efeae4..2a795309f 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2342,8 +2342,12 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ /* period number given use that */ periods = state->default_period_num; else - /* IRQ mode, use 2 periods or 3 for batch */ - periods = state->is_batch ? 3 : 2; + /* IRQ mode, use 3 periods. This is a bit of a workaround + * for Firewire devices, which seem to only work with 3 periods. + * For PipeWire it does not actually matter how many periods + * are used, we will always keep 1 filled, so we can work fine + * with anything from 2 periods to MAX. */ + periods = 3; CHECK(snd_pcm_hw_params_set_periods_near(hndl, params, &periods, &dir), "set_periods"); state->buffer_frames = period_size * periods; } else { From 0f6aae914f7a60e0970cab400a8416543369bbdc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 2 Sep 2025 18:52:38 +0200 Subject: [PATCH 0800/1014] alsa: don't add MAX_LATENCY when using IRQ scheduling The Max latency property only works for timer based scheduling so that we don't select a quantum larger than we can handle in our buffer. With IRQ based scheduling this does not make sense because we will reconfigure the buffer completely when we change quantums and so the currently selected buffer size does not limit the latency in any way. Fixes #4877 --- spa/plugins/alsa/alsa-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2a795309f..2c785872e 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3877,7 +3877,7 @@ void spa_alsa_emit_node_info(struct state *state, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, state->props.media_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); - if (state->have_format) + if (state->have_format && !state->disable_tsched) snprintf(latency, sizeof(latency), "%lu/%d", state->buffer_frames / (2 * state->frame_scale), state->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency[0] ? latency : NULL); From 233b7f1d4ada8d3ad3bb90ad34f1e121bbd4e562 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 3 Sep 2025 10:01:00 +0200 Subject: [PATCH 0801/1014] audiomixer: format is Id --- spa/plugins/audiomixer/audiomixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 4e9333dca..2d4dfd6cb 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -342,7 +342,7 @@ static int port_enum_formats(void *object, struct port *port, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Int(12, + SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(12, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16, From 9f88d6997f10983e9c5dfa0ee80c2df983efd24f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 3 Sep 2025 10:01:38 +0200 Subject: [PATCH 0802/1014] audiomixer: set change mask correctly --- spa/plugins/audiomixer/audiomixer.c | 2 ++ spa/plugins/audiomixer/mixer-dsp.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 2d4dfd6cb..f7558ef47 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -998,6 +998,7 @@ impl_init(const struct spa_handle_factory *factory, this->info.max_output_ports = 1; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; + this->info_all = this->info.change_mask; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; @@ -1006,6 +1007,7 @@ impl_init(const struct spa_handle_factory *factory, port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->info_all = port->info.change_mask; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index 50b582d1d..c3c17f541 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -933,6 +933,7 @@ impl_init(const struct spa_handle_factory *factory, this->info.max_output_ports = 1; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; + this->info_all = this->info.change_mask; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; @@ -941,6 +942,7 @@ impl_init(const struct spa_handle_factory *factory, port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->info_all = port->info.change_mask; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); From 1bf5ca28d830413aa56d62ff1f9cbe24a867b120 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 3 Sep 2025 10:38:59 +0200 Subject: [PATCH 0803/1014] modules: nmake dynamic ports work in link-factory Just making a port and adding it to a node does not make it a working port.. Make a new node function to get a new free port, this will actually call the implementation spa_node_add_port(), which will add the new port which we can then pick up and use. See #4876 --- src/modules/module-link-factory.c | 20 ++------------------ src/pipewire/impl-node.c | 13 +++++++++++++ src/pipewire/impl-node.h | 2 ++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index 8daa3b11c..d54df2758 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -279,28 +279,12 @@ static const struct pw_impl_link_events link_events = { static struct pw_impl_port *get_port(struct pw_impl_node *node, enum spa_direction direction) { struct pw_impl_port *p; - struct pw_context *context = pw_impl_node_get_context(node); - int res; p = pw_impl_node_find_port(node, direction, PW_ID_ANY); - if (p == NULL || pw_impl_port_is_linked(p)) { - uint32_t port_id; + if (p == NULL || pw_impl_port_is_linked(p)) + p = pw_impl_node_get_free_port(node, direction); - port_id = pw_impl_node_get_free_port_id(node, direction); - if (port_id == SPA_ID_INVALID) - return NULL; - - p = pw_context_create_port(context, direction, port_id, NULL, 0); - if (p == NULL) - return NULL; - - if ((res = pw_impl_port_add(p, node)) < 0) { - pw_log_warn("can't add port: %s", spa_strerror(res)); - errno = -res; - return NULL; - } - } return p; } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 822c86b21..2c1db3e2a 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -2709,6 +2709,19 @@ error: return SPA_ID_INVALID; } +SPA_EXPORT +struct pw_impl_port *pw_impl_node_get_free_port(struct pw_impl_node *node, enum pw_direction direction) +{ + uint32_t port_id = pw_impl_node_get_free_port_id(node, direction); + if (port_id == SPA_ID_INVALID) + return NULL; + + spa_node_add_port(node->node, direction, port_id, NULL); + + return pw_impl_node_find_port(node, direction, port_id); +} + + static void on_state_complete(void *obj, void *data, int res, uint32_t seq) { struct pw_impl_node *node = obj; diff --git a/src/pipewire/impl-node.h b/src/pipewire/impl-node.h index 908b92f88..7f7edb37a 100644 --- a/src/pipewire/impl-node.h +++ b/src/pipewire/impl-node.h @@ -168,6 +168,8 @@ pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, u /** Get a free unused port_id from the node */ uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction); +/** Get a free unused port from the node */ +struct pw_impl_port *pw_impl_node_get_free_port(struct pw_impl_node *node, enum pw_direction direction); int pw_impl_node_initialized(struct pw_impl_node *node); From 98b7a34102858e224382e270c844f180041a81aa Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 30 Aug 2025 15:09:40 +0300 Subject: [PATCH 0804/1014] doc: support alternative index name in @IDX@ --- doc/input-filter-md.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/doc/input-filter-md.py b/doc/input-filter-md.py index 60aa46279..cd86d23df 100755 --- a/doc/input-filter-md.py +++ b/doc/input-filter-md.py @@ -25,7 +25,7 @@ Assumes BUILD_DIR environment variable is set. containing all index items from the specified section. -# Section title @IDX@
+# Section title @IDX@
[] Adds the section title to the index, and expands to an anchor @@ -51,7 +51,7 @@ def index_key(section, name): BUILD_DIR = os.environ["BUILD_DIR"] PAR_RE = r"^@PAR@\s+([^\s]*)[ \t]+(\S+)(.*)$" -IDX_RE = r"^(#+)(.*)@IDX@[ \t]+(\S+)[ \t]*$" +IDX_RE = r"^(#+)(.*)@IDX@[ \t]+(\S+)([ \t]+\S+)?[ \t]*$" SECREF_RE = r"^@SECREF@[ \t]+([^\n]*)[ \t]*$" @@ -71,10 +71,16 @@ def main(args): level = m.group(1) title = name = m.group(2).strip() section = m.group(3) + alt = m.group(4) if title == title.upper(): name = name.capitalize() key = index_key(section, name) - return f"{level} {title} {{#{key}}}" + text = f"{level} {title} {{#{key}}}" + if alt and alt.strip(): + alt_key = index_key(section, alt.strip()) + if alt_key != key: + text += f"\n\\anchor {alt_key}" + return text def secref(m): import os @@ -148,9 +154,12 @@ def load_index(sections, text): def idx(m): name = m.group(2).strip() section = m.group(3) + alt = m.group(4) if name == name.upper(): name = name.capitalize() sections.setdefault(section, []).append(name) + if alt and alt.strip(): + sections.setdefault(section, []).append(alt.strip()) return "" text = re.sub(PAR_RE, par, text, flags=re.M) From bde2aa34ef16f57ccfa119195ea4e3e302a19f6f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 30 Aug 2025 15:26:44 +0300 Subject: [PATCH 0805/1014] doc: improve property documentation Add some more examples, and more xref links to index. --- doc/dox/config/pipewire-client.conf.5.md | 8 +- doc/dox/config/pipewire-jack.conf.5.md | 4 +- doc/dox/config/pipewire-props.7.md | 98 ++++++++++++++++++------ doc/dox/config/pipewire-pulse.conf.5.md | 8 +- doc/dox/config/pipewire.conf.5.md | 16 ++-- 5 files changed, 91 insertions(+), 43 deletions(-) diff --git a/doc/dox/config/pipewire-client.conf.5.md b/doc/dox/config/pipewire-client.conf.5.md index 66bf3d2ec..38da058d0 100644 --- a/doc/dox/config/pipewire-client.conf.5.md +++ b/doc/dox/config/pipewire-client.conf.5.md @@ -48,7 +48,7 @@ ALSA client match rules. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". -# STREAM PROPERTIES @IDX@ client.conf +# STREAM PROPERTIES @IDX@ client.conf stream.properties The client configuration files contain a stream.properties section that configures the options for client streams: ```css @@ -93,7 +93,7 @@ A list of object properties that can be applied to streams can be found in and \ref props__audio_converter_properties "pipewire-props(7) Audio Adapter Properties" -# STREAM RULES @IDX@ client.conf +# STREAM RULES @IDX@ client.conf stream.rules You can add \ref pipewire_conf__match_rules "match rules, see pipewire(1)" to set properties for certain streams and filters. @@ -127,7 +127,7 @@ stream.rules = [ Will set the node.name of Firefox to "My Name". -# ALSA CLIENT PROPERTIES @IDX@ client.conf +# ALSA CLIENT PROPERTIES @IDX@ client.conf alsa.properties An `alsa.properties` section can be added to configure client applications that connect via the PipeWire ALSA plugin. @@ -169,7 +169,7 @@ The number of bytes in the alsa buffer. The default is 0, which is to allow any This controls the volume curve used on the ALSA mixer. Possible values are `cubic` and `linear`. The default is to use `cubic`. -# ALSA CLIENT RULES @IDX@ client.conf +# ALSA CLIENT RULES @IDX@ client.conf alsa.rules It is possible to set ALSA client specific properties by using \ref pipewire_conf__match_rules "Match rules, see pipewire(1)". You can diff --git a/doc/dox/config/pipewire-jack.conf.5.md b/doc/dox/config/pipewire-jack.conf.5.md index b8a4a5cf0..8636aab21 100644 --- a/doc/dox/config/pipewire-jack.conf.5.md +++ b/doc/dox/config/pipewire-jack.conf.5.md @@ -38,7 +38,7 @@ JACK client match rules. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". -# JACK PROPERTIES @IDX@ jack.conf +# JACK PROPERTIES @IDX@ jack.conf jack.properties The configuration file can contain an extra JACK specific section called `jack.properties` like this: ```css @@ -206,7 +206,7 @@ JACK apps don't know about this flag yet and refuse to show the port. Set this to true for applications that know how to handle MIDI2 ports. \endparblock -# MATCH RULES @IDX@ jack.conf +# MATCH RULES @IDX@ jack.conf jack.rules `jack.rules` provides an `update-props` action that takes an object with properties that are updated on the client and node object of the jack client. diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index b47489718..338c3f604 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -41,32 +41,19 @@ objects. Usually, all device properties are configured in the session manager configuration, see the session manager documentation. Application properties are configured in -``client.conf`` (for native PipeWire and ALSA applications), and -``pipewire-pulse.conf`` (for Pulseaudio applications). +``client.conf`` (for native PipeWire and ALSA applications), +``pipewire-pulse.conf`` (for Pulseaudio applications), and +``jack.conf`` (for JACK applications). In minimal PipeWire setups without a session manager, the device properties can be configured via \ref pipewire_conf__context_objects "context.objects in pipewire.conf(5)" when creating the devices. -\see [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) - ## Examples -Client configuration (requires client application restart to apply) -```css -# ~/.config/pipewire/client.conf/custom-props.conf - -stream.rules = [ - { - matches = [ { application.name = "pw-play" } ] - actions = { update-props = { node.description = "Some pw-cat stream" } } - } -] -``` -\see \ref client_conf__stream_rules "pipewire-client.conf(5)", \ref client_conf__stream_rules "pipewire-pulse.conf(5)" - -Device configuration (using WirePlumber; requires WirePlumber restart to apply): +Device configuration using WirePlumber (requires WirePlumber restart to apply). +See [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) ```css # ~/.config/wireplumber/wireplumber.conf.d/custom-props.conf @@ -77,14 +64,18 @@ monitor.alsa.properties = { monitor.alsa.rules = [ { matches = [ { device.name = "~alsa_card.pci-.*" } ], - actions = { update-props = { api.alsa.soft-mixer = true } ] + actions = { update-props = { api.alsa.soft-mixer = true } } }, { matches = [ { node.name = "alsa_output.pci-0000_03_00.1.hdmi-stereo-extra3" } ] - actions = { update-props = { node.description = "Main Audio" } ] + actions = { update-props = { node.description = "Main Audio" } } } ] +monitor.alsa-midi.properties = { + api.alsa.seq.ump = true +} + monitor.bluez.properties = { bluez5.hfphsp-backend = ofono } @@ -92,12 +83,49 @@ monitor.bluez.properties = { monitor.bluez.rules = [ { matches = [ { device.name = "~bluez_card.*" } ], - actions = { update-props = { bluez5.dummy-avrcp player = true } ] + actions = { update-props = { bluez5.dummy-avrcp player = true } } } ] ``` -\see [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) +Native client configuration (requires client application restart to apply). +See \ref client_conf__stream_rules "pipewire-client.conf(5)" +```css +# ~/.config/pipewire/client.conf/custom-props.conf + +stream.rules = [ + { + matches = [ { application.name = "pw-play" } ] + actions = { update-props = { node.description = "Some pw-cat stream" } } + } +] +``` + +Pulseaudio client configuration (requires \ref page_man_pipewire-pulse_1 "pipewire-pulse(1)" restart to apply). +See \ref pipewire-pulse_conf__stream_rules "pipewire-pulse.conf(5)" +```css +# ~/.config/pipewire/pipewire-pulse.conf/custom-props.conf + +stream.rules = [ + { + matches = [ { application.name = "paplay" } ] + actions = { update-props = { node.description = "Some paplay stream" } } + } +] +``` + +JACK client configuration (requires client restart to apply). +See \ref jack_conf__match_rules "pipewire-jack.conf(5)" +```css +# ~/.config/pipewire/jack.conf/custom-props.conf + +jack.rules = [ + { + matches = [ { client.name = "jack_delay" } ] + actions = { update-props = { node.description = "Some JACK node" } } + } +] +``` # COMMON DEVICE PROPERTIES @IDX@ props @@ -106,9 +134,6 @@ These are common properties for devices. @PAR@ device-prop device.name # string A (unique) name for the device. It can be used by command-line and other tools to identify the device. -@PAR@ device-prop device.nick # string -A short name for the device. - @PAR@ device-prop device.param.PARAM = { ... } # JSON \parblock Set value of a device \ref spa_param_type "Param" to a JSON value when the device is loaded. @@ -901,6 +926,29 @@ Informative property. \endparblock +# ALSA MIDI PROPERTIES @IDX@ props + +## Node properties + +For ALSA MIDI in Wireplumber, MIDI bridge node properties are +configured in the monitor properties. + +@PAR@ monitor-prop api.alsa.seq.ump = true # boolean +Use MIDI 2.0 if possible. + +@PAR@ monitor-prop api.alsa.seq.min-pool = 500 # integer + +@PAR@ monitor-prop api.alsa.seq.max-pool = 2000 # integer + +@PAR@ monitor-prop clock.name = "clock.system.monotonic" # string +Clock to follow. + +@PAR@ monitor-prop api.alsa.path = "default" # string +Sequencer device to use. + +@PAR@ monitor-prop api.alsa.disable-longname = true # boolean +If card long name should not be passed to MIDI port. + # BLUETOOTH PROPERTIES @IDX@ props ## Monitor properties diff --git a/doc/dox/config/pipewire-pulse.conf.5.md b/doc/dox/config/pipewire-pulse.conf.5.md index d1a65cf49..ad1b213c7 100644 --- a/doc/dox/config/pipewire-pulse.conf.5.md +++ b/doc/dox/config/pipewire-pulse.conf.5.md @@ -54,7 +54,7 @@ for the detailed description. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". -# STREAM PROPERTIES @IDX@ pipewire-pulse.conf +# STREAM PROPERTIES @IDX@ pipewire-pulse.conf stream.properties The `stream.properties` section contains properties for streams created by the pipewire-pulse server. @@ -100,18 +100,18 @@ stream.properties = { } ``` -# STREAM RULES @IDX@ pipewire-pulse.conf +# STREAM RULES @IDX@ pipewire-pulse.conf stream.rules The `stream.rules` section works the same as \ref client_conf__stream_rules "pipewire-client.conf(5) stream.rules". -# PULSEAUDIO PROPERTIES @IDX@ pipewire-pulse.conf +# PULSEAUDIO PROPERTIES @IDX@ pipewire-pulse.conf pulse.properties For `pulse.properties` section, see \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)" for available options. -# PULSEAUDIO RULES @IDX@ pipewire-pulse.conf +# PULSEAUDIO RULES @IDX@ pipewire-pulse.conf pulse.rules For each client, a set of rules can be written in `pulse.rules` section to configure quirks of the client or to force some pulse diff --git a/doc/dox/config/pipewire.conf.5.md b/doc/dox/config/pipewire.conf.5.md index dcb1a0a04..57ea8c3de 100644 --- a/doc/dox/config/pipewire.conf.5.md +++ b/doc/dox/config/pipewire.conf.5.md @@ -140,7 +140,7 @@ Array of dictionaries. Match rules for modifying device properties on the server. -# CONTEXT PROPERTIES @IDX@ pipewire.conf +# CONTEXT PROPERTIES @IDX@ pipewire.conf context.properties Available PipeWire properties in `context.properties` and possible default values. @@ -302,7 +302,7 @@ the `context.modules` and `context.objects` sections can declare additional conditions that control whether a module or object is loaded depending on what properties are present. -# SPA LIBRARIES @IDX@ pipewire.conf +# SPA LIBRARIES @IDX@ pipewire.conf context.spa-libs SPA plugins are loaded based on their factory-name. This is a well known name that uniquely describes the features that the plugin should @@ -331,7 +331,7 @@ context.spa-libs = { } ``` -# MODULES @IDX@ pipewire.conf +# MODULES @IDX@ pipewire.conf context.modules PipeWire modules to be loaded. See \ref page_man_libpipewire-modules_7 "libpipewire-modules(7)". @@ -364,7 +364,7 @@ A \ref pipewire_conf__match_rules "match rule" `matches` condition. The module is loaded only if one of the expressions in the array matches to a context property. -# CONTEXT OBJECTS @IDX@ pipewire.conf +# CONTEXT OBJECTS @IDX@ pipewire.conf context.objects The `context.objects` section allows you to make some objects from factories (usually created by loading modules in `context.modules`). @@ -417,7 +417,7 @@ context.objects = [ ] ``` -# COMMAND EXECUTION @IDX@ pipewire.conf +# COMMAND EXECUTION @IDX@ pipewire.conf context.exec The `context.exec` section can be used to start arbitrary commands as part of the initialization of the PipeWire program. @@ -590,7 +590,7 @@ matches = [ ``` -# CONTEXT PROPERTIES RULES @IDX@ pipewire.conf +# CONTEXT PROPERTIES RULES @IDX@ pipewire.conf context.properties.rules `context.properties.rules` can be used to dynamically update the properties based on other properties. @@ -614,7 +614,7 @@ context.properties.rules = [ } ``` -# NODE RULES @IDX@ pipewire.conf +# NODE RULES @IDX@ pipewire.conf node.rules The node.rules are evaluated every time the properties on a node are set or updated. This can be used on the server side to override client set @@ -647,7 +647,7 @@ node.rules = [ Will set the `node.force-quantum` property of `jack_simple_client` to 512. -# DEVICE RULES @IDX@ pipewire.conf +# DEVICE RULES @IDX@ pipewire.conf device.rules The device.rules are evaluated every time the properties on a device are set or updated. This can be used on the server side to override client set From 36871ff1ffeb2531420f34e9a6892f350f9d44e6 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 31 Aug 2025 11:43:31 +0300 Subject: [PATCH 0806/1014] doc: show tabs on top on doxygen >= 1.13 --- doc/Doxyfile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 4d9dc63bf..f8cdbf528 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -38,6 +38,7 @@ IGNORE_PREFIX = pw_ \ spa_ \ SPA_ GENERATE_TREEVIEW = YES +DISABLE_INDEX = NO SEARCHENGINE = YES GENERATE_LATEX = NO From 0f1e4f870616042ecd0c2d157a46daa96e646394 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 2 Sep 2025 20:02:25 +0300 Subject: [PATCH 0807/1014] doc: add more properties missing from docs --- doc/dox/config/pipewire-props.7.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 338c3f604..6b854873c 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -744,6 +744,9 @@ See \ref spa_param_port_config for the meaning. Use \ref monitor-prop__alsa_card_profiles "ALSA Card Profiles" (ACP) for device configuration. This autodetects available ALSA devices and configures port and hardware mixers. +@PAR@ monitor-prop alsa.use-ucm # boolean +Enable or disable UCM for all devices. Default: unset. + @PAR@ monitor-prop alsa.udev.expose-busy # boolean Expose the ALSA card even if it is busy/in use. Default false. This can be useful when some of the PCMs are in use by other applications but the other free PCMs should still be exposed. @@ -898,12 +901,18 @@ Static set the device systemic latency, in nanoseconds. @PAR@ node-prop api.alsa.path # string UNDOCUMENTED +@PAR@ node-prop api.alsa.pcm.card # integer +Card index to open. Usually determined from `api.alsa.path`. + @PAR@ node-prop api.alsa.open.ucm # boolean Open device using UCM. @PAR@ node-prop api.alsa.bind-ctls # boolean UNDOCUMENTED +@PAR@ node-prop api.alsa.bind-ctl.NAME # boolean +UNDOCUMENTED + @PAR@ node-prop iec958.codecs # JSON array of string Enable only specific IEC958 codecs. This can be used to disable some codecs the hardware supports. Available values: PCM, AC3, DTS, MPEG, MPEG2-AAC, EAC3, TRUEHD, DTSHD @@ -925,6 +934,9 @@ Informative property. Informative property. \endparblock +@PAR@ node-prop api.alsa.dsd-lsb = false # boolean +Use LSB bit order for DSD audio output. + # ALSA MIDI PROPERTIES @IDX@ props @@ -937,8 +949,10 @@ configured in the monitor properties. Use MIDI 2.0 if possible. @PAR@ monitor-prop api.alsa.seq.min-pool = 500 # integer +UNDOCUMENTED @PAR@ monitor-prop api.alsa.seq.max-pool = 2000 # integer +UNDOCUMENTED @PAR@ monitor-prop clock.name = "clock.system.monotonic" # string Clock to follow. From 0877eba761e5f8993fbbc0457c9d7c0948d431e0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 3 Sep 2025 14:38:03 +0200 Subject: [PATCH 0808/1014] tools: add Latency reporting to pw-link Rework how the monitor mode works. Instead of having separate paths for the list and monitor mode, reuse the list mode. We simply mark all changes and then list the changes in a loop. This makes it possible to accumulate some updates and print them together. Add a -t option to list the latency params on a port. --- doc/dox/programs/pw-link.1.md | 5 +- src/tools/pw-link.c | 273 ++++++++++++++++++++-------------- 2 files changed, 168 insertions(+), 110 deletions(-) diff --git a/doc/dox/programs/pw-link.1.md b/doc/dox/programs/pw-link.1.md index 83a1d8372..44e72f303 100644 --- a/doc/dox/programs/pw-link.1.md +++ b/doc/dox/programs/pw-link.1.md @@ -4,7 +4,7 @@ The PipeWire Link Command # SYNOPSIS -**pw-link** \[*options*\] -o-l \[*out-pattern*\] \[*in-pattern*\] +**pw-link** \[*options*\] -o|-i|-l|-t \[*out-pattern*\] \[*in-pattern*\] **pw-link** \[*options*\] *output* *input* @@ -42,6 +42,9 @@ List input ports \par -l | \--links List links +\par -t | \--latency +List port latencies + \par -m | \--monitor Monitor links and ports. **pw-link** will not exit but monitor and print new and destroyed ports or links. diff --git a/src/tools/pw-link.c b/src/tools/pw-link.c index 3d87cbaa9..4104dd742 100644 --- a/src/tools/pw-link.c +++ b/src/tools/pw-link.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -38,11 +39,24 @@ union object_data { struct object { struct spa_list link; + struct data *d; uint32_t id; enum object_type type; struct pw_properties *props; union object_data data; +#define STATE_NONE 0 +#define STATE_NEW 1 +#define STATE_CHANGED 2 +#define STATE_DELETE 3 + int state; + + struct pw_proxy *proxy; + struct spa_hook proxy_listener; + struct spa_hook object_listener; + + struct spa_latency_info latency[2]; + bool latency_changed[2]; }; struct target_link { @@ -65,6 +79,7 @@ enum list_target { LIST_INPUT = 1 << 1, LIST_PORTS = LIST_OUTPUT | LIST_INPUT, LIST_LINKS = 1 << 2, + LIST_LATENCY = 1 << 3, }; struct data { @@ -98,12 +113,20 @@ struct data { bool monitoring; bool list_inputs; bool list_outputs; - const char *prefix; regex_t out_port_regex, *out_regex; regex_t in_port_regex, *in_regex; }; +static void destroy_object(struct object *obj) +{ + spa_list_remove(&obj->link); + pw_properties_free(obj->props); + if (obj->proxy) + pw_proxy_destroy(obj->proxy); + free(obj); +} + static void link_event(struct target_link *tl, enum pw_link_state state, int result) { /* Ignore non definitive states (negotiating, allocating, etc). */ @@ -183,6 +206,23 @@ static void core_sync(struct data *data) data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync); } +static const char *state_name(struct data *d, struct object *o) +{ + if (!d->opt_monitor) + return ""; + switch (o->state) { + case STATE_NONE: + return " "; + case STATE_NEW: + return "+"; + case STATE_CHANGED: + return "*"; + case STATE_DELETE: + return "-"; + } + return " "; +} + static struct object *find_object(struct data *data, enum object_type type, uint32_t id) { struct object *o; @@ -274,37 +314,58 @@ static char *port_alias(char *buffer, int size, struct object *n, struct object return buffer; } -static void print_port(struct data *data, const char *prefix, struct object *n, - struct object *p, bool verbose) +static void print_port(struct data *data, const char *prefix, const char *state, + struct object *n, struct object *p, bool verbose) { char buffer[1024], id[64] = ""; const char *prefix2 = ""; + if (state == NULL) + state = state_name(data, p); + if (data->opt_id) { snprintf(id, sizeof(id), "%4d ", p->id); prefix2 = " "; } - printf("%s%s%s%s\n", data->prefix, prefix, + printf("%s%s%s%s\n", state, prefix, id, port_name(buffer, sizeof(buffer), n, p)); if (verbose) { port_path(buffer, sizeof(buffer), n, p); if (buffer[0] != '\0') - printf("%s %s%s%s\n", data->prefix, prefix2, prefix, buffer); + printf("%s %s%s%s\n", state, prefix2, prefix, buffer); port_alias(buffer, sizeof(buffer), n, p); if (buffer[0] != '\0') - printf("%s %s%s%s\n", data->prefix, prefix2, prefix, buffer); + printf("%s %s%s%s\n", state, prefix2, prefix, buffer); } } -static void print_port_id(struct data *data, const char *prefix, uint32_t peer) +static void print_port_id(struct data *data, const char *prefix, uint32_t peer, struct object *l) { struct object *n, *p; if ((p = find_object(data, OBJECT_PORT, peer)) == NULL) return; if ((n = find_object(data, OBJECT_NODE, p->data.port.node)) == NULL) return; - print_port(data, prefix, n, p, false); + print_port(data, prefix, state_name(data, l), n, p, false); +} + +static void print_port_latency(struct data *data, const char *prefix, + struct object *p, enum spa_direction direction) +{ + const char *state; + struct spa_latency_info *info = &p->latency[direction]; + + if (p->state == STATE_NONE || p->state == STATE_CHANGED) + state = p->latency_changed[direction] ? "*" : "="; + else + state = state_name(data, p); + + printf("%s%s %s latency: { quantum=[ %f %f ], rate=[ %d %d ], ns=[ %"PRIi64" %"PRIi64" ] }\n", + state, prefix, direction == SPA_DIRECTION_INPUT ? "input ": "output", + info->min_quantum, info->max_quantum, + info->min_rate, info->max_rate, info->min_ns, info->max_ns); + p->latency_changed[direction] = false; } static void do_list_port_links(struct data *data, struct object *node, struct object *port) @@ -339,10 +400,10 @@ static void do_list_port_links(struct data *data, struct object *node, struct ob continue; if (first) { - print_port(data, "", node, port, data->opt_verbose); + print_port(data, "", NULL, node, port, data->opt_verbose); first = false; } - print_port_id(data, prefix, peer); + print_port_id(data, prefix, peer, o); } } @@ -389,6 +450,8 @@ static void do_list_ports(struct data *data, struct object *node, spa_list_for_each(o, &data->objects, link) { if (o->type != OBJECT_PORT) continue; + if (o->state == STATE_NONE) + continue; if (o->data.port.node != node->id) continue; if (o->data.port.direction != direction) @@ -398,7 +461,11 @@ static void do_list_ports(struct data *data, struct object *node, continue; if (data->opt_list & LIST_PORTS) - print_port(data, "", node, o, data->opt_verbose); + print_port(data, "", NULL, node, o, data->opt_verbose); + if (data->opt_list & LIST_LATENCY) { + print_port_latency(data, "", o, SPA_DIRECTION_INPUT); + print_port_latency(data, "", o, SPA_DIRECTION_OUTPUT); + } if (data->opt_list & LIST_LINKS) do_list_port_links(data, node, o); } @@ -406,7 +473,7 @@ static void do_list_ports(struct data *data, struct object *node, static void do_list(struct data *data) { - struct object *n; + struct object *n, *t; spa_list_for_each(n, &data->objects, link) { if (n->type != OBJECT_NODE) @@ -415,6 +482,13 @@ static void do_list(struct data *data) do_list_ports(data, n, PW_DIRECTION_OUTPUT, data->out_regex); if (data->list_inputs) do_list_ports(data, n, PW_DIRECTION_INPUT, data->in_regex); + + } + spa_list_for_each_safe(n, t, &data->objects, link) { + if (n->state == STATE_DELETE) + destroy_object(n); + else + n->state = STATE_NONE; } } @@ -598,64 +672,49 @@ static int do_unlink_ports(struct data *data) return 0; } -static int do_monitor_port(struct data *data, struct object *port) +static void +removed_proxy (void *data) { - regex_t *regex = NULL; - bool do_print = false; - struct object *node; - - if (port->data.port.direction == PW_DIRECTION_OUTPUT && data->list_outputs) { - regex = data->out_regex; - do_print = true; - } - if (port->data.port.direction == PW_DIRECTION_INPUT && data->list_inputs) { - regex = data->in_regex; - do_print = true; - } - if (!do_print) - return 0; - - if ((node = find_object(data, OBJECT_NODE, port->data.port.node)) == NULL) - return -ENOENT; - - if (regex && !port_regex(data, node, port, regex)) - return 0; - - print_port(data, "", node, port, data->opt_verbose); - return 0; + struct object *obj = data; + pw_proxy_destroy(obj->proxy); } -static int do_monitor_link(struct data *data, struct object *link) +static void +destroy_proxy (void *data) { - char buffer1[1024], buffer2[1024], id[64] = ""; - struct object *n1, *n2, *p1, *p2; - - if (!(data->opt_list & LIST_LINKS)) - return 0; - - if ((p1 = find_object(data, OBJECT_PORT, link->data.link.output_port)) == NULL) - return -ENOENT; - if ((n1 = find_object(data, OBJECT_NODE, p1->data.port.node)) == NULL) - return -ENOENT; - if (data->out_regex && !port_regex(data, n1, p1, data->out_regex)) - return 0; - - if ((p2 = find_object(data, OBJECT_PORT, link->data.link.input_port)) == NULL) - return -ENOENT; - if ((n2 = find_object(data, OBJECT_NODE, p2->data.port.node)) == NULL) - return -ENOENT; - if (data->in_regex && !port_regex(data, n2, p2, data->in_regex)) - return 0; - - if (data->opt_id) - snprintf(id, sizeof(id), "%4d ", link->id); - - printf("%s%s%s -> %s\n", data->prefix, id, - port_name(buffer1, sizeof(buffer1), n1, p1), - port_name(buffer2, sizeof(buffer2), n2, p2)); - return 0; + struct object *obj = data; + spa_hook_remove(&obj->proxy_listener); + spa_hook_remove(&obj->object_listener); + obj->proxy = NULL; } +static const struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .removed = removed_proxy, + .destroy = destroy_proxy, +}; + +static void port_event_param(void *_data, int seq, uint32_t id, + uint32_t index, uint32_t next, const struct spa_pod *param) +{ + struct object *obj = _data; + struct spa_latency_info info; + + if (id != SPA_PARAM_Latency || + spa_latency_parse(param, &info) < 0) + return; + obj->latency[info.direction] = info; + if (obj->state == STATE_NONE) + obj->state = STATE_CHANGED; + obj->latency_changed[info.direction] = true; + core_sync(obj->d); +} + +static const struct pw_port_events port_events = { + PW_VERSION_PORT_EVENTS, + .param = port_event_param +}; + static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) @@ -663,7 +722,7 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, struct data *d = data; enum object_type t; union object_data extra = {0}; - struct object *obj; + struct object *obj, *p; const char *str; if (props == NULL) @@ -698,6 +757,12 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL) return; extra.link.input_port = atoi(str); + if ((p = find_object(d, OBJECT_PORT, extra.link.output_port)) != NULL) + if (p->state == STATE_NONE) + p->state = STATE_CHANGED; + if ((p = find_object(d, OBJECT_PORT, extra.link.input_port)) != NULL) + if (p->state == STATE_NONE) + p->state = STATE_CHANGED; } else return; @@ -706,57 +771,42 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, obj->id = id; obj->props = pw_properties_new_dict(props); obj->data = extra; + obj->state = STATE_NEW; + obj->d = d; spa_list_append(&d->objects, &obj->link); - if (d->monitoring) { - d->prefix = "+ "; - switch (obj->type) { - case OBJECT_ANY: - spa_assert_not_reached(); - case OBJECT_NODE: - break; - case OBJECT_PORT: - do_monitor_port(d, obj); - break; - case OBJECT_LINK: - do_monitor_link(d, obj); - break; - } + switch (obj->type) { + case OBJECT_PORT: + { + uint32_t subs[] = { SPA_PARAM_Latency }; + obj->proxy = pw_registry_bind(d->registry, id, type, PW_VERSION_PORT, 0); + pw_proxy_add_object_listener(obj->proxy, &obj->object_listener, &port_events, obj); + pw_proxy_add_listener(obj->proxy, &obj->proxy_listener, &proxy_events, obj); + pw_port_subscribe_params((struct pw_port*)obj->proxy, subs, SPA_N_ELEMENTS(subs)); + break; } -} - -static void destroy_object(struct object *obj) -{ - spa_list_remove(&obj->link); - pw_properties_free(obj->props); - free(obj); + default: + break; + } + core_sync(d); } static void registry_event_global_remove(void *data, uint32_t id) { struct data *d = data; - struct object *obj; + struct object *obj, *p; if ((obj = find_object(d, OBJECT_ANY, id)) == NULL) return; - if (d->monitoring) { - d->prefix = "- "; - switch (obj->type) { - case OBJECT_ANY: - spa_assert_not_reached(); - case OBJECT_NODE: - break; - case OBJECT_PORT: - do_monitor_port(d, obj); - break; - case OBJECT_LINK: - do_monitor_link(d, obj); - break; - } + if (obj->type == OBJECT_LINK) { + if ((p = find_object(d, OBJECT_PORT, obj->data.link.output_port)) != NULL) + p->state = STATE_CHANGED; + if ((p = find_object(d, OBJECT_PORT, obj->data.link.input_port)) != NULL) + p->state = STATE_CHANGED; } - - destroy_object(obj); + obj->state = STATE_DELETE; + core_sync(d); } static const struct pw_registry_events registry_events = { @@ -806,6 +856,7 @@ static const struct pw_core_events core_events = { static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; + data->opt_monitor = false; pw_main_loop_quit(data->loop); } @@ -896,6 +947,7 @@ static int run(int argc, char *argv[]) { "props", required_argument, NULL, 'p' }, { "wait", no_argument, NULL, 'w' }, { "disconnect", no_argument, NULL, 'd' }, + { "latency", no_argument, NULL, 't' }, { NULL, 0, NULL, 0} }; @@ -905,7 +957,7 @@ static int run(int argc, char *argv[]) return -1; } - while ((c = getopt_long(argc, argv, "hVr:oilmIvLPp:wd", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:oilmIvLPp:wdt", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); @@ -933,6 +985,10 @@ static int run(int argc, char *argv[]) data.opt_mode = MODE_LIST; data.opt_list |= LIST_LINKS; break; + case 't': + data.opt_mode = MODE_LIST; + data.opt_list |= LIST_LATENCY; + break; case 'm': data.opt_monitor = true; break; @@ -1033,8 +1089,6 @@ static int run(int argc, char *argv[]) &data.registry_listener, ®istry_events, &data); - data.prefix = data.opt_monitor ? "= " : ""; - core_sync(&data); pw_main_loop_run(data.loop); @@ -1071,6 +1125,7 @@ static int run(int argc, char *argv[]) } if (data.nb_links > 0) { + core_sync(&data); pw_main_loop_run(data.loop); struct target_link *tl; @@ -1085,10 +1140,10 @@ static int run(int argc, char *argv[]) break; } - if (data.opt_monitor) { - data.monitoring = true; + while (data.opt_monitor) { pw_main_loop_run(data.loop); - data.monitoring = false; + if (data.opt_monitor) + do_list(&data); } return 0; From f10dec9daed0d8e8aa5a4f9acd3a739663477a7f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 5 Sep 2025 15:44:22 +0200 Subject: [PATCH 0809/1014] spa: fix typo in raw-types for LLFE Spotted by Nikolai Fixes #4881 --- spa/include/spa/param/audio/raw-types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index b3de74f86..c862999d5 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -192,7 +192,7 @@ static const struct spa_type_info spa_type_audio_channel[] = { { SPA_AUDIO_CHANNEL_TFRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFRC", NULL }, { SPA_AUDIO_CHANNEL_TSL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSL", NULL }, { SPA_AUDIO_CHANNEL_TSR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSR", NULL }, - { SPA_AUDIO_CHANNEL_LLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LLFR", NULL }, + { SPA_AUDIO_CHANNEL_LLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LLFE", NULL }, { SPA_AUDIO_CHANNEL_RLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RLFE", NULL }, { SPA_AUDIO_CHANNEL_BC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BC", NULL }, { SPA_AUDIO_CHANNEL_BLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BLC", NULL }, From 3b33f60d2ff3a87927e46e287f41182f435a3cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 1 Sep 2025 17:39:08 +0200 Subject: [PATCH 0810/1014] treewide: map `SPA_PROP_exposure` to `V4L2_CID_EXPOSURE_ABSOLUTE` Currently the v4l2 and libcamera plugins map `SPA_PROP_exposure` in incompatible ways. So change the v4l2 mapping to `V4L2_CID_EXPOSURE_ABSOLUTE` because at least that is in units of time (a step closer to addressing #4697), and because that is more relevant for UVC cameras. Also change the pipewire-v4l2 translation layer. --- pipewire-v4l2/src/pipewire-v4l2.c | 2 +- spa/plugins/v4l2/v4l2-utils.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index a3397ef64..c85bfd813 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -2115,7 +2115,7 @@ static struct { { V4L2_CID_SATURATION, SPA_PROP_saturation }, { V4L2_CID_HUE, SPA_PROP_hue }, { V4L2_CID_GAMMA, SPA_PROP_gamma }, - { V4L2_CID_EXPOSURE, SPA_PROP_exposure }, + { V4L2_CID_EXPOSURE_ABSOLUTE, SPA_PROP_exposure }, { V4L2_CID_GAIN, SPA_PROP_gain }, { V4L2_CID_SHARPNESS, SPA_PROP_sharpness }, }; diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index e5f153f4a..9cfd0afae 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -1347,7 +1347,7 @@ static struct { { V4L2_CID_SATURATION, SPA_PROP_saturation }, { V4L2_CID_HUE, SPA_PROP_hue }, { V4L2_CID_GAMMA, SPA_PROP_gamma }, - { V4L2_CID_EXPOSURE, SPA_PROP_exposure }, + { V4L2_CID_EXPOSURE_ABSOLUTE, SPA_PROP_exposure }, { V4L2_CID_GAIN, SPA_PROP_gain }, { V4L2_CID_SHARPNESS, SPA_PROP_sharpness }, }; From 5af834018324431b5ec80c1b5c274bc54cb3538d Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Sat, 23 Aug 2025 22:47:34 +0300 Subject: [PATCH 0811/1014] bluez5: media-source: don't set node.latency by default The hardcoded latency of 512/ is quite low on some ALSA devices. Instead of forcing that latency onto the graph, just don't set it at all unless it originates from the BAP presentation delay. That means that the functionality remains the same for BAP but changes for A2DP to favor the preferred quantum of the ALSA sink (or whatever is the driver). Also, avoid setting an empty string ("") latency and rate in the cases where it's not defined. This allows users to override those properties through the wireplumber monitor rules if they need to. --- spa/plugins/bluez5/media-source.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index dc3e53a7b..3394cb6c0 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -1135,15 +1135,23 @@ static void emit_node_info(struct impl *this, bool full) { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, - { SPA_KEY_NODE_LATENCY, this->is_input ? "" : latency }, { "media.name", media_name }, - { "node.rate", this->is_input ? "" : rate }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, { SPA_KEY_MEDIA_ROLE, media_role }, + + /* reserved for latency and rate; see below */ + { NULL, NULL }, + { NULL, NULL } }; - spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); - spa_scnprintf(rate, sizeof(rate), "1/%u", port->current_format.info.raw.rate); + if (!this->is_input && this->node_latency != 0) { + node_info_items[SPA_N_ELEMENTS(node_info_items) - 2].key = SPA_KEY_NODE_LATENCY; + node_info_items[SPA_N_ELEMENTS(node_info_items) - 2].value = latency; + node_info_items[SPA_N_ELEMENTS(node_info_items) - 1].key = "node.rate"; + node_info_items[SPA_N_ELEMENTS(node_info_items) - 1].value = rate; + spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); + spa_scnprintf(rate, sizeof(rate), "1/%u", port->current_format.info.raw.rate); + } if (full) this->info.change_mask = this->info_all; @@ -2074,7 +2082,7 @@ impl_init(const struct spa_handle_factory *factory, this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - this->node_latency = 512; + this->node_latency = 0; set_latency(this, false); From e9b78f1c31ac8ccf562a4827aab2801405f78417 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Sun, 24 Aug 2025 00:47:43 +0300 Subject: [PATCH 0812/1014] bluez5: media-source: add option to control the target latency of the decode-buffer On production systems, having a constant high latency is favored over dynamically adjusting it in order to optimize for low latency, because every time a dynamic adjustment happens, there's a glitch. This adds an option to let the user specify the exact amount of latency they want. --- doc/dox/config/pipewire-props.7.md | 5 +++++ spa/plugins/bluez5/media-source.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 6b854873c..1319297ff 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -1196,6 +1196,11 @@ this instance. Available values: - input: appear as source node. \endparblock +@PAR@ node-prop bluez5.decode-buffer.latency # integer +Applies on media source nodes and defines the target amount +of samples to be buffered on the output of the decoder. +Default: 0, which means it is automatically determined. + @PAR@ node-prop node.latency-offset-msec # string Applies only for BLE MIDI nodes. Latency adjustment to apply on the node. Larger values add a diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 3394cb6c0..6d2833983 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -141,6 +141,8 @@ struct impl { unsigned int is_duplex:1; unsigned int is_internal:1; + unsigned int decode_buffer_target; + unsigned int node_latency; int fd; @@ -885,6 +887,8 @@ static int transport_start(struct impl *this) this->quantum_limit, this->quantum_limit)) < 0) return res; + spa_bt_decode_buffer_set_target_latency(&port->buffer, (int32_t) this->decode_buffer_target); + if (this->codec->kind == MEDIA_CODEC_HFP) { /* 40 ms max buffer (on top of duration) */ spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, @@ -2057,6 +2061,9 @@ impl_init(const struct spa_handle_factory *factory, this->is_duplex = spa_atob(str); if ((str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) this->is_internal = spa_atob(str); + if ((str = spa_dict_lookup(info, "bluez5.decode-buffer.latency")) != NULL) { + spa_atou32(str, &this->decode_buffer_target, 0); + } } if (this->is_duplex) { From 47780884e1fa66be2a7291cefebf01aa49887dee Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 25 Aug 2025 18:22:58 +0300 Subject: [PATCH 0813/1014] bluez5: media-source: pass through node.rate and node.latency Allow user to specify custom values for node.rate and note.latency. Also restore the 512 default latency. --- spa/plugins/bluez5/media-source.c | 53 +++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 6d2833983..e662b76d4 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -47,6 +47,10 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.source.media"); struct props { char clock_name[64]; + char latency[64]; + bool has_latency; + char rate[64]; + bool has_rate; }; #define MAX_BUFFERS 32 @@ -184,6 +188,10 @@ struct impl { static void reset_props(struct props *props) { strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); + spa_zero(props->latency); + props->has_latency = false; + spa_zero(props->rate); + props->has_rate = false; } static int impl_node_enum_params(void *object, int seq, @@ -1135,32 +1143,44 @@ static void emit_node_info(struct impl *this, bool full) (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) media_role = "Communication"; - struct spa_dict_item node_info_items[] = { + struct spa_dict_item node_info_items[7] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, { "media.name", media_name }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, { SPA_KEY_MEDIA_ROLE, media_role }, - - /* reserved for latency and rate; see below */ - { NULL, NULL }, - { NULL, NULL } }; + size_t n_items = 5; - if (!this->is_input && this->node_latency != 0) { - node_info_items[SPA_N_ELEMENTS(node_info_items) - 2].key = SPA_KEY_NODE_LATENCY; - node_info_items[SPA_N_ELEMENTS(node_info_items) - 2].value = latency; - node_info_items[SPA_N_ELEMENTS(node_info_items) - 1].key = "node.rate"; - node_info_items[SPA_N_ELEMENTS(node_info_items) - 1].value = rate; + spa_assert(n_items + 2 <= SPA_N_ELEMENTS(node_info_items)); + + if (this->props.has_latency) { + node_info_items[n_items].key = SPA_KEY_NODE_LATENCY; + node_info_items[n_items].value = this->props.latency; + n_items++; + } else if (!this->is_input && this->node_latency != 0) { spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); + node_info_items[n_items].key = SPA_KEY_NODE_LATENCY; + node_info_items[n_items].value = latency; + n_items++; + } + + if (this->props.has_rate) { + node_info_items[n_items].key = "node.rate"; + node_info_items[n_items].value = this->props.rate; + n_items++; + } else if (!this->is_input && this->node_latency != 0) { spa_scnprintf(rate, sizeof(rate), "1/%u", port->current_format.info.raw.rate); + node_info_items[n_items].key = "node.rate"; + node_info_items[n_items].value = rate; + n_items++; } if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { - this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); + this->info.props = &SPA_DICT_INIT(node_info_items, n_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } @@ -2061,8 +2081,15 @@ impl_init(const struct spa_handle_factory *factory, this->is_duplex = spa_atob(str); if ((str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) this->is_internal = spa_atob(str); - if ((str = spa_dict_lookup(info, "bluez5.decode-buffer.latency")) != NULL) { + if ((str = spa_dict_lookup(info, "bluez5.decode-buffer.latency")) != NULL) spa_atou32(str, &this->decode_buffer_target, 0); + if ((str = spa_dict_lookup(info, SPA_KEY_NODE_LATENCY)) != NULL) { + spa_scnprintf(this->props.latency, sizeof(this->props.latency), "%s", str); + this->props.has_latency = true; + } + if ((str = spa_dict_lookup(info, "node.rate")) != NULL) { + spa_scnprintf(this->props.rate, sizeof(this->props.rate), "%s", str); + this->props.has_rate = true; } } @@ -2089,7 +2116,7 @@ impl_init(const struct spa_handle_factory *factory, this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - this->node_latency = 0; + this->node_latency = 512; set_latency(this, false); From 93941e5207dd265092ddcd26ab66d899947ea31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 18 Aug 2025 18:32:00 +0200 Subject: [PATCH 0814/1014] spa: libcamera: source: query frame buffer planes just once --- spa/plugins/libcamera/libcamera-source.cpp | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index a469ed377..cdc19d022 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -1251,7 +1251,9 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, } + const auto& planes = bufs[i]->planes(); spa_data *d = buffers[i]->datas; + for(uint32_t j = 0; j < buffers[i]->n_datas; ++j) { d[j].type = port->memtype; d[j].flags = SPA_DATA_FLAG_READABLE; @@ -1259,23 +1261,23 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, d[j].chunk->stride = port->streamConfig.stride; d[j].chunk->flags = 0; /* Update parameters according to the plane information */ - unsigned int numPlanes = bufs[i]->planes().size(); + unsigned int numPlanes = planes.size(); if (buffers[i]->n_datas < numPlanes) { if (j < buffers[i]->n_datas - 1) { - d[j].maxsize = bufs[i]->planes()[j].length; - d[j].chunk->offset = bufs[i]->planes()[j].offset; - d[j].chunk->size = bufs[i]->planes()[j].length; + d[j].maxsize = planes[j].length; + d[j].chunk->offset = planes[j].offset; + d[j].chunk->size = planes[j].length; } else { - d[j].chunk->offset = bufs[i]->planes()[j].offset; + d[j].chunk->offset = planes[j].offset; for (uint8_t k = j; k < numPlanes; k++) { - d[j].maxsize += bufs[i]->planes()[k].length; - d[j].chunk->size += bufs[i]->planes()[k].length; + d[j].maxsize += planes[k].length; + d[j].chunk->size += planes[k].length; } } } else if (buffers[i]->n_datas == numPlanes) { - d[j].maxsize = bufs[i]->planes()[j].length; - d[j].chunk->offset = bufs[i]->planes()[j].offset; - d[j].chunk->size = bufs[i]->planes()[j].length; + d[j].maxsize = planes[j].length; + d[j].chunk->offset = planes[j].offset; + d[j].chunk->size = planes[j].length; } else { spa_log_warn(impl->log, "buffer index: i: %d, data member " "numbers: %d is greater than plane number: %d", @@ -1288,7 +1290,7 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, if (port->memtype == SPA_DATA_DmaBuf || port->memtype == SPA_DATA_MemFd) { d[j].flags |= SPA_DATA_FLAG_MAPPABLE; - d[j].fd = bufs[i]->planes()[j].fd.get(); + d[j].fd = planes[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); d[j].data = nullptr; } else { From 756df7b6ae5ccd4360e6cdb9b18ab54cb9fa4ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 25 Aug 2025 22:24:11 +0200 Subject: [PATCH 0815/1014] spa: libcamera: source: remove `buffer::ptr` With the removal of `SPA_DATA_MemPtr` support, this member is no longer used. Fixes: b948ffdb2509c6 ("spa: libcamera: source: remove `SPA_DATA_MemPtr` support") --- spa/plugins/libcamera/libcamera-source.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index cdc19d022..c87d220f5 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -61,7 +61,6 @@ struct buffer { struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_meta_videotransform *videotransform; - void *ptr; }; struct port { From 7a98bcf73552829b6202fa14a081d4d31d232373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 5 Sep 2025 17:42:15 +0200 Subject: [PATCH 0816/1014] spa: libcamera: source: fix typo in log message ';' -> ':' --- spa/plugins/libcamera/libcamera-source.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index c87d220f5..08f92c5d7 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -2075,7 +2075,7 @@ int impl_node_process(void *object) if (port->control) process_control(impl, &port->control->sequence, port->control_size); - spa_log_trace(impl->log, "%p; status %d", impl, io->status); + spa_log_trace(impl->log, "%p: status %d", impl, io->status); if (io->status == SPA_STATUS_HAVE_DATA) { return SPA_STATUS_HAVE_DATA; From 64aaf8a832c4615af165abeb3b2db85a33f10642 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 6 Sep 2025 16:06:53 +0300 Subject: [PATCH 0817/1014] alsa: set minimum period count before automatic period size Some devices (FireWire) fail to produce audio if period count is < 3, and also have small buffer size. When quantum is too large, we might then get too few periods and broken sound. Set minimum for the period count in ALSA, to determine the maximum period size we can use. If smaller than what we were going to use, round down to power-of-2. See #4785 --- spa/plugins/alsa/alsa-pcm.c | 41 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2c785872e..299f752b6 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2328,6 +2328,35 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ } } + if (state->default_period_num != 0) { + /* period number given use that */ + periods = state->default_period_num; + } else if (state->disable_tsched) { + /* IRQ mode, use 3 periods. This is a bit of a workaround + * for Firewire devices, which seem to only work with 3 periods. + * For PipeWire it does not actually matter how many periods + * are used, we will always keep 1 filled, so we can work fine + * with anything from 2 periods to MAX. */ + periods = 3; + } else { + periods = UINT_MAX; + } + + if (state->default_period_size == 0) { + /* Some devices (FireWire) don't produce audio if period number is too + * small, so force a minimum. This will restrict possible period sizes if + * the device has small buffer (like FireWire), so force it only if + * period size was not set manually. + */ + snd_pcm_uframes_t period_size_max; + unsigned int periods_min = (periods == UINT_MAX) ? 3 : periods; + + CHECK(snd_pcm_hw_params_set_periods_min(hndl, params, &periods_min, &dir), "set_periods_min"); + CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), "get_period_size_max"); + if (period_size > period_size_max) + period_size = SPA_MIN(period_size, flp2(period_size_max)); + } + CHECK(snd_pcm_hw_params_set_period_size_near(hndl, params, &period_size, &dir), "set_period_size_near"); if (period_size == 0) { @@ -2337,17 +2366,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->period_frames = period_size; - if (state->default_period_num != 0 || state->disable_tsched) { - if (state->default_period_num != 0) - /* period number given use that */ - periods = state->default_period_num; - else - /* IRQ mode, use 3 periods. This is a bit of a workaround - * for Firewire devices, which seem to only work with 3 periods. - * For PipeWire it does not actually matter how many periods - * are used, we will always keep 1 filled, so we can work fine - * with anything from 2 periods to MAX. */ - periods = 3; + if (periods != UINT_MAX) { CHECK(snd_pcm_hw_params_set_periods_near(hndl, params, &periods, &dir), "set_periods"); state->buffer_frames = period_size * periods; } else { From 916896c1cc7452c738ae4593aa9cbae7f14a32b0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 6 Sep 2025 18:15:29 +0300 Subject: [PATCH 0818/1014] alsa: force IRQ scheduling for firewire in pro-audio profile FireWire ALSA driver latency is determined by the buffer size and not the period. Timer-based scheduling is then not really useful on these devices as the latency is fixed. In pro-audio profile, enable IRQ scheduling unconditionally for these devices, so that controlling the latency works properly. See #4785 --- spa/plugins/alsa/acp/acp.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index ecb017a9f..7fc749a7e 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -465,7 +465,14 @@ static int add_pro_profile(pa_card *impl, uint32_t index) } snd_ctl_close(ctl_hndl); - if (n_capture == 1 && n_playback == 1) { + /* FireWire ALSA driver latency is determined by the buffer size and not the + * period. Timer-based scheduling is then not really useful on these devices as + * the latency is fixed. Enable IRQ scheduling unconditionally for these devices, + * so that controlling the latency works properly. + */ + bool is_firewire = spa_streq(pa_proplist_gets(impl->proplist, "device.bus"), "firewire"); + + if ((n_capture == 1 && n_playback == 1) || is_firewire) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index); pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index); From bf783ab08fb185ea8576b2162a17f8e045f75cee Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 6 Sep 2025 19:14:22 +0300 Subject: [PATCH 0819/1014] alsa: report extra latency for FireWire drivers Based on testing, ALSA FireWire drivers introduce additional latency determined by the buffer size. Report that latency. Pass device.bus to the node, so it can recognize firewire. --- spa/plugins/alsa/alsa-acp-device.c | 6 ++++-- spa/plugins/alsa/alsa-pcm.c | 9 +++++++++ spa/plugins/alsa/alsa-pcm.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 4d8a6d92d..b2e06f67f 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -160,7 +160,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; - const char *stream, *card_id; + const char *stream, *card_id, *bus; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; @@ -175,7 +175,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; - items = alloca((dev->props.n_items + 11) * sizeof(*items)); + items = alloca((dev->props.n_items + 12) * sizeof(*items)); n_items = 0; snprintf(card_index, sizeof(card_index), "%d", card->index); @@ -193,6 +193,8 @@ static int emit_node(struct impl *this, struct acp_device *dev) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_STREAM, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, "audio-card-analog"); + bus = acp_dict_lookup(&card->props, SPA_KEY_DEVICE_BUS); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, bus); snprintf(channels, sizeof(channels), "%d", dev->format.channels); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels); diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 299f752b6..4daaa44c2 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -1012,6 +1012,8 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->num_bind_ctls = i; /* We'll do the actual binding after checking the card exists */ + } else if (spa_streq(k, SPA_KEY_DEVICE_BUS)) { + state->is_firewire = spa_streq(s, "firewire"); } else { alsa_set_param(state, k, s); } @@ -2054,6 +2056,13 @@ static void recalc_headroom(struct state *state) if (rate != 0 && state->rate != 0) latency = SPA_SCALE32_UP(latency, rate, state->rate); + if (state->is_firewire) { + /* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size + * XXX: contributes extra latency (as of kernel 6.16). + */ + latency += state->buffer_frames; + } + state->latency[state->port_direction].min_rate = state->latency[state->port_direction].max_rate = latency; } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 0d23fa2f6..b440c2796 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -154,6 +154,7 @@ struct state { unsigned int disable_batch:1; unsigned int disable_tsched:1; unsigned int is_split_parent:1; + unsigned int is_firewire:1; char clock_name[64]; uint32_t quantum_limit; From 28393cb896fd55ce178304c27aac9a9e90d2dbf9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 7 Sep 2025 16:44:03 +0300 Subject: [PATCH 0820/1014] audioconvert: add log topic for resampler --- spa/plugins/audioconvert/peaks-ops.h | 5 +++++ spa/plugins/audioconvert/resample-native-impl.h | 5 +++++ spa/plugins/audioconvert/resample-native.c | 2 ++ 3 files changed, 12 insertions(+) diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h index 0d2b661e8..24092a4f7 100644 --- a/spa/plugins/audioconvert/peaks-ops.h +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -6,6 +6,11 @@ #include #include +#include + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &resample_log_topic +extern struct spa_log_topic resample_log_topic; struct peaks { uint32_t cpu_flags; diff --git a/spa/plugins/audioconvert/resample-native-impl.h b/spa/plugins/audioconvert/resample-native-impl.h index 5ae2d861a..11a0851b3 100644 --- a/spa/plugins/audioconvert/resample-native-impl.h +++ b/spa/plugins/audioconvert/resample-native-impl.h @@ -5,9 +5,14 @@ #include #include +#include #include "resample.h" +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &resample_log_topic +extern struct spa_log_topic resample_log_topic; + typedef void (*resample_func_t)(struct resample *r, const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len); diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index 4996aac95..985f25497 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -11,6 +11,8 @@ #include "resample-native-precomp.h" #endif +SPA_LOG_TOPIC_DEFINE(resample_log_topic, "spa.resample"); + struct quality { uint32_t n_taps; double cutoff; From 396d37594c8f2b1dc67561d740816060a9a3ee4e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 7 Sep 2025 17:14:54 +0300 Subject: [PATCH 0821/1014] bluez5: media-source: drop all errqueue data when ignoring --- spa/plugins/bluez5/media-source.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index e662b76d4..e151b986e 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -620,8 +620,12 @@ static void handle_errqueue(struct impl *this) } this->errqueue_count = 0; - res = recv(this->fd, NULL, 0, MSG_ERRQUEUE | MSG_TRUNC); - spa_log_trace(this->log, "%p: ignoring errqueue data (%d)", this, res); + do { + char buf[512]; + + res = recv(this->fd, buf, sizeof(buf), MSG_ERRQUEUE | MSG_TRUNC | MSG_DONTWAIT); + spa_log_trace(this->log, "%p: ignoring errqueue data (%d)", this, res); + } while (res > 0); } static void media_on_ready_read(struct spa_source *source) From 7e04f8fe44622b64cfc3786944aba5e4bfc87620 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 12 Jul 2025 13:40:54 +0300 Subject: [PATCH 0822/1014] bluez5: ensure capture target latency is uniform for an ISO group All BAP client sources in the ISO group should use same target latency, so that capture streams stay in sync. --- spa/plugins/bluez5/decode-buffer.h | 18 +++++++++++------- spa/plugins/bluez5/iso-io.c | 27 +++++++++++++++++++++++++++ spa/plugins/bluez5/iso-io.h | 4 ++++ spa/plugins/bluez5/media-source.c | 22 ++++++++++++++++++---- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 0f1345572..c2104a5dc 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -221,7 +221,7 @@ static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_deco this->max_extra = samples; } -static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_decode_buffer *this) +static inline int32_t spa_bt_decode_buffer_get_auto_latency(struct spa_bt_decode_buffer *this) { const int32_t duration = this->duration; const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); @@ -229,16 +229,20 @@ static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_deco const int32_t spike = SPA_CLAMP(this->spike.max, 0, max_buf); int32_t target; - if (this->target) - target = this->target; - else - target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration), - SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), - duration, max_buf - 2*packet_size); + target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration), + SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), + duration, max_buf - 2*packet_size); return SPA_MIN(target, duration + SPA_CLAMP(this->max_extra, 0, INT32_MAX - duration)); } +static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_decode_buffer *this) +{ + if (this->target) + return this->target; + return spa_bt_decode_buffer_get_auto_latency(this); +} + static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration, double rate_diff, uint64_t next_nsec) { diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 8fc488eec..69f6748ea 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -21,6 +21,7 @@ #include "media-codecs.h" #include "defs.h" +#include "decode-buffer.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.iso"); #undef SPA_LOG_TOPIC_DEFAULT @@ -62,6 +63,8 @@ struct stream { uint32_t block_size; struct spa_bt_latency tx_latency; + + struct spa_bt_decode_buffer *source_buf; }; struct modify_info @@ -593,3 +596,27 @@ int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *this) return spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log); } + +/** Must be called from data thread */ +void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *this, struct spa_bt_decode_buffer *buffer) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + + stream->source_buf = buffer; +} + +/** Must be called from data thread */ +void spa_bt_iso_io_update_source_latency(struct spa_bt_iso_io *this) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + struct group *group = stream->group; + struct stream *s; + int32_t latency = 0; + + spa_list_for_each(s, &group->streams, link) + if (s->source_buf) + latency = SPA_MAX(latency, spa_bt_decode_buffer_get_auto_latency(s->source_buf)); + + if (stream->source_buf) + spa_bt_decode_buffer_set_target_latency(stream->source_buf, latency); +} diff --git a/spa/plugins/bluez5/iso-io.h b/spa/plugins/bluez5/iso-io.h index ed49c77c1..1ff6285c1 100644 --- a/spa/plugins/bluez5/iso-io.h +++ b/spa/plugins/bluez5/iso-io.h @@ -11,6 +11,7 @@ #include #include +struct spa_bt_decode_buffer; struct spa_bt_transport; /** @@ -46,4 +47,7 @@ void spa_bt_iso_io_destroy(struct spa_bt_iso_io *io); void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *io, spa_bt_iso_io_pull_t pull, void *user_data); int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *io); +void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *io, struct spa_bt_decode_buffer *buffer); +void spa_bt_iso_io_update_source_latency(struct spa_bt_iso_io *io); + #endif diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index e151b986e..7d78e186f 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -843,8 +843,10 @@ static int do_start_sco_iso_io(struct spa_loop *loop, bool async, uint32_t seq, if (this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, media_sco_pull, this); - if (this->transport->iso_io) + if (this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); + spa_bt_iso_io_set_source_buffer(this->transport->iso_io, &this->port.buffer); + } return 0; } @@ -1008,8 +1010,10 @@ static int do_remove_source(struct spa_loop *loop, if (this->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->timer_source); - if (this->transport && this->transport->iso_io) + if (this->transport && this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); + spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); + } if (this->transport && this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); set_timeout(this, 0); @@ -1646,9 +1650,19 @@ static void update_target_latency(struct impl *this) if (this->transport == NULL || !port->have_format) return; + if (this->codec->kind != MEDIA_CODEC_BAP) + return; - if (this->codec->kind != MEDIA_CODEC_BAP || this->is_input || - this->transport->delay_us == SPA_BT_UNKNOWN_DELAY) + if (this->is_input) { + /* BAP Client. Should use same buffer size for all streams in the same + * group, so that capture is in sync. + */ + if (this->transport->iso_io) + spa_bt_iso_io_update_source_latency(this->transport->iso_io); + return; + } + + if (this->transport->delay_us == SPA_BT_UNKNOWN_DELAY) return; get_samples(this, &duration); From f48a72a504fc339199d0d950d6886578b018bc8f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 12 Jul 2025 13:52:50 +0300 Subject: [PATCH 0823/1014] bluez5: smaller max latency for BAP client capture 4 packets should be enough jitter buffer. This matters only for BAP client, server is controlled by presentation delay. --- spa/plugins/bluez5/media-source.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 7d78e186f..a5746317f 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -903,7 +903,7 @@ static int transport_start(struct impl *this) spa_bt_decode_buffer_set_target_latency(&port->buffer, (int32_t) this->decode_buffer_target); - if (this->codec->kind == MEDIA_CODEC_HFP) { + if (this->codec->kind == MEDIA_CODEC_HFP || this->codec->kind == MEDIA_CODEC_BAP) { /* 40 ms max buffer (on top of duration) */ spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 40 / 1000); From e446e3aac57012facb6fbf4d7b12b00cd70a212f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 11 Jul 2025 15:11:38 +0300 Subject: [PATCH 0824/1014] bluez5: media-source: account for driver clock rate difference in rate match The rate matching calculations are done in the system clock domain. If the driver ticks at a different rate, the correction factor needs to be adjusted by the rate_diff. setup_matching() also needs to be before spa_bt_decode_buffer_process(): as follower we should use rate matching value calculated on the *previous* cycle, because this is what driver is doing when it adjusts it tick rate. --- spa/plugins/bluez5/media-source.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index a5746317f..f7aa2d4bd 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -713,6 +713,10 @@ static int setup_matching(struct impl *this) this->matching = this->following; this->resampling = this->matching || (port->current_format.info.raw.rate != this->position->clock.target_rate.denom); + + /* Rate match in system clock domain also when follower */ + if (this->matching && this->position->clock.rate_diff > 0) + port->rate_match->rate *= this->position->clock.rate_diff; } else { this->matching = false; this->resampling = false; @@ -1720,12 +1724,12 @@ static void process_buffering(struct impl *this) break; } + setup_matching(this); + spa_bt_decode_buffer_process(&port->buffer, samples, duration, this->position ? this->position->clock.rate_diff : 1.0, this->position ? this->position->clock.next_nsec : 0); - setup_matching(this); - /* copy data to buffers */ if (!spa_list_is_empty(&port->free)) { struct buffer *buffer; From 94c354c29010723be97ba6cb3e2c161bb82ebdcb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 7 Sep 2025 16:53:10 +0300 Subject: [PATCH 0825/1014] bluez5: decode-buffer: sub-sample accurate fill level tracking Take resampler delay into account when computing the buffer fill level, including the fractional part. If decode-buffer is now fed nominal packet reference times in write_packet(), it converges the total buffer + resampler latency to the target at sub-sample accuracy. This is needed for aligning RX of ISO streams in the same group, so that e.g. stereo pair alignment is achieved even though the streams have separate resamplers. Resampler phases get aligned via independent rate matching. --- spa/plugins/bluez5/decode-buffer.h | 141 +++++++++++++++++------------ spa/plugins/bluez5/media-source.c | 21 +++-- 2 files changed, 96 insertions(+), 66 deletions(-) diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index c2104a5dc..ab9c0b6a1 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -21,8 +21,9 @@ * The regular timer cycle cannot be aligned with this, so process() * may occur at any time. * - * The buffer level is the position of last received sample, relative to the current - * playback position. If it is larger than duration, there is no underrun. + * The instantaneous buffer level is the time position (in samples) of the last + * received sample, relative to the nominal time position of the last sample of the last + * received packet. If it is always larger than duration, there is no underrun. * * The rate correction aims to maintain the average level at a safety margin. */ @@ -59,6 +60,13 @@ struct spa_bt_decode_buffer uint32_t frame_size; uint32_t rate; + int64_t avg_period; + double rate_diff_max; + + int32_t target; /**< target buffer (0: automatic) */ + int32_t max_extra; + + bool no_overrun_drop; uint8_t *buffer_decoded; uint32_t buffer_size; @@ -72,15 +80,20 @@ struct spa_bt_decode_buffer struct spa_bt_rate_control ctl; double corr; - uint32_t duration; uint32_t pos; - int32_t target; /**< target buffer (0: automatic) */ - int32_t max_extra; - - int32_t level; - uint64_t next_nsec; + int64_t duration_ns; + int64_t next_nsec; double rate_diff; + int32_t delay; + int32_t delay_frac; + + double level; + + struct { + int64_t nsec; + int64_t position; + } rx; uint8_t buffering:1; }; @@ -99,6 +112,8 @@ static inline int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, s this->target = 0; this->buffering = true; this->max_extra = INT32_MAX; + this->avg_period = BUFFERING_SHORT_MSEC * SPA_NSEC_PER_MSEC; + this->rate_diff_max = BUFFERING_RATE_DIFF_MAX; spa_bt_rate_control_init(&this->ctl, 0); @@ -182,33 +197,28 @@ static inline size_t spa_bt_decode_buffer_get_size(struct spa_bt_decode_buffer * static inline void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size, uint64_t nsec) { - int32_t remain; - uint32_t avail; + const int32_t duration = this->duration_ns * this->rate / SPA_NSEC_PER_SEC; + + if (nsec) { + this->rx.nsec = nsec; + this->rx.position = size / this->frame_size; + } else { + this->rx.position += size / this->frame_size; + } spa_assert(size % this->frame_size == 0); this->write_index += size; spa_bt_ptp_update(&this->packet_size, size / this->frame_size, size / this->frame_size); - if (nsec && this->next_nsec && this->rate_diff != 0.0) { - int64_t dt = (this->next_nsec >= nsec) ? - (int64_t)(this->next_nsec - nsec) : -(int64_t)(nsec - this->next_nsec); - remain = (int32_t)SPA_CLAMP(dt * this->rate_diff * this->rate / SPA_NSEC_PER_SEC, - -(int32_t)this->duration, this->duration); + if (this->rx.nsec && this->next_nsec) { + uint32_t avail = spa_bt_decode_buffer_get_size(this) / this->frame_size; + int64_t dt = this->next_nsec - this->rx.nsec; + + this->level = dt * this->rate_diff * this->rate / SPA_NSEC_PER_SEC + + avail + this->delay + this->delay_frac/1e9 - this->rx.position; } else { - remain = 0; + this->level = spa_bt_decode_buffer_get_size(this) / this->frame_size - duration; } - - spa_bt_decode_buffer_get_read(this, &avail); - this->level = avail / this->frame_size + remain; -} - -static inline void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) -{ - int32_t size = (this->write_index - this->read_index) / this->frame_size; - - this->level = size; - this->corr = 1.0; - spa_bt_rate_control_init(&this->ctl, size); } static inline void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, int32_t samples) @@ -223,7 +233,7 @@ static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_deco static inline int32_t spa_bt_decode_buffer_get_auto_latency(struct spa_bt_decode_buffer *this) { - const int32_t duration = this->duration; + const int32_t duration = this->duration_ns * this->rate / SPA_NSEC_PER_SEC; const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); const int32_t max_buf = (this->buffer_size - this->buffer_reserve) / this->frame_size; const int32_t spike = SPA_CLAMP(this->spike.max, 0, max_buf); @@ -243,21 +253,37 @@ static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_deco return spa_bt_decode_buffer_get_auto_latency(this); } -static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration, - double rate_diff, uint64_t next_nsec) +static inline void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) +{ + int32_t target = spa_bt_decode_buffer_get_target_latency(this); + + this->rx.nsec = 0; + this->corr = 1.0; + spa_bt_rate_control_init(&this->ctl, target * SPA_NSEC_PER_SEC / this->rate); + spa_bt_decode_buffer_write_packet(this, 0, 0); +} + +static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, int64_t duration_ns, + double rate_diff, int64_t next_nsec, int32_t delay, int32_t delay_frac) { const uint32_t data_size = samples * this->frame_size; const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); - const int32_t max_level = SPA_MAX(8 * packet_size, (int32_t)duration); - const uint32_t avg_period = (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000; int32_t target; uint32_t avail; + double level; this->rate_diff = rate_diff; this->next_nsec = next_nsec; + this->delay = delay; + this->delay_frac = delay_frac; - if (SPA_UNLIKELY(duration != this->duration)) { - this->duration = duration; + /* The fractional delay is given at the start of current cycle. Make it relative + * to next_nsec used for the level calculations. + */ + this->delay_frac += (int32_t)(1e9 * samples - duration_ns * this->rate * this->rate_diff); + + if (SPA_UNLIKELY(duration_ns != this->duration_ns)) { + this->duration_ns = duration_ns; spa_bt_decode_buffer_recover(this); } @@ -275,57 +301,60 @@ static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *thi else return; - spa_bt_ptp_update(&this->spike, packet_size, duration); + spa_bt_ptp_update(&this->spike, packet_size, samples); spa_bt_decode_buffer_recover(this); } spa_bt_decode_buffer_get_read(this, &avail); /* Track buffer level */ - this->level = SPA_MAX(this->level, -max_level); + level = SPA_MAX(this->level, 0); - spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration); + spa_bt_ptp_update(&this->spike, + (int32_t)(this->ctl.avg * this->rate / SPA_NSEC_PER_SEC - level), + samples); - if (this->level > SPA_MAX(4 * target, 3*(int32_t)samples) && - avail > data_size) { + if (!this->no_overrun_drop && + level > SPA_MAX(4 * target, 3*(int32_t)samples) && avail > data_size) { /* Lagging too much: drop data */ uint32_t size = SPA_MIN(avail - data_size, - (this->level - target) * this->frame_size); + ((int32_t)ceil(level) - target) * this->frame_size); spa_bt_decode_buffer_read(this, size); - spa_log_trace(this->log, "%p overrun samples:%d level:%d target:%d", + spa_log_trace(this->log, "%p overrun samples:%d level:%.2f target:%d", this, (int)size/this->frame_size, - (int)this->level, (int)target); + level, (int)target); spa_bt_decode_buffer_recover(this); } - this->pos += duration; - if (this->pos > this->rate) { - spa_log_debug(this->log, - "%p avg:%d target:%d level:%d buffer:%d spike:%d corr:%f", + this->pos += samples; + + enum spa_log_level log_level = (this->pos > this->rate) ? SPA_LOG_LEVEL_DEBUG : SPA_LOG_LEVEL_TRACE; + if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, log_level))) { + spa_log_lev(this->log, log_level, + "%p avg:%.2f target:%d level:%.2f buffer:%d spike:%d corr:%g", this, - (int)this->ctl.avg, + this->ctl.avg * this->rate / SPA_NSEC_PER_SEC, (int)target, - (int)this->level, + level, (int)(avail / this->frame_size), (int)this->spike.max, - (double)this->corr); + (double)this->corr - 1); this->pos = 0; } this->corr = spa_bt_rate_control_update(&this->ctl, - this->level, target, duration, avg_period, - BUFFERING_RATE_DIFF_MAX); - - this->level -= samples; + level * SPA_NSEC_PER_SEC / this->rate, + ((double)target + 0.5/this->rate) * SPA_NSEC_PER_SEC / this->rate, + duration_ns, this->avg_period, this->rate_diff_max); spa_bt_decode_buffer_get_read(this, &avail); if (avail < data_size) { - spa_log_trace(this->log, "%p underrun samples:%d", this, + spa_log_debug(this->log, "%p underrun samples:%d", this, (data_size - avail) / this->frame_size); this->buffering = true; - spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration); + spa_bt_ptp_update(&this->spike, samples, 0); } } diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index f7aa2d4bd..7b01b7f01 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -1622,7 +1622,7 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return 0; } -static uint32_t get_samples(struct impl *this, uint32_t *result_duration) +static uint32_t get_samples(struct impl *this, int64_t *duration_ns) { struct port *port = &this->port; uint32_t samples, rate_denom; @@ -1636,12 +1636,12 @@ static uint32_t get_samples(struct impl *this, uint32_t *result_duration) rate_denom = port->current_format.info.raw.rate; } - *result_duration = duration * port->current_format.info.raw.rate / rate_denom; + *duration_ns = duration * SPA_NSEC_PER_SEC / rate_denom; if (SPA_LIKELY(port->rate_match) && this->resampling) { samples = port->rate_match->size; } else { - samples = *result_duration; + samples = duration; } return samples; } @@ -1649,7 +1649,7 @@ static uint32_t get_samples(struct impl *this, uint32_t *result_duration) static void update_target_latency(struct impl *this) { struct port *port = &this->port; - uint32_t samples, duration, latency; + uint32_t samples, latency; int64_t delay_sink; if (this->transport == NULL || !port->have_format) @@ -1669,8 +1669,6 @@ static void update_target_latency(struct impl *this) if (this->transport->delay_us == SPA_BT_UNKNOWN_DELAY) return; - get_samples(this, &duration); - /* Presentation delay for BAP server * * This assumes the time when we receive the packet is (on average) @@ -1708,8 +1706,8 @@ static void update_target_latency(struct impl *this) static void process_buffering(struct impl *this) { struct port *port = &this->port; - uint32_t duration; - const uint32_t samples = get_samples(this, &duration); + int64_t duration_ns; + const uint32_t samples = get_samples(this, &duration_ns); uint32_t data_size = samples * port->frame_size; uint32_t avail; @@ -1726,9 +1724,11 @@ static void process_buffering(struct impl *this) setup_matching(this); - spa_bt_decode_buffer_process(&port->buffer, samples, duration, + spa_bt_decode_buffer_process(&port->buffer, samples, duration_ns, this->position ? this->position->clock.rate_diff : 1.0, - this->position ? this->position->clock.next_nsec : 0); + this->position ? this->position->clock.next_nsec : 0, + this->resampling ? this->port.rate_match->delay : 0, + this->resampling ? this->port.rate_match->delay_frac : 0); /* copy data to buffers */ if (!spa_list_is_empty(&port->free)) { @@ -1780,6 +1780,7 @@ static void process_buffering(struct impl *this) if (this->update_delay_event) { int32_t target = spa_bt_decode_buffer_get_target_latency(&port->buffer); uint32_t decoder_delay = 0; + uint32_t duration = this->position ? this->position->clock.duration : 1024; if (this->codec->get_delay) this->codec->get_delay(this->codec_data, NULL, &decoder_delay); From 589bc4b6f46d3bae288b9f2a69e0709e4ee2e28d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 7 Sep 2025 17:04:08 +0300 Subject: [PATCH 0826/1014] bluez5: iso-io: sync to ISO RX clock, align stream RX in group Align RX of streams in same ISO group: - Ensure all streams in ISO group have same target latency also for BAP Client - Determine rate matching to ISO group clock from RX times of all streams in the group - Based on this, compute nominal packet RX times, and feed them to decode-buffer instead of the real RX time. This is enough for sub-sample level sync. - Customise buffer overrun handling for ISO so that it drops data to arrive exactly at the target, for faster convergence at RX start The ISO clock matching is done based on kernel-provided packet RX times, so it has unknown offset from the actual ISO clock, probably a few ms. Current kernels (6.17) do not provide anything better to use for the clock matching, and doing it properly appears to be controller vendor-defined (if possible at all). --- spa/plugins/bluez5/iso-io.c | 323 ++++++++++++++++++++++++++---- spa/plugins/bluez5/iso-io.h | 4 +- spa/plugins/bluez5/media-source.c | 27 ++- 3 files changed, 305 insertions(+), 49 deletions(-) diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 69f6748ea..8015a81de 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -35,6 +35,27 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.iso"); #define LATENCY_PERIOD (1000 * SPA_NSEC_PER_MSEC) #define MAX_LATENCY (50 * SPA_NSEC_PER_MSEC) +#define CLOCK_SYNC_AVG_PERIOD (500 * SPA_NSEC_PER_MSEC) +#define CLOCK_SYNC_RATE_DIFF_MAX 0.005 + +#define ISO_BUFFERING_AVG_PERIOD (50 * SPA_NSEC_PER_MSEC) +#define ISO_BUFFERING_RATE_DIFF_MAX 0.05 + +struct clock_sync { + /** Reference monotonic time for streams in the group */ + int64_t base_time; + + /** Average error for current cycle */ + int64_t avg_err; + unsigned int avg_num; + + /** Log rate limiting */ + uint64_t log_pos; + + /** Rate matching ISO clock to monotonic clock */ + struct spa_bt_rate_control dll; +}; + struct group { struct spa_log *log; struct spa_loop *data_loop; @@ -43,10 +64,13 @@ struct group { struct spa_list streams; int timerfd; uint8_t id; - uint64_t next; - uint64_t duration; + int64_t next; + int64_t duration_tx; + int64_t duration_rx; bool flush; bool started; + + struct clock_sync rx_sync; }; struct stream { @@ -65,6 +89,12 @@ struct stream { struct spa_bt_latency tx_latency; struct spa_bt_decode_buffer *source_buf; + + /** Stream packet sequence number, relative to group::rx_sync */ + int64_t rx_pos; + + /** Current graph clock position */ + uint64_t position; }; struct modify_info @@ -151,11 +181,11 @@ static uint64_t get_time_ns(struct spa_system *system, clockid_t clockid) static int set_timers(struct group *group) { - if (group->duration == 0) + if (group->duration_tx == 0) return -EINVAL; - group->next = SPA_ROUND_UP(get_time_ns(group->data_system, CLOCK_MONOTONIC) + group->duration, - group->duration); + group->next = SPA_ROUND_UP(get_time_ns(group->data_system, CLOCK_MONOTONIC) + group->duration_tx, + group->duration_tx); return set_timeout(group, group->next); } @@ -309,7 +339,7 @@ static void group_on_timeout(struct spa_source *source) done: /* Pull data for the next interval */ - group->next += exp * group->duration; + group->next += exp * group->duration_tx; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) @@ -355,7 +385,6 @@ static struct group *group_create(struct spa_bt_transport *t, group->log = log; group->data_loop = data_loop; group->data_system = data_system; - group->duration = 0; spa_list_init(&group->streams); @@ -412,12 +441,15 @@ static struct stream *stream_create(struct spa_bt_transport *t, struct group *gr struct spa_audio_info format = { 0 }; int res; bool sink; + int64_t interval, *duration; if (t->profile == SPA_BT_PROFILE_BAP_SINK || t->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) { sink = true; + duration = &group->duration_tx; } else { sink = false; + duration = &group->duration_rx; } if (t->media_codec->kind != MEDIA_CODEC_BAP || !t->media_codec->get_interval) { @@ -425,39 +457,40 @@ static struct stream *stream_create(struct spa_bt_transport *t, struct group *gr goto fail; } - if (sink) { - uint64_t interval; + res = t->media_codec->validate_config(t->media_codec, 0, t->configuration, t->configuration_len, &format); + if (res < 0) + goto fail; - res = t->media_codec->validate_config(t->media_codec, 0, t->configuration, t->configuration_len, &format); - if (res < 0) - goto fail; + codec_data = t->media_codec->init(t->media_codec, 0, t->configuration, t->configuration_len, + &format, NULL, t->write_mtu); + if (!codec_data) { + res = -EINVAL; + goto fail; + } - codec_data = t->media_codec->init(t->media_codec, 0, t->configuration, t->configuration_len, - &format, NULL, t->write_mtu); - if (!codec_data) { - res = -EINVAL; - goto fail; - } + block_size = t->media_codec->get_block_size(codec_data); + if (block_size < 0 || block_size > EMPTY_BUF_SIZE) { + res = -EINVAL; + goto fail; + } - block_size = t->media_codec->get_block_size(codec_data); - if (block_size < 0 || block_size > EMPTY_BUF_SIZE) { - res = -EINVAL; - goto fail; - } + interval = t->media_codec->get_interval(codec_data); + if (interval <= 5000) { + res = -EINVAL; + goto fail; + } - interval = t->media_codec->get_interval(codec_data); - if (interval <= 5000) { - res = -EINVAL; - goto fail; - } + if (*duration == 0) { + *duration = interval; + } else if (interval != *duration) { + /* SDU_Interval in ISO group must be same for each direction */ + res = -EINVAL; + goto fail; + } - if (group->duration == 0) { - group->duration = interval; - } else if (interval != group->duration) { - /* SDU_Interval in ISO group must be same for each direction */ - res = -EINVAL; - goto fail; - } + if (!sink) { + t->media_codec->deinit(codec_data); + codec_data = NULL; } stream = calloc(1, sizeof(struct stream)); @@ -467,7 +500,7 @@ static struct stream *stream_create(struct spa_bt_transport *t, struct group *gr stream->fd = t->fd; stream->sink = sink; stream->group = group; - stream->this.duration = sink ? group->duration : 0; + stream->this.duration = *duration; stream->codec = t->media_codec; stream->this.codec_data = codec_data; @@ -597,26 +630,234 @@ int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *this) return spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log); } -/** Must be called from data thread */ +/** + * Set decode buffer used by a stream when it has packet RX. Set to NULL when stream is + * inactive. + * + * Must be called from data thread. + */ void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *this, struct spa_bt_decode_buffer *buffer) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + struct group *group = stream->group; + struct clock_sync *sync = &group->rx_sync; + + spa_zero(sync->dll); stream->source_buf = buffer; + if (buffer) { + /* Take over buffer overrun handling */ + buffer->no_overrun_drop = true; + buffer->buffering = false; + buffer->avg_period = ISO_BUFFERING_AVG_PERIOD; + buffer->rate_diff_max = ISO_BUFFERING_RATE_DIFF_MAX; + } } -/** Must be called from data thread */ -void spa_bt_iso_io_update_source_latency(struct spa_bt_iso_io *this) +/** + * Get automatic group-wide stream RX target latency. This is useful only for BAP Client. + * BAP Server target latency is determined by the presentation delay. + * + * Must be called from data thread. + */ +int32_t spa_bt_iso_io_get_source_target_latency(struct spa_bt_iso_io *this) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; struct stream *s; int32_t latency = 0; + if (!stream->source_buf) + return 0; + spa_list_for_each(s, &group->streams, link) if (s->source_buf) latency = SPA_MAX(latency, spa_bt_decode_buffer_get_auto_latency(s->source_buf)); - if (stream->source_buf) - spa_bt_decode_buffer_set_target_latency(stream->source_buf, latency); + return latency; +} + +/** + * Called on stream packet RX with packet monotonic timestamp. + * + * Returns the logical SDU reference time, with respect to which decode-buffer should + * target its fill level. This is needed so that all streams converge to same latency + * (with sub-sample accuracy needed for eg. stereo stream alignment). + * + * Determines the ISO group clock rate matching from individual stream packet RX times. + * Packet arrival time is decomposed to + * + * now = group::rx_sync::base_time + stream::rx_pos * group::duration_rx + err + * + * Clock rate matching is done by drifting base_time by the rate difference, so that `err` + * is zero on average across different streams. If stream's rx_pos appears to be out of + * sync, it is resynchronized to a new position. + * + * The logical SDU timestamps for different streams are aligned and occur at equal + * intervals, but the RX timestamp `now` we actually get here is a software timestamp + * indicating when packet was received by kernel. In practice, they are not equally spaced + * but are approximately aligned between different streams. + * + * The Core v6.1 specification does **not** provide any way to synchronize Controller and + * Host clocks, so we can attempt to sync to ISO clock only based on the RX timestamps. + * + * Because the actual packet RX times are not equally spaced, it's ambiguous what the + * logical SDU reference time is. It's then impossible to achieve clock synchronization with + * better accuracy than this jitter (on Intel AX210 it's several ms jitter in a regular + * pattern, plus some random noise). + * + * Aligned playback for different devices cannot be implemented with the tools provided in + * the specification. Some implementation-defined clock synchronization mechanism is + * needed, but kernel (6.17) doesn't have anything and it's not clear such vendor-defined + * mechanisms exist over USB. + * + * The HW timestamps on packets do not help with this, as they are in controller's clock + * domain. They are only useful for aligning packets from different streams. They are also + * optional in the specification and controllers don't necessarily implement them. They + * are not used here. + * + * Must be called from data thread. + */ +int64_t spa_bt_iso_io_recv(struct spa_bt_iso_io *this, int64_t now) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + struct group *group = stream->group; + struct clock_sync *sync = &group->rx_sync; + struct stream *s; + bool resync = false; + int64_t err, t; + + spa_assert(stream->source_buf); + + if (sync->dll.corr == 0) { + sync->base_time = now; + spa_bt_rate_control_init(&sync->dll, 0); + } + + stream->rx_pos++; + t = sync->base_time + group->duration_rx * stream->rx_pos; + err = now - t; + + if (SPA_ABS(err) > group->duration_rx) { + resync = true; + spa_log_debug(group->log, "%p: ISO rx-resync large group:%u fd:%d", + group, group->id, stream->fd); + } + + spa_list_for_each(s, &group->streams, link) { + if (s == stream || !s->source_buf) + continue; + if (SPA_ABS(now - s->source_buf->rx.nsec) < group->duration_rx / 2 && + stream->rx_pos != s->rx_pos) { + spa_log_debug(group->log, "%p: ISO rx-resync balance group:%u fd:%d fd:%d", + group, group->id, stream->fd, s->fd); + resync = true; + break; + } + } + + if (resync) { + stream->rx_pos = (now - sync->base_time + group->duration_rx/2) / group->duration_rx; + t = sync->base_time + group->duration_rx * stream->rx_pos; + err = now - t; + spa_log_debug(group->log, "%p: ISO rx-resync group:%u fd:%d err:%"PRIi64, + group, group->id, stream->fd, err); + } + + sync->avg_err = (sync->avg_err * sync->avg_num + err) / (sync->avg_num + 1); + sync->avg_num++; + + return t; +} + +/** + * Call at end of stream process(), after consuming data. + * + * Apply ISO clock rate matching. + * + * Realign stream RX to target latency, if it is too far off, so that rate matching + * converges faster to alignment. + * + * Must be called from data thread + */ +void spa_bt_iso_io_check_rx_sync(struct spa_bt_iso_io *this, uint64_t position) +{ + struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); + struct group *group = stream->group; + struct stream *s; + const int64_t max_err = group->duration_rx; + struct clock_sync *sync = &group->rx_sync; + int32_t target; + bool overrun = false; + double corr; + + if (!stream->source_buf) + return; + + /* Check sync after all input streams have completed process() on same cycle */ + stream->position = position; + spa_list_for_each(s, &group->streams, link) { + if (!s->source_buf) + continue; + if (s->position != stream->position) + return; + } + + target = stream->source_buf->target; + + /* Rate match ISO clock */ + corr = spa_bt_rate_control_update(&sync->dll, sync->avg_err, 0, + group->duration_rx, CLOCK_SYNC_AVG_PERIOD, CLOCK_SYNC_RATE_DIFF_MAX); + sync->base_time += (int64_t)(group->duration_rx * (corr - 1)); + + enum spa_log_level log_level = (sync->log_pos > SPA_NSEC_PER_SEC) ? SPA_LOG_LEVEL_DEBUG + : SPA_LOG_LEVEL_TRACE; + if (SPA_UNLIKELY(spa_log_level_topic_enabled(group->log, SPA_LOG_TOPIC_DEFAULT, log_level))) { + spa_log_lev(group->log, log_level, + "%p: ISO rx-sync group:%u base:%"PRIi64" avg:%g err:%"PRIi64" corr:%g", + group, group->id, sync->base_time, sync->dll.avg, sync->avg_err, corr-1); + sync->log_pos = 0; + } + sync->log_pos += stream->source_buf->duration_ns; + + sync->avg_err = 0; + sync->avg_num = 0; + + /* Handle overrun (e.g. resyncs streams after initial buffering) */ + spa_list_for_each(s, &group->streams, link) { + if (s->source_buf) { + double level = s->source_buf->level; + int max_level = target + max_err * s->source_buf->rate / SPA_NSEC_PER_SEC; + + if (level > max_level) + overrun = true; + } + } + + if (!overrun) + return; + + spa_list_for_each(s, &group->streams, link) { + if (!s->source_buf) + continue; + + int32_t level = (int32_t)s->source_buf->level; + + if (level > target) { + uint32_t drop = (level - target) * s->source_buf->frame_size; + uint32_t avail = spa_bt_decode_buffer_get_size(s->source_buf); + + drop = SPA_MIN(drop, avail); + + spa_log_debug(group->log, "%p: ISO overrun group:%u fd:%d level:%f target:%d drop:%u", + group, group->id, s->fd, + s->source_buf->level, + target, + drop/s->source_buf->frame_size); + + spa_bt_decode_buffer_read(s->source_buf, drop); + } + + spa_bt_decode_buffer_recover(s->source_buf); + } } diff --git a/spa/plugins/bluez5/iso-io.h b/spa/plugins/bluez5/iso-io.h index 1ff6285c1..c59ebc075 100644 --- a/spa/plugins/bluez5/iso-io.h +++ b/spa/plugins/bluez5/iso-io.h @@ -48,6 +48,8 @@ void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *io, spa_bt_iso_io_pull_t pull, v int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *io); void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *io, struct spa_bt_decode_buffer *buffer); -void spa_bt_iso_io_update_source_latency(struct spa_bt_iso_io *io); +int32_t spa_bt_iso_io_get_source_target_latency(struct spa_bt_iso_io *io); +void spa_bt_iso_io_check_rx_sync(struct spa_bt_iso_io *io, uint64_t position); +int64_t spa_bt_iso_io_recv(struct spa_bt_iso_io *io, int64_t now); #endif diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 7b01b7f01..4fab044a0 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -572,6 +572,9 @@ static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_ spa_log_trace(this->log, "%p: read socket data size:%d", this, src_size); + if (this->transport->iso_io) + now = spa_bt_iso_io_recv(this->transport->iso_io, now); + do { int32_t consumed; uint32_t avail; @@ -674,8 +677,10 @@ stop: this->io_error = true; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); - if (this->transport && this->transport->iso_io) + if (this->transport && this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); + spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); + } } static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, uint64_t now) @@ -931,6 +936,8 @@ static int transport_start(struct impl *this) if (this->codec->kind != MEDIA_CODEC_HFP) { spa_bt_recvmsg_init(&this->recv, this->fd, this->data_system, this->log); + spa_loop_locked(this->data_loop, do_start_sco_iso_io, 0, NULL, 0, this); + this->source.data = this; this->source.fd = this->fd; @@ -944,10 +951,8 @@ static int transport_start(struct impl *this) spa_zero(this->source); if (spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system) < 0) goto fail; - } - - if (this->transport->iso_io || this->transport->sco_io) spa_loop_locked(this->data_loop, do_start_sco_iso_io, 0, NULL, 0, this); + } this->transport_started = true; @@ -1045,8 +1050,10 @@ static int do_remove_transport_source(struct spa_loop *loop, if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); - if (this->transport->iso_io) + if (this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); + spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); + } if (this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); @@ -1661,8 +1668,11 @@ static void update_target_latency(struct impl *this) /* BAP Client. Should use same buffer size for all streams in the same * group, so that capture is in sync. */ - if (this->transport->iso_io) - spa_bt_iso_io_update_source_latency(this->transport->iso_io); + if (this->transport->iso_io) { + int32_t target = spa_bt_iso_io_get_source_target_latency(this->transport->iso_io); + + spa_bt_decode_buffer_set_target_latency(&port->buffer, target); + } return; } @@ -1777,6 +1787,9 @@ static void process_buffering(struct impl *this) spa_list_append(&port->ready, &buffer->link); } + if (this->transport->iso_io && this->position) + spa_bt_iso_io_check_rx_sync(this->transport->iso_io, this->position->clock.position); + if (this->update_delay_event) { int32_t target = spa_bt_decode_buffer_get_target_latency(&port->buffer); uint32_t decoder_delay = 0; From cec0ab322e73de044b84cb014ad83cd9816e8993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 15 Aug 2025 18:21:53 +0200 Subject: [PATCH 0827/1014] meson.build: set `PW_BUILDDIR` in devenv This is needed by `pw-v4l2`. --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index 0a01a8f37..58a00daf8 100644 --- a/meson.build +++ b/meson.build @@ -626,5 +626,6 @@ devenv.prepend('LD_LIBRARY_PATH', builddir / 'pipewire-jack' / 'src') devenv.set('PIPEWIRE_LOG_SYSTEMD', 'false') devenv.set('PW_UNINSTALLED', '1') +devenv.set('PW_BUILDDIR', meson.project_build_root()) meson.add_devenv(devenv) From 91b764faac64a2a264527127cf2790cbcdd08de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 15 Aug 2025 18:33:17 +0200 Subject: [PATCH 0828/1014] pipewire-v4l2: support choice sizes Add support for `SPA_CHOICE_{None,Enum,Step,Range}`. They can all be naturally mapped to `v4l2_frmsizeenum`. --- pipewire-v4l2/src/pipewire-v4l2.c | 97 +++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index c85bfd813..8fc07151a 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -1400,7 +1400,6 @@ static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *ar spa_list_for_each(p, &g->param_list, link) { const struct format_info *fi; uint32_t media_type, media_subtype, format; - struct spa_rectangle size; if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) continue; @@ -1424,22 +1423,96 @@ static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *ar if (fi->fourcc != arg->pixel_format) continue; - if (spa_pod_parse_object(p->param, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size)) < 0) + + const struct spa_pod_prop *size_prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_size); + if (!size_prop) continue; - arg->type = V4L2_FRMSIZE_TYPE_DISCRETE; - arg->discrete.width = size.width; - arg->discrete.height = size.height; + uint32_t n_sizes, choice; + const struct spa_pod *size_pods = spa_pod_get_values(&size_prop->value, &n_sizes, &choice); + if (!size_pods || n_sizes <= 0) + continue; + + const struct spa_rectangle *sizes = SPA_POD_BODY_CONST(size_pods); + if (size_pods->type != SPA_TYPE_Rectangle || size_pods->size != sizeof(*sizes)) + continue; + + switch (choice) { + case SPA_CHOICE_Enum: + n_sizes -= 1; + sizes += 1; + SPA_FALLTHROUGH; + case SPA_CHOICE_None: + arg->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + for (size_t i = 0; i < n_sizes; i++, count++) { + arg->discrete.width = sizes[i].width; + arg->discrete.height = sizes[i].height; + + pw_log_debug("count:%u %.4s %ux%u", count, (char*)&fi->fourcc, + arg->discrete.width, arg->discrete.height); + + if (count == arg->index) { + found = true; + break; + } + } - pw_log_debug("count:%d %.4s %dx%d", count, (char*)&fi->fourcc, - size.width, size.height); - if (count == arg->index) { - found = true; break; + case SPA_CHOICE_Range: + if (n_sizes < 3) + continue; + + arg->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + arg->stepwise.min_width = sizes[1].width; + arg->stepwise.min_height = sizes[1].height; + arg->stepwise.max_width = sizes[2].width; + arg->stepwise.max_height = sizes[2].height; + arg->stepwise.step_width = arg->stepwise.step_height = 1; + + pw_log_debug("count:%u %.4s (%ux%u)-(%ux%u)", count, (char*)&fi->fourcc, + arg->stepwise.min_width, arg->stepwise.min_height, + arg->stepwise.max_width, arg->stepwise.max_height); + + if (count == arg->index) { + found = true; + break; + } + + count++; + + break; + case SPA_CHOICE_Step: + if (n_sizes < 4) + continue; + + arg->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + arg->stepwise.min_width = sizes[1].width; + arg->stepwise.min_height = sizes[1].height; + arg->stepwise.max_width = sizes[2].width; + arg->stepwise.max_height = sizes[2].height; + arg->stepwise.step_width = sizes[3].width; + arg->stepwise.step_height = sizes[3].height; + + pw_log_debug("count:%u %.4s (%ux%u)-(%ux%u)/(+%u,%u)", count, (char*)&fi->fourcc, + arg->stepwise.min_width, arg->stepwise.min_height, + arg->stepwise.min_width, arg->stepwise.min_height, + arg->stepwise.step_width, arg->stepwise.step_height); + + if (count == arg->index) { + found = true; + break; + } + + count++; + + break; + default: + continue; } - count++; + + if (found) + break; } pw_thread_loop_unlock(file->loop); From 9d01a26242f8aba6a2bd2e836735f61d8933b88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 8 Sep 2025 12:44:39 +0200 Subject: [PATCH 0829/1014] pipewire: mem: forward declare `spa_hook` This type is used in function arguments, so forward declare it. --- src/pipewire/mem.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h index 525e58525..52abbd54d 100644 --- a/src/pipewire/mem.h +++ b/src/pipewire/mem.h @@ -7,6 +7,8 @@ #include +struct spa_hook; + #ifdef __cplusplus extern "C" { #endif From 93774b1d149323a72a0f094d589e95e3f9404968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 8 Sep 2025 12:45:16 +0200 Subject: [PATCH 0830/1014] pipewire: mem: log page size on creation --- src/pipewire/mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index f5cc198f0..1fa371247 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -167,7 +167,7 @@ struct pw_mempool *pw_mempool_new(struct pw_properties *props) impl->pagesize = sysconf(_SC_PAGESIZE); - pw_log_debug("%p: new", this); + pw_log_debug("%p: new pagesize:%" PRIu32 "", this, impl->pagesize); spa_hook_list_init(&impl->listener_list); pw_map_init(&impl->map, 64, 64); From 0b08468035f1a4194dbb53bc3d60d6077f011be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Mon, 8 Sep 2025 12:47:10 +0200 Subject: [PATCH 0831/1014] pipewire: mem: pw_memblock_map(): fix pointer when reusing mapping Previously the pointer was determined as follows: mm->this.ptr = SPA_PTROFF(m->ptr, range.start, void); however, when `pw_map_range` is calculated, `pw_map_range::start` is the offset from the beginning of the first page, starting at `pw_map_range::offset`. This works correctly if `memblock_map()` runs because that will map the file with expected offset, so using `range.start` is correct. However, when a mapping is reused (i.e. `memblock_find_mapping()`) finds something, then `range.start` is not necessarily correct. Consider the following example: * page size is 10 * one memblock with size 20 (2 pages) * the applications wants to mappings: * (offset=5,size=10) * (offset=15,size=5) After the first request from the application, a `mapping` object is created that covers the first two pages of the memblock: offset=0 and size=20. During the second request, the calculated `pw_map_range` is as follows: { start = 5, offset = 10, size = 10 } and the only previously created mapping is reused since (0 <= 5) and (10 <= 20). When the pointer of the mapping is adjusted afterwards it will be incorrect since `m->ptr` points to byte 0 on page 0 (instead of byte 0 on page 1 -- that is assumed). Thereforce the two will unexpectedly overlap. Fix that by using `offset - m->offset` when adjusting the mapping's pointer. Also move the `range` variable into a smaller scope because it only makes sense there. And add a test that check the above previously incorrect case. Fixes: 2caf81c97c0aee ("mem: improve memory handling") Fixes #4884 --- src/pipewire/mem.c | 15 +++++++------- test/meson.build | 1 + test/test-mempool.c | 49 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 test/test-mempool.c diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index 1fa371247..642a6b78e 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -393,7 +393,6 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block, struct mempool *p = SPA_CONTAINER_OF(block->pool, struct mempool, this); struct mapping *m; struct memmap *mm; - struct pw_map_range range; struct stat sb; if (b->this.fd == -1) { @@ -418,13 +417,15 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block, return NULL; } - pw_map_range_init(&range, offset, size, p->pagesize); - m = memblock_find_mapping(b, flags, offset, size); - if (m == NULL) + if (m == NULL) { + struct pw_map_range range; + pw_map_range_init(&range, offset, size, p->pagesize); + m = memblock_map(b, flags, range.offset, range.size); - if (m == NULL) - return NULL; + if (m == NULL) + return NULL; + } mm = calloc(1, sizeof(struct memmap)); if (mm == NULL) { @@ -439,7 +440,7 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block, mm->this.flags = flags; mm->this.offset = offset; mm->this.size = size; - mm->this.ptr = SPA_PTROFF(m->ptr, range.start, void); + mm->this.ptr = SPA_PTROFF(m->ptr, offset - m->offset, void); pw_log_debug("%p: map:%p block:%p fd:%d flags:%08x ptr:%p (%u %u) mapping:%p ref:%d", p, &mm->this, b, b->this.fd, b->this.flags, mm->this.ptr, offset, size, m, m->ref); diff --git a/test/meson.build b/test/meson.build index c5671149d..ad658bcc6 100644 --- a/test/meson.build +++ b/test/meson.build @@ -52,6 +52,7 @@ test('test-pw-utils', 'test-properties.c', 'test-array.c', 'test-map.c', + 'test-mempool.c', 'test-utils.c', include_directories: pwtest_inc, dependencies: [ spa_dep ], diff --git a/test/test-mempool.c b/test/test-mempool.c new file mode 100644 index 000000000..36f69c571 --- /dev/null +++ b/test/test-mempool.c @@ -0,0 +1,49 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 PipeWire authors */ +/* SPDX-License-Identifier: MIT */ + +#include + +#include +#include + +#include "pwtest.h" + +PWTEST(mempool_issue4884) +{ + /* + * See https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4884. This + * test checks if the offset is correctly applied when a mapping is reused. + */ + + long page_size = sysconf(_SC_PAGESIZE); + pwtest_errno_ok(page_size); + pwtest_int_ge(page_size, 8); + + struct pw_mempool *p = pw_mempool_new(NULL); + pwtest_ptr_notnull(p); + + struct pw_memblock *b = pw_mempool_alloc(p, PW_MEMBLOCK_FLAG_READWRITE, SPA_DATA_MemFd, 2 * page_size); + pwtest_ptr_notnull(b); + + struct pw_memmap *m1 = pw_mempool_map_id(p, b->id, PW_MEMMAP_FLAG_READWRITE, page_size / 2, page_size, NULL); + pwtest_ptr_notnull(m1); + pwtest_ptr_eq(m1->block, b); + + struct pw_memmap *m2 = pw_mempool_map_id(p, b->id, PW_MEMMAP_FLAG_READWRITE, 3 * page_size / 2, page_size / 2, NULL); + pwtest_ptr_notnull(m2); + pwtest_ptr_eq(m2->block, b); + + pwtest_int_eq(SPA_PTRDIFF(m2->ptr, m1->ptr), page_size); + + pw_mempool_destroy(p); + + return PWTEST_PASS; +} + +PWTEST_SUITE(pw_mempool) +{ + pwtest_add(mempool_issue4884, PWTEST_NOARG); + + return PWTEST_PASS; +} From 144c3630eae00a7908cf8939f843846999b2134d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Sep 2025 14:29:18 +0200 Subject: [PATCH 0832/1014] examples: fix texture updates --- src/examples/video-play-pull.c | 40 +++++++++++++++++++++------------- src/examples/video-play.c | 33 +++++++++++++++------------- src/examples/video-src.c | 2 +- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index bc66c6601..f0c5892ac 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -171,14 +171,15 @@ on_process(void *_data) /* copy video image in texture */ if (data->is_yuv) { sstride = data->stride; - SDL_UpdateYUVTexture(data->texture, - NULL, - sdata, - sstride, - SPA_PTROFF(sdata, sstride * data->size.height, void), - sstride / 2, - SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void), - sstride / 2); + if (buf->n_datas == 1) { + SDL_UpdateTexture(data->texture, NULL, + sdata, sstride); + } else { + SDL_UpdateYUVTexture(data->texture, NULL, + sdata, sstride, + buf->datas[1].data, sstride / 2, + buf->datas[2].data, sstride / 2); + } } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { @@ -331,7 +332,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) const struct spa_pod *params[5]; Uint32 sdl_format; void *d; - int32_t mult, size; + int32_t mult, size, blocks; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) @@ -383,20 +384,29 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { - pw_stream_set_error(stream, -EINVAL, "invalid texture format"); - return; - } - SDL_UnlockTexture(data->texture); switch(sdl_format) { case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: + data->stride = data->size.width; size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; + blocks = 3; + break; + case SDL_PIXELFORMAT_YUY2: + data->stride = data->size.width * 2; + size = data->stride * data->size.height; + data->is_yuv = true; + blocks = 1; break; default: + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + data->stride = data->size.width * 2; + } + else + SDL_UnlockTexture(data->texture); size = data->stride * data->size.height; + blocks = 1; break; } @@ -410,7 +420,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stride; if (buf->n_datas == 1) { - datas[0] = sdata; - datas[1] = SPA_PTROFF(sdata, sstride * data->size.height, void); - datas[2] = SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void); + SDL_UpdateTexture(data->texture, NULL, + sdata, sstride); } else { datas[0] = sdata; datas[1] = buf->datas[1].data; datas[2] = buf->datas[2].data; + SDL_UpdateYUVTexture(data->texture, NULL, + datas[0], sstride, + datas[1], sstride / 2, + datas[2], sstride / 2); } - SDL_UpdateYUVTexture(data->texture, NULL, - datas[0], sstride, - datas[1], sstride / 2, - datas[2], sstride / 2); } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); - goto done; } sstride = buf->datas[0].chunk->stride; @@ -355,21 +353,26 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); - if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { - fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); - pw_stream_set_error(stream, -EINVAL, "invalid format"); - return; - } - SDL_UnlockTexture(data->texture); - switch(sdl_format) { case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: + data->stride = data->size.width; size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; blocks = 3; break; + case SDL_PIXELFORMAT_YUY2: + data->is_yuv = true; + data->stride = data->size.width * 2; + size = (data->stride * data->size.height); + blocks = 1; + break; default: + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + data->stride = data->size.width * 2; + } else + SDL_UnlockTexture(data->texture); size = data->stride * data->size.height; blocks = 1; break; diff --git a/src/examples/video-src.c b/src/examples/video-src.c index f439f168e..92db5ff78 100644 --- a/src/examples/video-src.c +++ b/src/examples/video-src.c @@ -172,7 +172,7 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum { struct data *data = _data; - printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); + printf("stream state: \"%s\" %s\n", pw_stream_state_as_string(state), error ? error : ""); switch (state) { case PW_STREAM_STATE_ERROR: From 1e5a938e434555c65a588bdeee382997e3d1b835 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Sep 2025 15:10:12 +0200 Subject: [PATCH 0833/1014] adapter: disable rate_match for the video adapter We don't actually implement this for the video adapter. We should ideally check for the SPA_IO_RateMatch param to decide this.. --- spa/plugins/audioconvert/audioadapter.c | 6 +++++- spa/plugins/videoconvert/videoadapter.c | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 1b6fd55f9..c5b113b55 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -93,6 +93,7 @@ struct impl { struct spa_callbacks callbacks; unsigned int add_listener:1; + unsigned int have_rate_match:1; unsigned int have_format:1; unsigned int recheck_format:1; unsigned int started:1; @@ -283,7 +284,7 @@ static int link_io(struct impl *this) spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; - if (this->follower == this->target) { + if (this->follower == this->target || !this->have_rate_match) { rate_match = NULL; rate_match_size = 0; } else { @@ -2144,6 +2145,9 @@ impl_init(const struct spa_handle_factory *factory, this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); + /* FIXME, we should check the IO params for SPA_IO_RateMatch */ + this->have_rate_match = true; + this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); this->ploader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index ccf9365f3..fab360acb 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -94,6 +94,7 @@ struct impl { struct spa_callbacks callbacks; unsigned int add_listener:1; + unsigned int have_rate_match:1; unsigned int have_format:1; unsigned int recheck_format:1; unsigned int started:1; @@ -284,7 +285,7 @@ static int link_io(struct impl *this) spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; - if (this->follower == this->target) { + if (this->follower == this->target || !this->have_rate_match) { rate_match = NULL; rate_match_size = 0; } else { From d6488c53511401ba08e03ab3e5272cd808b930f2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Sep 2025 15:11:45 +0200 Subject: [PATCH 0834/1014] stream: decouple the requested size from scheduling The docs say that a requested size of 0 can be returned and it means that there is no suggestion for the size. Make this so by decoupling the requested size value and the triggering of the process callback. If we have no rate_match and no quantum (because the driver didn't set it) we still want to schedule with a 0 requested size. --- src/pipewire/stream.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 904e44a67..b0192a7a9 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -97,6 +97,7 @@ struct stream { struct spa_io_buffers *io; struct spa_io_rate_match *rate_match; uint32_t rate_queued; + uint32_t have_requested; uint64_t rate_size; uint64_t port_change_mask_all; @@ -449,9 +450,10 @@ static inline uint32_t update_requested(struct stream *impl) buffer = &impl->buffers[id]; buffer->this.requested = impl->rate_size; - pw_log_trace_fp("%p: update buffer:%u req:%"PRIu64, impl, id, buffer->this.requested); + pw_log_trace_fp("%p: update buffer:%u req:%"PRIu64" %p", impl, id, buffer->this.requested, + impl->rate_match); - return buffer->this.requested > 0 ? 1 : 0; + return impl->have_requested; } static inline void call_process(struct stream *impl) @@ -681,9 +683,11 @@ static inline void copy_position(struct stream *impl, int64_t queued) if (SPA_LIKELY(impl->rate_match != NULL)) { impl->rate_queued = impl->rate_match->delay; impl->rate_size = impl->rate_match->size; + impl->have_requested = impl->rate_size != 0; } else { impl->rate_queued = 0; impl->rate_size = impl->quantum; + impl->have_requested = 1; } SPA_SEQ_WRITE(impl->seq); } From 2891e579a129b4b3dc10d8cd965545425fa66f59 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Sep 2025 15:14:36 +0200 Subject: [PATCH 0835/1014] impl-node: improve the node unprepare function do_node_unprepare runs in both the server and the client when a node is stopped. On the server size, set the status to FINISHED and trigger any targets. This ensures the node will not be scheduled in this cycle anymore. We have to do this because we can't know if the node is still alive or not. When the client receives the stop message, it will unprepare and set the status to INACTIVE. This ensures the driver will no longer trigger the node. If the server didn't already trigger the targets, do this in the remote node then. This avoid a race where both the client and the server are setting the status and if the INACTIVE state is set by the server, it might stall processing of the client. Fixes #4840 --- src/pipewire/impl-node.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 2c1db3e2a..5d360e88c 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -240,17 +240,21 @@ do_node_unprepare(struct spa_loop *loop, bool async, uint32_t seq, { struct pw_impl_node *this = user_data; struct pw_node_target *t; - int old_state; + int old_state, new_state; uint64_t trigger = 0; pw_log_trace("%p: unprepare %d remote:%d exported:%d", this, this->rt.prepared, this->remote, this->exported); - /* We mark ourself as finished now, this will avoid going further into the process loop - * in case our fd was ready (removing ourselfs from the loop should avoid that as well). - * If we were supposed to be scheduled make sure we continue the graph for the peers we - * were supposed to trigger */ - old_state = SPA_ATOMIC_XCHG(this->rt.target.activation->status, PW_NODE_ACTIVATION_INACTIVE); + /* The remote client will INACTIVE itself and remove itself from the loop to avoid + * being scheduled. + * The server will mark remote nodes as FINISHED. This will make sure the node will not + * trigger the peers anymore when it will stop because we do that on the server side + * because the client might simply be dead and not able to resume anything. + */ + new_state = this->remote ? PW_NODE_ACTIVATION_FINISHED : PW_NODE_ACTIVATION_INACTIVE; + + old_state = SPA_ATOMIC_XCHG(this->rt.target.activation->status, new_state); if (PW_NODE_ACTIVATION_PENDING_TRIGGER(old_state)) trigger = get_time_ns(this->rt.target.system); From 862ac1f7c80a98ace88bd83a5b9850f19a85bbee Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 9 Sep 2025 15:54:21 +0200 Subject: [PATCH 0836/1014] examples: use BGRA as the format RGB is not supported by videoconvert. --- src/examples/video-src-alloc.c | 4 ++-- src/examples/video-src-fixate.c | 12 ++++++------ src/examples/video-src-reneg.c | 8 ++++---- src/examples/video-src.c | 8 ++++++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/examples/video-src-alloc.c b/src/examples/video-src-alloc.c index 1165183c4..8348fdf92 100644 --- a/src/examples/video-src-alloc.c +++ b/src/examples/video-src-alloc.c @@ -22,7 +22,7 @@ #include -#define BPP 3 +#define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 @@ -404,7 +404,7 @@ int main(int argc, char *argv[]) SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGB), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), diff --git a/src/examples/video-src-fixate.c b/src/examples/video-src-fixate.c index 724974a44..2d77a8887 100644 --- a/src/examples/video-src-fixate.c +++ b/src/examples/video-src-fixate.c @@ -24,7 +24,7 @@ #include -#define BPP 3 +#define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 @@ -414,11 +414,11 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) modifier = modifiers[rand()%n_modifiers]; } - params[0] = fixate_format(&b, SPA_VIDEO_FORMAT_RGB, &modifier); + params[0] = fixate_format(&b, SPA_VIDEO_FORMAT_RGBA, &modifier); - params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGB, + params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, supported_modifiers, sizeof(supported_modifiers)/sizeof(supported_modifiers[0])); - params[2] = build_format(&b, SPA_VIDEO_FORMAT_RGB, + params[2] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, NULL, 0); printf("announcing fixated EnumFormats\n"); @@ -540,9 +540,9 @@ int main(int argc, char *argv[]) * The server will select a format that matches and informs us about this * in the stream param_changed event. */ - params[0] = build_format(&b, SPA_VIDEO_FORMAT_RGB, + params[0] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, supported_modifiers, sizeof(supported_modifiers)/sizeof(supported_modifiers[0])); - params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGB, NULL, 0); + params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, NULL, 0); printf("announcing starting EnumFormats\n"); for (unsigned int i=0; i < 2; i++) { diff --git a/src/examples/video-src-reneg.c b/src/examples/video-src-reneg.c index d9f6abf8b..9c322d61a 100644 --- a/src/examples/video-src-reneg.c +++ b/src/examples/video-src-reneg.c @@ -22,7 +22,7 @@ #include -#define BPP 3 +#define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 @@ -373,7 +373,7 @@ static void on_reneg_timeout(void *userdata, uint64_t expirations) SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGB), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); @@ -398,7 +398,7 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); /* create a thread loop and start it */ - data.loop = pw_thread_loop_new("video-src-alloc", NULL); + data.loop = pw_thread_loop_new("video-src-reneg", NULL); /* take the lock around all PipeWire functions. In callbacks, the lock * is already taken for you but it's ok to lock again because the lock is @@ -449,7 +449,7 @@ int main(int argc, char *argv[]) SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGB), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), diff --git a/src/examples/video-src.c b/src/examples/video-src.c index 92db5ff78..cba32c94e 100644 --- a/src/examples/video-src.c +++ b/src/examples/video-src.c @@ -16,10 +16,11 @@ #include #include #include +#include #include -#define BPP 3 +#define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 @@ -224,6 +225,9 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) if (param == NULL || id != SPA_PARAM_Format) return; + fprintf(stderr, "got format:\n"); + spa_debug_format(2, NULL, param); + spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); @@ -317,7 +321,7 @@ int main(int argc, char *argv[]) SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), - SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGB), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_BGRA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), From 2be4c2ba51e642a8c7e8ccf0931bad9d1bdff12d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 10 Sep 2025 12:11:18 +0200 Subject: [PATCH 0837/1014] meta: add SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE This flag is set by the producer and should be cleared by the consumer when it promises to signal the release point. When a consumer dequeues a buffer with the flag set, it should assume the client is not going to signal the release point and so it should reuse the buffer right away. This can only happen when the client didn't dequeue the buffer at all (killed, timeout, error, ...) or when it dequeued and queued the buffer without clearing the flag. See #4885 --- spa/include/spa/buffer/meta.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index 85d9d655b..19eee808a 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -184,6 +184,9 @@ struct spa_meta_videotransform { * layout with 2 extra fds. */ struct spa_meta_sync_timeline { +#define SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE (1<<0) /**< this flag is set by the producer and cleared + * by the consumer when it promises to signal + * the release point */ uint32_t flags; uint32_t padding; uint64_t acquire_point; /**< the timeline acquire point, this is when the data From 818ef4e13833016bb131fa75d7d8bb6f9a4eaff8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 12 Sep 2025 16:17:26 +0200 Subject: [PATCH 0838/1014] spa: flags just have 1 value --- doc/dox/api/spa-pod.dox | 5 +++-- spa/include/spa/pod/pod.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/dox/api/spa-pod.dox b/doc/dox/api/spa-pod.dox index 0bba2a474..67ee20750 100644 --- a/doc/dox/api/spa-pod.dox +++ b/doc/dox/api/spa-pod.dox @@ -1019,8 +1019,9 @@ A choice contains an array of possible values. child2 and child3, in steps of child4 in the value array. - Enum (3) : child1 is a default value, options are any value from the value array, preferred values come first. - - Flags (4) : child1 is a default value, options are any value from - the value array, preferred values come first. + - Flags (4) : only child1 is a flag value. When filtering, the flags + are AND-ed together. + - flags: must be 0 ## Pod (20) diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 3cd1aae49..91c4d57ce 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -134,7 +134,7 @@ enum spa_choice_type { SPA_CHOICE_Range, /**< range: default, min, max */ SPA_CHOICE_Step, /**< range with step: default, min, max, step */ SPA_CHOICE_Enum, /**< list: default, alternative,... */ - SPA_CHOICE_Flags, /**< flags: default, possible flags,... */ + SPA_CHOICE_Flags, /**< flags: first value is flags */ }; struct spa_pod_choice_body { From 41e6e875e5e48b9e908b007e246a9ee961e60e24 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 12 Sep 2025 16:33:48 +0200 Subject: [PATCH 0839/1014] spa: clean up the filter code Use SPA_FLAG_IS_SET() and use the same if/else pattern. --- spa/include/spa/pod/filter.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 836095029..d5fe18d24 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -292,7 +292,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, p2 = spa_pod_object_find_prop(of, p2, p1->key); if (p2 != NULL) res = spa_pod_filter_prop(b, p1, p2); - else if ((p1->flags & SPA_POD_PROP_FLAG_MANDATORY) != 0) + else if (SPA_FLAG_IS_SET(p1->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; else spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); @@ -305,11 +305,12 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, p1 = spa_pod_object_find_prop(op, p1, p2->key); if (p1 != NULL) continue; - if ((p2->flags & SPA_POD_PROP_FLAG_MANDATORY) != 0) + if (SPA_FLAG_IS_SET(p2->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; + else + spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); if (res < 0) break; - spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); } } spa_pod_builder_pop(b, &f); From 9df770eb168749926cd37558d05bbdf00d5e4b4d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 12 Sep 2025 16:54:04 +0200 Subject: [PATCH 0840/1014] spa: support props flags building and parsing Add an option to make a property with specific flags. Do this by changing the parser and builder to see the invalid property as an escape sequence followed by the property key and the flags. --- spa/include/spa/pod/builder.h | 8 ++++++-- spa/include/spa/pod/parser.h | 12 +++++++++--- spa/include/spa/pod/vararg.h | 2 ++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index ababd2e8d..27a3611a6 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -625,10 +625,14 @@ spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args) switch (ftype) { case SPA_TYPE_Object: { - uint32_t key = va_arg(args, uint32_t); + uint32_t key = va_arg(args, uint32_t), flags = 0; if (key == 0) goto exit; - spa_pod_builder_prop(builder, key, 0); + if (key == SPA_ID_INVALID) { + key = va_arg(args, uint32_t); + flags = va_arg(args, uint32_t); + } + spa_pod_builder_prop(builder, key, flags); break; } case SPA_TYPE_Sequence: diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 6598c3060..0e687ac9d 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -800,13 +800,19 @@ SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_lis struct spa_pod_prop prop; if (f->pod.type == SPA_TYPE_Object) { - uint32_t key = va_arg(args, uint32_t); + uint32_t key = va_arg(args, uint32_t), *flags = NULL; if (key == 0) break; - - if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) + if (key == SPA_ID_INVALID) { + key = va_arg(args, uint32_t); + flags = va_arg(args, uint32_t*); + } + if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) { pod = prop.value; + if (flags) + *flags = prop.flags; + } } if ((format = va_arg(args, char *)) == NULL) diff --git a/spa/include/spa/pod/vararg.h b/spa/include/spa/pod/vararg.h index f777f953a..b2e5ec8eb 100644 --- a/spa/include/spa/pod/vararg.h +++ b/spa/include/spa/pod/vararg.h @@ -20,6 +20,8 @@ extern "C" { #define SPA_POD_Prop(key,...) \ key, ##__VA_ARGS__ +#define SPA_POD_Propf(key,flags,...) \ + SPA_ID_INVALID, key, flags, ##__VA_ARGS__ #define SPA_POD_Control(offset,type,...) \ offset, type, ##__VA_ARGS__ From f81bb670c35331772746388156bfb7ff4070af4a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 15 Sep 2025 10:15:01 +0200 Subject: [PATCH 0841/1014] Revert "impl-node: improve the node unprepare function" This reverts commit 2891e579a129b4b3dc10d8cd965545425fa66f59. This seems to cause regresssions with nodes xrun etc. See #4893 --- src/pipewire/impl-node.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 5d360e88c..2c1db3e2a 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -240,21 +240,17 @@ do_node_unprepare(struct spa_loop *loop, bool async, uint32_t seq, { struct pw_impl_node *this = user_data; struct pw_node_target *t; - int old_state, new_state; + int old_state; uint64_t trigger = 0; pw_log_trace("%p: unprepare %d remote:%d exported:%d", this, this->rt.prepared, this->remote, this->exported); - /* The remote client will INACTIVE itself and remove itself from the loop to avoid - * being scheduled. - * The server will mark remote nodes as FINISHED. This will make sure the node will not - * trigger the peers anymore when it will stop because we do that on the server side - * because the client might simply be dead and not able to resume anything. - */ - new_state = this->remote ? PW_NODE_ACTIVATION_FINISHED : PW_NODE_ACTIVATION_INACTIVE; - - old_state = SPA_ATOMIC_XCHG(this->rt.target.activation->status, new_state); + /* We mark ourself as finished now, this will avoid going further into the process loop + * in case our fd was ready (removing ourselfs from the loop should avoid that as well). + * If we were supposed to be scheduled make sure we continue the graph for the peers we + * were supposed to trigger */ + old_state = SPA_ATOMIC_XCHG(this->rt.target.activation->status, PW_NODE_ACTIVATION_INACTIVE); if (PW_NODE_ACTIVATION_PENDING_TRIGGER(old_state)) trigger = get_time_ns(this->rt.target.system); From 09cd7bf7837b6f73757ae2e11c74fc1895f76030 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 15 Sep 2025 10:22:16 +0200 Subject: [PATCH 0842/1014] impl-node: only do unprepare once There is not reason to do the unprepare logic twice and it might actually interfere with the actions of the client. See #4840 Fixes #4893 --- src/pipewire/impl-node.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 2c1db3e2a..14f76b9e7 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -246,17 +246,21 @@ do_node_unprepare(struct spa_loop *loop, bool async, uint32_t seq, pw_log_trace("%p: unprepare %d remote:%d exported:%d", this, this->rt.prepared, this->remote, this->exported); - /* We mark ourself as finished now, this will avoid going further into the process loop - * in case our fd was ready (removing ourselfs from the loop should avoid that as well). - * If we were supposed to be scheduled make sure we continue the graph for the peers we - * were supposed to trigger */ + if (!this->rt.prepared) + return 0; + + /* The remote client will INACTIVE itself and remove itself from the loop to avoid + * being scheduled. + * The server will mark remote nodes as FINISHED and trigger the peers. This will + * make sure the remote node will not trigger the peers anymore when it will + * stop (it only triggers peers when it has PENDING_TRIGGER (<= AWAKE)). We have + * to trigger the peers on the server because the client might simply be dead and + * not able to trigger anything. + */ old_state = SPA_ATOMIC_XCHG(this->rt.target.activation->status, PW_NODE_ACTIVATION_INACTIVE); if (PW_NODE_ACTIVATION_PENDING_TRIGGER(old_state)) trigger = get_time_ns(this->rt.target.system); - if (!this->rt.prepared) - return 0; - if (!this->remote) spa_loop_remove_source(loop, &this->source); From f89428d9f87488075c22b3c0046e90c53a920a7a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 15 Sep 2025 12:29:57 +0200 Subject: [PATCH 0843/1014] tools: print async node state in pw-top Pass the node async state in the profiler and use this in pw-top to draw the node with = instead of a + in the tree when it's async. --- doc/dox/programs/pw-top.1.md | 6 +++--- spa/include/spa/param/profiler.h | 3 ++- src/modules/module-profiler.c | 3 ++- src/tools/pw-top.c | 6 ++++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/doc/dox/programs/pw-top.1.md b/doc/dox/programs/pw-top.1.md index 460fd80d4..e1a866b55 100644 --- a/doc/dox/programs/pw-top.1.md +++ b/doc/dox/programs/pw-top.1.md @@ -14,7 +14,7 @@ node and device statistics. A hierarchical view is shown of Driver nodes and follower nodes. The Driver nodes are actively using a timer to schedule dataflow in the followers. The followers of a driver node as shown below their driver -with a + sign in a tree-like representation. +with a + sign (or = for async nodes) in a tree-like representation. The columns presented are as follows: @@ -173,8 +173,8 @@ For Video formats, the layout is \ \parblock Name assigned to the device/node, as found in *pw-dump* node.name -Names are prefixed by *+* when they are linked to a driver (entry -above with no +) +Names are prefixed by *+*/*=* when they are linked to a driver (entry +above with no +/=) \endparblock # COMMANDS diff --git a/spa/include/spa/param/profiler.h b/spa/include/spa/param/profiler.h index eb3fbadc9..8c87662d7 100644 --- a/spa/include/spa/param/profiler.h +++ b/spa/include/spa/param/profiler.h @@ -66,7 +66,8 @@ enum spa_profiler { * Long : finish, * Int : status, * Fraction : latency, - * Int : xrun_count)) */ + * Int : xrun_count)) + * Bool : async)) */ SPA_PROFILER_followerClock, /**< follower clock information * (Struct( * Int : clock id, diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index 3031caec3..d6ed9914c 100644 --- a/src/modules/module-profiler.c +++ b/src/modules/module-profiler.c @@ -298,7 +298,8 @@ static void context_do_profile(void *data) SPA_POD_Long(n->async ? na->prev_finish_time : na->finish_time), SPA_POD_Int(na->status), SPA_POD_Fraction(&latency), - SPA_POD_Int(na->xrun_count)); + SPA_POD_Int(na->xrun_count), + SPA_POD_Bool(n->async)); if (n->driver) { spa_pod_builder_prop(&b, SPA_PROFILER_followerClock, 0); diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 6972ff2b7..6b936a9bf 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -44,6 +44,7 @@ struct measurement { int64_t finish; struct spa_fraction latency; uint32_t xrun_count; + bool async; }; struct node { @@ -419,7 +420,8 @@ static int process_follower_block(struct data *d, const struct spa_pod *pod, str SPA_POD_Long(&m.finish), SPA_POD_Int(&m.status), SPA_POD_Fraction(&m.latency), - SPA_POD_OPT_Int(&m.xrun_count))) < 0) + SPA_POD_OPT_Int(&m.xrun_count), + SPA_POD_OPT_Bool(&m.async))) < 0) return res; if ((n = find_node(d, id)) == NULL) @@ -540,7 +542,7 @@ static void print_node(struct data *d, struct node *dr, struct node *n, int y) i->xrun_count - dr->info_base : n->measurement.xrun_count - n->measurement_base, active ? n->format : "", - n->driver == n ? "" : " + ", + n->driver == n ? "" : n->measurement.async ? " = " : " + ", n->name); } From 4dccddd564395f3019964e96741f8fcf9a39debb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 15 Sep 2025 17:36:20 +0200 Subject: [PATCH 0844/1014] impl-link: add 1 quantum latency for async links Update the scheduling doc with some information about how async scheduling works. Also add something about the latency. Async links add 1 quantum of latency so take that into account when aggregating latencies. Also a source directly linked to an async node does not add latency (we evaluate the tee before incrementing the cycle so that it effectively is executed in the previous cycle and consumed immediately by async nodes). We can do this because the driver source always provides data before the async node, and never concurrently. Add a listener to the link for the node driver change as well because that can now influence the latency for async nodes. --- doc/dox/internals/scheduling.dox | 116 ++++++++++++++++++++++++++++++- src/pipewire/impl-link.c | 23 ++++-- src/pipewire/impl-port.c | 21 +++++- src/pipewire/private.h | 1 + 4 files changed, 151 insertions(+), 10 deletions(-) diff --git a/doc/dox/internals/scheduling.dox b/doc/dox/internals/scheduling.dox index 989aa0306..17e9fe28c 100644 --- a/doc/dox/internals/scheduling.dox +++ b/doc/dox/internals/scheduling.dox @@ -152,6 +152,7 @@ will then: - Check the previous cycle. Did it complete? Mark xrun on unfinished nodes. - Perform reposition requests if any, timebase changes, etc.. - The pending counter of each follower node is set to the required field. + - Update the cycle counter in the driver activation io. - It then loops over all targets of the driver and atomically decrements the required field of the activation record. When the required field is 0, the eventfd is signaled and the node can be scheduled. @@ -186,6 +187,118 @@ fields from all the nodes in the target list of the driver are now 0. The driver calculates some stats about cpu time etc. +# Async scheduling + +When a node has the node.async property set to true, it will be considered an async +node and will be scheduled differently. + +Async nodes don't increment the pending counter of their peers and the upstream peers +also don't increment the async node pending counters. Only the driver increments the +pending counter to the async node. + +This means that the async nodes do not depend on any other node and also are not a +dependency for other nodes. This also means that the async nodes can be scheduled as +soon as the driver has started the graph. + +The completion of the async node does not influence the completion of the graph in +any way and async nodes are therefor interesting is real-time performance can not +be guaranteed, for example when the processing threads are not running in a real-time +priority. + +A link between a port of an async node and another port (async or not) is called an +async link and will have the link.async=true property. + +Because async nodes then run concurrently with other nodes, a method must be in place +to avoid concurrent access to buffer data. This is done by sending a spa_io_async_buffers +io to the (mixer) ports of an async link. The spa_io_async_buffers has 2 spa_io_buffer +slots. + +The driver will increment a cycle counter for each cycle that it starts. Output ports +will write to the spa_io_async_buffers (cycle+1)&1 slot and input ports will read from +(cycle&1) slots. This way the async node will always consume the output of the previous +cycle and will provide data for the next cycle. They will therefore always add 1 cycle +of latency in the graph. + +A special exception is made for the output ports of the driver node. When the driver is +started, the output port buffers are copied to the previous cycle spa_io_buffer slot. +This way, the async nodes will immediately pick up the new data from the driver source. + +Because there are 2 buffers in flight on the spa_io_async_buffers io area, the link needs +to negotiate at least 2 buffers for this to work. + + +## Example + +A, B, C are async nodes and have async links between their ports. The async +link has the spa_io_async_buffers with 2 slots (named 0 and 1) below. All the +slots are empty. + + +--------+ +-------+ +-------+ + | A | | B | | C | + | 0 -( )-> 0 0 -( )-> 0 | + | 1 ( ) 1 1 ( ) 1 | + +--------+ +-------+ +-------+ + + +cycle 0: A produces a buffer AB0 on the output port in the (cycle+1)&1 slot (1). + B consumes slot cycle&1 (0) with the empty buffer and produces BC0 in slot 1 + C consumes slot cycle&1 (0) with the empty buffer + + +--------+ +-------+ +-------+ + | A | | B | | C | + | (AB0) 0 -( )-> 0 ( ) 0 -( )-> 0 ( ) | + | 1 (AB0) 1 1 (BC0) 1 | + +--------+ +-------+ +-------+ + + +cycle 1: A produces a buffer AB1 on the output port in the (cycle+1)&1 slot (0). + B consumes slot cycle&1 (1) with buffer AB0 and produces BC1 in slot 0 + C consumes slot cycle&1 (1) with buffer BC0 + + +--------+ +-------+ +-------+ + | A | | B | | C | + | (AB1) 0 -(AB1)-> 0 (AB0) 0 -(BC1)-> 0 (BC0) | + | 1 (AB0) 1 1 (BC0) 1 | + +--------+ +-------+ +-------+ + +cycle 2: A produces a buffer AB2 on the output port in the (cycle+1)&1 slot (1). + B consumes slot cycle&1 (0) with buffer AB1 and produces BC2 in slot 1 + C consumes slot cycle&1 (0) with buffer BC1 + + +--------+ +-------+ +-------+ + | A | | B | | C | + | (AB2) 0 -(AB1)-> 0 (AB1) 0 -(BC1)-> 0 (BC1) | + | 1 (AB2) 1 1 (BC2) 1 | + +--------+ +-------+ +-------+ + +Each async link adds 1 cycle of latency to the chain. Notice how AB0 from cycle 0, +produces BC1 in cycle 1, which arrives in node C at cycle 2. + +## Latency reporting + +Because the latency is really introduced by the links, the additional cycle of +latency is added when the SPA_PARAM_Latency is copied between the output and +input ports of a link. + +It is possible for a sync node A to be linked to another sync node D and an +async node B: + + +--------+ +-------+ + | A | | B | + | (AB1) 0 -(AB1)-> 0 (AB0) 0 ... + | 1 \(AB0) 1 1 + +--------+ \ +-------+ + \ + \ +-------+ + \ | D | + -(AB1)-> 0 (AB1) | + | | + +-------+ + +The Output latency on A's output port is what A reports. When it copied to the +input port of B, 1 cycle is added and when it is copied to D, nothing is added. + + # Remote nodes. For remote nodes, the eventfd and the activation is transferred from the server @@ -206,7 +319,8 @@ After they complete (and only when the profiler is active), they will trigger an extra eventfd to signal the server that the graph completed. This is used by the server to generate the profiler info. -## Lazy scheduling + +# Lazy scheduling Normally, a driver will wake up the graph and all the followers need to process the data in sync. There are cases where: diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index c764f445e..5f1dc1b10 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -52,8 +52,6 @@ struct impl { struct pw_properties *properties; struct spa_io_buffers io[2]; - - bool async; }; /** \endcond */ @@ -799,7 +797,7 @@ int pw_impl_link_activate(struct pw_impl_link *this) !impl->input.node->runnable || !impl->output.node->runnable) return 0; - if (impl->async) { + if (this->async) { io_type = SPA_IO_AsyncBuffers; io_size = sizeof(struct spa_io_async_buffers); } else { @@ -1200,16 +1198,29 @@ static void node_active_changed(void *data, bool active) pw_impl_link_prepare(&impl->this); } +static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_impl_node *driver) +{ + struct impl *impl = data; + if (impl->this.async) { + /* for async links, input and output port latency depends on if the + * output node is directly driving the input node. */ + pw_impl_port_recalc_latency(impl->output.port); + pw_impl_port_recalc_latency(impl->input.port); + } +} + static const struct pw_impl_node_events input_node_events = { PW_VERSION_IMPL_NODE_EVENTS, .result = input_node_result, .active_changed = node_active_changed, + .driver_changed = node_driver_changed, }; static const struct pw_impl_node_events output_node_events = { PW_VERSION_IMPL_NODE_EVENTS, .result = output_node_result, .active_changed = node_active_changed, + .driver_changed = node_driver_changed, }; static bool pw_impl_node_can_reach(struct pw_impl_node *output, struct pw_impl_node *input, int hop) @@ -1496,11 +1507,11 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, if (this->passive && str == NULL) pw_properties_set(properties, PW_KEY_LINK_PASSIVE, "true"); - impl->async = (output_node->async || input_node->async) && + this->async = (output_node->async || input_node->async) && SPA_FLAG_IS_SET(output->flags, PW_IMPL_PORT_FLAG_ASYNC) && SPA_FLAG_IS_SET(input->flags, PW_IMPL_PORT_FLAG_ASYNC); - if (impl->async) + if (this->async) pw_properties_set(properties, PW_KEY_LINK_ASYNC, "true"); spa_hook_list_init(&this->listener_list); @@ -1551,7 +1562,7 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, pw_log_info("(%s) (%s) -> (%s) async:%d:%d:%d:%04x:%04x:%d", this->name, output_node->name, input_node->name, output_node->driving, output_node->async, input_node->async, - output->flags, input->flags, impl->async); + output->flags, input->flags, this->async); pw_impl_port_emit_link_added(output, this); pw_impl_port_emit_link_added(input, this); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 89d652d89..9c8744ff4 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1679,7 +1679,7 @@ int pw_impl_port_for_each_link(struct pw_impl_port *port, int pw_impl_port_recalc_latency(struct pw_impl_port *port) { struct pw_impl_link *l; - struct spa_latency_info latency, *current; + struct spa_latency_info latency, *current, other_latency; struct pw_impl_port *other; struct spa_pod *param; struct spa_pod_builder b = { 0 }; @@ -1702,7 +1702,14 @@ int pw_impl_port_recalc_latency(struct pw_impl_port *port) port->info.id, other->info.id); continue; } - spa_latency_info_combine(&latency, &other->latency[other->direction]); + other_latency = other->latency[other->direction]; + if (l->async && other->node->driver_node != port->node) { + /* we add 1 cycle delay from async links */ + other_latency.min_quantum++; + other_latency.max_quantum++; + } + spa_latency_info_combine(&latency, &other_latency); + pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port->info.id, other->info.id, latency.min_quantum, latency.max_quantum, @@ -1718,7 +1725,15 @@ int pw_impl_port_recalc_latency(struct pw_impl_port *port) port->info.id, other->info.id); continue; } - spa_latency_info_combine(&latency, &other->latency[other->direction]); + other_latency = other->latency[other->direction]; + if (l->async && other->node != port->node->driver_node) { + /* we only add 1 cycle delay for async links that + * are not from our driver */ + other_latency.min_quantum++; + other_latency.max_quantum++; + } + spa_latency_info_combine(&latency, &other_latency); + pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port->info.id, other->info.id, latency.min_quantum, latency.max_quantum, diff --git a/src/pipewire/private.h b/src/pipewire/private.h index c610def55..74624c9e3 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -1022,6 +1022,7 @@ struct pw_impl_link { void *user_data; + unsigned int async:1; unsigned int registered:1; unsigned int feedback:1; unsigned int preparing:1; From 96ac4ae10d5625da5182a472473224d9621be621 Mon Sep 17 00:00:00 2001 From: Philippe Normand Date: Sat, 13 Sep 2025 10:08:56 +0100 Subject: [PATCH 0845/1014] pipewiresrc: Fix caps leak pwsrc->possible_caps wasn't un-reffed before re-assignment. --- src/gst/gstpipewiresrc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index e5c0bc7ee..ae779bd11 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -1162,7 +1162,7 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) GST_DEBUG_OBJECT (basesrc, "connect capture with path %s, target-object %s", pwsrc->stream->path, pwsrc->stream->target_object); - pwsrc->possible_caps = gst_caps_ref (possible_caps); + gst_caps_replace (&pwsrc->possible_caps, possible_caps); pwsrc->negotiated = FALSE; enum pw_stream_flags flags; From 6686d5d1e6f778bcdb27d1616bfc1836f0477148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Dr=C4=85g?= Date: Sat, 13 Sep 2025 11:48:42 +0200 Subject: [PATCH 0846/1014] Update Polish translation --- po/pl.po | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/po/pl.po b/po/pl.po index 0bb385bae..c75ca20bd 100644 --- a/po/pl.po +++ b/po/pl.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2025-08-27 03:33+0000\n" -"PO-Revision-Date: 2025-08-27 14:12+0200\n" +"POT-Creation-Date: 2025-09-13 03:33+0000\n" +"PO-Revision-Date: 2025-09-13 11:47+0200\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" @@ -75,7 +75,7 @@ msgstr "%s na %s@%s" msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:1049 +#: src/tools/pw-cat.c:1084 #, c-format msgid "" "%s [options] [|-]\n" @@ -90,7 +90,7 @@ msgstr "" " -v, --verbose Wyświetla więcej komunikatów\n" "\n" -#: src/tools/pw-cat.c:1056 +#: src/tools/pw-cat.c:1091 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -128,7 +128,7 @@ msgstr "" " -P --properties Ustawia właściwości węzła\n" "\n" -#: src/tools/pw-cat.c:1074 +#: src/tools/pw-cat.c:1109 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -148,6 +148,7 @@ msgid "" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" +" -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Częstotliwość próbki (wymagane do " @@ -169,9 +170,10 @@ msgstr "" " -M, --force-midi Wymusza format MIDI, można użyć " "„midi”\n" " albo „ump” (domyślnie ump)\n" +" -n, --sample-count LICZBA Zatrzymuje po LICZBIE próbek\n" "\n" -#: src/tools/pw-cat.c:1093 +#: src/tools/pw-cat.c:1129 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -179,6 +181,7 @@ msgid "" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" +" -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Tryb odtwarzania\n" @@ -187,6 +190,7 @@ msgstr "" " -d, --dsd Tryb DSD\n" " -o, --encoded Tryb zakodowany\n" " -s, --sysex Tryb SysEx\n" +" -c, --midi-clip Tryb klipu MIDI\n" "\n" #: src/tools/pw-cli.c:2386 @@ -213,12 +217,12 @@ msgstr "" msgid "Pro Audio" msgstr "Dźwięk w zastosowaniach profesjonalnych" -#: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635 #: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Wyłączone" -#: spa/plugins/alsa/acp/acp.c:603 +#: spa/plugins/alsa/acp/acp.c:610 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [błąd UCM biblioteki ALSA]" From 9daf499fffba2cfca49e6abce4044186b60600e9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 8 Sep 2025 20:09:25 +0300 Subject: [PATCH 0847/1014] ci: build pages only from master branch The job on master branch builds pages for all versions at the same time. It should not be run on branches. --- .gitlab-ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b8a703ab6..3f52e5fa1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -687,6 +687,3 @@ pages: - public rules: - if: $CI_COMMIT_BRANCH == 'master' - - if: $CI_COMMIT_BRANCH == '1.0' - - if: $CI_COMMIT_BRANCH == '1.2' - - if: $CI_COMMIT_BRANCH == '1.4' From 336b2dbbc2e11cfe0b9b0a477a16492cf6e6b463 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 09:29:43 +0200 Subject: [PATCH 0848/1014] modules: remove output latency handling in raop sink pw_stream now handles the other (output) latency for us, it will keep the param and report it. If we are not interested in upstream latency we don't have to parse and store it and we can just be concerned with the latency we report on our input port (input latency). --- src/modules/module-raop-sink.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index eb2a339e7..6e9fbf8ab 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -272,7 +272,6 @@ struct impl { bool mute; float volume; - struct spa_latency_info latency_info; struct spa_process_latency_info process_latency; struct spa_ringbuffer ring; @@ -856,7 +855,7 @@ static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) static void update_latency(struct impl *impl) { uint32_t n_params = 0; - const struct spa_pod *params[3]; + const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_latency_info latency; @@ -867,7 +866,6 @@ static void update_latency(struct impl *impl) spa_process_latency_info_add(&impl->process_latency, &latency); params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &impl->latency_info); params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); rtp_stream_update_params(impl->stream, params, n_params); } @@ -1670,18 +1668,6 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp rtp_stream_set_param(impl->stream, id, param); } -static void param_latency_changed(struct impl *impl, const struct spa_pod *param) -{ - struct spa_latency_info latency; - - if (param == NULL || spa_latency_parse(param, &latency) < 0) - return; - if (latency.direction == SPA_DIRECTION_OUTPUT) - impl->latency_info = latency; - - update_latency(impl); -} - static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_process_latency_info info; @@ -1711,9 +1697,6 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * if (param != NULL) stream_props_changed(impl, id, param); break; - case SPA_PARAM_Latency: - param_latency_changed(impl, param); - break; case SPA_PARAM_ProcessLatency: param_process_latency_changed(impl, param); break; @@ -1846,8 +1829,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->loop = pw_context_get_main_loop(context); - impl->latency_info = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - ip = pw_properties_get(props, "raop.ip"); port = pw_properties_get(props, "raop.port"); if (ip == NULL || port == NULL) { @@ -2009,6 +1990,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_log_error("can't create raop stream: %m"); goto error; } + update_latency(impl); impl->headers = pw_properties_new(NULL, NULL); From af6571d0c727f181f2a928ea2a4a007039878be6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 10:42:44 +0200 Subject: [PATCH 0849/1014] test: fix assigment and compare error Fixes #4898 --- test/test-logger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-logger.c b/test/test-logger.c index 178263ada..2c5a5bd9a 100644 --- a/test/test-logger.c +++ b/test/test-logger.c @@ -355,7 +355,7 @@ PWTEST(logger_debug_env_invalid) fsync(STDERR_FILENO); lseek(fd, SEEK_SET, 0); - while ((rc = read(fd, buf, sizeof(buf) - 1) > 0)) { + while ((rc = read(fd, buf, sizeof(buf) - 1)) > 0) { if (strstr(buf, "Ignoring invalid format in log level")) { error_message_found = true; break; From 03c5f493dc209c53e04cfedc487f2e739af076f1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 10:47:12 +0200 Subject: [PATCH 0850/1014] control: fix event compare function We can only compare UMP when both types are 2 or 4, so it must be different from 2 *and* 4 to be rejected. Fixes #4899 --- pipewire-jack/src/pipewire-jack.c | 4 ++-- spa/plugins/control/mixer.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 92d777f7e..f05bf48f3 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1523,8 +1523,8 @@ static inline int event_sort(struct spa_pod_control *a, const void *abody, const uint32_t *sa = abody, *sb = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; - if ((sa[0] >> 28) != 2 || (sa[0] >> 28) != 4 || - (sb[0] >> 28) != 2 || (sb[0] >> 28) != 4) + if (((sa[0] >> 28) != 2 && (sa[0] >> 28) != 4) || + ((sb[0] >> 28) != 2 && (sb[0] >> 28) != 4)) return 0; return event_compare(sa[0] >> 16, sb[0] >> 16); } diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index d71a1e16d..caf3a6b06 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -679,8 +679,8 @@ static inline int event_sort(struct spa_pod_control *a, const void *abody, const uint32_t *da = abody, *db = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; - if ((da[0] >> 28) != 2 || (da[0] >> 28) != 4 || - (db[0] >> 28) != 2 || (db[0] >> 28) != 4) + if (((da[0] >> 28) != 2 && (da[0] >> 28) != 4) || + ((db[0] >> 28) != 2 && (db[0] >> 28) != 4)) return 0; return event_compare(da[0] >> 16, db[0] >> 16); } From 83b59d6ebe79ebdd918018cc682633da44ffeb79 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 11:00:04 +0200 Subject: [PATCH 0851/1014] pod: add SPA_POD_PROP_FLAG_DROP The property will be dropped from the filtered result when one of the pods to filter does not have the property. This can be used as a feature mask. If side A provides a flags property and B doesn't, the property will be removed from the result. Without the flag, property A would be added and it would not be possible to see if filtering happened (when B had compatible flags) or not. --- spa/include/spa/pod/filter.h | 4 ++-- spa/include/spa/pod/pod.h | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index d5fe18d24..6e238c2e6 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -294,7 +294,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, res = spa_pod_filter_prop(b, p1, p2); else if (SPA_FLAG_IS_SET(p1->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; - else + else if (!SPA_FLAG_IS_SET(p1->flags, SPA_POD_PROP_FLAG_DROP)) spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); if (res < 0) break; @@ -307,7 +307,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, continue; if (SPA_FLAG_IS_SET(p2->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; - else + else if (!SPA_FLAG_IS_SET(p2->flags, SPA_POD_PROP_FLAG_DROP)) spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); if (res < 0) break; diff --git a/spa/include/spa/pod/pod.h b/spa/include/spa/pod/pod.h index 91c4d57ce..e20990cc6 100644 --- a/spa/include/spa/pod/pod.h +++ b/spa/include/spa/pod/pod.h @@ -201,8 +201,11 @@ struct spa_pod_prop { * Int : n_items, * (String : key, * String : value)*)) */ -#define SPA_POD_PROP_FLAG_MANDATORY (1u<<3) /**< is mandatory */ +#define SPA_POD_PROP_FLAG_MANDATORY (1u<<3) /**< is mandatory, when filtering, both sides + * need this property or filtering fails. */ #define SPA_POD_PROP_FLAG_DONT_FIXATE (1u<<4) /**< choices need no fixation */ +#define SPA_POD_PROP_FLAG_DROP (1u<<5) /**< drop property, when filtering, both sides + * need the property or it will be dropped. */ uint32_t flags; /**< flags for property */ struct spa_pod value; /* value follows */ From 6305eada809eb88554791bd9bcf19baa1837475b Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 13:41:11 +0200 Subject: [PATCH 0852/1014] impl-port: add port.exclusive flag Add a port.exclusive flag and inherit the value from the node.exclusive flag if not otherwise specified. Make it so that exclusive ports can only be linked once. This is important for explicit sync where there can be only one producer and one consumer in order to signal the timeline objects correctly. --- src/pipewire/impl-node.c | 1 + src/pipewire/impl-port.c | 4 ++++ src/pipewire/keys.h | 1 + src/pipewire/private.h | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 14f76b9e7..754dbe602 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -1159,6 +1159,7 @@ static void check_properties(struct pw_impl_node *node) node->transport_sync = pw_properties_get_bool(node->properties, PW_KEY_NODE_TRANSPORT_SYNC, false); impl->cache_params = pw_properties_get_bool(node->properties, PW_KEY_NODE_CACHE_PARAMS, true); driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_DRIVER, false); + node->exclusive = pw_properties_get_bool(node->properties, PW_KEY_NODE_EXCLUSIVE, false); if (node->driver != driver) { pw_log_debug("%p: driver %d -> %d", node, node->driver, driver); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 9c8744ff4..c85b05b88 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -370,6 +370,9 @@ int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mi uint32_t port_id; int res = 0; + if (port->exclusive && port->n_mix != 0) + return -EBUSY; + port_id = pw_map_insert_new(&port->mix_port_map, mix); if (port_id == SPA_ID_INVALID) return -errno; @@ -1236,6 +1239,7 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) is_monitor = pw_properties_get_bool(port->properties, PW_KEY_PORT_MONITOR, false); port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); + port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); is_control = PW_IMPL_PORT_IS_CONTROL(port); if (is_control) { diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index ae7df2372..88b549d89 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -245,6 +245,7 @@ extern "C" { #define PW_KEY_PORT_PASSIVE "port.passive" /**< the ports wants passive links, since 0.3.67 */ #define PW_KEY_PORT_IGNORE_LATENCY "port.ignore-latency" /**< latency ignored by peers, since 0.3.71 */ #define PW_KEY_PORT_GROUP "port.group" /**< the port group of the port 1.2.0 */ +#define PW_KEY_PORT_EXCLUSIVE "port.exclusive" /**< link port only once 1.6.0 */ /** link properties */ #define PW_KEY_LINK_ID "link.id" /**< a link id */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 74624c9e3..e886f54e1 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -782,6 +782,7 @@ struct pw_impl_node { unsigned int sync:1; /**< the sync-groups are active */ unsigned int async:1; /**< async processing, one cycle latency */ unsigned int lazy:1; /**< the graph is lazy scheduling */ + unsigned int exclusive:1; /**< ports can only be linked once */ uint32_t transport; /**< latest transport request */ @@ -964,6 +965,7 @@ struct pw_impl_port { unsigned int have_latency_param:1; unsigned int ignore_latency:1; unsigned int have_latency:1; + unsigned int exclusive:1; /**< port can only be linked once */ unsigned int have_tag_param:1; struct spa_pod *tag[2]; /**< tags */ From e6bcd7b61179a791341ea04f9c866ea0fffcec98 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 16 Sep 2025 14:09:08 +0200 Subject: [PATCH 0853/1014] doc: mention async link latency in latency doc --- doc/dox/internals/latency.dox | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/doc/dox/internals/latency.dox b/doc/dox/internals/latency.dox index 1a5893184..0ff2cbe95 100644 --- a/doc/dox/internals/latency.dox +++ b/doc/dox/internals/latency.dox @@ -100,6 +100,20 @@ When the ProcessLatency changes, the procedure to notify of latency changes is: PipeWire will automatically aggregate latency from links and propagate the new latencies down and upstream. +# Async nodes + +When a node has the node.async property set to true, it will be considered an async +node and will be scheduled differently, see scheduling.dox. + +A link between a port of an async node and another port (async or not) is called an +async link and will have the link.async=true property. + +An async link will add 1 quantum of latency between the nodes it links. A special +exception is made for the output ports of the driver node, which do not add latency. + +The Latency param will be updated with 1 extra quantum when they travel over an async +link. + # Examples ## A source node with a given ProcessLatency @@ -240,4 +254,29 @@ We also see that node.FL has different min/max-rate input latencies. This inform used to insert a delay node to align the latencies again. For example, if we delay the signal between node.FL and FL.sink with 1536 samples, the latencies will be aligned again. +## An async output stream and sink node linked together + +The sink has 1 quantum of Input latency. The stream has no output latency. When the Input latency +travels over the async link 1 quantum of latency is added and the Input latency on the stream is +now 2 quanta. Similar for the stream Output latency that receives an additional 1 quantum of +latency when it arrives in the sink over the async link. + +``` + Latency: [{ "direction": "output", "min-quantum": 0, "max-quantum": 0 } ] + Latency: [{ "direction": "input", "min-quantum": 2, "max-quantum": 2 } ] + ^ + | Latency: [{ "direction": "output", "min-quantum": 1, "max-quantum": 1 } ] + | Latency: [{ "direction": "input", "min-quantum": 1, "max-quantum": 1 } ] + | | + +----------+v v+--------+ + | async FL ------------ FL | + | stream + | sink | + | FR --+ FR | + +----------+ | +--------+ + v + Latency: [{ "direction": "output", "min-quantum": 0, "max-quantum": 0 } ] + Latency: [{ "direction": "input", "min-quantum": 0, "max-quantum": 0 } ] +``` + + */ From 707bd256b9ef1a5014f3b7943534d05d8904342f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 17 Sep 2025 10:21:10 +0200 Subject: [PATCH 0854/1014] systemd: remove RestrictNamespaces from service file Wireplumber loads the libcamera nodes into the pipewire server. We need to remove the RestrictNamespaces option from the service file to allow libcamera to load sandboxed IPA modules. --- src/daemon/systemd/system/pipewire.service.in | 1 - src/daemon/systemd/user/pipewire.service.in | 1 - 2 files changed, 2 deletions(-) diff --git a/src/daemon/systemd/system/pipewire.service.in b/src/daemon/systemd/system/pipewire.service.in index dc8db3f8f..aeddea300 100644 --- a/src/daemon/systemd/system/pipewire.service.in +++ b/src/daemon/systemd/system/pipewire.service.in @@ -18,7 +18,6 @@ Requires=pipewire.socket LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes -RestrictNamespaces=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in index 27818b4b9..c2621e421 100644 --- a/src/daemon/systemd/user/pipewire.service.in +++ b/src/daemon/systemd/user/pipewire.service.in @@ -20,7 +20,6 @@ ConditionUser=!root LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes -RestrictNamespaces=yes SystemCallArchitectures=native SystemCallFilter=@system-service mincore Type=simple From d5608c07c301eda8e79b072f94b5a8d67cb3fbbe Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 17 Sep 2025 13:42:12 +0200 Subject: [PATCH 0855/1014] control: unit test for event sort After some discussions with CLAUDE it made me this unit test, which I think is ok and actually tests useful things. --- spa/plugins/control/meson.build | 30 +++++ spa/plugins/control/test-mixer-ump-sort.c | 151 ++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 spa/plugins/control/test-mixer-ump-sort.c diff --git a/spa/plugins/control/meson.build b/spa/plugins/control/meson.build index adabdfab3..b31986b05 100644 --- a/spa/plugins/control/meson.build +++ b/spa/plugins/control/meson.build @@ -8,3 +8,33 @@ controllib = shared_library('spa-control', dependencies : [ spa_dep, mathlib ], install : true, install_dir : spa_plugindir / 'control') + +test_inc = include_directories('../test') + +test_apps = [ + 'test-mixer-ump-sort', +] + +foreach a : test_apps + test(a, + executable(a, a + '.c', + dependencies : [ spa_dep ], + include_directories : [ configinc, test_inc ], + install_rpath : spa_plugindir / 'control', + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'control'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'control' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'control', + configuration: test_conf + ) + endif +endforeach diff --git a/spa/plugins/control/test-mixer-ump-sort.c b/spa/plugins/control/test-mixer-ump-sort.c new file mode 100644 index 000000000..23a87e18d --- /dev/null +++ b/spa/plugins/control/test-mixer-ump-sort.c @@ -0,0 +1,151 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Claude Code */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include + +#include +#include +#include + +/* Include the mixer source to access static inline functions */ +#include "mixer.c" + +/* Simple test framework macros */ +#define TEST_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + return 1; \ + } \ + } while (0) + +#define TEST_PASS() \ + do { \ + printf("PASS\n"); \ + return 0; \ + } while (0) + +/* Helper to create mock control structures for testing */ +static struct spa_pod_control *create_mock_control(uint8_t *buffer, size_t *offset, + uint64_t timestamp, uint32_t type, const void *data, size_t data_size) +{ + struct spa_pod_control *control = (struct spa_pod_control *)(buffer + *offset); + control->offset = timestamp; + control->type = type; + control->value.size = data_size; + control->value.type = SPA_TYPE_Bytes; + + /* Copy data after the control structure */ + memcpy(buffer + *offset + sizeof(struct spa_pod_control), data, data_size); + *offset += sizeof(struct spa_pod_control) + SPA_ROUND_UP_N(data_size, 8); + + return control; +} + +static int test_ump_event_sort_offset_priority(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_early = 0x20904060; /* Note On Ch 0 */ + uint32_t ump_late = 0x20904060; /* Note On Ch 0 */ + + struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_early, 4); + const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); + + struct spa_pod_control *b = create_mock_control(buffer, &offset, 200, SPA_CONTROL_UMP, &ump_late, 4); + const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); + + /* Earlier offset should sort before later offset */ + TEST_ASSERT(event_sort(a, abody, b, bbody) < 0, "Earlier offset should sort before later offset"); + /* Later offset should sort after earlier offset */ + TEST_ASSERT(event_sort(b, bbody, a, abody) > 0, "Later offset should sort after earlier offset"); + + TEST_PASS(); +} + +static int test_ump_event_sort_same_offset_different_channels(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_ch0 = 0x20904060; /* Note On Ch 0 */ + uint32_t ump_ch1 = 0x20914060; /* Note On Ch 1 */ + + struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch0, 4); + const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); + + struct spa_pod_control *b = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch1, 4); + const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); + + /* Different channels at same offset should return 0 (no preference) */ + TEST_ASSERT(event_sort(a, abody, b, bbody) == 0, "Different channels at same offset should return 0"); + TEST_ASSERT(event_sort(b, bbody, a, abody) == 0, "Different channels at same offset should return 0"); + + TEST_PASS(); +} + +static int test_ump_event_sort_priority_controller_vs_note(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_note_on = 0x20904060; /* Note On Ch 0 (priority 4) */ + uint32_t ump_controller = 0x20B04060; /* Controller Ch 0 (priority 2) */ + + struct spa_pod_control *note_on = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_note_on, 4); + const void *note_body = (uint8_t*)note_on + sizeof(struct spa_pod_control); + + struct spa_pod_control *controller = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_controller, 4); + const void *ctrl_body = (uint8_t*)controller + sizeof(struct spa_pod_control); + + /* Controller (higher priority) should sort before Note On (lower priority) */ + TEST_ASSERT(event_sort(note_on, note_body, controller, ctrl_body) > 0, "Controller should sort before Note On"); + TEST_ASSERT(event_sort(controller, ctrl_body, note_on, note_body) <= 0, "Controller should sort before Note On"); + + TEST_PASS(); +} + +static int test_event_compare_priority_table(void) +{ + /* Test controller (0xB0) vs note on (0x90) on same channel */ + TEST_ASSERT(event_compare(0x90, 0xB0) > 0, "Controller has higher priority than Note On"); + TEST_ASSERT(event_compare(0xB0, 0x90) < 0, "Controller has higher priority than Note On"); + + /* Test program change (0xC0) vs note off (0x80) on same channel */ + TEST_ASSERT(event_compare(0x80, 0xC0) > 0, "Program change has higher priority than Note Off"); + TEST_ASSERT(event_compare(0xC0, 0x80) < 0, "Program change has higher priority than Note Off"); + + /* Test different channels should return 0 */ + TEST_ASSERT(event_compare(0x90, 0x91) == 0, "Different channels should return 0"); + + TEST_PASS(); +} + +int main(void) +{ + int result = 0; + + printf("Running mixer UMP sort tests...\n"); + + printf("test_ump_event_sort_offset_priority: "); + result |= test_ump_event_sort_offset_priority(); + + printf("test_ump_event_sort_same_offset_different_channels: "); + result |= test_ump_event_sort_same_offset_different_channels(); + + printf("test_ump_event_sort_priority_controller_vs_note: "); + result |= test_ump_event_sort_priority_controller_vs_note(); + + printf("test_event_compare_priority_table: "); + result |= test_event_compare_priority_table(); + + if (result == 0) { + printf("All tests passed!\n"); + } else { + printf("Some tests failed!\n"); + } + + return result; +} \ No newline at end of file From cfde4c1b17468d82c34555814dc19998d00d7bd1 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 17 Sep 2025 10:58:58 -0400 Subject: [PATCH 0856/1014] pulse: Handle timed out streams If we don't get a link on a stream, we might never send a create stream reply. The client handles this fine by timing out after 30s and dropping the stream, but the server holds on to the pw_stream forever (or until the client quits). Let's add a timer to clean up such streams on the server. Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4901 --- src/modules/module-protocol-pulse/stream.c | 28 ++++++++++++++++++++++ src/modules/module-protocol-pulse/stream.h | 1 + 2 files changed, 29 insertions(+) diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 7b7f6edc1..99a9d0a88 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -40,6 +40,23 @@ static int parse_frac(struct pw_properties *props, const char *key, return 0; } +void create_stream_timeout(void *user_data, uint64_t expirations) { + struct stream *stream = user_data; + + if (stream->create_tag != SPA_ID_INVALID) { + pw_log_warn("[%s] timeout on stream %p channel:%d", stream->client->name, stream, stream->channel); + + /* Don't try to signal anything to the client, it's already killed the stream on its end */ + stream->drain_tag = 0; + stream->killed = false; + + stream_free(stream); + } else { + pw_loop_destroy_source(stream->impl->main_loop, stream->timer); + stream->timer = NULL; + } +} + struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag, const struct sample_spec *ss, const struct channel_map *map, const struct buffer_attr *attr) @@ -89,6 +106,12 @@ struct stream *stream_new(struct client *client, enum stream_type type, uint32_t spa_assert_not_reached(); } + /* Time out if we don't get a link and can't send a reply to create in 35s. Client will time out in + * 30s and clean up its stream anyway. */ + struct timespec create_timeout = { .tv_sec = 35, .tv_nsec = 0 }; + stream->timer = pw_loop_add_timer(stream->impl->main_loop, create_stream_timeout, stream); + pw_loop_update_timer(stream->impl->main_loop, stream->timer, &create_timeout, NULL, false); + return stream; error_errno: @@ -106,6 +129,11 @@ void stream_free(struct stream *stream) pw_log_debug("client %p: stream %p channel:%d", client, stream, stream->channel); + if (stream->timer) { + pw_loop_destroy_source(stream->impl->main_loop, stream->timer); + stream->timer = NULL; + } + if (stream->drain_tag) reply_error(client, -1, stream->drain_tag, -ENOENT); diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index 7807fd491..20ca0cc5e 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -50,6 +50,7 @@ struct stream { struct pw_stream *stream; struct spa_hook stream_listener; + struct spa_source *timer; struct spa_io_position *position; struct spa_ringbuffer ring; From 361a0de85a281d4a485ac8cc8c1a922c82454577 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 17 Sep 2025 19:18:27 +0200 Subject: [PATCH 0857/1014] pulse-server: make timer function static and fix formatting --- src/modules/module-protocol-pulse/stream.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 99a9d0a88..8505ebd74 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -40,7 +40,8 @@ static int parse_frac(struct pw_properties *props, const char *key, return 0; } -void create_stream_timeout(void *user_data, uint64_t expirations) { +static void create_stream_timeout(void *user_data, uint64_t expirations) +{ struct stream *stream = user_data; if (stream->create_tag != SPA_ID_INVALID) { From 38cb14d39d7cafe6c2fe5f5a34588125439245d0 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 18 Sep 2025 13:52:51 +0200 Subject: [PATCH 0858/1014] timer-queue: add a new timer queue helper This allows you to schedule timeouts. It keeps a sorted list of timeouts and uses just 1 timerfd to schedule the head of the timeout list. --- doc/tree.dox | 1 + src/pipewire/context.c | 10 ++ src/pipewire/context.h | 3 + src/pipewire/log.c | 1 + src/pipewire/meson.build | 2 + src/pipewire/pipewire.h | 1 + src/pipewire/private.h | 1 + src/pipewire/timer-queue.c | 195 +++++++++++++++++++++++++++++++++++++ src/pipewire/timer-queue.h | 51 ++++++++++ 9 files changed, 265 insertions(+) create mode 100644 src/pipewire/timer-queue.c create mode 100644 src/pipewire/timer-queue.h diff --git a/doc/tree.dox b/doc/tree.dox index ecc43d604..f62bc62dd 100644 --- a/doc/tree.dox +++ b/doc/tree.dox @@ -43,6 +43,7 @@ This determines the ordering of items in Doxygen sidebar. \addtogroup pw_protocol \addtogroup pw_resource \addtogroup pw_thread_loop +\addtogroup pw_timer_queue \addtogroup pw_work_queue \} diff --git a/src/pipewire/context.c b/src/pipewire/context.c index 29065ef83..7b6d426ab 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -582,6 +582,8 @@ void pw_context_destroy(struct pw_context *context) if (context->work_queue) pw_work_queue_destroy(context->work_queue); + if (context->timer_queue) + pw_timer_queue_destroy(context->timer_queue); pw_properties_free(context->properties); pw_properties_free(context->conf); @@ -752,6 +754,14 @@ struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context) return context->work_queue; } +SPA_EXPORT +struct pw_timer_queue *pw_context_get_timer_queue(struct pw_context *context) +{ + if (context->timer_queue == NULL) + context->timer_queue = pw_timer_queue_new(context->main_loop); + return context->timer_queue; +} + SPA_EXPORT struct pw_mempool *pw_context_get_mempool(struct pw_context *context) { diff --git a/src/pipewire/context.h b/src/pipewire/context.h index eef297b4f..61c6662c4 100644 --- a/src/pipewire/context.h +++ b/src/pipewire/context.h @@ -144,6 +144,9 @@ void pw_context_release_loop(struct pw_context *context, struct pw_loop *loop); /** Get the work queue from the context: Since 0.3.26 */ struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context); +/** Get the timer queue from the context: Since 1.6.0 */ +struct pw_timer_queue *pw_context_get_timer_queue(struct pw_context *context); + /** Get the memory pool from the context: Since 0.3.74 */ struct pw_mempool *pw_context_get_mempool(struct pw_context *context); diff --git a/src/pipewire/log.c b/src/pipewire/log.c index c8410a6f5..7e7d6a66d 100644 --- a/src/pipewire/log.c +++ b/src/pipewire/log.c @@ -69,6 +69,7 @@ PW_LOG_TOPIC(log_proxy, "pw.proxy"); PW_LOG_TOPIC(log_resource, "pw.resource"); PW_LOG_TOPIC(log_stream, "pw.stream"); PW_LOG_TOPIC(log_thread_loop, "pw.thread-loop"); +PW_LOG_TOPIC(log_timer_queue, "pw.timer-queue"); PW_LOG_TOPIC(log_work_queue, "pw.work-queue"); PW_LOG_TOPIC(PW_LOG_TOPIC_DEFAULT, "default"); diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build index b19631a92..9769369a1 100644 --- a/src/pipewire/meson.build +++ b/src/pipewire/meson.build @@ -41,6 +41,7 @@ pipewire_headers = [ 'stream.h', 'thread.h', 'thread-loop.h', + 'timer-queue.h', 'type.h', 'utils.h', 'work-queue.h', @@ -78,6 +79,7 @@ pipewire_sources = [ 'stream.c', 'thread.c', 'thread-loop.c', + 'timer-queue.c', 'utils.c', 'work-queue.c', ] diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h index b3aa58d1a..6f26a38b9 100644 --- a/src/pipewire/pipewire.h +++ b/src/pipewire/pipewire.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/src/pipewire/private.h b/src/pipewire/private.h index e886f54e1..b5d609256 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -405,6 +405,7 @@ struct pw_context { struct spa_thread_utils *thread_utils; struct pw_loop *main_loop; /**< main loop for control */ struct pw_work_queue *work_queue; /**< work queue */ + struct pw_timer_queue *timer_queue; /**< timer queue */ struct spa_support support[16]; /**< support for spa plugins */ uint32_t n_support; /**< number of support items */ diff --git a/src/pipewire/timer-queue.c b/src/pipewire/timer-queue.c new file mode 100644 index 000000000..8f2948597 --- /dev/null +++ b/src/pipewire/timer-queue.c @@ -0,0 +1,195 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include + +#include "timer-queue.h" + +PW_LOG_TOPIC_EXTERN(log_timer_queue); +#define PW_LOG_TOPIC_DEFAULT log_timer_queue + +struct pw_timer_queue { + struct pw_loop *loop; + struct spa_list entries; + struct timespec *next_timeout; + struct spa_source *timer; +}; + +static void rearm_timer(struct pw_timer_queue *queue) +{ + struct timespec *timeout = NULL; + struct pw_timer *timer; + + if (!spa_list_is_empty(&queue->entries)) { + timer = spa_list_first(&queue->entries, struct pw_timer, link); + timeout = &timer->timeout; + } + if (timeout != queue->next_timeout) { + if (timeout) + pw_log_debug("%p: arming with timeout %ld.%09ld", queue, + timeout->tv_sec, timeout->tv_nsec); + else + pw_log_debug("%p: disarming (no entries)", queue); + + queue->next_timeout = timeout; + pw_loop_update_timer(queue->loop, queue->timer, + timeout, NULL, true); + } +} + +static void timer_timeout(void *user_data, uint64_t expirations) +{ + struct pw_timer_queue *queue = user_data; + struct pw_timer *timer; + + pw_log_debug("%p: timeout fired, expirations=%"PRIu64, queue, expirations); + + if (spa_list_is_empty(&queue->entries)) { + pw_log_debug("%p: no entries to process", queue); + return; + } + timer = spa_list_first(&queue->entries, struct pw_timer, link); + if (&timer->timeout != queue->next_timeout) { + /* this can happen when the timer expired but before we could + * dispatch the event, the timer got removed or a new one got + * added. The timer does not match the one we last scheduled + * and we need to wait for the rescheduled timer instead */ + pw_log_debug("%p: timer was rearmed", queue); + return; + } + + pw_log_debug("%p: processing timer %p", queue, timer); + timer->queue = NULL; + spa_list_remove(&timer->link); + + timer->callback(timer->data); + + rearm_timer(queue); +} + +SPA_EXPORT +struct pw_timer_queue *pw_timer_queue_new(struct pw_loop *loop) +{ + struct pw_timer_queue *queue; + int res; + + queue = calloc(1, sizeof(struct pw_timer_queue)); + if (queue == NULL) + return NULL; + + queue->loop = loop; + queue->timer = pw_loop_add_timer(loop, timer_timeout, queue); + if (queue->timer == NULL) { + res = -errno; + goto error_free; + } + + spa_list_init(&queue->entries); + pw_log_debug("%p: initialized", queue); + return queue; + +error_free: + free(queue); + errno = -res; + return NULL; +} + +SPA_EXPORT +void pw_timer_queue_destroy(struct pw_timer_queue *queue) +{ + struct pw_timer *timer; + int count = 0; + + pw_log_debug("%p: clearing", queue); + + if (queue->timer) + pw_loop_destroy_source(queue->loop, queue->timer); + + spa_list_consume(timer, &queue->entries, link) { + timer->queue = NULL; + spa_list_remove(&timer->link); + count++; + } + if (count > 0) + pw_log_debug("%p: cancelled %d entries", queue, count); + + free(queue); +} + +static int timespec_compare(const struct timespec *a, const struct timespec *b) +{ + if (a->tv_sec < b->tv_sec) + return -1; + if (a->tv_sec > b->tv_sec) + return 1; + if (a->tv_nsec < b->tv_nsec) + return -1; + if (a->tv_nsec > b->tv_nsec) + return 1; + return 0; +} + +SPA_EXPORT +int pw_timer_queue_add(struct pw_timer_queue *queue, struct pw_timer *timer, + struct timespec *abs_time, int64_t timeout_ns, + pw_timer_callback callback, void *data) +{ + struct timespec timeout; + struct pw_timer *iter; + + if (timer->queue != NULL) + return -EBUSY; + + if (abs_time == NULL) { + /* Use CLOCK_MONOTONIC to match the timerfd clock used by SPA loop */ + if (clock_gettime(CLOCK_MONOTONIC, &timeout) < 0) + return -errno; + } else { + timeout = *abs_time; + } + if (timeout_ns > 0) { + timeout.tv_sec += timeout_ns / SPA_NSEC_PER_SEC; + timeout.tv_nsec += timeout_ns % SPA_NSEC_PER_SEC; + if (timeout.tv_nsec >= SPA_NSEC_PER_SEC) { + timeout.tv_sec++; + timeout.tv_nsec -= SPA_NSEC_PER_SEC; + } + } + + timer->queue = queue; + timer->timeout = timeout; + timer->callback = callback; + timer->data = data; + + pw_log_debug("%p: adding timer %p with timeout %ld.%09ld", + queue, timer, timeout.tv_sec, timeout.tv_nsec); + + /* Insert timer in sorted order (earliest timeout first) */ + spa_list_for_each(iter, &queue->entries, link) { + if (timespec_compare(&timer->timeout, &iter->timeout) < 0) + break; + } + spa_list_append(&iter->link, &timer->link); + + rearm_timer(queue); + return 0; +} + +SPA_EXPORT +int pw_timer_queue_cancel(struct pw_timer *timer) +{ + struct pw_timer_queue *queue = timer->queue; + + if (queue == NULL) + return 0; + + pw_log_debug("%p: cancelling timer %p", queue, timer); + + timer->queue = NULL; + spa_list_remove(&timer->link); + + rearm_timer(queue); + + return 0; +} diff --git a/src/pipewire/timer-queue.h b/src/pipewire/timer-queue.h new file mode 100644 index 000000000..81dc7b447 --- /dev/null +++ b/src/pipewire/timer-queue.h @@ -0,0 +1,51 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef PIPEWIRE_TIMER_QUEUE_H +#define PIPEWIRE_TIMER_QUEUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \defgroup pw_timer_queue Timer Queue + * Processing of timer events. + */ + +/** + * \addtogroup pw_timer_queue + * \{ + */ +struct pw_timer_queue; + +#include + +typedef void (*pw_timer_callback) (void *data); + +struct pw_timer { + struct spa_list link; + struct pw_timer_queue *queue; + struct timespec timeout; + pw_timer_callback callback; + void *data; + uint32_t padding[16]; +}; + +struct pw_timer_queue *pw_timer_queue_new(struct pw_loop *loop); +void pw_timer_queue_destroy(struct pw_timer_queue *queue); + +int pw_timer_queue_add(struct pw_timer_queue *queue, struct pw_timer *timer, + struct timespec *abs_time, int64_t timeout_ns, + pw_timer_callback callback, void *data); +int pw_timer_queue_cancel(struct pw_timer *timer); + +/** + * \} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* PIPEWIRE_TIMER_QUEUE_H */ From ca713c08ee7a20b5d933aee04b8616917c9abc8e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 18 Sep 2025 13:55:43 +0200 Subject: [PATCH 0859/1014] pulse-server: use the new timer-queue for timeouts Use the timer queue for scheduling stream and object data timeouts. This avoids allocating timerfds for these timeouts and the timer queue can handle many timeouts more efficiently. --- src/modules/module-protocol-pulse/internal.h | 1 + src/modules/module-protocol-pulse/manager.c | 27 +++++-------------- .../module-protocol-pulse/pulse-server.c | 1 + src/modules/module-protocol-pulse/stream.c | 15 +++-------- src/modules/module-protocol-pulse/stream.h | 2 +- 5 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h index cd031692b..906ae6ed1 100644 --- a/src/modules/module-protocol-pulse/internal.h +++ b/src/modules/module-protocol-pulse/internal.h @@ -59,6 +59,7 @@ struct impl { struct spa_hook_list hooks; struct spa_list servers; + struct pw_timer_queue *timer_queue; struct pw_work_queue *work_queue; struct spa_list cleanup_clients; diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c index ec8b0963d..5b597f4eb 100644 --- a/src/modules/module-protocol-pulse/manager.c +++ b/src/modules/module-protocol-pulse/manager.c @@ -27,6 +27,7 @@ struct manager { struct pw_manager this; struct pw_loop *loop; + struct pw_timer_queue *timer_queue; struct spa_hook core_listener; struct spa_hook registry_listener; @@ -48,7 +49,7 @@ struct object_data { struct object *object; const char *key; size_t size; - struct spa_source *timer; + struct pw_timer timer; }; struct object { @@ -178,10 +179,7 @@ static void object_reset_params(struct object *o) static void object_data_free(struct object_data *d) { spa_list_remove(&d->link); - if (d->timer) { - pw_loop_destroy_source(d->object->manager->loop, d->timer); - d->timer = NULL; - } + pw_timer_queue_cancel(&d->timer); free(d); } @@ -752,6 +750,7 @@ struct pw_manager *pw_manager_new(struct pw_core *core) context = pw_core_get_context(core); m->loop = pw_context_get_main_loop(context); + m->timer_queue = pw_context_get_timer_queue(context); spa_hook_list_init(&m->hooks); @@ -888,7 +887,7 @@ done: return SPA_PTROFF(d, sizeof(struct object_data), void); } -static void object_data_timeout(void *data, uint64_t count) +static void object_data_timeout(void *data) { struct object_data *d = data; struct object *o = d->object; @@ -897,11 +896,6 @@ static void object_data_timeout(void *data, uint64_t count) pw_log_debug("manager:%p object id:%d data '%s' lifetime ends", m, o->this.id, d->key); - if (d->timer) { - pw_loop_destroy_source(m->loop, d->timer); - d->timer = NULL; - } - manager_emit_object_data_timeout(m, &o->this, d->key); } @@ -911,7 +905,6 @@ void *pw_manager_object_add_temporary_data(struct pw_manager_object *obj, const struct object *o = SPA_CONTAINER_OF(obj, struct object, this); struct object_data *d; void *data; - struct timespec timeout = {0}, interval = {0}; data = pw_manager_object_add_data(obj, key, size); if (data == NULL) @@ -919,14 +912,8 @@ void *pw_manager_object_add_temporary_data(struct pw_manager_object *obj, const d = SPA_PTROFF(data, -sizeof(struct object_data), void); - if (d->timer == NULL) - d->timer = pw_loop_add_timer(o->manager->loop, object_data_timeout, d); - if (d->timer == NULL) - return NULL; - - timeout.tv_sec = lifetime_nsec / SPA_NSEC_PER_SEC; - timeout.tv_nsec = lifetime_nsec % SPA_NSEC_PER_SEC; - pw_loop_update_timer(o->manager->loop, d->timer, &timeout, &interval, false); + pw_timer_queue_add(o->manager->timer_queue, &d->timer, NULL, + lifetime_nsec, object_data_timeout, d); return data; } diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 944066852..c945cb0a5 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -5524,6 +5524,7 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, impl->main_loop = pw_context_get_main_loop(context); impl->work_queue = pw_context_get_work_queue(context); + impl->timer_queue = pw_context_get_timer_queue(context); if (props == NULL) props = pw_properties_new(NULL, NULL); diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 8505ebd74..fcc94bd3e 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -40,7 +40,7 @@ static int parse_frac(struct pw_properties *props, const char *key, return 0; } -static void create_stream_timeout(void *user_data, uint64_t expirations) +static void create_stream_timeout(void *user_data) { struct stream *stream = user_data; @@ -52,9 +52,6 @@ static void create_stream_timeout(void *user_data, uint64_t expirations) stream->killed = false; stream_free(stream); - } else { - pw_loop_destroy_source(stream->impl->main_loop, stream->timer); - stream->timer = NULL; } } @@ -109,9 +106,8 @@ struct stream *stream_new(struct client *client, enum stream_type type, uint32_t /* Time out if we don't get a link and can't send a reply to create in 35s. Client will time out in * 30s and clean up its stream anyway. */ - struct timespec create_timeout = { .tv_sec = 35, .tv_nsec = 0 }; - stream->timer = pw_loop_add_timer(stream->impl->main_loop, create_stream_timeout, stream); - pw_loop_update_timer(stream->impl->main_loop, stream->timer, &create_timeout, NULL, false); + pw_timer_queue_add(stream->impl->timer_queue, &stream->timer, NULL, + 35 * SPA_NSEC_PER_SEC, create_stream_timeout, stream); return stream; @@ -130,10 +126,7 @@ void stream_free(struct stream *stream) pw_log_debug("client %p: stream %p channel:%d", client, stream, stream->channel); - if (stream->timer) { - pw_loop_destroy_source(stream->impl->main_loop, stream->timer); - stream->timer = NULL; - } + pw_timer_queue_cancel(&stream->timer); if (stream->drain_tag) reply_error(client, -1, stream->drain_tag, -ENOENT); diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index 20ca0cc5e..eb692dc59 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -50,7 +50,7 @@ struct stream { struct pw_stream *stream; struct spa_hook stream_listener; - struct spa_source *timer; + struct pw_timer timer; struct spa_io_position *position; struct spa_ringbuffer ring; From 06efc8ffb66884033697d8650d9f0b9d0b9479fb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 18 Sep 2025 14:22:00 +0200 Subject: [PATCH 0860/1014] pulse-server: clear timer when stream is created Make a function when the stream is created so that we can clear the create_tag and the timer. --- src/modules/module-protocol-pulse/pulse-server.c | 4 ++-- src/modules/module-protocol-pulse/stream.c | 9 +++++++++ src/modules/module-protocol-pulse/stream.h | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index c945cb0a5..6c2ba43c6 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -621,7 +621,7 @@ static int reply_create_playback_stream(struct stream *stream, struct pw_manager TAG_INVALID); } - stream->create_tag = SPA_ID_INVALID; + stream_created(stream); return client_queue_message(client, reply); } @@ -783,7 +783,7 @@ static int reply_create_record_stream(struct stream *stream, struct pw_manager_o TAG_INVALID); } - stream->create_tag = SPA_ID_INVALID; + stream_created(stream); return client_queue_message(client, reply); } diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index fcc94bd3e..496bcb52c 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -119,6 +119,15 @@ error_errno: return NULL; } +void stream_created(struct stream *stream) +{ + struct client *client = stream->client; + pw_log_debug("client %p: stream %p channel:%d", client, stream, stream->channel); + + stream->create_tag = SPA_ID_INVALID; + pw_timer_queue_cancel(&stream->timer); +} + void stream_free(struct stream *stream) { struct client *client = stream->client; diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index eb692dc59..022f0ee74 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -106,6 +106,7 @@ struct stream { struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag, const struct sample_spec *ss, const struct channel_map *map, const struct buffer_attr *attr); +void stream_created(struct stream *stream); void stream_free(struct stream *stream); void stream_flush(struct stream *stream); uint32_t stream_pop_missing(struct stream *stream); From c296c52cae9a28a8b2a310541e24746d4c040a91 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 18 Sep 2025 15:08:24 +0200 Subject: [PATCH 0861/1014] stream: avoid work at the end of the cycle Driver output streams will start the cycle with a _trigger() operation, which will call the process function (if necessary) to dequeue/queue a buffer before starting the graph cycle. At the end of the cycle, the internal stream process function is called again to recycle any buffers but we should not try to dequeue a new buffer (if there was any in the queue) and say that we have data. Do this by keeping track of when the internal process function was called because of trigger or because of the end of the cycle. At the end of the cycle, we can call the trigger_end() but we should not prepare a new buffer on the output io. --- src/pipewire/stream.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index b0192a7a9..c4b785017 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -157,6 +157,8 @@ struct stream { int in_set_param; int in_emit_param_changed; int pending_drain; + + int in_trigger; }; static int get_param_index(uint32_t id) @@ -1107,7 +1109,7 @@ static int impl_node_process_output(void *object) struct spa_io_buffers *io = impl->io; struct buffer *b; int res; - bool ask_more; + bool ask_more, driver_end; if (io == NULL) return -EIO; @@ -1117,6 +1119,8 @@ again: io->status, io->buffer_id); ask_more = false; + driver_end = stream->node->driving && !impl->in_trigger; + if ((res = io->status) != SPA_STATUS_HAVE_DATA) { /* recycle old buffer */ if ((b = get_buffer(stream, io->buffer_id)) != NULL) { @@ -1126,8 +1130,9 @@ again: ask_more = true; } - /* pop new buffer */ - if ((b = queue_pop(impl, &impl->queued)) != NULL) { + /* pop new buffer but only if we are not a driver and completing + * the cycle. */ + if (!driver_end && (b = queue_pop(impl, &impl->queued)) != NULL) { impl->drained = false; io->buffer_id = b->id; res = io->status = SPA_STATUS_HAVE_DATA; @@ -1145,6 +1150,9 @@ again: pw_log_trace_fp("%p: no more buffers %p", stream, io); ask_more = true; } + } else if (driver_end) { + /* if we are completing the cycle, don't say we have more data */ + res = SPA_STATUS_NEED_DATA; } copy_position(impl, impl->queued.outcount); @@ -1159,7 +1167,7 @@ again: pw_log_trace_fp("%p: res %d", stream, res); - if (stream->node->driving && impl->using_trigger && res != SPA_STATUS_HAVE_DATA) + if (driver_end && impl->using_trigger) call_trigger_done(impl); return res; @@ -2484,7 +2492,9 @@ do_trigger_deprecated(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct stream *impl = user_data; + impl->in_trigger++; int res = impl->node_methods.process(impl); + impl->in_trigger--; return spa_node_call_ready(&impl->callbacks, res); } @@ -2661,7 +2671,9 @@ do_trigger_driver(struct spa_loop *loop, int res; if (impl->direction == SPA_DIRECTION_OUTPUT) { call_process(impl); + impl->in_trigger++; res = impl->node_methods.process(impl); + impl->in_trigger--; } else { res = SPA_STATUS_NEED_DATA; } From 1717ff336e9eb3c544cf987fe195e8fd68b40163 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 18 Sep 2025 15:21:50 +0200 Subject: [PATCH 0862/1014] impl-port: init some stuff earlier Initialize the mix_hooks, port_map and latency earlier, before we call pw_impl_port_set_mix() and update_info, that could potentially expect this to be initialized. --- src/pipewire/impl-port.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index c85b05b88..f0ae5487c 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -800,6 +800,12 @@ struct pw_impl_port *pw_context_create_port( spa_list_init(&this->control_list[1]); spa_hook_list_init(&this->listener_list); + spa_hook_list_init(&impl->mix_hooks); + + pw_map_init(&this->mix_port_map, 64, 64); + + this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); if (this->direction == PW_DIRECTION_INPUT) mix_methods = &schedule_mix_node; @@ -810,15 +816,9 @@ struct pw_impl_port *pw_context_create_port( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, mix_methods, impl); - spa_hook_list_init(&impl->mix_hooks); pw_impl_port_set_mix(this, NULL, 0); - pw_map_init(&this->mix_port_map, 64, 64); - - this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); - this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - if (info) update_info(this, info); From 83242a5c3c0188b5714b29c564d3aff8e7975715 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 09:17:47 +0200 Subject: [PATCH 0863/1014] buffers: small cleanup of the buffer allocation Calculate the min_buffers separately and then use that to ensure we have enough buffers. This makes it easier to increase the min amount later. --- src/pipewire/buffers.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index 9ead5175a..7a02a2d9f 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -188,7 +188,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t i, j, offset, n_params, n_metas; struct spa_meta *metas; - uint32_t max_buffers, blocks; + uint32_t min_buffers, max_buffers, blocks; size_t minsize, stride, align; uint32_t *data_sizes; int32_t *data_strides; @@ -253,6 +253,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, } max_buffers = context->settings.link_max_buffers; + min_buffers = 1; align = pw_properties_get_uint32(context->properties, PW_KEY_CPU_MAX_ALIGN, MAX_ALIGN); @@ -306,7 +307,9 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, } if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_ASYNC)) - max_buffers = SPA_MAX(2u, max_buffers); + min_buffers += 1; + + max_buffers = SPA_MAX(min_buffers, max_buffers); if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED_MEM)) { if (types != SPA_ID_INVALID) From b57c6d3729a2214133b4e84d3733640aea6c1964 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 13:07:19 +0200 Subject: [PATCH 0864/1014] examples: count the params as we add them Count the params as we add them to the param arrays and use that to update the stream params instead of using hardcoded indexes and sizes. This makes it easier to add params and it also revealed a miscounted param. --- src/examples/audio-capture.c | 5 +++-- src/examples/audio-dsp-filter.c | 5 +++-- src/examples/audio-src-ring.c | 5 +++-- src/examples/audio-src-ring2.c | 5 +++-- src/examples/audio-src.c | 5 +++-- src/examples/midi-src.c | 5 +++-- src/examples/video-dsp-src.c | 20 ++++++++++--------- src/examples/video-play-fixate.c | 11 +++++++---- src/examples/video-play-pull.c | 18 +++++++++-------- src/examples/video-play-reneg.c | 20 +++++++++++-------- src/examples/video-play.c | 18 +++++++++-------- src/examples/video-src-alloc.c | 18 +++++++++-------- src/examples/video-src-fixate.c | 33 ++++++++++++++++---------------- src/examples/video-src-reneg.c | 23 ++++++++++++---------- src/examples/video-src.c | 13 +++++++------ 15 files changed, 115 insertions(+), 89 deletions(-) diff --git a/src/examples/audio-capture.c b/src/examples/audio-capture.c index c44f905f6..2429a2d34 100644 --- a/src/examples/audio-capture.c +++ b/src/examples/audio-capture.c @@ -118,6 +118,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -164,7 +165,7 @@ int main(int argc, char *argv[]) * id means that this is a format enumeration (of 1 value). * We leave the channels and rate empty to accept the native graph * rate and channels. */ - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32)); @@ -176,7 +177,7 @@ int main(int argc, char *argv[]) PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, 1); + params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.loop); diff --git a/src/examples/audio-dsp-filter.c b/src/examples/audio-dsp-filter.c index f5bda85c9..8a43147a6 100644 --- a/src/examples/audio-dsp-filter.c +++ b/src/examples/audio-dsp-filter.c @@ -80,6 +80,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -133,7 +134,7 @@ int main(int argc, char *argv[]) NULL), NULL, 0); - params[0] = spa_process_latency_build(&b, + params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &SPA_PROCESS_LATENCY_INFO_INIT( .ns = 10 * SPA_NSEC_PER_MSEC @@ -144,7 +145,7 @@ int main(int argc, char *argv[]) * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, - params, 1) < 0) { + params, n_params) < 0) { fprintf(stderr, "can't connect\n"); return -1; } diff --git a/src/examples/audio-src-ring.c b/src/examples/audio-src-ring.c index b96e26f10..c53f65f5c 100644 --- a/src/examples/audio-src-ring.c +++ b/src/examples/audio-src-ring.c @@ -162,6 +162,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -198,7 +199,7 @@ int main(int argc, char *argv[]) /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, @@ -212,7 +213,7 @@ int main(int argc, char *argv[]) PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, 1); + params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.main_loop); diff --git a/src/examples/audio-src-ring2.c b/src/examples/audio-src-ring2.c index b3943cd11..734cbf885 100644 --- a/src/examples/audio-src-ring2.c +++ b/src/examples/audio-src-ring2.c @@ -191,6 +191,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -228,7 +229,7 @@ int main(int argc, char *argv[]) /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, @@ -242,7 +243,7 @@ int main(int argc, char *argv[]) PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, 1); + params, n_params); /* prefill the ringbuffer */ fill_f32(&data, samples, BUFFER_SIZE); diff --git a/src/examples/audio-src.c b/src/examples/audio-src.c index 08c93421e..914d18fe9 100644 --- a/src/examples/audio-src.c +++ b/src/examples/audio-src.c @@ -101,6 +101,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -141,7 +142,7 @@ int main(int argc, char *argv[]) /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, @@ -155,7 +156,7 @@ int main(int argc, char *argv[]) PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, - params, 1); + params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.loop); diff --git a/src/examples/midi-src.c b/src/examples/midi-src.c index edcaa0f08..90fd36c3d 100644 --- a/src/examples/midi-src.c +++ b/src/examples/midi-src.c @@ -178,6 +178,7 @@ int main(int argc, char *argv[]) uint8_t buffer[1024]; struct spa_pod_builder builder; struct spa_pod *params[1]; + uint32_t n_params = 0; pw_init(&argc, &argv); @@ -226,7 +227,7 @@ int main(int argc, char *argv[]) */ spa_pod_builder_init(&builder, buffer, sizeof(buffer)); - params[0] = spa_pod_builder_add_object(&builder, + params[n_params++] = spa_pod_builder_add_object(&builder, /* POD Object for the buffer parameter */ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, /* Default 1 buffer, minimum of 1, max of 32 buffers. @@ -242,7 +243,7 @@ int main(int argc, char *argv[]) SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); pw_filter_update_params(data.filter, data.port, - (const struct spa_pod **)params, SPA_N_ELEMENTS(params)); + (const struct spa_pod **)params, n_params); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ diff --git a/src/examples/video-dsp-src.c b/src/examples/video-dsp-src.c index 5992088e7..61a189cfd 100644 --- a/src/examples/video-dsp-src.c +++ b/src/examples/video-dsp-src.c @@ -238,6 +238,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; + uint32_t n_params = 0; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); @@ -250,38 +251,38 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) data->stride = SPA_ROUND_UP_N(data->position->video.size.width * BPP, 4); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->position->video.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); - params[1] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - params[2] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( sizeof(struct spa_meta_region) * 16, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 16)); - params[3] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); #define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP) - params[4] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_Int( CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT))); - pw_stream_update_params(stream, params, 5); + pw_stream_update_params(stream, params, n_params); } static void @@ -309,6 +310,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -335,7 +337,7 @@ int main(int argc, char *argv[]) PW_KEY_MEDIA_CLASS, "Video/Source", NULL)); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), @@ -348,7 +350,7 @@ int main(int argc, char *argv[]) spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_OUTPUT); items[0] = SPA_DICT_ITEM_INIT("my-tag-key", "my-special-tag-value"); spa_tag_build_add_dict(&b, &SPA_DICT_INIT(items, 1)); - params[1] = spa_tag_build_end(&b, &f); + params[n_params++] = spa_tag_build_end(&b, &f); } pw_stream_add_listener(data.stream, @@ -361,7 +363,7 @@ int main(int argc, char *argv[]) PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS, - params, 2); + params, n_params); pw_main_loop_run(data.loop); diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 1a9128351..feb97b23c 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -297,6 +297,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[1]; + uint32_t n_params = 0; Uint32 sdl_format; void *d; @@ -341,7 +342,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), @@ -350,7 +351,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); if (data->mod_info[0].n_modifiers > 0) { - params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, data->mod_info[0].modifiers, data->mod_info[0].n_modifiers); + params[n_params++] = build_format(b, + &info, SPA_VIDEO_FORMAT_RGB, + data->mod_info[0].modifiers, + data->mod_info[0].n_modifiers); } params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, NULL, 0); for (int i=0; i < n_params; i++) { spa_debug_format(2, NULL, params[i]); } - return n_params; } diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c index f0c5892ac..13cc21cf8 100644 --- a/src/examples/video-play-pull.c +++ b/src/examples/video-play-pull.c @@ -330,6 +330,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; + uint32_t n_params = 0; Uint32 sdl_format; void *d; int32_t mult, size, blocks; @@ -417,7 +418,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), @@ -426,19 +427,19 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); - params[0] = sdl_build_formats(&info, b); + params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); - params[1] = spa_pod_builder_add_object(b, + params[n_params++] = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), @@ -480,7 +482,7 @@ static int build_format(struct data *data, struct spa_pod_builder *b, const stru fprintf(stderr, "supported DSP formats:\n"); spa_debug_format(2, NULL, params[1]); - return 2; + return n_params; } static void do_quit(void *userdata, int signal_number) diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c index 60a3a8bb4..3c9a1e984 100644 --- a/src/examples/video-play-reneg.c +++ b/src/examples/video-play-reneg.c @@ -190,6 +190,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[1]; + uint32_t n_params = 0; Uint32 sdl_format; void *d; @@ -234,7 +235,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), @@ -243,7 +244,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); - params[0] = sdl_build_formats(&info, b); + params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); - return 1; + return n_params; } static int reneg_format(struct data *data) @@ -272,6 +274,7 @@ static int reneg_format(struct data *data) uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[2]; + uint32_t n_params = 0; int32_t width, height; if (data->format.info.raw.format == 0) @@ -281,7 +284,7 @@ static int reneg_format(struct data *data) height = data->counter & 1 ? 240 : 480; fprintf(stderr, "renegotiate to %dx%d:\n", width, height); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -289,7 +292,7 @@ static int reneg_format(struct data *data) SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&data->format.info.raw.framerate)); - pw_stream_update_params(data->stream, params, 1); + pw_stream_update_params(data->stream, params, n_params); data->counter++; return 0; @@ -300,16 +303,17 @@ static int reneg_buffers(struct data *data) uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[2]; + uint32_t n_params = 0; fprintf(stderr, "renegotiate buffers\n"); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); - pw_stream_update_params(data->stream, params, 1); + pw_stream_update_params(data->stream, params, n_params); data->counter++; return 0; diff --git a/src/examples/video-play.c b/src/examples/video-play.c index 55c656277..f4f65bb8f 100644 --- a/src/examples/video-play.c +++ b/src/examples/video-play.c @@ -289,6 +289,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; + uint32_t n_params = 0; Uint32 sdl_format; void *d; int32_t mult, size, blocks; @@ -385,7 +386,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), @@ -394,19 +395,19 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); - params[0] = sdl_build_formats(&info, b); + params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); - params[1] = spa_pod_builder_add_object(b, + params[n_params++] = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), @@ -446,7 +448,7 @@ static int build_format(struct data *data, struct spa_pod_builder *b, const stru fprintf(stderr, "supported DSP formats:\n"); spa_debug_format(2, NULL, params[1]); - return 2; + return n_params; } static void do_quit(void *userdata, int signal_number) diff --git a/src/examples/video-src-alloc.c b/src/examples/video-src-alloc.c index 8348fdf92..add9597ed 100644 --- a/src/examples/video-src-alloc.c +++ b/src/examples/video-src-alloc.c @@ -290,6 +290,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; + uint32_t n_params = 0; if (param == NULL || id != SPA_PARAM_Format) return; @@ -298,7 +299,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), @@ -306,31 +307,31 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), @@ -321,31 +322,31 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<cycle & 1 ? 320 : 640; height = data->cycle & 1 ? 240 : 480; fprintf(stderr, "renegotiate to %dx%d:\n", width, height); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -377,7 +379,7 @@ static void on_reneg_timeout(void *userdata, uint64_t expirations) SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); - pw_stream_update_params(data->stream, params, 1); + pw_stream_update_params(data->stream, params, n_params); data->cycle++; } @@ -392,6 +394,7 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; + uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); @@ -445,7 +448,7 @@ int main(int argc, char *argv[]) * The server will select a format that matches and informs us about this * in the stream param_changed event. */ - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -468,7 +471,7 @@ int main(int argc, char *argv[]) PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, - params, 1); + params, n_params); /* unlock, run the loop and wait, this will trigger the callbacks */ pw_thread_loop_wait(data.loop); diff --git a/src/examples/video-src.c b/src/examples/video-src.c index cba32c94e..452924e59 100644 --- a/src/examples/video-src.c +++ b/src/examples/video-src.c @@ -217,6 +217,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; + uint32_t n_params = 0; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); @@ -232,38 +233,38 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); - params[0] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); - params[1] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); - params[2] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( sizeof(struct spa_meta_region) * 16, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 16)); - params[3] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); #define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP) - params[4] = spa_pod_builder_add_object(&b, + params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_Int( CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT))); - pw_stream_update_params(stream, params, 5); + pw_stream_update_params(stream, params, n_params); } static void From a859c7a6515ce50cf4fa1ed4905483844dd2bf6c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 13:40:52 +0200 Subject: [PATCH 0865/1014] builder: add support for FEATURE choice This is the same as the Flags choice but the property (if any) has the DROP flag set. This means that when filtering, the property is dropped when one side is missing the property. Otherwise, the flags are AND-ed together with a negotiation failure when the result if 0. This can be used to make sure both sides present compatible feature bits. The result of the filter is then: 1. no property (one side didn't present bits). This is likely because the other side is old and doesn't know about the feature bits yet. Code can take a backwards compatibility codepath. 2. a negotiation failure, both sides presented bits but the AND is 0, they don't have compatible features. 3. a property with bits (features) that are compatible. This is different from normal flags in that the flags are not dropped when the other size is missing the property. --- spa/include/spa/pod/builder.h | 43 ++++++++++++++++++++++------------- spa/include/spa/pod/vararg.h | 3 +++ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 27a3611a6..d1801832c 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -486,7 +486,7 @@ spa_pod_builder_control(struct spa_pod_builder *builder, uint32_t offset, uint32 return spa_pod_builder_raw(builder, &p, sizeof(p)); } -SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) +SPA_API_POD_BUILDER uint32_t spa_choice_from_id_flags(char id, uint32_t *flags) { switch (id) { case 'r': @@ -495,6 +495,9 @@ SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) return SPA_CHOICE_Step; case 'e': return SPA_CHOICE_Enum; + case 'F': + *flags |= SPA_POD_PROP_FLAG_DROP; + SPA_FALLTHROUGH; case 'f': return SPA_CHOICE_Flags; case 'n': @@ -502,6 +505,11 @@ SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) return SPA_CHOICE_None; } } +SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) +{ + uint32_t flags = 0; + return spa_choice_from_id_flags(id, &flags); +} #define SPA_POD_BUILDER_COLLECT(builder,type,args) \ do { \ @@ -621,43 +629,46 @@ spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args) int n_values = 1; struct spa_pod_frame f; bool choice; + uint32_t key = 0, flags = 0, offset = 0, type = 0, ctype = 0; switch (ftype) { case SPA_TYPE_Object: - { - uint32_t key = va_arg(args, uint32_t), flags = 0; + key = va_arg(args, uint32_t); if (key == 0) goto exit; if (key == SPA_ID_INVALID) { key = va_arg(args, uint32_t); flags = va_arg(args, uint32_t); } - spa_pod_builder_prop(builder, key, flags); break; - } case SPA_TYPE_Sequence: - { - uint32_t offset = va_arg(args, uint32_t); - uint32_t type = va_arg(args, uint32_t); + offset = va_arg(args, uint32_t); + type = va_arg(args, uint32_t); if (type == 0) goto exit; - spa_pod_builder_control(builder, offset, type); - SPA_FALLTHROUGH - } - default: break; } + + if ((format = va_arg(args, const char *)) == NULL) break; choice = *format == '?'; if (choice) { - uint32_t type = spa_choice_from_id(*++format); + ctype = spa_choice_from_id_flags(*++format, &flags); if (*format != '\0') format++; - - spa_pod_builder_push_choice(builder, &f, type, 0); - + } + switch (ftype) { + case SPA_TYPE_Object: + spa_pod_builder_prop(builder, key, flags); + break; + case SPA_TYPE_Sequence: + spa_pod_builder_control(builder, offset, type); + break; + } + if (choice) { + spa_pod_builder_push_choice(builder, &f, ctype, 0); n_values = va_arg(args, int); } while (n_values-- > 0) diff --git a/spa/include/spa/pod/vararg.h b/spa/include/spa/pod/vararg.h index b2e5ec8eb..8e9137df1 100644 --- a/spa/include/spa/pod/vararg.h +++ b/spa/include/spa/pod/vararg.h @@ -30,6 +30,7 @@ extern "C" { #define SPA_CHOICE_STEP(def,min,max,step) 4,(def),(min),(max),(step) #define SPA_CHOICE_ENUM(n_vals,...) (n_vals),##__VA_ARGS__ #define SPA_CHOICE_FLAGS(flags) 1, (flags) +#define SPA_CHOICE_FEATURES(features) 1, (features) #define SPA_CHOICE_BOOL(def) 3,(def),(def),!(def) #define SPA_POD_Bool(val) "b", val @@ -43,12 +44,14 @@ extern "C" { #define SPA_POD_CHOICE_RANGE_Int(def,min,max) "?ri", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Int(def,min,max,step) "?si", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_CHOICE_FLAGS_Int(flags) "?fi", SPA_CHOICE_FLAGS(flags) +#define SPA_POD_CHOICE_FEATURES_Int(features) "?Fi", SPA_CHOICE_FEATURES(features) #define SPA_POD_Long(val) "l", val #define SPA_POD_CHOICE_ENUM_Long(n_vals,...) "?el", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Long(def,min,max) "?rl", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Long(def,min,max,step) "?sl", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_CHOICE_FLAGS_Long(flags) "?fl", SPA_CHOICE_FLAGS(flags) +#define SPA_POD_CHOICE_FEATURES_LONG(features) "?Fl", SPA_CHOICE_FEATURES(features) #define SPA_POD_Float(val) "f", val #define SPA_POD_CHOICE_ENUM_Float(n_vals,...) "?ef", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) From 00d983a40dfa16a4e1c4258700777ac86e3949fb Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 14:05:04 +0200 Subject: [PATCH 0866/1014] meta: add metadata features Add a new features property to the metadata param. This should be of type CHOICE_FEATURES_Int and should contain the extra features supported by this metadata. Make a special features metadata type that is a combination of the metadata type in the upper 16 bits and the features for that type in the lower 16 bits. Make a function to search if a type has certain feature bits. On the server, when negotiating buffers and metadata, check the result of the features after filtering and if they are not 0, place them as 0 sized extra feature metadata on the buffer. Add some metadata features for the sync_timeline, one that specifies that the RELEASE flag is supported. With this in place, a producer can see if a consumer supports the UNSCHEDULED_RELEASE flag. See #4885 --- spa/include/spa/buffer/buffer.h | 11 +++++++++++ spa/include/spa/buffer/meta.h | 11 ++++++++++- spa/include/spa/param/buffers.h | 1 + src/pipewire/buffers.c | 12 +++++++++--- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/spa/include/spa/buffer/buffer.h b/spa/include/spa/buffer/buffer.h index 39ca6fb81..29ef9f534 100644 --- a/spa/include/spa/buffer/buffer.h +++ b/spa/include/spa/buffer/buffer.h @@ -118,6 +118,17 @@ SPA_API_BUFFER void *spa_buffer_find_meta_data(const struct spa_buffer *b, uint3 return NULL; } +SPA_API_BUFFER bool spa_buffer_has_meta_features(const struct spa_buffer *b, uint32_t type, uint32_t features) +{ + uint32_t i; + for (i = 0; i < b->n_metas; i++) { + uint32_t t = b->metas[i].type; + if ((t >> 16) == type && (t & features) == features) + return true; + } + return false; +} + /** * \} */ diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index 19eee808a..287410f02 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -37,10 +37,17 @@ enum spa_meta_type { SPA_META_Busy, /**< don't write to buffer when count > 0 */ SPA_META_VideoTransform, /**< struct spa_meta_transform */ SPA_META_SyncTimeline, /**< struct spa_meta_sync_timeline */ - _SPA_META_LAST, /**< not part of ABI/API */ + + SPA_META_START_custom = 0x200, + + SPA_META_START_features = 0x10000, /* features start, these have 0 size, the + * type in the upper 16 bits and a bitmask in + * the lower 16 bits with type specific features. */ }; +#define SPA_META_TYPE_FEATURES(type,features) (((type)<<16)|(features)) + /** * A metadata element. * @@ -183,6 +190,8 @@ struct spa_meta_videotransform { * this metadata as SPA_PARAM_BUFFERS_metaType when negotiating a buffer * layout with 2 extra fds. */ +#define SPA_META_FEATURE_SYNC_TIMELINE_RELEASE (1<<0) /**< metadata supports RELEASE */ + struct spa_meta_sync_timeline { #define SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE (1<<0) /**< this flag is set by the producer and cleared * by the consumer when it promises to signal diff --git a/spa/include/spa/param/buffers.h b/spa/include/spa/param/buffers.h index cf0b4a698..bb07a3415 100644 --- a/spa/include/spa/param/buffers.h +++ b/spa/include/spa/param/buffers.h @@ -33,6 +33,7 @@ enum spa_param_meta { SPA_PARAM_META_START, SPA_PARAM_META_type, /**< the metadata, one of enum spa_meta_type (Id enum spa_meta_type) */ SPA_PARAM_META_size, /**< the expected maximum size the meta (Int) */ + SPA_PARAM_META_features, /**< meta data features (Features Int) */ }; /** properties for SPA_TYPE_OBJECT_ParamIO */ diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index 7a02a2d9f..71b4e11f0 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -221,12 +221,12 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, if ((res = param_filter(result, &input, &output, SPA_PARAM_Meta, &b)) > 0) n_params += res; - metas = alloca(sizeof(struct spa_meta) * n_params); + metas = alloca(sizeof(struct spa_meta) * n_params * 2); n_metas = 0; params = alloca(n_params * sizeof(struct spa_pod *)); for (i = 0, offset = 0; i < n_params; i++) { - uint32_t type, size; + uint32_t type, size, features = 0; params[i] = SPA_PTROFF(buffer, offset, struct spa_pod); spa_pod_fixate(params[i]); @@ -239,7 +239,8 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, if (spa_pod_parse_object(params[i], SPA_TYPE_OBJECT_ParamMeta, NULL, SPA_PARAM_META_type, SPA_POD_Id(&type), - SPA_PARAM_META_size, SPA_POD_Int(&size)) < 0) { + SPA_PARAM_META_size, SPA_POD_Int(&size), + SPA_PARAM_META_features, SPA_POD_OPT_Int(&features)) < 0) { pw_log_warn("%p: invalid Meta param", result); continue; } @@ -250,6 +251,11 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, metas[n_metas].type = type; metas[n_metas].size = size; n_metas++; + if (features != 0) { + metas[n_metas].type = SPA_META_TYPE_FEATURES(type, features); + metas[n_metas].size = 0; + n_metas++; + } } max_buffers = context->settings.link_max_buffers; From 12464ed1bbbd1275be3b6964fdffaf1e594c75fc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 15:53:59 +0200 Subject: [PATCH 0867/1014] impl-port: copy node.terminal and node.physical properties Make 2 new node properties to make all ports of a node terminal or physical. Skip the monitor ports for this, though, they can never be terminal or physical. This is important for JACK clients that often enumerate physical terminal ports in order to link to them and with this you can make JACK clients link to virtual sinks and sources as well. --- src/pipewire/impl-port.c | 7 +++++++ src/pipewire/keys.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index f0ae5487c..efd9e27f1 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1238,6 +1238,13 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) is_monitor = pw_properties_get_bool(port->properties, PW_KEY_PORT_MONITOR, false); + if (!is_monitor) { + if ((str = pw_properties_get(nprops, PW_KEY_NODE_TERMINAL)) != NULL) + pw_properties_set(port->properties, PW_KEY_PORT_TERMINAL, str); + if ((str = pw_properties_get(nprops, PW_KEY_NODE_PHYSICAL)) != NULL) + pw_properties_set(port->properties, PW_KEY_PORT_PHYSICAL, str); + } + port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 88b549d89..3d119fefb 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -229,6 +229,9 @@ extern "C" { * playback or disable the prefix * completely if an empty string * is provided */ +#define PW_KEY_NODE_PHYSICAL "node.physical" /**< ports from the node are physical */ +#define PW_KEY_NODE_TERMINAL "node.terminal" /**< ports from the node are terminal */ + /** Port keys */ #define PW_KEY_PORT_ID "port.id" /**< port id */ #define PW_KEY_PORT_NAME "port.name" /**< port name */ From 6bc451cf6d15aacb3d5e2438fc31d7d8a91859de Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 16:16:25 +0200 Subject: [PATCH 0868/1014] timer: fix compilation on arm --- src/pipewire/timer-queue.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pipewire/timer-queue.c b/src/pipewire/timer-queue.c index 8f2948597..f40c44bf6 100644 --- a/src/pipewire/timer-queue.c +++ b/src/pipewire/timer-queue.c @@ -27,8 +27,8 @@ static void rearm_timer(struct pw_timer_queue *queue) } if (timeout != queue->next_timeout) { if (timeout) - pw_log_debug("%p: arming with timeout %ld.%09ld", queue, - timeout->tv_sec, timeout->tv_nsec); + pw_log_debug("%p: arming with timeout %"PRIi64, queue, + (int64_t)SPA_TIMESPEC_TO_NSEC(timeout)); else pw_log_debug("%p: disarming (no entries)", queue); @@ -162,8 +162,9 @@ int pw_timer_queue_add(struct pw_timer_queue *queue, struct pw_timer *timer, timer->callback = callback; timer->data = data; - pw_log_debug("%p: adding timer %p with timeout %ld.%09ld", - queue, timer, timeout.tv_sec, timeout.tv_nsec); + pw_log_debug("%p: adding timer %p with timeout %"PRIi64, + queue, timer, (int64_t)SPA_TIMESPEC_TO_NSEC(&timeout)); + /* Insert timer in sorted order (earliest timeout first) */ spa_list_for_each(iter, &queue->entries, link) { From 0267a5906e5e1e631ebe74b9ae48a7190aee89e5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 22 Sep 2025 10:55:32 +0200 Subject: [PATCH 0869/1014] doc: add DSP filter tutorial Add CLAUDE generated tutorial7 based on the audio-dsp-filter example. --- doc/dox/tutorial/index.dox | 2 +- doc/dox/tutorial/tutorial6.dox | 4 +- doc/dox/tutorial/tutorial7.dox | 242 +++++++++++++++++++++++++++++++++ doc/examples/tutorial7.c | 152 +++++++++++++++++++++ doc/meson.build | 2 + 5 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 doc/dox/tutorial/tutorial7.dox create mode 100644 doc/examples/tutorial7.c diff --git a/doc/dox/tutorial/index.dox b/doc/dox/tutorial/index.dox index 9f178e35d..39cb6b4fb 100644 --- a/doc/dox/tutorial/index.dox +++ b/doc/dox/tutorial/index.dox @@ -9,12 +9,12 @@ PipeWire API step-by-step with simple short examples. - \subpage page_tutorial4 - \subpage page_tutorial5 - \subpage page_tutorial6 +- \subpage page_tutorial7 # More Example Programs - \ref audio-src.c "": \snippet{doc} audio-src.c title -- \ref audio-dsp-filter.c "": \snippet{doc} audio-dsp-filter.c title - \ref video-play.c "": \snippet{doc} video-play.c title - \subpage page_examples diff --git a/doc/dox/tutorial/tutorial6.dox b/doc/dox/tutorial/tutorial6.dox index 0cee850d9..3fee6853d 100644 --- a/doc/dox/tutorial/tutorial6.dox +++ b/doc/dox/tutorial/tutorial6.dox @@ -1,6 +1,6 @@ /** \page page_tutorial6 Tutorial - Part 6: Binding Objects -\ref page_tutorial5 | \ref page_tutorial "Index" +\ref page_tutorial5 | \ref page_tutorial "Index" | \ref page_tutorial7 In this tutorial we show how to bind to an object so that we can receive events and call methods on the object. @@ -64,6 +64,6 @@ you created. Otherwise, they will be leaked: } \endcode -\ref page_tutorial5 | \ref page_tutorial "Index" +\ref page_tutorial5 | \ref page_tutorial "Index" | \ref page_tutorial7 */ diff --git a/doc/dox/tutorial/tutorial7.dox b/doc/dox/tutorial/tutorial7.dox new file mode 100644 index 000000000..50fa50443 --- /dev/null +++ b/doc/dox/tutorial/tutorial7.dox @@ -0,0 +1,242 @@ +/** \page page_tutorial7 Tutorial - Part 7: Creating an Audio DSP Filter + +\ref page_tutorial6 | \ref page_tutorial "Index" + +In this tutorial we show how to use \ref pw_filter "pw_filter" to create +a real-time audio processing filter. This is useful for implementing audio +effects, equalizers, analyzers, and other DSP applications. + +Let's take a look at the code before we break it down: + +\snippet tutorial7.c code + +Save as tutorial7.c and compile with: + + gcc -Wall tutorial7.c -o tutorial7 -lm $(pkg-config --cflags --libs libpipewire-0.3) + +## Overview + +Unlike \ref pw_stream "pw_stream" which is designed for applications that +produce or consume audio data, \ref pw_filter "pw_filter" is designed for +applications that process existing audio streams. Filters have both input +and output ports and operate in the DSP domain using 32-bit floating point +samples. + +## Setting up the Filter + +We start with the usual boilerplate and define our data structure: + +\code{.c} +struct data { + struct pw_main_loop *loop; + struct pw_filter *filter; + struct port *in_port; + struct port *out_port; +}; +\endcode + +The filter object manages both input and output ports. Each port represents +an audio channel that can be connected to other applications. + +## Creating the Filter + +\code{.c} +data.filter = pw_filter_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-filter", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Filter", + PW_KEY_MEDIA_ROLE, "DSP", + NULL), + &filter_events, + &data); +\endcode + +We use `pw_filter_new_simple()` which automatically manages the core connection +for us. The properties are important: + +- `PW_KEY_MEDIA_TYPE`: "Audio" indicates this is an audio filter +- `PW_KEY_MEDIA_CATEGORY`: "Filter" tells the session manager this processes audio +- `PW_KEY_MEDIA_ROLE`: "DSP" indicates this is for audio processing + +## Adding Ports + +Next we add input and output ports: + +\code{.c} +data.in_port = pw_filter_add_port(data.filter, + PW_DIRECTION_INPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "input", + NULL), + NULL, 0); + +data.out_port = pw_filter_add_port(data.filter, + PW_DIRECTION_OUTPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "output", + NULL), + NULL, 0); +\endcode + +Key points about filter ports: + +- `PW_DIRECTION_INPUT` and `PW_DIRECTION_OUTPUT` specify the port direction +- `PW_FILTER_PORT_FLAG_MAP_BUFFERS` allows direct memory access to buffers +- `PW_KEY_FORMAT_DSP` indicates this uses 32-bit float DSP format +- DSP ports work with normalized floating-point samples (typically -1.0 to 1.0) + +## Setting Process Latency + +\code{.c} +params[n_params++] = spa_process_latency_build(&b, + SPA_PARAM_ProcessLatency, + &SPA_PROCESS_LATENCY_INFO_INIT( + .ns = 10 * SPA_NSEC_PER_MSEC + )); +\endcode + +This tells PipeWire that our filter adds 10 milliseconds of processing latency. +This information helps the audio system maintain proper timing and latency +compensation throughout the audio graph. + +## Connecting the Filter + +\code{.c} +if (pw_filter_connect(data.filter, + PW_FILTER_FLAG_RT_PROCESS, + params, n_params) < 0) { + fprintf(stderr, "can't connect\n"); + return -1; +} +\endcode + +The `PW_FILTER_FLAG_RT_PROCESS` flag ensures our process callback runs in the +real-time audio thread. This is crucial for low-latency audio processing but +means our process function must be real-time safe (no allocations, file I/O, +or blocking operations). + +## The Process Callback + +The heart of the filter is the process callback: + +\snippet tutorial7.c on_process + +The process function is called for each audio buffer and works as follows: + +1. Get the number of samples to process from `position->clock.duration` +2. Get input and output buffer pointers using `pw_filter_get_dsp_buffer()` +3. Process the audio data (here we just copy input to output) +4. The framework handles queueing the processed buffers + +### Key Points about DSP Processing: + +- **Float Format**: DSP buffers use 32-bit float samples, typically normalized to [-1.0, 1.0] +- **Real-time Safe**: The process function runs in the audio thread and must be real-time safe +- **Buffer Management**: `pw_filter_get_dsp_buffer()` handles the buffer lifecycle automatically +- **Sample-accurate**: Processing happens at the audio sample rate with precise timing + +## Advanced Usage + +This example shows a simple passthrough, but you can implement any audio processing: + +\code{.c} +/* Example: Simple volume control */ +for (uint32_t i = 0; i < n_samples; i++) { + out[i] = in[i] * 0.5f; // Reduce volume by half +} + +/* Example: Simple high-pass filter */ +static float last_sample = 0.0f; +float alpha = 0.99f; +for (uint32_t i = 0; i < n_samples; i++) { + out[i] = alpha * (out[i] + in[i] - last_sample); + last_sample = in[i]; +} +\endcode + +## Comparison with pw_stream + +| Feature | pw_stream | pw_filter | +|---------|-----------|-----------| +| **Use case** | Audio playback/recording | Audio processing/effects | +| **Data format** | Various (S16, S32, etc.) | 32-bit float DSP | +| **Ports** | Single direction | Input and output | +| **Buffer management** | Manual queue/dequeue | Automatic via get_dsp_buffer | +| **Typical apps** | Media players, recorders | Equalizers, effects, analyzers | + +## Connecting and Linking the Filter + +### Manual Linking Options + +Filters require manual connection by design. You can connect them using: + +#### Using pw-link command line: +\code{.sh} +# List output ports (sources) +pw-link -o + +# List input ports (sinks) +pw-link -i + +# List existing connections +pw-link -l + +# Connect a source to filter input +pw-link "source_app:output_FL" "audio-filter:input" + +# Connect filter output to sink +pw-link "audio-filter:output" "sink_app:input_FL" +\endcode + + +### Understanding Filter Auto-Connection Behavior + +**Important**: Unlike audio sources and sinks, filters are **not automatically connected** by WirePlumber. This is by design because filters are meant to be explicitly inserted into audio chains where needed. + +**Why filters don't auto-connect**: +- Filters process existing audio streams rather than generate/consume them +- Auto-connecting filters could create unwanted audio processing +- Filters typically require specific placement in the audio graph +- Manual connection gives users control over when/where effects are applied + +### Testing the Filter + +The filter requires manual connection to test. Here's the recommended workflow: + +1. **Start an audio source** (e.g., `pw-play music.wav`) +2. **Run your filter** (`./tutorial7`) +3. **Check available ports**: + ```sh + # List output ports + pw-link -o | grep -E "(pw-play|audio-filter)" + # List input ports + pw-link -i | grep -E "(audio-filter|playback)" + ``` +4. **Connect the audio chain manually**: + ```sh + # Connect source -> filter -> sink + pw-link "pw-play:output_FL" "audio-filter:input" + pw-link "audio-filter:output" "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_FL" + ``` + +You should hear the audio pass through your filter. Modify the process function +to add effects like volume changes, filtering, or other audio processing. + +**Alternative: Use a patchbay tool** +- **Helvum**: `flatpak install flathub org.pipewire.Helvum` +- **qpwgraph**: Available in most Linux distributions +- **Carla**: Full-featured audio plugin host + +These tools provide graphical interfaces for connecting PipeWire nodes and are ideal for experimenting with filter placement. + +\ref page_tutorial6 | \ref page_tutorial "Index" + +*/ diff --git a/doc/examples/tutorial7.c b/doc/examples/tutorial7.c new file mode 100644 index 000000000..253698ed9 --- /dev/null +++ b/doc/examples/tutorial7.c @@ -0,0 +1,152 @@ +/* + [title] + \ref page_tutorial7 + [title] + */ +/* [code] */ +#include +#include +#include +#include + +#include +#include + +#include +#include + +struct data; + +struct port { + struct data *data; +}; + +struct data { + struct pw_main_loop *loop; + struct pw_filter *filter; + struct port *in_port; + struct port *out_port; +}; + +/* [on_process] */ +static void on_process(void *userdata, struct spa_io_position *position) +{ + struct data *data = userdata; + float *in, *out; + uint32_t n_samples = position->clock.duration; + + pw_log_trace("do process %d", n_samples); + + in = pw_filter_get_dsp_buffer(data->in_port, n_samples); + out = pw_filter_get_dsp_buffer(data->out_port, n_samples); + + if (in == NULL || out == NULL) + return; + + /* Simple passthrough - copy input to output. + * Here you could implement any audio processing: + * - Filters (lowpass, highpass, bandpass) + * - Effects (reverb, delay, distortion) + * - Dynamic processing (compressor, limiter) + * - Equalization + * - etc. + */ + memcpy(out, in, n_samples * sizeof(float)); +} +/* [on_process] */ + +static const struct pw_filter_events filter_events = { + PW_VERSION_FILTER_EVENTS, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + const struct spa_pod *params[1]; + uint32_t n_params = 0; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw_init(&argc, &argv); + + /* make a main loop. If you already have another main loop, you can add + * the fd of this pipewire mainloop to it. */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* Create a simple filter, the simple filter manages the core and remote + * objects for you if you don't need to deal with them. + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the filter state. The most important event + * you need to listen to is the process event where you need to process + * the data. + */ + data.filter = pw_filter_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-filter", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Filter", + PW_KEY_MEDIA_ROLE, "DSP", + NULL), + &filter_events, + &data); + + /* make an audio DSP input port */ + data.in_port = pw_filter_add_port(data.filter, + PW_DIRECTION_INPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "input", + NULL), + NULL, 0); + + /* make an audio DSP output port */ + data.out_port = pw_filter_add_port(data.filter, + PW_DIRECTION_OUTPUT, + PW_FILTER_PORT_FLAG_MAP_BUFFERS, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "output", + NULL), + NULL, 0); + + /* Set processing latency information */ + params[n_params++] = spa_process_latency_build(&b, + SPA_PARAM_ProcessLatency, + &SPA_PROCESS_LATENCY_INFO_INIT( + .ns = 10 * SPA_NSEC_PER_MSEC + )); + + /* Now connect this filter. We ask that our process function is + * called in a realtime thread. */ + if (pw_filter_connect(data.filter, + PW_FILTER_FLAG_RT_PROCESS, + params, n_params) < 0) { + fprintf(stderr, "can't connect\n"); + return -1; + } + + /* and wait while we let things run */ + pw_main_loop_run(data.loop); + + pw_filter_destroy(data.filter); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return 0; +} +/* [code] */ \ No newline at end of file diff --git a/doc/meson.build b/doc/meson.build index 7bd86bfac..f4aa4ba6a 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -79,6 +79,7 @@ extra_docs = [ 'dox/tutorial/tutorial4.dox', 'dox/tutorial/tutorial5.dox', 'dox/tutorial/tutorial6.dox', + 'dox/tutorial/tutorial7.dox', 'dox/api/index.dox', 'dox/api/spa-index.dox', 'dox/api/spa-plugins.dox', @@ -173,6 +174,7 @@ example_files = [ 'tutorial4.c', 'tutorial5.c', 'tutorial6.c', + 'tutorial7.c', ] example_dep_files = [] foreach h : example_files From ad33ff34f7a34c27d433e5b0a05ca409ad2ddca9 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 22 Sep 2025 11:24:23 +0200 Subject: [PATCH 0870/1014] doc: fix some spelling, grammar and formatting mistakes --- doc/dox/internals/scheduling.dox | 62 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/doc/dox/internals/scheduling.dox b/doc/dox/internals/scheduling.dox index 17e9fe28c..38b05596b 100644 --- a/doc/dox/internals/scheduling.dox +++ b/doc/dox/internals/scheduling.dox @@ -2,26 +2,26 @@ This document tries to explain how the PipeWire graph is scheduled. -Graph are constructed from linked nodes together with their ports. This +Graphs are constructed from linked nodes together with their ports. This results in a dependency graph between nodes. Special care is taken for loopback links so that the graph remains a directed graph. # Processing threads -The server (and clients) have two processing threads: +The server (and clients) has two processing threads: -- A main thread that will do all IPC with clients and server and configures the +- A main thread that will do all IPC with clients and server and configure the nodes in the graph for processing. -- A (or more) data processing thread that only does the data processing. +- One (or more) data processing threads that only do the data processing. The data processing threads are given realtime priority and are designed to run with as little overhead as possible. All of the node resources such as -buffers, io areas and metadata will be set up in shared memory before the +buffers, I/O areas and metadata will be set up in shared memory before the node is scheduled to run. This document describes the processing that happens in the data processing -thread after the main-thread has configured it. +thread after the main thread has configured it. # Nodes @@ -41,7 +41,7 @@ Each node also has: +-v---------+ activation { status:OK, // bitmask of NEED_DATA, HAVE_DATA or OK - pending:0, // number of unsatisfied dependencies to be able to run + pending:0, // number of unsatisfied dependencies needed to be able to run required:0 // number of dependencies with other nodes } ``` @@ -49,7 +49,7 @@ Each node also has: The activation record has the following information: - processing state and pending dependencies. As long as there are pending dependencies - the node can not be processed. This is the only relevant information for actually + the node cannot be processed. This is the only relevant information for actually scheduling the graph and is shown in the above illustration. - Current status of the node and profiling info (TRIGGERED, AWAKE, FINISHED, timestamps when the node changed state). @@ -157,10 +157,10 @@ will then: field of the activation record. When the required field is 0, the eventfd is signaled and the node can be scheduled. -In our example above, Node A and B will have their pending state decremented. Node A +In our example above, nodes A and B will have their pending state decremented. Node A will be 0 and will be triggered first (node B has 2 pending dependencies to start with and will not be triggered yet). The driver itself also has 2 dependencies left and will not -be triggered (complete) yet. +be triggered (completed) yet. ## Scheduling node A @@ -172,12 +172,12 @@ After processing, node A goes through the list of targets and decrements each pe field (node A has a reference to B and the driver). In our above example, the driver is decremented (from 2 to 1) but is not yet triggered. -node B is decremented (from 1 to 0) and is triggered by writing to the eventfd. +Node B is decremented (from 1 to 0) and is triggered by writing to the eventfd. ## Scheduling node B Node B is scheduled and processes the input from node A. It then goes through the list of -targets and decrements the pending fields. It decrements the pending field of the +targets and decrements the pending fields. It decrements the pending field of the driver (from 1 to 0) and triggers the driver. ## Scheduling the driver @@ -185,7 +185,7 @@ driver (from 1 to 0) and triggers the driver. The graph always completes after the driver is triggered and scheduled. All required fields from all the nodes in the target list of the driver are now 0. -The driver calculates some stats about cpu time etc. +The driver calculates some stats about CPU time etc. # Async scheduling @@ -201,8 +201,8 @@ dependency for other nodes. This also means that the async nodes can be schedule soon as the driver has started the graph. The completion of the async node does not influence the completion of the graph in -any way and async nodes are therefor interesting is real-time performance can not -be guaranteed, for example when the processing threads are not running in a real-time +any way and async nodes are therefore interesting when real-time performance can not +be guaranteed, for example when the processing threads are not running with a real-time priority. A link between a port of an async node and another port (async or not) is called an @@ -210,7 +210,7 @@ async link and will have the link.async=true property. Because async nodes then run concurrently with other nodes, a method must be in place to avoid concurrent access to buffer data. This is done by sending a spa_io_async_buffers -io to the (mixer) ports of an async link. The spa_io_async_buffers has 2 spa_io_buffer +I/O to the (mixer) ports of an async link. The spa_io_async_buffers has 2 spa_io_buffer slots. The driver will increment a cycle counter for each cycle that it starts. Output ports @@ -223,7 +223,7 @@ A special exception is made for the output ports of the driver node. When the dr started, the output port buffers are copied to the previous cycle spa_io_buffer slot. This way, the async nodes will immediately pick up the new data from the driver source. -Because there are 2 buffers in flight on the spa_io_async_buffers io area, the link needs +Because there are 2 buffers in flight on the spa_io_async_buffers I/O area, the link needs to negotiate at least 2 buffers for this to work. @@ -233,43 +233,49 @@ A, B, C are async nodes and have async links between their ports. The async link has the spa_io_async_buffers with 2 slots (named 0 and 1) below. All the slots are empty. +``` +--------+ +-------+ +-------+ | A | | B | | C | | 0 -( )-> 0 0 -( )-> 0 | | 1 ( ) 1 1 ( ) 1 | +--------+ +-------+ +-------+ - +``` cycle 0: A produces a buffer AB0 on the output port in the (cycle+1)&1 slot (1). B consumes slot cycle&1 (0) with the empty buffer and produces BC0 in slot 1 C consumes slot cycle&1 (0) with the empty buffer +``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB0) 0 -( )-> 0 ( ) 0 -( )-> 0 ( ) | | 1 (AB0) 1 1 (BC0) 1 | +--------+ +-------+ +-------+ - +``` cycle 1: A produces a buffer AB1 on the output port in the (cycle+1)&1 slot (0). B consumes slot cycle&1 (1) with buffer AB0 and produces BC1 in slot 0 C consumes slot cycle&1 (1) with buffer BC0 +``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB1) 0 -(AB1)-> 0 (AB0) 0 -(BC1)-> 0 (BC0) | | 1 (AB0) 1 1 (BC0) 1 | +--------+ +-------+ +-------+ +``` cycle 2: A produces a buffer AB2 on the output port in the (cycle+1)&1 slot (1). B consumes slot cycle&1 (0) with buffer AB1 and produces BC2 in slot 1 C consumes slot cycle&1 (0) with buffer BC1 +``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB2) 0 -(AB1)-> 0 (AB1) 0 -(BC1)-> 0 (BC1) | | 1 (AB2) 1 1 (BC2) 1 | +--------+ +-------+ +-------+ +``` Each async link adds 1 cycle of latency to the chain. Notice how AB0 from cycle 0, produces BC1 in cycle 1, which arrives in node C at cycle 2. @@ -283,6 +289,7 @@ input ports of a link. It is possible for a sync node A to be linked to another sync node D and an async node B: +``` +--------+ +-------+ | A | | B | | (AB1) 0 -(AB1)-> 0 (AB0) 0 ... @@ -294,14 +301,15 @@ async node B: -(AB1)-> 0 (AB1) | | | +-------+ +``` -The Output latency on A's output port is what A reports. When it copied to the +The output latency on A's output port is what A reports. When it is copied to the input port of B, 1 cycle is added and when it is copied to D, nothing is added. -# Remote nodes. +# Remote nodes -For remote nodes, the eventfd and the activation is transferred from the server +For remote nodes, the eventfd and the activation are transferred from the server to the client. This means that writing to the remote client eventfd will wake the client directly @@ -311,7 +319,7 @@ All remote clients also get the activation and eventfd of the peer and driver th are linked to and can directly trigger peers and drivers without going to the server first. -## Remote driver nodes. +## Remote driver nodes Remote drivers start the graph cycle directly without going to the server first. @@ -342,7 +350,7 @@ When the graph is started or partially controlled by RequestProcess events and commands we say we have lazy scheduling. The driver is not always scheduling according to its own rhythm but also depending on the follower. -We can't just enable lazy scheduling when no follower will emit RequestProcess events +We cannot just enable lazy scheduling when no follower will emit RequestProcess events or when no driver will listen for RequestProcess commands. Two new node properties are defined: @@ -357,9 +365,9 @@ defined: >1 means request events as a follower are supported with increasing preference We can only enable lazy scheduling when both the driver and (at least one) follower - has the node.supports-lazy and node.supports-request property respectively. + have the node.supports-lazy and node.supports-request properties respectively. - Node can end up as a driver (is_driver()) and lazy scheduling can be enabled (is_lazy()), + Nodes can end up as a driver (is_driver()) and lazy scheduling can be enabled (is_lazy()), which results in the following cases: driver producer @@ -416,7 +424,7 @@ Some use cases: consumer - node.driver = false - -> producer selected as driver, consumer is simple follower. + -> producer selected as driver, consumer is a simple follower. lazy scheduling inactive (no lazy driver or no request follower) From 3c921acb48a4310be363e10a7c6271b7d2decaf7 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 22 Sep 2025 14:21:46 +0200 Subject: [PATCH 0871/1014] impl-port: rework port properties Don't update info.props all the time, just once when we create the properties, the dict will not change after that. Move the port property check code to a new function. Keep track if we auto generated path, name or alias and if we explicitly update it or not. Listen for node property changes and update the port properties if necessary. Some of the port properties or feature depend on the node properties so we want to keep those in sync. --- src/pipewire/impl-client.c | 5 +- src/pipewire/impl-core.c | 3 +- src/pipewire/impl-device.c | 4 - src/pipewire/impl-factory.c | 2 - src/pipewire/impl-link.c | 3 +- src/pipewire/impl-module.c | 3 +- src/pipewire/impl-node.c | 3 - src/pipewire/impl-port.c | 319 +++++++++++++++++++----------------- src/pipewire/private.h | 4 +- 9 files changed, 173 insertions(+), 173 deletions(-) diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index 06234e2b7..36b4370b2 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -216,7 +216,6 @@ static int update_properties(struct pw_impl_client *client, const struct spa_dic } changed += pw_properties_set(client->properties, dict->items[i].key, dict->items[i].value); } - client->info.props = &client->properties->dict; pw_log_debug("%p: updated %d properties", client, changed); @@ -481,6 +480,8 @@ struct pw_impl_client *pw_context_create_client(struct pw_impl_core *core, pw_mempool_add_listener(this->pool, &impl->pool_listener, &pool_events, impl); this->properties = properties; + this->info.props = &this->properties->dict; + this->permission_func = client_permission_func; this->permission_data = impl; @@ -493,7 +494,6 @@ struct pw_impl_client *pw_context_create_client(struct pw_impl_core *core, pw_context_add_listener(this->context, &impl->context_listener, &context_events, impl); - this->info.props = &this->properties->dict; return this; @@ -559,7 +559,6 @@ int pw_impl_client_register(struct pw_impl_client *client, pw_properties_setf(client->properties, PW_KEY_OBJECT_ID, "%d", client->info.id); pw_properties_setf(client->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(client->global)); - client->info.props = &client->properties->dict; pw_global_add_listener(client->global, &client->global_listener, &global_events, client); pw_global_update_keys(client->global, client->info.props, keys); diff --git a/src/pipewire/impl-core.c b/src/pipewire/impl-core.c index 1901b4b52..3a43a8e88 100644 --- a/src/pipewire/impl-core.c +++ b/src/pipewire/impl-core.c @@ -416,6 +416,7 @@ struct pw_impl_core *pw_context_create_core(struct pw_context *context, this->info.version = pw_get_library_version(); this->info.cookie = pw_rand32(); this->info.name = name; + this->info.props = &this->properties->dict; spa_hook_list_init(&this->listener_list); if (user_data_size > 0) @@ -552,7 +553,6 @@ int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_d int changed; changed = pw_properties_update(core->properties, dict); - core->info.props = &core->properties->dict; pw_log_debug("%p: updated %d properties", core, changed); @@ -603,7 +603,6 @@ int pw_impl_core_register(struct pw_impl_core *core, pw_properties_setf(core->properties, PW_KEY_OBJECT_ID, "%d", core->info.id); pw_properties_setf(core->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(core->global)); - core->info.props = &core->properties->dict; pw_global_update_keys(core->global, core->info.props, keys); diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 73af60cdf..53b76b236 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -143,7 +143,6 @@ static int execute_match(void *data, const char *location, const char *action, struct pw_impl_device *this = match->device; if (spa_streq(action, "update-props")) { match->count += pw_properties_update_string(this->properties, val, len); - this->info.props = &this->properties->dict; } return 1; } @@ -197,7 +196,6 @@ struct pw_impl_device *pw_context_create_device(struct pw_context *context, this->context = context; this->properties = properties; - this->info.props = &properties->dict; this->info.params = this->params; spa_hook_list_init(&this->listener_list); @@ -617,7 +615,6 @@ int pw_impl_device_register(struct pw_impl_device *device, pw_properties_setf(device->properties, PW_KEY_OBJECT_ID, "%d", device->info.id); pw_properties_setf(device->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(device->global)); - device->info.props = &device->properties->dict; pw_global_update_keys(device->global, device->info.props, global_keys); @@ -690,7 +687,6 @@ static int update_properties(struct pw_impl_device *device, const struct spa_dic int changed; changed = pw_properties_update_ignore(device->properties, dict, filter ? ignored : NULL); - device->info.props = &device->properties->dict; pw_log_debug("%p: updated %d properties", device, changed); diff --git a/src/pipewire/impl-factory.c b/src/pipewire/impl-factory.c index 413fa3391..9a8e1c508 100644 --- a/src/pipewire/impl-factory.c +++ b/src/pipewire/impl-factory.c @@ -139,7 +139,6 @@ int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const str int changed; changed = pw_properties_update(factory->properties, dict); - factory->info.props = &factory->properties->dict; pw_log_debug("%p: updated %d properties", factory, changed); @@ -192,7 +191,6 @@ int pw_impl_factory_register(struct pw_impl_factory *factory, pw_properties_set(factory->properties, PW_KEY_FACTORY_NAME, factory->info.name); pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_NAME, "%s", factory->info.type); pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_VERSION, "%d", factory->info.version); - factory->info.props = &factory->properties->dict; pw_global_update_keys(factory->global, factory->info.props, keys); diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 5f1dc1b10..f810f597b 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -1493,6 +1493,7 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, this->context = context; this->properties = properties; + this->info.props = &this->properties->dict; this->info.state = PW_LINK_STATE_INIT; this->output = output; @@ -1518,7 +1519,6 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, impl->format_filter = format_filter; this->info.format = NULL; - this->info.props = &this->properties->dict; this->rt.out_mix.peer_id = input->global->id; this->rt.in_mix.peer_id = output->global->id; @@ -1682,7 +1682,6 @@ int pw_impl_link_register(struct pw_impl_link *link, pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_PORT, "%u", link->info.output_port_id); pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_NODE, "%u", link->info.input_node_id); pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_PORT, "%u", link->info.input_port_id); - link->info.props = &link->properties->dict; pw_global_update_keys(link->global, link->info.props, keys); diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c index 10aa432fe..22c8e91fa 100644 --- a/src/pipewire/impl-module.c +++ b/src/pipewire/impl-module.c @@ -206,6 +206,7 @@ pw_context_load_module(struct pw_context *context, this = &impl->this; this->context = context; this->properties = properties; + this->info.props = &this->properties->dict; properties = NULL; spa_hook_list_init(&this->listener_list); @@ -234,7 +235,6 @@ pw_context_load_module(struct pw_context *context, pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id); pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(this->global)); - this->info.props = &this->properties->dict; pw_global_update_keys(this->global, &this->properties->dict, keys); @@ -354,7 +354,6 @@ int pw_impl_module_update_properties(struct pw_impl_module *module, const struct int changed; changed = pw_properties_update(module->properties, dict); - module->info.props = &module->properties->dict; pw_log_debug("%p: updated %d properties", module, changed); diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 754dbe602..75e945617 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -989,7 +989,6 @@ int pw_impl_node_register(struct pw_impl_node *this, pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->global->id); pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(this->global)); - this->info.props = &this->properties->dict; pw_global_update_keys(this->global, &this->properties->dict, global_keys); @@ -1114,7 +1113,6 @@ static int execute_match(void *data, const char *location, const char *action, struct pw_impl_node *this = match->node; if (spa_streq(action, "update-props")) { match->count += pw_properties_update_string(this->properties, val, len); - this->info.props = &this->properties->dict; } return 1; } @@ -1785,7 +1783,6 @@ static int update_properties(struct pw_impl_node *node, const struct spa_dict *d int changed; changed = pw_properties_update_ignore(node->properties, dict, filter ? ignored : NULL); - node->info.props = &node->properties->dict; pw_log_debug("%p: updated %d properties", node, changed); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index efd9e27f1..221f907cd 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -39,6 +39,8 @@ struct impl { struct spa_list param_list; struct spa_list pending_list; + struct spa_hook node_listener; + unsigned int cache_params:1; }; @@ -452,6 +454,134 @@ int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix return res; } +static int check_properties(struct pw_impl_port *port) +{ + struct pw_impl_node *node = port->node; + bool is_control, is_network, is_monitor, is_device, is_duplex, is_virtual; + const char *media_class, *override_device_prefix, *channel_names; + const char *str, *prefix, *path, *desc, *nick, *name; + const struct pw_properties *nprops; + char position[256]; + int changed = 0; + + nprops = pw_impl_node_get_properties(node); + media_class = pw_properties_get(nprops, PW_KEY_MEDIA_CLASS); + is_network = pw_properties_get_bool(nprops, PW_KEY_NODE_NETWORK, false); + + is_control = PW_IMPL_PORT_IS_CONTROL(port); + is_monitor = pw_properties_get_bool(port->properties, PW_KEY_PORT_MONITOR, false); + + if (!is_monitor) { + if ((str = pw_properties_get(nprops, PW_KEY_NODE_TERMINAL)) != NULL) + changed += pw_properties_set(port->properties, PW_KEY_PORT_TERMINAL, str); + if ((str = pw_properties_get(nprops, PW_KEY_NODE_PHYSICAL)) != NULL) + changed += pw_properties_set(port->properties, PW_KEY_PORT_PHYSICAL, str); + } + + port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); + port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); + + /* inherit passive state from parent node */ + port->passive = pw_properties_get_bool(port->properties, PW_KEY_PORT_PASSIVE, + port->direction == PW_DIRECTION_INPUT ? + node->in_passive : node->out_passive); + + if (media_class != NULL && + (strstr(media_class, "Sink") != NULL || + strstr(media_class, "Source") != NULL)) + is_device = true; + else + is_device = false; + + is_duplex = media_class != NULL && strstr(media_class, "Duplex") != NULL; + is_virtual = media_class != NULL && strstr(media_class, "Virtual") != NULL; + + override_device_prefix = pw_properties_get(nprops, PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX); + + if (is_network) { + prefix = port->direction == PW_DIRECTION_INPUT ? + "send" : is_monitor ? "monitor" : "receive"; + } else if (is_duplex) { + prefix = port->direction == PW_DIRECTION_INPUT ? + "playback" : "capture"; + } else if (is_virtual) { + prefix = port->direction == PW_DIRECTION_INPUT ? + "input" : "capture"; + } else if (is_device) { + if (override_device_prefix != NULL) + prefix = is_monitor ? "monitor" : override_device_prefix; + else + prefix = port->direction == PW_DIRECTION_INPUT ? + "playback" : is_monitor ? "monitor" : "capture"; + } else { + prefix = port->direction == PW_DIRECTION_INPUT ? + "input" : is_monitor ? "monitor" : "output"; + } + + path = pw_properties_get(nprops, PW_KEY_OBJECT_PATH); + desc = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION); + nick = pw_properties_get(nprops, PW_KEY_NODE_NICK); + name = pw_properties_get(nprops, PW_KEY_NODE_NAME); + + if (pw_properties_get(port->properties, PW_KEY_OBJECT_PATH) == NULL || + port->auto_path) { + if ((str = name) == NULL && (str = nick) == NULL && (str = desc) == NULL) + str = "node"; + + changed += pw_properties_setf(port->properties, PW_KEY_OBJECT_PATH, "%s:%s_%d", + path ? path : str, prefix, pw_impl_port_get_id(port)); + port->auto_path = true; + } + + str = pw_properties_get(port->properties, PW_KEY_AUDIO_CHANNEL); + if (str == NULL || spa_streq(str, "UNK")) + snprintf(position, sizeof(position), "%d", port->port_id + 1); + else if (str != NULL) + snprintf(position, sizeof(position), "%s", str); + + channel_names = pw_properties_get(nprops, PW_KEY_NODE_CHANNELNAMES); + if (channel_names != NULL) { + struct spa_json it[1]; + char v[256]; + uint32_t i; + + if (spa_json_begin_array_relax(&it[0], channel_names, strlen(channel_names)) > 0) { + for (i = 0; i < port->port_id + 1; i++) + if (spa_json_get_string(&it[0], v, sizeof(v)) <= 0) + break; + + if (i == port->port_id + 1 && strlen(v) > 0) + snprintf(position, sizeof(position), "%s", v); + } + } + + if (pw_properties_get(port->properties, PW_KEY_PORT_NAME) == NULL || + port->auto_name) { + if (is_control) + changed += pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s", prefix); + else if (prefix == NULL || strlen(prefix) == 0) + changed += pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s", position); + else + changed += pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%s", prefix, position); + port->auto_name = true; + } + if (pw_properties_get(port->properties, PW_KEY_PORT_ALIAS) == NULL || + port->auto_alias) { + if ((str = nick) == NULL && (str = desc) == NULL && (str = name) == NULL) + str = "node"; + + if (is_control) + changed += pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", + str, prefix); + else { + changed += pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", + str, pw_properties_get(port->properties, PW_KEY_PORT_NAME)); + } + port->auto_alias = true; + } + return changed; +} + static int update_properties(struct pw_impl_port *port, const struct spa_dict *dict, bool filter) { static const char * const ignored[] = { @@ -462,37 +592,21 @@ static int update_properties(struct pw_impl_port *port, const struct spa_dict *d PW_KEY_PORT_ID, NULL }; - int changed; changed = pw_properties_update_ignore(port->properties, dict, filter ? ignored : NULL); - if (changed) { - const char *name, *alias; - name = spa_dict_lookup(dict, PW_KEY_PORT_NAME); - alias = spa_dict_lookup(dict, PW_KEY_PORT_ALIAS); - - if (alias != NULL) { - /* alias was explicitly updated, don't generate one from - * the port.name */ - port->alias_port_name = false; - } - else if (name != NULL && port->alias_port_name) { - const struct pw_properties *nprops = pw_impl_node_get_properties(port->node); - const char *node_desc = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION); - const char *node_nick = pw_properties_get(nprops, PW_KEY_NODE_NICK); - const char *node_name = pw_properties_get(nprops, PW_KEY_NODE_NAME); - const char *str; - - if ((str = node_nick) == NULL && (str = node_desc) == NULL && (str = node_name) == NULL) - str = "node"; - pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", str, name); - } - } - port->info.props = &port->properties->dict; + pw_log_debug("%p: updated %d properties", port, changed); if (changed) { - pw_log_debug("%p: updated %d properties", port, changed); + /* check for explicit updates so we don't have to autogenerate */ + if (spa_dict_lookup(dict, PW_KEY_OBJECT_PATH) != NULL) + port->auto_path = false; + if (spa_dict_lookup(dict, PW_KEY_PORT_NAME) != NULL) + port->auto_name = false; + if (spa_dict_lookup(dict, PW_KEY_PORT_ALIAS) != NULL) + port->auto_alias = false; + check_properties(port); port->info.change_mask |= PW_PORT_CHANGE_MASK_PROPS; } return changed; @@ -1185,7 +1299,6 @@ int pw_impl_port_register(struct pw_impl_port *port, pw_properties_setf(port->properties, PW_KEY_OBJECT_ID, "%d", port->info.id); pw_properties_setf(port->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(port->global)); - port->info.props = &port->properties->dict; pw_global_update_keys(port->global, &port->properties->dict, global_keys); @@ -1194,18 +1307,32 @@ int pw_impl_port_register(struct pw_impl_port *port, return pw_global_register(port->global); } +static void node_info_changed(void *data, const struct pw_node_info *info) +{ + struct pw_impl_port *port = data; + if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { + if (check_properties(port) > 0) { + port->info.change_mask |= PW_PORT_CHANGE_MASK_PROPS; + emit_info_changed(port); + } + } + +} +static const struct pw_impl_node_events node_events = { + PW_VERSION_IMPL_NODE_EVENTS, + .info_changed = node_info_changed, +}; + SPA_EXPORT int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) { + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); uint32_t port_id = port->port_id; struct spa_list *ports; struct pw_map *portmap; struct pw_impl_port *find; - bool is_control, is_network, is_monitor, is_device, is_duplex, is_virtual; - const char *media_class, *override_device_prefix, *channel_names; - const char *str, *dir, *prefix, *path, *desc, *nick, *name; - const struct pw_properties *nprops; - char position[256]; + const char *dir; + bool is_control; int res; if (port->node != NULL) @@ -1227,146 +1354,28 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) return res; port->node = node; + pw_impl_node_add_listener(node, &impl->node_listener, &node_events, port); pw_impl_node_emit_port_init(node, port); check_params(port); - nprops = pw_impl_node_get_properties(node); - media_class = pw_properties_get(nprops, PW_KEY_MEDIA_CLASS); - is_network = pw_properties_get_bool(nprops, PW_KEY_NODE_NETWORK, false); - - is_monitor = pw_properties_get_bool(port->properties, PW_KEY_PORT_MONITOR, false); - - if (!is_monitor) { - if ((str = pw_properties_get(nprops, PW_KEY_NODE_TERMINAL)) != NULL) - pw_properties_set(port->properties, PW_KEY_PORT_TERMINAL, str); - if ((str = pw_properties_get(nprops, PW_KEY_NODE_PHYSICAL)) != NULL) - pw_properties_set(port->properties, PW_KEY_PORT_PHYSICAL, str); - } - - port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); - port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); + check_properties(port); is_control = PW_IMPL_PORT_IS_CONTROL(port); if (is_control) { dir = port->direction == PW_DIRECTION_INPUT ? "control" : "notify"; - pw_properties_set(port->properties, PW_KEY_PORT_CONTROL, "true"); - } - else { - dir = port->direction == PW_DIRECTION_INPUT ? "in" : "out"; - } - pw_properties_set(port->properties, PW_KEY_PORT_DIRECTION, dir); - - /* inherit passive state from parent node */ - if (port->direction == PW_DIRECTION_INPUT) - port->passive = node->in_passive; - else - port->passive = node->out_passive; - /* override with specific port property if available */ - port->passive = pw_properties_get_bool(port->properties, PW_KEY_PORT_PASSIVE, - port->passive); - - if (media_class != NULL && - (strstr(media_class, "Sink") != NULL || - strstr(media_class, "Source") != NULL)) - is_device = true; - else - is_device = false; - - is_duplex = media_class != NULL && strstr(media_class, "Duplex") != NULL; - is_virtual = media_class != NULL && strstr(media_class, "Virtual") != NULL; - - override_device_prefix = pw_properties_get(nprops, PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX); - - if (is_network) { - prefix = port->direction == PW_DIRECTION_INPUT ? - "send" : is_monitor ? "monitor" : "receive"; - } else if (is_duplex) { - prefix = port->direction == PW_DIRECTION_INPUT ? - "playback" : "capture"; - } else if (is_virtual) { - prefix = port->direction == PW_DIRECTION_INPUT ? - "input" : "capture"; - } else if (is_device) { - if (override_device_prefix != NULL) - prefix = is_monitor ? "monitor" : override_device_prefix; - else - prefix = port->direction == PW_DIRECTION_INPUT ? - "playback" : is_monitor ? "monitor" : "capture"; - } else { - prefix = port->direction == PW_DIRECTION_INPUT ? - "input" : is_monitor ? "monitor" : "output"; - } - - path = pw_properties_get(nprops, PW_KEY_OBJECT_PATH); - desc = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION); - nick = pw_properties_get(nprops, PW_KEY_NODE_NICK); - name = pw_properties_get(nprops, PW_KEY_NODE_NAME); - - if (pw_properties_get(port->properties, PW_KEY_OBJECT_PATH) == NULL) { - if ((str = name) == NULL && (str = nick) == NULL && (str = desc) == NULL) - str = "node"; - - pw_properties_setf(port->properties, PW_KEY_OBJECT_PATH, "%s:%s_%d", - path ? path : str, prefix, pw_impl_port_get_id(port)); - } - - str = pw_properties_get(port->properties, PW_KEY_AUDIO_CHANNEL); - if (str == NULL || spa_streq(str, "UNK")) - snprintf(position, sizeof(position), "%d", port->port_id + 1); - else if (str != NULL) - snprintf(position, sizeof(position), "%s", str); - - channel_names = pw_properties_get(nprops, PW_KEY_NODE_CHANNELNAMES); - if (channel_names != NULL) { - struct spa_json it[1]; - char v[256]; - uint32_t i; - - if (spa_json_begin_array_relax(&it[0], channel_names, strlen(channel_names)) > 0) { - for (i = 0; i < port->port_id + 1; i++) - if (spa_json_get_string(&it[0], v, sizeof(v)) <= 0) - break; - - if (i == port->port_id + 1 && strlen(v) > 0) - snprintf(position, sizeof(position), "%s", v); - } - } - - if (pw_properties_get(port->properties, PW_KEY_PORT_NAME) == NULL) { - if (is_control) - pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s", prefix); - else if (prefix == NULL || strlen(prefix) == 0) - pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s", position); - else - pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%s", prefix, position); - } - if (pw_properties_get(port->properties, PW_KEY_PORT_ALIAS) == NULL) { - if ((str = nick) == NULL && (str = desc) == NULL && (str = name) == NULL) - str = "node"; - - if (is_control) - pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", - str, prefix); - else { - pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s", - str, pw_properties_get(port->properties, PW_KEY_PORT_NAME)); - port->alias_port_name = true; - } - } - - port->info.props = &port->properties->dict; - - if (is_control) { pw_log_debug("%p: setting node control", port); + pw_properties_set(port->properties, PW_KEY_PORT_CONTROL, "true"); } else { + dir = port->direction == PW_DIRECTION_INPUT ? "in" : "out"; pw_log_debug("%p: setting mixer position io", port); spa_node_set_io(port->mix, SPA_IO_Position, node->rt.position, sizeof(struct spa_io_position)); } + pw_properties_set(port->properties, PW_KEY_PORT_DIRECTION, dir); pw_log_debug("%p: %d add to node %p", port, port_id, node); @@ -1419,6 +1428,7 @@ static int do_remove_port(struct spa_loop *loop, static void pw_impl_port_remove(struct pw_impl_port *port) { + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); struct pw_impl_node *node = port->node; int res; @@ -1448,6 +1458,7 @@ static void pw_impl_port_remove(struct pw_impl_port *port) spa_list_remove(&port->link); pw_impl_node_emit_port_removed(node, port); + spa_hook_remove(&impl->node_listener); port->node = NULL; } diff --git a/src/pipewire/private.h b/src/pipewire/private.h index b5d609256..072eac596 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -959,7 +959,9 @@ struct pw_impl_port { } rt; /**< data only accessed from the data thread */ unsigned int destroying:1; unsigned int passive:1; - unsigned int alias_port_name:1; + unsigned int auto_path:1; /* path was automatically generated */ + unsigned int auto_name:1; /* name was automatically generated */ + unsigned int auto_alias:1; /* alias was automatically generated */ int busy_count; struct spa_latency_info latency[2]; /**< latencies */ From f0a5f09420fbae4e06b90c60213b977c4ff6e75b Mon Sep 17 00:00:00 2001 From: lumingzh Date: Mon, 22 Sep 2025 08:54:57 +0800 Subject: [PATCH 0872/1014] update Chinese translation --- po/zh_CN.po | 225 ++++++++++++++++++++++++++++------------------------ 1 file changed, 123 insertions(+), 102 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index 1022f5d65..697be961c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -6,15 +6,15 @@ # Cheng-Chia Tseng , 2010, 2012. # Frank Hill , 2015. # Mingye Wang (Arthur2e5) , 2015. -# lumingzh , 2024. +# lumingzh , 2024-2025. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2024-09-09 16:36+0000\n" -"PO-Revision-Date: 2024-10-08 09:41+0800\n" +"POT-Creation-Date: 2025-09-21 15:33+0000\n" +"PO-Revision-Date: 2025-09-22 08:53+0800\n" "Last-Translator: lumingzh \n" "Language-Team: Chinese (China) \n" "Language: zh_CN\n" @@ -22,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2016-03-22 13:23+0000\n" -"X-Generator: Gtranslator 47.0\n" +"X-Generator: Gtranslator 49.0\n" "Plural-Forms: nplurals=1; plural=0;\n" #: src/daemon/pipewire.c:29 @@ -42,11 +42,11 @@ msgstr "" " -c, --config 加载配置 (默认 %s)\n" " -P --properties 设置上下文属性\n" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "PipeWire 多媒体系统" -#: src/daemon/pipewire.desktop.in:5 +#: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "启动 PipeWire 多媒体系统" @@ -60,26 +60,26 @@ msgstr "至 %s%s%s 的隧道" msgid "Dummy Output" msgstr "虚拟输出" -#: src/modules/module-pulse-tunnel.c:774 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "用于 %s@%s 的隧道" -#: src/modules/module-zeroconf-discover.c:318 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "未知设备" -#: src/modules/module-zeroconf-discover.c:330 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%2$s@%3$s 上的 %1$s" -#: src/modules/module-zeroconf-discover.c:334 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%2$s 上的 %1$s" -#: src/tools/pw-cat.c:996 +#: src/tools/pw-cat.c:1084 #, c-format msgid "" "%s [options] [|-]\n" @@ -94,7 +94,7 @@ msgstr "" " -v, --verbose 输出详细操作\n" "\n" -#: src/tools/pw-cat.c:1003 +#: src/tools/pw-cat.c:1091 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -126,7 +126,7 @@ msgstr "" " -P --properties 设置节点属性\n" "\n" -#: src/tools/pw-cat.c:1021 +#: src/tools/pw-cat.c:1109 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -144,6 +144,9 @@ msgid "" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" +" -M, --force-midi Force midi format, one of \"midi\" " +"or \"ump\", (default ump)\n" +" -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate 采样率 (录制模式需要) (默认 %u)\n" @@ -151,22 +154,27 @@ msgstr "" " --channel-map 通道映射\n" " \"stereo\", \"surround-51\",... " "中的其一或\n" -" 以\",\"分隔的通道名列表: 如 \"FL," -"FR\"\n" +" 以\",\"分隔的通道名列表: 如 " +"\"FL,FR\"\n" " --format 采样格式 %s (录制模式需要) (默认 " "%s)\n" " --volume 媒体流音量 0-1.0 (默认 %.3f)\n" " -q --quality 重采样质量 (0 - 15) (默认 %d)\n" " -a, --raw 原生模式\n" +" -M, --force-midi 强制 midi 格式,\"midi\" 或 \"ump\" " +"其一 (默认 ump)\n" +" -n, --sample-count COUNT 计数采样后停止\n" "\n" -#: src/tools/pw-cat.c:1039 +#: src/tools/pw-cat.c:1129 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" +" -s, --sysex SysEx mode\n" +" -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback 回放模式\n" @@ -174,9 +182,11 @@ msgstr "" " -m, --midi Midi 模式\n" " -d, --dsd DSD 模式\n" " -o, --encoded 编码模式\n" +" -s, --sysex SysEx 模式\n" +" -c, --midi-clip MIDI 剪辑模式\n" "\n" -#: src/tools/pw-cli.c:2285 +#: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" @@ -194,15 +204,20 @@ msgstr "" " -m, --monitor 监视器活动\n" "\n" -#: spa/plugins/alsa/acp/acp.c:327 +#: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "专业音频" -#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 -#: spa/plugins/bluez5/bluez5-device.c:1701 +#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "关" +#: spa/plugins/alsa/acp/acp.c:610 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [ALSA UCM 错误]" + #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "输入" @@ -226,7 +241,7 @@ msgstr "输入插孔" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1989 +#: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "话筒" @@ -292,12 +307,15 @@ msgid "No Bass Boost" msgstr "无重低音增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1995 +#: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "扬声器" +#. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 +#: spa/plugins/bluez5/bluez5-device.c:2384 +#: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "模拟耳机" @@ -374,15 +392,15 @@ msgstr "语音输入" msgid "Virtual Surround 7.1" msgstr "虚拟环绕 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4456 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "模拟单声道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4457 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "模拟单声道 (左声道)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4458 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "模拟单声道 (右声道)" @@ -391,147 +409,147 @@ msgstr "模拟单声道 (右声道)" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4459 -#: spa/plugins/alsa/acp/alsa-mixer.c:4467 -#: spa/plugins/alsa/acp/alsa-mixer.c:4468 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "模拟立体声" -#: spa/plugins/alsa/acp/alsa-mixer.c:4460 +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "单声道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "立体声" -#: spa/plugins/alsa/acp/alsa-mixer.c:4469 -#: spa/plugins/alsa/acp/alsa-mixer.c:4627 -#: spa/plugins/bluez5/bluez5-device.c:1977 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "耳机" -#: spa/plugins/alsa/acp/alsa-mixer.c:4470 -#: spa/plugins/alsa/acp/alsa-mixer.c:4628 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "扬声麦克风" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "多声道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "模拟环绕 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "模拟环绕 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "模拟环绕 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "模拟环绕 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4477 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "模拟环绕 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4478 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "模拟环绕 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4479 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "模拟环绕 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4480 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "模拟环绕 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4481 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "模拟环绕 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "模拟环绕 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "模拟环绕 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "数字立体声 (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "数字环绕 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "数字环绕 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "数字环绕 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "数字立体声 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "数字环绕 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "语音" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "游戏" -#: spa/plugins/alsa/acp/alsa-mixer.c:4625 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "模拟单声道双工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4626 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "模拟立体声双工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "数字立体声双工 (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4630 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "多声道双工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4631 +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "模拟立体声双工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4632 +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "单声道语音 + 7.1 环绕声" -#: spa/plugins/alsa/acp/alsa-mixer.c:4733 +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "%s 输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:4741 +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "%s 输入" -#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -547,7 +565,7 @@ msgstr[0] "" "snd_pcm_avail() 返回的值非常大:%lu 字节(%lu 毫秒)。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1297 +#: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -563,7 +581,7 @@ msgstr[0] "" "snd_pcm_delay() 返回的值非常大:%li 字节(%s%lu 毫秒)。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1344 +#: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -574,7 +592,7 @@ msgstr "" "snd_pcm_avail_delay() 返回的值非常很奇怪:延迟 %lu 小于可用 (avail) %lu。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1387 +#: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -594,111 +612,114 @@ msgstr[0] "" msgid "(invalid)" msgstr "(无效)" -#: spa/plugins/alsa/acp/compat.c:193 +#: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "内置音频" -#: spa/plugins/alsa/acp/compat.c:198 +#: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "调制解调器" -#: spa/plugins/bluez5/bluez5-device.c:1712 +#: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)" -#: spa/plugins/bluez5/bluez5-device.c:1760 +#: spa/plugins/bluez5/bluez5-device.c:2014 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "助听器音频流 (ASHA 信宿)" + +#: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "高保真回放 (A2DP 信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1763 +#: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1771 +#: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "高保真回放 (A2DP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1773 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "高保真双工 (A2DP 信源/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1823 +#: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "高保真回放 (BAP 信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1828 +#: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "高保真输入 (BAP 信源, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1832 +#: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1841 +#: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "高保真回放 (BAP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1845 +#: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "高保真输入 (BAP 信源)" -#: spa/plugins/bluez5/bluez5-device.c:1848 +#: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "高保真双工 (BAP 信源/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1897 +#: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:1978 -#: spa/plugins/bluez5/bluez5-device.c:1983 -#: spa/plugins/bluez5/bluez5-device.c:1990 -#: spa/plugins/bluez5/bluez5-device.c:1996 -#: spa/plugins/bluez5/bluez5-device.c:2002 -#: spa/plugins/bluez5/bluez5-device.c:2008 -#: spa/plugins/bluez5/bluez5-device.c:2014 -#: spa/plugins/bluez5/bluez5-device.c:2020 -#: spa/plugins/bluez5/bluez5-device.c:2026 +#: spa/plugins/bluez5/bluez5-device.c:2361 +#: spa/plugins/bluez5/bluez5-device.c:2366 +#: spa/plugins/bluez5/bluez5-device.c:2373 +#: spa/plugins/bluez5/bluez5-device.c:2379 +#: spa/plugins/bluez5/bluez5-device.c:2385 +#: spa/plugins/bluez5/bluez5-device.c:2391 +#: spa/plugins/bluez5/bluez5-device.c:2397 +#: spa/plugins/bluez5/bluez5-device.c:2403 +#: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "免手操作" -#: spa/plugins/bluez5/bluez5-device.c:1984 +#: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "免手操作 (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2001 -msgid "Headphone" -msgstr "头戴耳机" - -#: spa/plugins/bluez5/bluez5-device.c:2007 +#: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "便携式" -#: spa/plugins/bluez5/bluez5-device.c:2013 +#: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "车内" -#: spa/plugins/bluez5/bluez5-device.c:2019 +#: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "高保真" -#: spa/plugins/bluez5/bluez5-device.c:2025 +#: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "电话" -#: spa/plugins/bluez5/bluez5-device.c:2032 +#: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "蓝牙" -#: spa/plugins/bluez5/bluez5-device.c:2033 +#: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "蓝牙 (HFP)" +#~ msgid "Headphone" +#~ msgstr "头戴耳机" + #~ msgid "Headset Head Unit (HSP/HFP)" #~ msgstr "头戴式耳机单元 (HSP/HFP)" From 2e2f7c9f7952823d6a876597dae1e68bd3009705 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 22 Sep 2025 20:27:19 +0300 Subject: [PATCH 0873/1014] alsa: don't fail if 3 periods_min fails Some drivers (emu10k1) appear to not necessarily support more than 2 periods. Don't fail start if snd_pcm_hw_params_set_periods_min() fails, then we just set nearest possible periods and buffer sizes. --- spa/plugins/alsa/alsa-pcm.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 4daaa44c2..b9e631c58 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2360,10 +2360,14 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ snd_pcm_uframes_t period_size_max; unsigned int periods_min = (periods == UINT_MAX) ? 3 : periods; - CHECK(snd_pcm_hw_params_set_periods_min(hndl, params, &periods_min, &dir), "set_periods_min"); - CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), "get_period_size_max"); - if (period_size > period_size_max) - period_size = SPA_MIN(period_size, flp2(period_size_max)); + err = snd_pcm_hw_params_set_periods_min(hndl, params, &periods_min, &dir); + if (!err) { + CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), "get_period_size_max"); + if (period_size > period_size_max) + period_size = SPA_MIN(period_size, flp2(period_size_max)); + } else { + spa_log_debug(state->log, "set_periods_min: %s", snd_strerror(err)); + } } CHECK(snd_pcm_hw_params_set_period_size_near(hndl, params, &period_size, &dir), "set_period_size_near"); From f1e1f720bf46f37d709adb5557ee0ea0b5abed6c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 24 Sep 2025 12:36:13 +0200 Subject: [PATCH 0874/1014] adapter: fix Start of adapter Commit cbbf37c3b8b6c77e111ab229c3315aadb9690bf9 changed the logic of the Start command. Before this commit, when there was no converter, the follower would always get the Start command. After the commit, the follower would only get Start when previously Paused. This however breaks when we set a format or buffers on the follower without a converter because those actions might change the state of the follower to Paused implicitly. We should simply remove the started check here and always call Start on the converter and follower, the implementations themselves will keep track if anything needs to be done. Fixes #4911 --- spa/plugins/audioconvert/audioadapter.c | 2 -- spa/plugins/videoconvert/videoadapter.c | 2 -- 2 files changed, 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index c5b113b55..04a33ccfb 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1071,8 +1071,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); - if (this->started) - return 0; if ((res = negotiate_format(this)) < 0) return res; this->ready = true; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index fab360acb..271a255e8 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1094,8 +1094,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); - if (this->started) - return 0; if ((res = negotiate_format(this)) < 0) return res; this->ready = true; From 63df661eff4633988e1555828b62193b9f92baab Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Wed, 24 Sep 2025 20:18:56 +0200 Subject: [PATCH 0875/1014] module-rtp: Handle Latency and ProcessLatency in stream --- src/modules/module-raop-sink.c | 45 ++----------------- src/modules/module-rtp/stream.c | 80 ++++++++++++++++++++++++++++++++- src/modules/module-rtp/stream.h | 3 ++ 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 6e9fbf8ab..5f464628c 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -272,8 +271,6 @@ struct impl { bool mute; float volume; - struct spa_process_latency_info process_latency; - struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; @@ -852,30 +849,13 @@ static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) return (uint64_t) msec * impl->rate / 1000; } -static void update_latency(struct impl *impl) -{ - uint32_t n_params = 0; - const struct spa_pod *params[2]; - uint8_t buffer[1024]; - struct spa_pod_builder b; - struct spa_latency_info latency; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - - latency = SPA_LATENCY_INFO(PW_DIRECTION_INPUT); - - spa_process_latency_info_add(&impl->process_latency, &latency); - params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); - rtp_stream_update_params(impl->stream, params, n_params); -} - static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content) { struct impl *impl = data; const char *str; char progress[128]; struct timespec timeout, interval; + struct spa_process_latency_info process_latency; pw_log_info("record status: %d", status); switch (status) { @@ -903,9 +883,10 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head if (spa_atou32(str, &l, 0)) impl->latency = SPA_MAX(l, impl->latency); } - impl->process_latency.rate = impl->latency + msec_to_samples(impl, RAOP_LATENCY_MS); + spa_zero(process_latency); + process_latency.rate = impl->latency + msec_to_samples(impl, RAOP_LATENCY_MS); - update_latency(impl); + rtp_stream_update_process_latency(impl->stream, &process_latency); rtp_stream_set_first(impl->stream); @@ -1668,20 +1649,6 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp rtp_stream_set_param(impl->stream, id, param); } -static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) -{ - struct spa_process_latency_info info; - - if (param == NULL) - spa_zero(info); - else if (spa_process_latency_parse(param, &info) < 0) - return; - if (spa_process_latency_info_compare(&impl->process_latency, &info) == 0) - return; - impl->process_latency = info; - update_latency(impl); -} - static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; @@ -1697,9 +1664,6 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * if (param != NULL) stream_props_changed(impl, id, param); break; - case SPA_PARAM_ProcessLatency: - param_process_latency_changed(impl, param); - break; default: break; } @@ -1990,7 +1954,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_log_error("can't create raop stream: %m"); goto error; } - update_latency(impl); impl->headers = pw_properties_new(NULL, NULL); diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index bb9da4995..35c6009f8 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,9 @@ struct impl { /* And some bookkeping for the sender processing */ uint64_t rtp_base_ts; uint32_t rtp_last_ts; + + /* The process latency, set by on_stream_param_changed(). */ + struct spa_process_latency_info process_latency; }; /* Atomic internal_state accessors. @@ -482,10 +486,70 @@ static void on_stream_state_changed(void *d, enum pw_stream_state old, } } +static void update_latency_params(struct impl *impl) +{ + uint32_t n_params = 0; + const struct spa_pod *params[2]; + uint8_t buffer[1024]; + struct spa_pod_builder b; + struct spa_latency_info main_latency; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + /* main_latency is the latency in the direction indicated by impl->direction. + * In RTP streams, this consists solely of the process latency. (In theory, + * PipeWire SPA nodes could have additional latencies on top of the process + * latency, but this is not the case here.) The other direction is already + * handled by pw_stream. + * + * The main_latncy is passed as updated SPA_PARAM_Latency params to the stream. + * That way, the stream always gets information of latency for _both_ directions; + * the direction indicated by impl->direction is covered by main_latency, and + * the opposite direction is already taken care of by the default pw_stream + * param handling. + * + * The process latency is also passed on as an SPA_PARAM_ProcessLatency param. + */ + + main_latency = SPA_LATENCY_INFO(impl->direction); + spa_process_latency_info_add(&impl->process_latency, &main_latency); + + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &main_latency); + params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, + &impl->process_latency); + + pw_stream_update_params(impl->stream, params, n_params); +} + +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + struct spa_process_latency_info process_latency; + + if (param == NULL) + spa_zero(process_latency); + + else if (spa_process_latency_parse(param, &process_latency) < 0) + return; + if (spa_process_latency_info_compare(&impl->process_latency, &process_latency) == 0) + return; + + impl->process_latency = process_latency; + + update_latency_params(impl); +} + static void on_stream_param_changed (void *d, uint32_t id, const struct spa_pod *param) { struct impl *impl = d; - rtp_stream_emit_param_changed(impl, id, param); + + switch (id) { + case SPA_PARAM_ProcessLatency: + param_process_latency_changed(impl, param); + break; + default: + rtp_stream_emit_param_changed(impl, id, param); + break; + } }; static const struct pw_stream_events stream_events = { @@ -1007,3 +1071,17 @@ int rtp_stream_update_params(struct rtp_stream *s, struct impl *impl = (struct impl*)s; return pw_stream_update_params(impl->stream, params, n_params); } + +void rtp_stream_update_process_latency(struct rtp_stream *s, + const struct spa_process_latency_info *process_latency) +{ + struct impl *impl = (struct impl*)s; + + if (spa_process_latency_info_compare(&impl->process_latency, process_latency) == 0) + return; + + spa_memcpy(&(impl->process_latency), process_latency, + sizeof(const struct spa_process_latency_info)); + + update_latency_params(impl); +} diff --git a/src/modules/module-rtp/stream.h b/src/modules/module-rtp/stream.h index 67908d706..ea358f350 100644 --- a/src/modules/module-rtp/stream.h +++ b/src/modules/module-rtp/stream.h @@ -82,6 +82,9 @@ int rtp_stream_update_params(struct rtp_stream *stream, const struct spa_pod **params, uint32_t n_params); +void rtp_stream_update_process_latency(struct rtp_stream *stream, + const struct spa_process_latency_info *process_latency); + #ifdef __cplusplus } #endif From 65e49b38d1a1a8c3321dca7546a99b120507f62a Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 15 Sep 2025 12:19:05 +0200 Subject: [PATCH 0876/1014] module-rtp: Add process.latency.from.sess prop to set process latency --- src/modules/module-rtp/stream.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 35c6009f8..f5ef5188b 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -614,10 +614,11 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, struct spa_pod_builder b; uint32_t n_params, min_samples, max_samples; float min_ptime, max_ptime; - const struct spa_pod *params[1]; + const struct spa_pod *params[3]; enum pw_stream_flags flags; float latency_msec; int res; + bool process_latency_from_sess; impl = calloc(1, sizeof(*impl)); if (impl == NULL) { @@ -901,6 +902,8 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, pw_properties_set(props, "rtp.ts-refclk", str); } + process_latency_from_sess = pw_properties_get_bool(props, "process.latency.from.sess", false); + spa_dll_init(&impl->dll); spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->rate); impl->corr = 1.0; @@ -943,6 +946,33 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, goto out; } + if (process_latency_from_sess) { + /* If process.latency.from.sess is set to true, then the sess.latency.msec + * quantity is to be set as the process latency at startup. But since the + * sess.latency.msec value is converted to impl->target_buffer, and that + * quantity in turn is subjected to constraint checks (see above), it is + * possible that the _actual_ session latency no longer equals the value + * of sess.latency.msec by the time this location is reached. To take into + * account these constraint adjustments, convert back the impl->target_buffer + * to nanoseconds, and use that as the process latency. + * + * Then, just like how update_latency_params() does it, construct the + * SPA_PARAM_Latency and SPA_PARAM_ProcessLatency params to let the new + * pw_stream know of these latency figures right from the start. */ + + struct spa_latency_info latency; + + impl->process_latency.ns = (int64_t)(impl->target_buffer * 1e9 / impl->rate); + pw_log_debug("set process latency to %" PRId64 " based on sess.latency.msec " + "value %f", impl->process_latency.ns, latency_msec); + + latency = SPA_LATENCY_INFO(impl->direction); + spa_process_latency_info_add(&(impl->process_latency), &latency); + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, + &(impl->process_latency)); + } + pw_stream_add_listener(impl->stream, &impl->stream_listener, &impl->stream_events, impl); From f8389cbdb740ef656dca65c0a51139c390238282 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 25 Sep 2025 12:29:05 +0200 Subject: [PATCH 0877/1014] alsa: improve force_rate handling Replace force_rate with force_quantum. We use force_rate when we need to play an IEC958 or a DSD format but it does not make sense to just force the rate without also forcing the duration. This is also what happens when doing IRQ based scheduling, we then force both the duration and rate of the graph so we can reuse this logic. Also when forcing a quantum, take into account the suggested duration and rate of the graph and scale that with the currently configured rate for the period size. This gives a quantum that will match the requested rate better. This is important for the DSD, where rate are very high and we want the period size to be something reasonable relative to the selected graph rate. For batch devices (and when using a timer) we also configure a period size that is half the duration of the quantum, to make sure we get some headroom. We however need to force the full duration as the quantum, so keep track of this scaling and apply when calculating the duration. --- spa/plugins/alsa/alsa-pcm.c | 40 +++++++++++++++++++++---------------- spa/plugins/alsa/alsa-pcm.h | 3 ++- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index b9e631c58..9e13b09e5 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -2069,7 +2069,7 @@ static void recalc_headroom(struct state *state) int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) { - unsigned int rrate, rchannels, val, rscale = 1; + unsigned int rrate, rchannels, val, rscale = 1, period_scale = 1; snd_pcm_uframes_t period_size; int err, dir; snd_pcm_hw_params_t *params; @@ -2085,7 +2085,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->have_format, state->started); state->use_mmap = !state->disable_mmap; - state->force_rate = false; + state->force_quantum = state->disable_tsched; switch (fmt->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: @@ -2148,7 +2148,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO, IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER, 0, aes3); - state->force_rate = true; + state->force_quantum = true; break; } case SPA_MEDIA_SUBTYPE_dsd: @@ -2186,6 +2186,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ default: return -ENOTSUP; } + state->force_quantum = true; break; } default: @@ -2316,10 +2317,13 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ default_period = SPA_SCALE32_UP(DEFAULT_PERIOD, state->rate, DEFAULT_RATE); default_period = flp2(2 * default_period - 1); - /* no period size specified. If we are batch or not using timers, - * use the graph duration as the period */ - if (period_size == 0 && (state->is_batch || state->disable_tsched)) - period_size = state->position ? state->position->clock.target_duration : default_period; + /* no period size specified. If we are batch or forcing our quantum, + * use the graph requested quantum scaled by our rate */ + if (period_size == 0 && (state->is_batch || state->force_quantum) && state->position) { + period_size = SPA_SCALE32_UP(state->position->clock.target_duration, + state->rate, state->position->clock.target_rate.denom); + period_size = flp2(period_size); + } if (period_size == 0) period_size = default_period; @@ -2330,6 +2334,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ * period size to our default so that we don't create too much * headroom. */ period_size = SPA_MIN(period_size, default_period) / 2; + period_scale = 2; } else { /* disable ALSA wakeups */ if (snd_pcm_hw_params_can_disable_period_wakeup(params)) @@ -2362,7 +2367,8 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ err = snd_pcm_hw_params_set_periods_min(hndl, params, &periods_min, &dir); if (!err) { - CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), "get_period_size_max"); + CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), + "get_period_size_max"); if (period_size > period_size_max) period_size = SPA_MIN(period_size, flp2(period_size_max)); } else { @@ -2378,6 +2384,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ } state->period_frames = period_size; + state->duration = period_size * period_scale; if (periods != UINT_MAX) { CHECK(snd_pcm_hw_params_set_periods_near(hndl, params, &periods, &dir), "set_periods"); @@ -2991,23 +2998,22 @@ static inline int check_position_config(struct state *state, bool starting) uint64_t target_duration; struct spa_fraction target_rate; struct spa_io_position *pos; + bool can_force; if (SPA_UNLIKELY((pos = state->position) == NULL)) return 0; - if (state->disable_tsched && (starting || state->started) && !state->following) { - target_duration = state->period_frames; + /* we can force rate/duration when we are the driver and started/starting */ + can_force = (starting || state->started) && !state->following; + + if (state->force_quantum && can_force) { target_rate = SPA_FRACTION(1, state->rate); - pos->clock.target_duration = target_duration; + target_duration = state->duration; pos->clock.target_rate = target_rate; + pos->clock.target_duration = target_duration; } else { + target_rate = pos->clock.target_rate; target_duration = pos->clock.target_duration; - if (state->force_rate && !state->following) { - target_rate = SPA_FRACTION(1, state->rate); - pos->clock.target_rate = target_rate; - } else { - target_rate = pos->clock.target_rate; - } } if (target_duration == 0 || target_rate.denom == 0) return -EIO; diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index b440c2796..60b6ac8b4 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -169,6 +169,7 @@ struct state { uint32_t delay; uint32_t read_size; uint32_t max_read; + uint32_t duration; uint64_t port_info_all; struct spa_port_info port_info; @@ -232,7 +233,7 @@ struct state { unsigned int dsd_lsb:1; unsigned int linked:1; unsigned int is_batch:1; - unsigned int force_rate:1; + unsigned int force_quantum:1; uint64_t iec958_codecs; From 19198d2982e6ed413fc826ab16b4ebf0ea6d0bd8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 25 Sep 2025 13:56:51 +0200 Subject: [PATCH 0878/1014] pw-cat: fix DSD file reading Now that the server asks for the right amount of samples for DSD, just give it the right amount of samples without doing some weird scaling. Make a method to calculate the size (stride) of one sample, which depends on the interleave and channels of the stream. See !2540 --- src/tools/dfffile.c | 11 ++++++----- src/tools/dfffile.h | 2 ++ src/tools/dsffile.c | 11 ++++++----- src/tools/dsffile.h | 2 ++ src/tools/pw-cat.c | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/tools/dfffile.c b/src/tools/dfffile.c index c2e4db22e..c77b26adb 100644 --- a/src/tools/dfffile.c +++ b/src/tools/dfffile.c @@ -263,6 +263,11 @@ exit_free: return NULL; } +uint32_t dff_layout_stride(const struct dff_layout *layout) +{ + return layout->channels * SPA_ABS(layout->interleave); +} + static const uint8_t bitrev[256] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, @@ -289,14 +294,10 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l int32_t step = SPA_ABS(layout->interleave); uint32_t channels = f->info.channels; bool rev = layout->lsb != f->info.lsb; - size_t total, offset, scale, pos; + size_t total, offset, pos; offset = f->offset; pos = offset % f->blocksize; - scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); - - samples *= step; - samples *= scale; for (total = 0; total < samples; total++) { uint32_t i; diff --git a/src/tools/dfffile.h b/src/tools/dfffile.h index de8153142..49384fd2c 100644 --- a/src/tools/dfffile.h +++ b/src/tools/dfffile.h @@ -24,6 +24,8 @@ struct dff_layout { bool lsb; }; +uint32_t dff_layout_stride(const struct dff_layout *layout); + struct dff_file * dff_file_open(const char *filename, const char *mode, struct dff_file_info *info); ssize_t dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_layout *layout); diff --git a/src/tools/dsffile.c b/src/tools/dsffile.c index 4122944a4..757962e22 100644 --- a/src/tools/dsffile.c +++ b/src/tools/dsffile.c @@ -175,6 +175,11 @@ exit_free: return NULL; } +uint32_t dsf_layout_stride(const struct dsf_layout *layout) +{ + return layout->channels * SPA_ABS(layout->interleave); +} + static const uint8_t bitrev[256] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, @@ -200,16 +205,12 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l uint8_t *d = data; int step = SPA_ABS(layout->interleave); bool rev = layout->lsb != f->info.lsb; - size_t total, block, offset, pos, scale; + size_t total, block, offset, pos; size_t blocksize = f->info.blocksize * f->info.channels; block = f->offset / f->info.blocksize; offset = block * blocksize; pos = f->offset % f->info.blocksize; - scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); - - samples *= step; - samples *= scale; for (total = 0; total < samples; total++) { uint32_t i; diff --git a/src/tools/dsffile.h b/src/tools/dsffile.h index 303bf6c71..2970ca62b 100644 --- a/src/tools/dsffile.h +++ b/src/tools/dsffile.h @@ -24,6 +24,8 @@ struct dsf_layout { bool lsb; }; +uint32_t dsf_layout_stride(const struct dsf_layout *layout); + struct dsf_file * dsf_file_open(const char *filename, const char *mode, struct dsf_file_info *info); ssize_t dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_layout *layout); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index d179d0a67..a6238b910 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -870,7 +870,7 @@ on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) data->dff.layout.channels = info.info.dsd.channels; data->dff.layout.lsb = info.info.dsd.bitorder == SPA_PARAM_BITORDER_lsb; - data->stride = data->dsf.layout.channels * SPA_ABS(data->dsf.layout.interleave); + data->stride = dsf_layout_stride(&data->dsf.layout); if (data->verbose) { fprintf(stderr, "DSD: channels:%d bitorder:%s interleave:%d stride:%d\n", From 9c42c06af00818038be494fd41632cb2d50a0df5 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 23 Sep 2025 13:20:12 +0300 Subject: [PATCH 0879/1014] alsa: Use the minimum period size as headroom for SOF cards Configure the headroom to be equal of the minimum allowed period size for the configuration. This is desirable when the ALSA driver's hw_ptr is 'jumpy' due to underplaying hardware architecture, like SOF. In case of SOF the DSP firmware will burst read at stream start to fill it's host facing buffer and later settles to a constant pace. The minimal period size is constrained by the driver to cover the initial burst and settling time of the hw_ptr. Guard this mode of working with a new boolean flag, which is only enabled for SOF cards, kept it disabled for other cards to avoid any unforeseen side effects. Even if the use-period-size-min-as-headroom is set to true, the manual headroom configuration will take precedence to allow experimentation. Link: https://github.com/thesofproject/linux/issues/5284 Link: https://github.com/thesofproject/sof/issues/9695#issuecomment-2569033847 Link: https://github.com/thesofproject/sof/issues/10172 Link: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4489 Signed-off-by: Peter Ujfalusi --- spa/plugins/alsa/alsa-pcm.c | 26 ++++++++++++++++++++++---- spa/plugins/alsa/alsa-pcm.h | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 9e13b09e5..138a36cda 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -975,6 +975,13 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) if (info && (str = spa_dict_lookup(info, "device.profile.pro")) != NULL) state->is_pro = spa_atob(str); + if (info && spa_strstartswith(spa_dict_lookup(info, SPA_KEY_API_ALSA_CARD_NAME), "sof-") && + state->stream == SND_PCM_STREAM_PLAYBACK) { + state->use_period_size_min_as_headroom = true; + spa_log_info(state->log, + "ALSA SOF driver detected: default api.alsa.use-period-size-min-as-headroom=true"); + } + state->multi_rate = true; state->htimestamp = false; state->htimestamp_max_errors = MAX_HTIMESTAMP_ERROR; @@ -2034,7 +2041,12 @@ static void recalc_headroom(struct state *state) if (state->position != NULL) rate = state->position->clock.target_rate.denom; - state->headroom = state->default_headroom; + if (state->use_period_size_min_as_headroom) + state->headroom = state->default_headroom ? + state->default_headroom : state->period_size_min; + else + state->headroom = state->default_headroom; + if (!state->disable_tsched || state->resample) { /* When using timers, we might miss the pointer update for batch * devices so add some extra headroom. With IRQ, we know the pointers @@ -2356,6 +2368,12 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ periods = UINT_MAX; } + /* Query the minimum period size for this configuration + * This information is used as headroom if use_period_size_min_as_headroom is + * set and default_headroom is 0 (not forced by user) + */ + CHECK(snd_pcm_hw_params_get_period_size_min(params, &state->period_size_min, &dir), "snd_pcm_hw_params_get_period_size_min"); + if (state->default_period_size == 0) { /* Some devices (FireWire) don't produce audio if period number is too * small, so force a minimum. This will restrict possible period sizes if @@ -2417,14 +2435,14 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ recalc_headroom(state); spa_log_info(state->log, "%s: format:%s access:%s-%s rate:%d channels:%d " - "buffer frames %lu, period frames %lu, periods %u, frame_size %zd " + "buffer frames %lu, period frames %lu (min:%lu), periods %u, frame_size %zd " "headroom %u start-delay:%u batch:%u tsched:%u resample:%u", state->name, snd_pcm_format_name(state->format), state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", state->rate, state->channels, state->buffer_frames, state->period_frames, - periods, state->frame_size, state->headroom, state->start_delay, - state->is_batch, !state->disable_tsched, state->resample); + state->period_size_min, periods, state->frame_size, state->headroom, + state->start_delay, state->is_batch, !state->disable_tsched, state->resample); /* write the parameters to device */ CHECK(snd_pcm_hw_params(hndl, params), "set_hw_params"); diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 60b6ac8b4..bc96a3924 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -203,6 +203,7 @@ struct state { int n_fds; uint32_t threshold; uint32_t last_threshold; + snd_pcm_uframes_t period_size_min; uint32_t headroom; uint32_t start_delay; uint32_t min_delay; @@ -234,6 +235,7 @@ struct state { unsigned int linked:1; unsigned int is_batch:1; unsigned int force_quantum:1; + unsigned int use_period_size_min_as_headroom:1; uint64_t iec958_codecs; From 06214b6087f1c19ab6e4e61732436a634ec5fdb1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 10:50:52 +0200 Subject: [PATCH 0880/1014] pulse-server: map some more errno to errors Mostly EADDRINUSE to ERR_BUSY, which happens when loading TCP when it's already loaded on the address. --- src/modules/module-protocol-pulse/defs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index fa47c3d81..893e18b97 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -109,8 +109,8 @@ static inline int res_to_err(int res) case -ENOTSUP: case -EPROTONOSUPPORT: case -ESOCKTNOSUPPORT: return ERR_NOTSUPPORTED; case -ENOSYS: return ERR_NOTIMPLEMENTED; case -EIO: return ERR_IO; - case -EBUSY: return ERR_BUSY; - case -ENFILE: case -EMFILE: return ERR_INTERNAL; + case -EBUSY: case -EADDRINUSE: case -EAGAIN: return ERR_BUSY; + case -ENFILE: case -EMFILE: -ENOMEM: return ERR_INTERNAL; } return ERR_UNKNOWN; } From ecac86b0caeeae508d6f1a262ef701cf82b65880 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 10:52:40 +0200 Subject: [PATCH 0881/1014] avahi: handle fd allocation errors When we fail to allocate an io source or a timerfd, return NULL instead of crashing later on. See #4913 --- src/modules/module-zeroconf-discover/avahi-poll.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/module-zeroconf-discover/avahi-poll.c b/src/modules/module-zeroconf-discover/avahi-poll.c index ab54888f6..65b64736a 100644 --- a/src/modules/module-zeroconf-discover/avahi-poll.c +++ b/src/modules/module-zeroconf-discover/avahi-poll.c @@ -72,7 +72,10 @@ static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event w->userdata = userdata; w->source = pw_loop_add_io(impl->loop, fd, to_pw_events(event), false, watch_callback, w); - + if (w->source == NULL) { + free(w); + return NULL; + } return w; } @@ -117,7 +120,10 @@ static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, w->callback = callback; w->userdata = userdata; w->source = pw_loop_add_timer(impl->loop, timeout_callback, w); - + if (w->source == NULL) { + free(w); + return NULL; + } if (tv != NULL) { value.tv_sec = tv->tv_sec; value.tv_nsec = tv->tv_usec * 1000UL; From 5a894270e64b36bbe411247b773ba71eeb6fa5fd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 10:55:10 +0200 Subject: [PATCH 0882/1014] pulse-server: add a pipewire-pulse:list-modules message It list all available module names, which you can then describe further. Make a little module_info iterator function for this. --- .../module-protocol-pulse/message-handler.c | 10 ++++++++++ src/modules/module-protocol-pulse/module.c | 20 +++++++++++++------ src/modules/module-protocol-pulse/module.h | 1 + 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c index 47b14ab21..ca1c17cf5 100644 --- a/src/modules/module-protocol-pulse/message-handler.c +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -118,6 +118,16 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ } else if (spa_streq(message, "pipewire-pulse:log-level")) { int res = pw_log_set_level_string(params); fprintf(response, "%d", res); + } else if (spa_streq(message, "pipewire-pulse:list-modules")) { + bool first = true; + const struct module_info *i = NULL; + fputc('[', response); + while ((i = module_info_next(client->impl, i)) != NULL) { + fprintf(response, "%s{\"name\":\"%s\"}", + first ? "" : ",\n", i->name); + first = false; + } + fputc(']', response); } else if (spa_streq(message, "pipewire-pulse:describe-module")) { const struct module_info *i = module_info_find(client->impl, params); diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index fe4b346d9..a0de01d0d 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -293,20 +293,28 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti } } -const struct module_info *module_info_find(struct impl *impl, const char *name) +const struct module_info *module_info_next(struct impl *impl, const struct module_info *info) { extern const struct module_info __start_pw_mod_pulse_modules[]; extern const struct module_info __stop_pw_mod_pulse_modules[]; - const struct module_info *info = __start_pw_mod_pulse_modules; + if (info == NULL) + info = __start_pw_mod_pulse_modules; + else + info++; + if (info == __stop_pw_mod_pulse_modules) + return NULL; + return info; +} - for (; info < __stop_pw_mod_pulse_modules; info++) { +const struct module_info *module_info_find(struct impl *impl, const char *name) +{ + const struct module_info *info = NULL; + + while ((info = module_info_next(impl, info)) != NULL) { if (spa_streq(info->name, name)) return info; } - - spa_assert(info == __stop_pw_mod_pulse_modules); - return NULL; } diff --git a/src/modules/module-protocol-pulse/module.h b/src/modules/module-protocol-pulse/module.h index fdc45e46e..6bae8d9a5 100644 --- a/src/modules/module-protocol-pulse/module.h +++ b/src/modules/module-protocol-pulse/module.h @@ -62,6 +62,7 @@ struct module { #define module_emit_loaded(m,r) spa_hook_list_call(&m->listener_list, struct module_events, loaded, 0, r) #define module_emit_destroy(m) spa_hook_list_call(&(m)->listener_list, struct module_events, destroy, 0) +const struct module_info *module_info_next(struct impl *impl, const struct module_info *info); const struct module_info *module_info_find(struct impl *impl, const char *name); struct module *module_create(struct impl *impl, const char *name, const char *args); From 17cad8e7ef77ca90caebee412946b2dc4b9b44f8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 10:57:04 +0200 Subject: [PATCH 0883/1014] pulse-server: add a help message Shows all available messages on /core because I keep forgetting. --- .../module-protocol-pulse/message-handler.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c index ca1c17cf5..fdbe3ac5d 100644 --- a/src/modules/module-protocol-pulse/message-handler.c +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -93,7 +93,19 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ { pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params); - if (spa_streq(message, "list-handlers")) { + if (spa_streq(message, "help")) { + fprintf(response, + "/core []\n" + "available commands:\n" + " help this help\n" + " list-handlers show available object handlers\n" + " pipewire-pulse:malloc-info show malloc_info\n" + " pipewire-pulse:malloc-trim run malloc_trim\n" + " pipewire-pulse:log-level update log level with \n" + " pipewire-pulse:list-modules list all module names\n" + " pipewire-pulse:describe-module describe module info for " + ); + } else if (spa_streq(message, "list-handlers")) { bool first = true; fputc('[', response); From 9be06c46b75b729347b7dcc01943388dbcb5c8b8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 11:43:52 +0200 Subject: [PATCH 0884/1014] pulse-server: fix case statement --- src/modules/module-protocol-pulse/defs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 893e18b97..378644bda 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -110,7 +110,7 @@ static inline int res_to_err(int res) case -ENOSYS: return ERR_NOTIMPLEMENTED; case -EIO: return ERR_IO; case -EBUSY: case -EADDRINUSE: case -EAGAIN: return ERR_BUSY; - case -ENFILE: case -EMFILE: -ENOMEM: return ERR_INTERNAL; + case -ENFILE: case -EMFILE: case -ENOMEM: return ERR_INTERNAL; } return ERR_UNKNOWN; } From 678e571d80718c767dbcaa1f1582422dd0795270 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 13:02:53 +0200 Subject: [PATCH 0885/1014] timer-queue: delete next timer event when it got fired When we fire the timer event, mark the next timeout as NULL because nothing else is going to timeout anymore until we rearm the timer. This has the effect that if we cancel and add the same timer from the callback that we will reprogram the timer with the new timeout instead of thinking the item as already programmed. --- src/pipewire/timer-queue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pipewire/timer-queue.c b/src/pipewire/timer-queue.c index f40c44bf6..cb7495dc0 100644 --- a/src/pipewire/timer-queue.c +++ b/src/pipewire/timer-queue.c @@ -58,6 +58,7 @@ static void timer_timeout(void *user_data, uint64_t expirations) pw_log_debug("%p: timer was rearmed", queue); return; } + queue->next_timeout = NULL; pw_log_debug("%p: processing timer %p", queue, timer); timer->queue = NULL; From fdc74df38312529551b9cad7cb1b1d31d26a6772 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 26 Sep 2025 13:39:49 +0200 Subject: [PATCH 0886/1014] modules: use timer-queue in avahi-poll Pass the pw_context to get to the shared queue and loop. Patch up the users of avahi-poll. Fixes #4913 --- .../modules/module-zeroconf-publish.c | 4 +- src/modules/module-raop-discover.c | 4 +- src/modules/module-rtp-session.c | 2 +- src/modules/module-snapcast-discover.c | 6 +- src/modules/module-zeroconf-discover.c | 5 +- .../module-zeroconf-discover/avahi-poll.c | 65 ++++++++++++------- .../module-zeroconf-discover/avahi-poll.h | 4 +- 7 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c index 414299467..d4425a3fe 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -637,7 +637,6 @@ static const struct impl_events impl_events = { static int module_zeroconf_publish_load(struct module *module) { struct module_zeroconf_publish_data *data = module->user_data; - struct pw_loop *loop; int error; data->core = pw_context_connect(module->impl->context, NULL, 0); @@ -650,8 +649,7 @@ static int module_zeroconf_publish_load(struct module *module) &data->core_listener, &core_events, data); - loop = pw_context_get_main_loop(module->impl->context); - data->avahi_poll = pw_avahi_poll_new(loop); + data->avahi_poll = pw_avahi_poll_new(module->impl->context); data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, data, &error); diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 1d5a9a82a..53032d0be 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -561,10 +561,8 @@ static int start_client(struct impl *impl) static int start_avahi(struct impl *impl) { - struct pw_loop *loop; - loop = pw_context_get_main_loop(impl->context); - impl->avahi_poll = pw_avahi_poll_new(loop); + impl->avahi_poll = pw_avahi_poll_new(impl->context); return start_client(impl); } diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index 4e9919f93..c77912fc5 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -1835,7 +1835,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((res = setup_apple_session(impl)) < 0) goto out; - impl->avahi_poll = pw_avahi_poll_new(impl->loop); + impl->avahi_poll = pw_avahi_poll_new(impl->context); if ((impl->client = avahi_client_new(impl->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, impl, diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index 17069c9fc..c4f7c6580 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -166,13 +166,13 @@ static const struct spa_dict_item module_props[] = { struct impl { struct pw_context *context; - struct pw_loop *loop; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_properties *properties; bool discover_local; + struct pw_loop *loop; AvahiPoll *avahi_poll; AvahiClient *client; @@ -850,10 +850,8 @@ static int start_client(struct impl *impl) static int start_avahi(struct impl *impl) { - struct pw_loop *loop; - loop = pw_context_get_main_loop(impl->context); - impl->avahi_poll = pw_avahi_poll_new(loop); + impl->avahi_poll = pw_avahi_poll_new(impl->context); return start_client(impl); } diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 57462342d..e165a1174 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -487,10 +487,7 @@ static int start_client(struct impl *impl) static int start_avahi(struct impl *impl) { - struct pw_loop *loop; - - loop = pw_context_get_main_loop(impl->context); - impl->avahi_poll = pw_avahi_poll_new(loop); + impl->avahi_poll = pw_avahi_poll_new(impl->context); return start_client(impl); } diff --git a/src/modules/module-zeroconf-discover/avahi-poll.c b/src/modules/module-zeroconf-discover/avahi-poll.c index 65b64736a..4ead6427a 100644 --- a/src/modules/module-zeroconf-discover/avahi-poll.c +++ b/src/modules/module-zeroconf-discover/avahi-poll.c @@ -8,7 +8,9 @@ struct impl { AvahiPoll api; + struct pw_context *context; struct pw_loop *loop; + struct pw_timer_queue *timer_queue; }; struct AvahiWatch { @@ -22,7 +24,7 @@ struct AvahiWatch { struct AvahiTimeout { struct impl *impl; - struct spa_source *source; + struct pw_timer timer; AvahiTimeoutCallback callback; void *userdata; }; @@ -99,17 +101,40 @@ static void watch_free(AvahiWatch *w) free(w); } -static void timeout_callback(void *data, uint64_t expirations) +static void timeout_callback(void *data) { AvahiTimeout *w = data; w->callback(w, w->userdata); } +static int schedule_timeout(AvahiTimeout *t, const struct timeval *tv) +{ + struct timeval now; + int64_t timeout_ns; + + if (tv == NULL) + return 0; + + /* Get current REALTIME (same clock domain as Avahi) */ + if (gettimeofday(&now, NULL) < 0) + return -errno; + + /* Calculate relative timeout: target - now */ + timeout_ns = ((int64_t)tv->tv_sec - now.tv_sec) * SPA_NSEC_PER_SEC + + ((int64_t)tv->tv_usec - now.tv_usec) * 1000UL; + + /* Ensure minimum timeout */ + if (timeout_ns <= 0) + timeout_ns = 1; + + return pw_timer_queue_add(t->impl->timer_queue, &t->timer, NULL, + timeout_ns, timeout_callback, t); +} + static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) { struct impl *impl = api->userdata; - struct timespec value; AvahiTimeout *w; w = calloc(1, sizeof(*w)); @@ -119,38 +144,27 @@ static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, w->impl = impl; w->callback = callback; w->userdata = userdata; - w->source = pw_loop_add_timer(impl->loop, timeout_callback, w); - if (w->source == NULL) { + + if (schedule_timeout(w, tv) < 0) { free(w); return NULL; } - if (tv != NULL) { - value.tv_sec = tv->tv_sec; - value.tv_nsec = tv->tv_usec * 1000UL; - pw_loop_update_timer(impl->loop, w->source, &value, NULL, true); - } + return w; } static void timeout_update(AvahiTimeout *t, const struct timeval *tv) { - struct impl *impl = t->impl; - struct timespec value, *v = NULL; + /* Cancel the existing timer */ + pw_timer_queue_cancel(&t->timer); - if (tv != NULL) { - value.tv_sec = tv->tv_sec; - value.tv_nsec = tv->tv_usec * 1000UL; - if (value.tv_sec == 0 && value.tv_nsec == 0) - value.tv_nsec = 1; - v = &value; - } - pw_loop_update_timer(impl->loop, t->source, v, NULL, true); + /* Schedule new timeout if provided */ + schedule_timeout(t, tv); } static void timeout_free(AvahiTimeout *t) { - struct impl *impl = t->impl; - pw_loop_destroy_source(impl->loop, t->source); + pw_timer_queue_cancel(&t->timer); free(t); } @@ -164,7 +178,7 @@ static const AvahiPoll avahi_poll_api = { .timeout_free = timeout_free, }; -AvahiPoll* pw_avahi_poll_new(struct pw_loop *loop) +AvahiPoll* pw_avahi_poll_new(struct pw_context *context) { struct impl *impl; @@ -172,7 +186,10 @@ AvahiPoll* pw_avahi_poll_new(struct pw_loop *loop) if (impl == NULL) return NULL; - impl->loop = loop; + impl->context = context; + impl->loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); + impl->api = avahi_poll_api; impl->api.userdata = impl; diff --git a/src/modules/module-zeroconf-discover/avahi-poll.h b/src/modules/module-zeroconf-discover/avahi-poll.h index f651c5fd8..fce9b9d1b 100644 --- a/src/modules/module-zeroconf-discover/avahi-poll.h +++ b/src/modules/module-zeroconf-discover/avahi-poll.h @@ -4,8 +4,8 @@ #include -#include +#include -AvahiPoll* pw_avahi_poll_new(struct pw_loop *loop); +AvahiPoll* pw_avahi_poll_new(struct pw_context *context); void pw_avahi_poll_free(AvahiPoll *p); From 468a9ac9542070cadf26046ca0ea3767c0d04c15 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 29 Sep 2025 09:39:57 +0200 Subject: [PATCH 0887/1014] pulse-server: add the peer ip to client properties When clients connect with IP, add the peer IP address to properties. We might use this later to make a better stream node.name than a copy of the client application name. --- src/modules/module-protocol-pulse/server.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index 13425debc..4e744e33f 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -31,6 +31,7 @@ #include #include +#include "network-utils.h" #include "client.h" #include "commands.h" #include "defs.h" @@ -375,6 +376,7 @@ on_connect(void *data, int fd, uint32_t mask) struct client *client = NULL; const char *client_access = NULL; const char *error_reason = NULL; + char ipname[256]; pid_t pid; length = sizeof(name); @@ -418,10 +420,18 @@ on_connect(void *data, int fd, uint32_t mask) if (client->props == NULL) goto error; + pw_properties_setf(client->props, "pulse.server.type", "%s", server->addr.ss_family == AF_UNIX ? "unix" : "tcp"); + if (server->addr.ss_family != AF_UNIX) { + uint16_t port = 0; + if (pw_net_get_ip(&name, ipname, sizeof(ipname), NULL, &port) >= 0) + pw_properties_setf(client->props, + "pulse.server.peer", "%s:%d", ipname, port); + } + client->routes = pw_properties_new(NULL, NULL); if (client->routes == NULL) goto error; From 3cf182255ff5774d9d16f0d264948aabfe7ad547 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 29 Sep 2025 14:38:19 +0200 Subject: [PATCH 0888/1014] context: handle leaf nodes better Find leaf nodes by looking at the number of max in/out ports and the link group. This should give us nodes that only consume/produce data. If a leaf node is linked to a driver with only passive links, it will never be able to be scheduled unless we also make it runnable when the driver is made runnable from another node. This can happen when you do: pw-record -P '{ node.passive=true }' test.wav and then pw-record test2.wav Without this, the first pw-record would never be scheduled. With the patch it will be scheduled when the second pw-record is started. Fixes #4915 --- src/pipewire/context.c | 18 ++++++++++++++++-- src/pipewire/impl-node.c | 4 ++++ src/pipewire/private.h | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/pipewire/context.c b/src/pipewire/context.c index 7b6d426ab..8956c9c03 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -1101,11 +1101,25 @@ static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, pw_log_debug(" next node %p: '%s' runnable:%u %p %p %p", n, n->name, n->runnable, n->groups, n->link_groups, sync); } - spa_list_for_each(n, collect, sort_link) - if (!n->driving && n->runnable) { + /* All non-driver runnable nodes (ie. reachable with a non-passive link) now make + * all linked nodes up and downstream runnable as well */ + spa_list_for_each(n, collect, sort_link) { + if (!n->driver && n->runnable) { run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); } + } + /* now we might have made a driver runnable, if the node is not runnable at this point + * it means it was linked to the driver with passives links and some other node + * made the driver active. If the node is a leaf it can not be activated in any other + * way and we will also make it, and all its peers, runnable */ + spa_list_for_each(n, collect, sort_link) { + if (!n->driver && n->driver_node->runnable && !n->runnable && n->leaf) { + n->runnable = true; + run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); + run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); + } + } return 0; } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 75e945617..87fbe58e2 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -1309,6 +1309,10 @@ static void check_properties(struct pw_impl_node *node) } } node->lock_rate = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_RATE, false); + /* the leaf node is one that only produces/consumes the data. We can deduce this from the + * absence of a link-group and the fact that it has no output/input ports. */ + node->leaf = node->link_groups == NULL && + (node->info.max_input_ports == 0 || node->info.max_output_ports == 0); value = pw_properties_get_uint32(node->properties, PW_KEY_NODE_FORCE_RATE, SPA_ID_INVALID); if (value == 0) diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 072eac596..ebe0c6d5d 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -784,6 +784,7 @@ struct pw_impl_node { unsigned int async:1; /**< async processing, one cycle latency */ unsigned int lazy:1; /**< the graph is lazy scheduling */ unsigned int exclusive:1; /**< ports can only be linked once */ + unsigned int leaf:1; /**< node only produces/consumes data */ uint32_t transport; /**< latest transport request */ From 3f9ae1ee1045983bd225131fb34de03b5000b692 Mon Sep 17 00:00:00 2001 From: Niklas Carlsson Date: Mon, 29 Sep 2025 12:09:57 +0200 Subject: [PATCH 0889/1014] filter-graph: allow 8 channels in max plugin Mimic the same channel behavior as for other plugins that allows for 8 channels, such as Mixer and Mult. --- spa/plugins/filter-graph/plugin_builtin.c | 61 +++++++++++++++++------ src/modules/module-filter-chain.c | 7 ++- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 955c6442d..647c9a51b 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -2155,24 +2155,33 @@ static const struct spa_fga_descriptor param_eq_desc = { static void max_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; - float *out = impl->port[0], *in1 = impl->port[1], *in2 = impl->port[2]; - unsigned long n; + float *out = impl->port[0]; + float *src[8]; + unsigned long n, p, n_srcs = 0; if (out == NULL) return; - if (in1 != NULL && in2 != NULL) { - for (n = 0; n < SampleCount; n++) - out[n] = SPA_MAX(in1[n], in2[n]); - } else if (in1 != NULL) { - for (n = 0; n < SampleCount; n++) - out[n] = in1[n]; - } else if (in2 != NULL) { - for (n = 0; n < SampleCount; n++) - out[n] = in2[n]; + for (p = 1; p < 9; p++) { + if (impl->port[p] != NULL) + src[n_srcs++] = impl->port[p]; + } + + if (n_srcs == 0) { + spa_memzero(out, SampleCount * sizeof(float)); + } else if (n_srcs == 1) { + spa_memcpy(out, src[0], SampleCount * sizeof(float)); } else { - for (n = 0; n < SampleCount; n++) - out[n] = 0.0f; + for (p = 0; p < n_srcs; p++) { + if (p == 0) { + for (n = 0; n < SampleCount; n++) + out[n] = SPA_MAX(src[p][n], src[p + 1][n]); + p++; + } else { + for (n = 0; n < SampleCount; n++) + out[n] = SPA_MAX(out[n], src[p][n]); + } + } } } @@ -2189,7 +2198,31 @@ static struct spa_fga_port max_ports[] = { { .index = 2, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, - } + }, + { .index = 3, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 8, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, }; static const struct spa_fga_descriptor max_desc = { diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 6c2f16200..84b70d651 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -537,9 +537,12 @@ extern struct spa_handle_factory spa_filter_graph_factory; * * ### Max * - * Use the `max` plugin if you need to select the max value of two channels. + * Use the `max` plugin if you need to select the max value of a number of input ports. * - * It has two input ports "In 1" and "In 2" and one output port "Out". + * It has 8 input ports named "In 1" to "In 8" and one output port "Out". + * + * All input ports samples are checked to find the maximum value per sample. Unused + * input ports will be ignored and not cause overhead. * * ### dcblock * From 22488078354128eece821e2557edd7aeb2a4a68b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 27 Sep 2025 11:55:32 +0300 Subject: [PATCH 0890/1014] bluez5: sco-io: send keepalive TX data if sink is not feeding it When using LC3-24kHz, remote device drops connection after a few seconds if there is no sink playback. Avoid this by sending silence, one TX packet for each RX packet, if sink hasn't been feeding data within a timeout. --- spa/plugins/bluez5/sco-io.c | 87 ++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/spa/plugins/bluez5/sco-io.c b/spa/plugins/bluez5/sco-io.c index 8195cc738..b18a52a61 100644 --- a/spa/plugins/bluez5/sco-io.c +++ b/spa/plugins/bluez5/sco-io.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sco-io"); */ #define MAX_MTU 1024 +#define KEEPALIVE_NSEC (500 * SPA_NSEC_PER_MSEC) + struct spa_bt_sco_io { uint8_t read_buffer[MAX_MTU]; @@ -72,8 +75,57 @@ struct spa_bt_sco_io { int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time); void *source_userdata; + + const struct media_codec *codec; + void *codec_data; + + uint64_t last_tx_time; + uint64_t last_rx_time; + uint16_t keepalive_seqnum; + bool keepalive; }; +static void keepalive_send(struct spa_bt_sco_io *io) +{ + static const uint8_t zeros[2048]; + uint8_t buf[MAX_MTU]; + int res, need_flush = 0; + size_t size; + + if (io->codec->id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { + /* Doesn't have fixed block size; TX same amount as RX instead */ + size = SPA_MIN(sizeof(buf), io->read_size); + memset(buf, 0, size); + goto send; + } + + size = res = io->codec->start_encode(io->codec_data, buf, sizeof(buf), ++io->keepalive_seqnum, + io->last_rx_time / SPA_NSEC_PER_USEC); + if (res < 0) + return; + + do { + size_t encoded; + + res = io->codec->encode(io->codec_data, zeros, sizeof(zeros), + SPA_PTROFF(buf, size, void), sizeof(buf) - size, + &encoded, &need_flush); + if (res < 0) + return; + + size += encoded; + if (size >= sizeof(buf)) + return; + } while (!need_flush); + +send: + if (!io->keepalive) + spa_bt_sco_io_write_start(io); + + io->keepalive = false; + spa_bt_sco_io_write(io, buf, size); + io->keepalive = true; +} static void sco_io_on_ready(struct spa_source *source) { @@ -107,6 +159,9 @@ static void sco_io_on_ready(struct spa_source *source) } io->read_size = res; + io->last_rx_time = rx_time; + if (!io->last_tx_time) + io->last_tx_time = rx_time; if (io->source_cb) { int res; @@ -115,6 +170,13 @@ static void sco_io_on_ready(struct spa_source *source) io->source_cb = NULL; } } + + /* If sink has not supplied packets for some time, for each RX packet send + * same amount of silence to keep the connection alive. Some devices (with + * LC3-24kHZ) require this and it doesn't hurt for others. + */ + if (io->last_tx_time + KEEPALIVE_NSEC < io->last_rx_time || io->keepalive) + keepalive_send(io); } read_done: @@ -157,11 +219,19 @@ int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t siz size_t packet_size; int res; + io->last_tx_time = io->last_rx_time; + if (io->read_size == 0) { /* The proper write packet size is not known yet */ return 0; } + if (io->keepalive) { + /* Transition from keepalive to sink-fed data */ + io->write_size = 0; + io->keepalive = false; + } + packet_size = SPA_MIN(SPA_MIN(io->write_mtu, io->read_size), sizeof(io->write_buffer)); if (io->write_size >= packet_size) { @@ -213,7 +283,8 @@ void spa_bt_sco_io_write_start(struct spa_bt_sco_io *io) struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_system *data_system, struct spa_log *log) { - struct spa_bt_sco_io *io; + spa_autofree struct spa_bt_sco_io *io = NULL; + struct spa_audio_info format = { 0 }; spa_log_topic_init(log, &log_topic); @@ -245,6 +316,15 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s } } + io->codec = transport->media_codec; + + if (io->codec->validate_config(io->codec, 0, NULL, 0, &format) < 0) + return NULL; + + io->codec_data = io->codec->init(io->codec, 0, NULL, 0, &format, NULL, transport->write_mtu); + if (!io->codec_data) + return NULL; + spa_log_debug(io->log, "%p: initial packet size:%d", io, (int)io->read_size); spa_bt_recvmsg_init(&io->recv, io->fd, io->data_system, io->log); @@ -257,7 +337,7 @@ struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, s io->source.rmask = 0; spa_loop_add_source(io->data_loop, &io->source); - return io; + return spa_steal_ptr(io); } static int do_remove_source(struct spa_loop *loop, @@ -279,6 +359,7 @@ void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) { spa_log_debug(io->log, "%p: destroy", io); spa_loop_locked(io->data_loop, do_remove_source, 0, NULL, 0, io); + io->codec->deinit(io->codec_data); free(io); } @@ -290,4 +371,6 @@ void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void { io->source_cb = source_cb; io->source_userdata = userdata; + io->last_rx_time = 0; + io->last_tx_time = 0; } From 74e6e6c29d196573ae65e6ce7c72aa891a0b57c5 Mon Sep 17 00:00:00 2001 From: filmsi Date: Sun, 28 Sep 2025 10:59:58 +0000 Subject: [PATCH 0891/1014] Update Slovenian translation --- po/sl.po | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/po/sl.po b/po/sl.po index bb0c0c53e..bb0015c60 100644 --- a/po/sl.po +++ b/po/sl.po @@ -9,16 +9,16 @@ msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2025-08-20 03:34+0000\n" -"PO-Revision-Date: 2025-08-20 16:01+0200\n" +"POT-Creation-Date: 2025-09-11 03:34+0000\n" +"PO-Revision-Date: 2025-09-11 11:47+0200\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n" -"%100<=4 ? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && " +"n%100<=4 ? 2 : 3);\n" "X-Generator: Poedit 2.2.1\n" #: src/daemon/pipewire.c:29 @@ -76,7 +76,7 @@ msgstr "%s na %s@%s" msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:1049 +#: src/tools/pw-cat.c:1084 #, c-format msgid "" "%s [options] [|-]\n" @@ -92,7 +92,7 @@ msgstr "" "\n" "\n" -#: src/tools/pw-cat.c:1056 +#: src/tools/pw-cat.c:1091 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -129,7 +129,7 @@ msgstr "" " -P --properties Nastavi lastnosti vozlišča\n" "\n" -#: src/tools/pw-cat.c:1074 +#: src/tools/pw-cat.c:1109 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -149,6 +149,7 @@ msgid "" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" +" -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Mera vzorčenja (zaht. za rec) " @@ -166,11 +167,12 @@ msgstr "" " -q --quality Kakovost prevzorčenja (0 - 15) " "(privzeto %d)\n" " -a, --raw neobdelan način (RAW)\n" -" -M, --force-midi Vsili zapis midi, eden izmed \"midi" -"\" ali \"ump\" (privzeto ump)\n" +" -M, --force-midi Vsili zapis midi, eden izmed " +"\"midi\" ali \"ump\" (privzeto ump)\n" +" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n" "\n" -#: src/tools/pw-cat.c:1093 +#: src/tools/pw-cat.c:1129 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -178,6 +180,7 @@ msgid "" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" +" -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Način predvajanja\n" @@ -186,6 +189,7 @@ msgstr "" " -d, --dsd Način DSD\n" " -o, --encoded Kodiran način\n" " -s, --sysex Način SysEx\n" +" -c, --midi-clip Način posnetka MIDI\n" "\n" #: src/tools/pw-cli.c:2386 @@ -212,12 +216,12 @@ msgstr "" msgid "Pro Audio" msgstr "Profesionalni zvok" -#: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635 #: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Izklopljeno" -#: spa/plugins/alsa/acp/acp.c:603 +#: spa/plugins/alsa/acp/acp.c:610 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [napaka ALSA UCM]" @@ -588,13 +592,13 @@ msgstr[3] "" #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" From 532140ca9078d1d4c1cff2ca2f68bfe89678ef7c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 4 Apr 2025 16:05:51 +0200 Subject: [PATCH 0892/1014] bluez5: Don't assume channels fit in uint8_t There is no reason to believe the number of channels can fit in a uint8_t. Limit the number of channels in some places where it can not be avoided. --- spa/plugins/bluez5/a2dp-codec-opus.c | 6 +++--- spa/plugins/bluez5/bap-codec-lc3.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index efcc10023..21ede7d34 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -497,7 +497,7 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, const uint8_t **surround_mapping, uint32_t *positions) { - const uint8_t channels = conf->channels; + const uint32_t channels = conf->channels; const uint32_t location = OPUS_05_GET_LOCATION(*conf); const uint8_t coupled_streams = conf->coupled_streams; const uint8_t *permutation = NULL; @@ -561,7 +561,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, a2dp_opus_05_t a2dp_opus_05 = { .info = codec->vendor, .main = { - .channels = SPA_AUDIO_MAX_CHANNELS, + .channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | @@ -571,7 +571,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, OPUS_05_INIT_BITRATE(0) }, .bidi = { - .channels = SPA_AUDIO_MAX_CHANNELS, + .channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 144b8a0fd..818f4ee36 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -903,7 +903,7 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f static uint8_t channels_to_positions(uint32_t channels, uint32_t *position) { - uint8_t n_channels = get_channel_count(channels); + uint32_t n_channels = get_channel_count(channels); uint8_t n_positions = 0; spa_assert(n_channels <= SPA_AUDIO_MAX_CHANNELS); From c91f75ae2e68e601e5f90790704776cd1b3f755c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 1 Oct 2025 09:21:36 +0200 Subject: [PATCH 0893/1014] audio: bump max channels to 128 --- spa/include/spa/param/audio/raw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 804e5df1d..2fd7fcfe3 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,7 +18,7 @@ extern "C" { * \{ */ -#define SPA_AUDIO_MAX_CHANNELS 64u +#define SPA_AUDIO_MAX_CHANNELS 128u enum spa_audio_format { SPA_AUDIO_FORMAT_UNKNOWN, From 0915ed8be02f9d4747e8a715e0f3f78f9ed9c8ae Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 1 Oct 2025 11:03:53 +0200 Subject: [PATCH 0894/1014] adapter: enhance converter flags with follower flags We don't want to override the converter flags with the follower flags, just enhance them with specific follower flags. Otherwise we lose the DYNAMIC_DATA and other port flags from the converter. See #4918 --- spa/plugins/audioconvert/audioadapter.c | 2 +- spa/plugins/videoconvert/videoadapter.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 04a33ccfb..0891462a2 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1262,7 +1262,7 @@ static void convert_port_info(void *data, port_id--; } else if (info) { pi = *info; - pi.flags = this->follower_port_flags & + pi.flags |= this->follower_port_flags & (SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL); diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 271a255e8..23b55d768 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -1285,7 +1285,7 @@ static void convert_port_info(void *data, port_id--; } else if (info) { pi = *info; - pi.flags = this->follower_port_flags & + pi.flags |= this->follower_port_flags & (SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL); From 91ae1c13b7150f9d88fdf0d7d1e41d2f4819b804 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 1 Oct 2025 11:11:36 +0200 Subject: [PATCH 0895/1014] impl-port: only use DYNAMIC_DATA when allowed Allocate buffers with the DYNAMIC_DATA flag set only when the port actually allows it. See #4918 --- src/pipewire/impl-port.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 221f907cd..69725c4c3 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1955,8 +1955,9 @@ static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_NEGOTIATE)) { int alloc_flags; - /* try dynamic data */ - alloc_flags = PW_BUFFERS_FLAG_DYNAMIC; + alloc_flags = 0; + if (SPA_FLAG_IS_SET(port->spa_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) + alloc_flags |= PW_BUFFERS_FLAG_DYNAMIC; if (SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_ASYNC)) alloc_flags |= PW_BUFFERS_FLAG_ASYNC; From 4625f7ee3a7c5e6ec1276df8fb8c16788c4612f2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 1 Oct 2025 11:13:42 +0200 Subject: [PATCH 0896/1014] mixer-dsp: only use passthrough when DYNAMIC_DATA We can only change the data pointers on the output buffer when the data had the DYNAMIC_DATA flag. --- spa/plugins/audiomixer/mixer-dsp.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index c3c17f541..83af86ea5 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -727,6 +727,7 @@ static int impl_node_process(void *object) struct buffer *outb; const void **datas; uint32_t cycle = this->position->clock.cycle & 1; + struct spa_data *d; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -793,11 +794,12 @@ static int impl_node_process(void *object) return -EPIPE; } - if (n_buffers == 1) { + d = outb->buf.datas; + + if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) { + spa_log_trace_fp(this->log, "%p: %d passthrough", this, n_buffers); *outb->buffer = *buffers[0]->buffer; } else { - struct spa_data *d = outb->buf.datas; - *outb->buffer = outb->buf; maxsize = SPA_MIN(maxsize, d[0].maxsize); From 8277bf6b36b24d45111a1f2ae96bb923f0ad298e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 2 Oct 2025 01:25:56 +0300 Subject: [PATCH 0897/1014] bluez5: improve error messages when connection drops Log something less confusing when connection to remote device drops unexpectedly. Silence logging transport Release() error in cases where the transport was simultaneously deleted. --- spa/plugins/bluez5/bluez5-dbus.c | 5 +++++ spa/plugins/bluez5/media-sink.c | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 1beddebd4..07b122c63 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -4143,6 +4143,11 @@ release: */ spa_log_debug(monitor->log, "Failed to release idle transport %s: %s", transport->path, err.message); + } else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) || + spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) { + /* Transport disappeared */ + spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s", + transport->path, err.message); } else { spa_log_error(monitor->log, "Failed to release transport %s: %s", transport->path, err.message); diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index f5708d080..271cc419c 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -1300,7 +1300,8 @@ static void media_on_flush_error(struct spa_source *source) spa_log_trace(this->log, "%p: flush event", this); if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { - spa_log_warn(this->log, "%p: error %d", this, source->rmask); + spa_log_warn(this->log, "%p: connection (%s) terminated unexpectedly", + this, this->transport ? this->transport->path : ""); if (this->flush_source.loop) { spa_bt_latency_flush(&this->tx_latency, this->flush_source.fd, this->log); spa_loop_remove_source(this->data_loop, &this->flush_source); From 6755f24a3d662bc75733b9d0158d8163dc3ff730 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 2 Oct 2025 01:33:23 +0300 Subject: [PATCH 0898/1014] bluez5: reduce log level for unhandled RFCOMM commands Don't log warnings about not passing on RFCOMM commands to modem if there is no modem configured, as this is normal occurrence. --- spa/plugins/bluez5/backend-native.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 044ad5bd6..0dbe8c9ae 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1394,7 +1394,7 @@ next_indicator: // This command is sent when we activate Apple extensions rfcomm_send_reply(rfcomm, "OK"); } else if (!mm_is_available(backend->modemmanager)) { - spa_log_warn(backend->log, "RFCOMM receive command but modem not available: %s", buf); + spa_log_info(backend->log, "RFCOMM receive command but modem not available: %s", buf); rfcomm_send_error(rfcomm, CMEE_NO_CONNECTION_TO_PHONE); return true; From 0de80603e156906d08566d2ab6a8171e869c74d5 Mon Sep 17 00:00:00 2001 From: zay-yar-lwin Date: Thu, 2 Oct 2025 11:38:51 +0630 Subject: [PATCH 0899/1014] update Burmese Translation --- po/my.po | 99 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/po/my.po b/po/my.po index 68a9be7ea..a6c2212a0 100644 --- a/po/my.po +++ b/po/my.po @@ -9,15 +9,15 @@ msgstr "" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2021-08-26 03:31+0000\n" -"PO-Revision-Date: 2021-08-26 21:52+0630\n" +"PO-Revision-Date: 2025-10-02 11:36+0630\n" +"Last-Translator: zayar lwin \n" "Language-Team: lw1nzayar@yandex.com\n" +"Language: my\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 2.4.2\n" -"Last-Translator: zayar lwin \n" -"Language: my\n" +"X-Generator: Poedit 3.7\n" #: src/daemon/pipewire.c:45 #, c-format @@ -27,23 +27,24 @@ msgid "" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" -"%s [options]\n" -" -h, --help Show this help\n" -" --version Show version\n" -" -c, --config Load config (Default %s)\n" +"%s [options]\r\n" +" -h, --help Show this help\r\n" +" --version Show version\r\n" +" -c, --config Load config (Default %s)\r\n" +"\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစစ်စတမ်" +msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစနစ်" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစစ်စတမ် စတင်ရန်" +msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစနစ် စတင်ရန်" #: src/examples/media-session/alsa-monitor.c:588 #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" -msgstr "နဂိုတည်းကထည့်သွင်းထားသည်ံ့ အသံကရိယာ" +msgstr "မူလပါရှိပြီးအသံကိရိယာ" #: src/examples/media-session/alsa-monitor.c:592 #: spa/plugins/alsa/acp/compat.c:194 @@ -53,7 +54,7 @@ msgstr "ဆ.သ.ရ-စက်" #: src/examples/media-session/alsa-monitor.c:601 #: src/modules/module-zeroconf-discover.c:296 msgid "Unknown device" -msgstr "မသိသောစက်" +msgstr "မသိ" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:173 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:173 @@ -69,12 +70,12 @@ msgstr "%s@%sအတွက် လုံခြုံစွာဒေတာပို #: src/modules/module-zeroconf-discover.c:308 #, c-format msgid "%s on %s@%s" -msgstr "" +msgstr "%s on %s@%s" #: src/modules/module-zeroconf-discover.c:312 #, c-format msgid "%s on %s" -msgstr "" +msgstr "%s on %s" #: src/tools/pw-cat.c:1016 #, c-format @@ -85,6 +86,11 @@ msgid "" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" +"%s [options] \r\n" +" -h, --help Show this help\r\n" +" --version Show version\r\n" +" -v, --verbose Enable verbose operations\r\n" +"\r\n" #: src/tools/pw-cat.c:1023 #, c-format @@ -103,6 +109,20 @@ msgid "" " --list-targets List available targets for --target\n" "\n" msgstr "" +" -R, --remote Remote daemon name\r\n" +" --media-type Set media type (default %s)\r\n" +" --media-category Set media category (default %s)\r\n" +" --media-role Set media role (default %s)\r\n" +" --target Set node target (default %s)\r\n" +" 0 means don't link\r\n" +" --latency Set node latency (default %s)\r\n" +" Xunit (unit = s, ms, us, ns)\r\n" +" or direct samples (256)\r\n" +" the rate is the one of the source " +"file\r\n" +" --list-targets List available targets for --" +"target\r\n" +"\r\n" #: src/tools/pw-cat.c:1041 #, c-format @@ -123,6 +143,22 @@ msgid "" "%d)\n" "\n" msgstr "" +" --rate Sample rate (req. for rec) (default " +"%u)\r\n" +" --channels Number of channels (req. for rec) " +"(default %u)\r\n" +" --channel-map Channel map\r\n" +" one of: \"stereo\", " +"\"surround-51\",... or\r\n" +" comma separated list of channel " +"names: eg. \"FL,FR\"\r\n" +" --format Sample format %s (req. for rec) " +"(default %s)\r\n" +" --volume Stream volume 0-1.0 (default %.3f)" +"\r\n" +" -q --quality Resampler quality (0 - 15) (default " +"%d)\r\n" +"\r\n" #: src/tools/pw-cat.c:1058 msgid "" @@ -131,6 +167,10 @@ msgid "" " -m, --midi Midi mode\n" "\n" msgstr "" +" -p, --playback Playback mode\r\n" +" -r, --record Recording mode\r\n" +" -m, --midi Midi mode\r\n" +"\r\n" #: src/tools/pw-cli.c:2954 #, c-format @@ -142,6 +182,12 @@ msgid "" " -r, --remote Remote daemon name\n" "\n" msgstr "" +"%s [options] [command]\r\n" +" -h, --help Show this help\r\n" +" --version Show version\r\n" +" -d, --daemon Start as daemon (Default false)\r\n" +" -r, --remote Remote daemon name\r\n" +"\r\n" #: spa/plugins/alsa/acp/acp.c:306 msgid "Pro Audio" @@ -473,12 +519,12 @@ msgstr "မိုနို စကားပြောဆိုသံ + ၇.၁ ပ #: spa/plugins/alsa/acp/alsa-mixer.c:4750 #, c-format msgid "%s Output" -msgstr "%s ထုတ်ပို့ကရိယာ" +msgstr "%s အသံထုတ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4757 #, c-format msgid "%s Input" -msgstr "%s လက်ခံကရိယာ" +msgstr "%s အသံသွင်း" #: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format @@ -493,20 +539,26 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" +"snd_pcm_avail() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %lu ဘိုက် (%lu ms)။\n" +"ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " +"ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" +"snd_pcm_delay() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %li ဘိုက် (%s%lu ms)။\n" +"ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " +"ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format @@ -516,6 +568,10 @@ msgid "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" +"snd_pcm_avail_delay() မှ ထူးဆန်းသောတန်ဖိုးများ ပြန်ပေးခဲ့သည်- နှောင့်နှေးမှု %lu သည် ရနိုင်မှု %lu " +"ထက် နည်းနေပါသည်။\n" +"ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဘာ '%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " +"ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format @@ -530,6 +586,9 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" +"snd_pcm_mmap_begin() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %lu ဘိုက် (%lu ms)။\n" +"ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " +"ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" From 52041e888c2c19ceff1b34a0fbd132bc0c752521 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 25 Sep 2025 20:06:30 +0200 Subject: [PATCH 0900/1014] meson.build: Make libswscale a requirement only if videoconvert is enabled --- meson.build | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 58a00daf8..83e964a07 100644 --- a/meson.build +++ b/meson.build @@ -339,14 +339,23 @@ endif pw_cat_ffmpeg = get_option('pw-cat-ffmpeg') ffmpeg = get_option('ffmpeg') if pw_cat_ffmpeg.allowed() or ffmpeg.allowed() + # libswscale is only used by videoconvert. FFmpeg might however be used for + # compressed audio (both for decoding said compressed audio and for parsing + # it in pw-cat). If users only care about audio, then libswscale would still + # become a requirement if its required flag is defined only by FFmpeg options. + # Make the videoconvert option a factor in swscale_dep as well to avoid this. + videoconvert = get_option('videoconvert') avcodec_dep = dependency('libavcodec', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) avformat_dep = dependency('libavformat', required: pw_cat_ffmpeg.enabled()) avfilter_dep = dependency('libavfilter', required: ffmpeg.enabled()) avutil_dep = dependency('libavutil', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) - swscale_dep = dependency('libswscale', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) + swscale_dep = dependency('libswscale', required: (pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) and videoconvert.enabled()) else avcodec_dep = dependency('', required: false) + avformat_dep = dependency('', required: false) avfilter_dep = dependency('', required: false) + avutil_dep = dependency('', required: false) + swscale_dep = dependency('', required: false) endif cdata.set('HAVE_PW_CAT_FFMPEG_INTEGRATION', pw_cat_ffmpeg.allowed()) From 83ee3021e9e234e4b463159a118ffec00cb1f5dc Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 2 Oct 2025 12:12:53 +0200 Subject: [PATCH 0901/1014] spa: Remove channel field from spa_audio_info_mpegh structure A fixed channel count makes no sense with an entity based 3D audio format like MPEG-H, because MPEG-H decoders do not simply decode; they "spatialize" the entities, meaning that said entities are decoded and rendered accordingto the needs of the target playback system and its channel count. --- spa/include/spa/param/audio/mpegh-utils.h | 6 +----- spa/include/spa/param/audio/mpegh.h | 8 +++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/spa/include/spa/param/audio/mpegh-utils.h b/spa/include/spa/param/audio/mpegh-utils.h index 778dfa946..67e18cf4c 100644 --- a/spa/include/spa/param/audio/mpegh-utils.h +++ b/spa/include/spa/param/audio/mpegh-utils.h @@ -33,8 +33,7 @@ spa_format_audio_mpegh_parse(const struct spa_pod *format, struct spa_audio_info int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); + SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate)); return res; } @@ -52,9 +51,6 @@ spa_format_audio_mpegh_build(struct spa_pod_builder *builder, uint32_t id, if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); - if (info->channels != 0) - spa_pod_builder_add(builder, - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } diff --git a/spa/include/spa/param/audio/mpegh.h b/spa/include/spa/param/audio/mpegh.h index 5c4c8afa1..1c9549338 100644 --- a/spa/include/spa/param/audio/mpegh.h +++ b/spa/include/spa/param/audio/mpegh.h @@ -27,10 +27,16 @@ extern "C" { * * MPEG-H is documented in the ISO/IEC 23008-3 specification. * MHAS is specified in ISO/IEC 23008-3, Clause 14. + * + * Note that unlike other formats, this one does not specify a channel + * count. This is because MPEG-H is entity-based; it contains multiple + * entities of different types (channel beds, audio objects etc.) which + * do not map 1:1 to channels. The channel amount is determined by + * decoders instead, based on the audio scene content and the target + * playback system. */ struct spa_audio_info_mpegh { uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_MPEGH_INIT(...) ((struct spa_audio_info_mpegh) { __VA_ARGS__ }) From 1fdde582c017252d8529ad6af2683c513c080779 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 2 Oct 2025 16:13:07 +0200 Subject: [PATCH 0902/1014] buffers: add some more comments to the flags --- src/pipewire/buffers.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pipewire/buffers.h b/src/pipewire/buffers.h index 9c1ae2b78..6796b2b39 100644 --- a/src/pipewire/buffers.h +++ b/src/pipewire/buffers.h @@ -25,9 +25,13 @@ extern "C" { #define PW_BUFFERS_FLAG_NONE 0 #define PW_BUFFERS_FLAG_NO_MEM (1<<0) /**< don't allocate buffer memory */ -#define PW_BUFFERS_FLAG_SHARED (1<<1) /**< buffers can be shared */ +#define PW_BUFFERS_FLAG_SHARED (1<<1) /**< buffers can be shared, This makes sure that + * the data types on the buffers don't contain + * any MemPtr, only fd based memory. */ #define PW_BUFFERS_FLAG_DYNAMIC (1<<2) /**< buffers have dynamic data */ -#define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory */ +#define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory. This will + * allocate the metadata, chunks and possibly + * data in shared mem. */ #define PW_BUFFERS_FLAG_IN_PRIORITY (1<<4) /**< input parameters have priority */ #define PW_BUFFERS_FLAG_ASYNC (1<<5) /**< one of the nodes is async */ From 4161fa3071767c2c888fc54fc999c6bfa987bf27 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 2 Oct 2025 16:13:32 +0200 Subject: [PATCH 0903/1014] impl-port: also honour CAN_ALLOC flag between mixer/node When we have a mixer node and we need to negotiate buffers between the mixer and the node, take the CAN_ALLOC flag into account. This is for input ports, which can have a mixer. If you make a filter with a CAN_ALLOC input port, it will now not already contain buffer data. See #4918 --- src/pipewire/impl-port.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 69725c4c3..a17d2490f 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1960,6 +1960,10 @@ static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, alloc_flags |= PW_BUFFERS_FLAG_DYNAMIC; if (SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_ASYNC)) alloc_flags |= PW_BUFFERS_FLAG_ASYNC; + if (SPA_FLAG_IS_SET(port->spa_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS)) { + alloc_flags |= PW_BUFFERS_FLAG_NO_MEM; + flags |= SPA_NODE_BUFFERS_FLAG_ALLOC; + } pw_log_debug("%p: %d.%d negotiate %d buffers on node: %p flags:%08x", port, port->direction, port->port_id, n_buffers, node->node, From 0b0226322fe6d7407ddd3bd2348cf0eb8faddd68 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 2 Oct 2025 16:25:11 +0200 Subject: [PATCH 0904/1014] examples: add DSP sink example Add example of ALLOC_BUFFERS on the input port. See #4918 --- src/examples/audio-dsp-sink.c | 225 ++++++++++++++++++++++++++++++++++ src/examples/meson.build | 1 + 2 files changed, 226 insertions(+) create mode 100644 src/examples/audio-dsp-sink.c diff --git a/src/examples/audio-dsp-sink.c b/src/examples/audio-dsp-sink.c new file mode 100644 index 000000000..3f18adb66 --- /dev/null +++ b/src/examples/audio-dsp-sink.c @@ -0,0 +1,225 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Audio sink using \ref pw_filter "pw_filter" + [title] + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +/* define to make this filter allocate buffer memory */ +#define ALLOC_BUFFERS + +struct data; + +struct port { + struct data *data; +}; + +struct data { + struct pw_main_loop *loop; + struct pw_filter *filter; + struct port *in_port; + bool move; + uint32_t quantum_limit; +}; + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * out = pw_filter_dequeue_buffer(filter, in_port); + * + * .. consume data in the buffer ... + * + * pw_filter_queue_buffer(filter, in_port, out); + * + * For DSP ports, there is a shortcut to directly dequeue, get + * the data and requeue the buffer with pw_filter_get_dsp_buffer(). + */ +static void on_process(void *userdata, struct spa_io_position *position) +{ + struct data *data = userdata; + float *in, max; + struct port *in_port = data->in_port; + uint32_t i, n_samples = position->clock.duration, peak; + + pw_log_trace("do process %d", n_samples); + + in = pw_filter_get_dsp_buffer(in_port, n_samples); + if (in == NULL) + return; + + /* move cursor up */ + if (data->move) + fprintf(stdout, "%c[%dA", 0x1b, 2); + fprintf(stdout, "captured %d samples\n", n_samples); + max = 0.0f; + for (i = 0; i < n_samples; i++) + max = fmaxf(max, fabsf(in[i])); + + peak = (uint32_t)SPA_CLAMPF(max * 30, 0.f, 39.f); + + fprintf(stdout, "input: |%*s%*s| peak:%f\n", peak+1, "*", 40 - peak, "", max); + data->move = true; + fflush(stdout); +} + +#ifdef ALLOC_BUFFERS +/* close the memfd we set on the buffers here */ +static void on_remove_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) +{ + struct spa_buffer *buf = buffer->buffer; + struct spa_data *d; + + d = buf->datas; + pw_log_info("remove buffer %p", buffer); + + munmap(d[0].data, d[0].maxsize); + close(d[0].fd); +} + +/* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need + * to provide buffer memory. */ +static void on_add_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) +{ + struct data *data = _data; + struct spa_buffer *buf = buffer->buffer; + struct spa_data *d; + + pw_log_info("add buffer %p", buffer); + d = buf->datas; + + if ((d[0].type & (1<quantum_limit * sizeof(float); + + /* truncate to the right size */ + if (ftruncate(d[0].fd, d[0].maxsize) < 0) { + pw_log_error("can't truncate to %d: %m", d[0].maxsize); + return; + } + /* now mmap so we can read it in the process function above */ + d[0].data = mmap(NULL, d[0].maxsize, PROT_READ | PROT_WRITE, + MAP_SHARED, d[0].fd, d[0].mapoffset); + if (d[0].data == MAP_FAILED) { + pw_log_error("can't mmap memory: %m"); + return; + } +} +#endif + +static const struct pw_filter_events filter_events = { + PW_VERSION_FILTER_EVENTS, + .process = on_process, +#ifdef ALLOC_BUFFERS + .add_buffer = on_add_buffer, + .remove_buffer = on_remove_buffer, +#endif +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + uint32_t flags; + + pw_init(&argc, &argv); + + data.quantum_limit= 8192; + + /* make a main loop. If you already have another main loop, you can add + * the fd of this pipewire mainloop to it. */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* Create a simple filter, the simple filter manages the core and remote + * objects for you if you don't need to deal with them. + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the filter state. The most important event + * you need to listen to is the process event where you need to process + * the data. + */ + data.filter = pw_filter_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-dsp-sink", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Sink", + PW_KEY_MEDIA_ROLE, "DSP", + PW_KEY_MEDIA_CLASS, "Stream/Input/Audio", + PW_KEY_NODE_AUTOCONNECT, "true", + NULL), + &filter_events, + &data); + + flags = PW_FILTER_PORT_FLAG_MAP_BUFFERS; +#ifdef ALLOC_BUFFERS + flags |= PW_FILTER_PORT_FLAG_ALLOC_BUFFERS; +#endif + + /* make an audio DSP output port */ + data.in_port = pw_filter_add_port(data.filter, + PW_DIRECTION_INPUT, + flags, + sizeof(struct port), + pw_properties_new( + PW_KEY_FORMAT_DSP, "32 bit float mono audio", + PW_KEY_PORT_NAME, "input", + NULL), + NULL, 0); + + /* Now connect this filter. We ask that our process function is + * called in a realtime thread. */ + if (pw_filter_connect(data.filter, + PW_FILTER_FLAG_RT_PROCESS, + NULL, 0) < 0) { + fprintf(stderr, "can't connect\n"); + return -1; + } + + /* and wait while we let things run */ + pw_main_loop_run(data.loop); + + pw_filter_destroy(data.filter); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return 0; +} diff --git a/src/examples/meson.build b/src/examples/meson.build index 7d45a34f9..8a00371d2 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -5,6 +5,7 @@ examples = [ 'audio-src-ring2', 'audio-dsp-src', 'audio-dsp-filter', + 'audio-dsp-sink', 'audio-capture', 'video-play', 'video-src', From fe78e80614cae06fbab9da8856c028e6955ec9af Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 2 Oct 2025 16:39:18 +0200 Subject: [PATCH 0905/1014] tools: fix compilation after removal of field --- src/tools/pw-cat.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index a6238b910..6e9326642 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -489,7 +489,6 @@ static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *c case AV_CODEC_ID_MPEGH_3D_AUDIO: info->media_subtype = SPA_MEDIA_SUBTYPE_mpegh; info->info.mpegh.rate = data->rate; - info->info.mpegh.channels = data->channels; break; default: fprintf(stderr, "Unsupported encoded media subtype\n"); From df2f36ad8f40e09b514424a79f5bffd570be80a2 Mon Sep 17 00:00:00 2001 From: Gabriel Golfetti Date: Sat, 4 Oct 2025 10:04:50 +0000 Subject: [PATCH 0906/1014] Add support for mappable buffers in mixer-dsp --- spa/plugins/audioconvert/audioconvert.c | 31 ++++++--- spa/plugins/audiomixer/mixer-dsp.c | 92 ++++++++++++++++++++----- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 1f315bf5a..70affb04f 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -3176,6 +3176,7 @@ impl_node_port_use_buffers(void *object, struct impl *this = object; struct port *port; uint32_t i, j, maxsize; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -3186,12 +3187,16 @@ impl_node_port_use_buffers(void *object, spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", this, n_buffers, direction, port_id); - clear_buffers(this, port); + if (n_buffers > 0 && !port->have_format) { + res = -EIO; + goto error; + } + if (n_buffers > MAX_BUFFERS) { + res = -ENOSPC; + goto error; + } - if (n_buffers > 0 && !port->have_format) - return -EIO; - if (n_buffers > MAX_BUFFERS) - return -ENOSPC; + clear_buffers(this, port); maxsize = this->quantum_limit * sizeof(float); @@ -3200,6 +3205,11 @@ impl_node_port_use_buffers(void *object, uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; + if (n_datas > MAX_DATAS) { + res = -ENOSPC; + goto error; + } + b = &port->buffers[i]; b->id = i; b->flags = 0; @@ -3224,7 +3234,8 @@ impl_node_port_use_buffers(void *object, if (data == MAP_FAILED) { spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", this, j, i, d[j].type, data); - return -EINVAL; + res = -EINVAL; + goto error; } SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", @@ -3233,7 +3244,8 @@ impl_node_port_use_buffers(void *object, if (data == NULL) { spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", this, j, i, d[j].type, data); - return -EINVAL; + res = -EINVAL; + goto error; } else if (!SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); @@ -3245,11 +3257,14 @@ impl_node_port_use_buffers(void *object, } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, i); + port->n_buffers++; } port->maxsize = maxsize; - port->n_buffers = n_buffers; return 0; +error: + clear_buffers(this, port); + return res; } struct io_data { diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index 83af86ea5..5bd4e1a1e 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.mixer-dsp"); #define MAX_BUFFERS 64 +#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS 512 #define MAX_ALIGN MIX_OPS_MAX_ALIGN @@ -47,12 +49,15 @@ static void port_props_reset(struct port_props *props) struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1 << 0) +#define BUFFER_FLAG_MAPPED (1 << 1) uint32_t flags; struct spa_list link; struct spa_buffer *buffer; struct spa_meta_header *h; struct spa_buffer buf; + + void *datas[MAX_DATAS]; }; struct port { @@ -450,11 +455,25 @@ next: static int clear_buffers(struct impl *this, struct port *port) { - if (port->n_buffers > 0) { - spa_log_debug(this->log, "%p: clear buffers %p", this, port); - port->n_buffers = 0; - spa_list_init(&port->queue); + uint32_t i, j; + + spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b = &port->buffers[i]; + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + for (j = 0; j < b->buffer->n_datas; j++) { + if (b->datas[j]) { + spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", + this, i, j, b->datas[j]); + munmap(b->datas[j], b->buffer->datas[j].maxsize); + b->datas[j] = NULL; + } + } + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); + } } + port->n_buffers = 0; + spa_list_init(&port->queue); return 0; } @@ -581,7 +600,8 @@ impl_node_port_use_buffers(void *object, { struct impl *this = object; struct port *port; - uint32_t i; + uint32_t i, j; + int res; spa_return_val_if_fail(this != NULL, -EINVAL); @@ -594,17 +614,27 @@ impl_node_port_use_buffers(void *object, spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); - clear_buffers(this, port); + if (n_buffers > 0 && !port->have_format) { + res = -EIO; + goto error; + } + if (n_buffers > MAX_BUFFERS) { + res = -ENOSPC; + goto error; + } - if (n_buffers > 0 && !port->have_format) - return -EIO; - if (n_buffers > MAX_BUFFERS) - return -ENOSPC; + clear_buffers(this, port); for (i = 0; i < n_buffers; i++) { struct buffer *b; + uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; + if (n_datas > MAX_DATAS) { + res = -ENOSPC; + goto error; + } + b = &port->buffers[i]; b->buffer = buffers[i]; b->flags = 0; @@ -612,23 +642,51 @@ impl_node_port_use_buffers(void *object, b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->buf = *buffers[i]; - if (d[0].data == NULL) { - spa_log_error(this->log, "%p: invalid memory on buffer %d", this, i); - return -EINVAL; - } - if (!SPA_IS_ALIGNED(d[0].data, this->max_align)) { - spa_log_warn(this->log, "%p: memory on buffer %d not aligned", this, i); + for (j = 0; j < n_datas; j++) { + void *data = d[j].data; + if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { + int prot = 0; + if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_READABLE)) + prot |= PROT_READ; + if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_WRITABLE)) + prot |= PROT_WRITE; + data = mmap(NULL, d[j].maxsize, + prot, MAP_SHARED, d[j].fd, d[j].mapoffset); + if (data == MAP_FAILED) { + spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", + this, j, i, d[j].type, data); + res = -EINVAL; + goto error; + } + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", + this, j, i, d[j].type, data, b); + } + if (data == NULL) { + spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", + this, j, i, d[j].type, data); + res = -EINVAL; + goto error; + } else if (!SPA_IS_ALIGNED(data, this->max_align)) { + spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", + this, j, i); + } + + d[j].data = b->datas[j] = data; } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, b); + port->n_buffers++; spa_log_debug(this->log, "%p: port %d:%d buffer:%d n_data:%d data:%p maxsize:%d", this, direction, port_id, i, buffers[i]->n_datas, d[0].data, d[0].maxsize); } - port->n_buffers = n_buffers; return 0; +error: + clear_buffers(this, port); + return res; } struct io_info { From aede3d049ce9f5c105a03f303b79d5c011e798ad Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 3 Oct 2025 18:45:58 +0200 Subject: [PATCH 0907/1014] buffer: update comment, we have flags now --- spa/include/spa/buffer/alloc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/buffer/alloc.h b/spa/include/spa/buffer/alloc.h index ca2c317ab..fdda40bfe 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -122,7 +122,7 @@ SPA_API_BUFFER_ALLOC int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info * | | uint32_t offset | * | | uint32_t size | * | | int32_t stride | - * | | int32_t dummy | + * | | int32_t flags | * | | ... chunks | * | +------------------------------+ * +>| data | memory for n_datas data, aligned From 1a54b9e66ffe57a2f21296ab27cb6394fa17053d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 10:38:08 +0200 Subject: [PATCH 0908/1014] impl-port: handle errors from the mixer use_buffers Also handle errors when we fail to set mixer buffers on the output port of the mixer. --- src/pipewire/impl-port.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index a17d2490f..aff08e4be 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -1946,7 +1946,7 @@ int pw_impl_port_set_param(struct pw_impl_port *port, uint32_t id, uint32_t flag static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { - int res; + int res, res2; struct pw_impl_node *node = port->node; if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_MIX_ONLY)) @@ -2000,9 +2000,16 @@ static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags, flags, buffers, n_buffers); if (SPA_RESULT_IS_OK(res)) { - spa_node_port_use_buffers(port->mix, + res2 = spa_node_port_use_buffers(port->mix, pw_direction_reverse(port->direction), 0, 0, buffers, n_buffers); + if (res2 < 0) { + if (res2 != -ENOTSUP && n_buffers > 0) { + pw_log_warn("%p: mix use buffers failed: %d (%s)", + port, res2, spa_strerror(res2)); + return res2; + } + } } if (n_buffers > 0) { spa_node_port_set_io(node->node, From af3ad7bf9f861d6b3ca0446d90e47e526bc6f807 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 10:47:21 +0200 Subject: [PATCH 0909/1014] buffers: improve allocation There are really 2 options for the buffer allocation: 1. allocate the buffers skeleton and meta/chunk/data in malloc memory. This is when the PW_BUFFERS_FLAG_SHARED is unset. 2. allocate buffers skeleton in alloc memory and the meta/chunk/data in shared memory when the PW_BUFFERS_FLAG_SHARED is set. Optionally the data can be left unallocated in both cases when the PW_BUFFERS_FLAG_NO_MEM is set. In this case we also need to pass the SPA_BUFFER_ALLOC_FLAG_NO_DATA flag to allocator or else it will set the data pointers to 0 sized memory in the skeleton. If we use SHARED and we allocated memory, we can also set the MemFd and mapoffset into our shared mem. We can do this even if the data_type is MemPtr. We can decide on the datatype to use earlier, based on the negotiated flags. In the MemFd case, make sure the buffer data is page aligned in that case to make things easier. Also force everything in SHARED mem when the data is in SHARED mem. We also don't need to PW_BUFFERS_FLAG_SHARED_MEM because we work with the negotiated flags now to decide if SHARED mem is needed or not. With this change, a node port could provide a MemFd data_type mask in the Buffers param and this would negotiate shared mem with the mixer. Previously, it would only ever allocate malloc memory. See #4918 --- src/pipewire/buffers.c | 83 +++++++++++++++++++++++++++------------- src/pipewire/buffers.h | 3 -- src/pipewire/impl-link.c | 6 +-- 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index 71b4e11f0..db1a01551 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -40,37 +40,37 @@ static int alloc_buffers(struct pw_mempool *pool, { struct spa_buffer **buffers; void *skel, *data; - uint32_t i; + uint32_t i, j; struct spa_data *datas; struct pw_memblock *m; struct spa_buffer_alloc_info info = { 0, }; - if (!SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) - SPA_FLAG_SET(info.flags, SPA_BUFFER_ALLOC_FLAG_INLINE_ALL); - datas = alloca(sizeof(struct spa_data) * n_datas); for (i = 0; i < n_datas; i++) { struct spa_data *d = &datas[i]; - spa_zero(*d); - if (data_sizes[i] > 0) { - /* we allocate memory */ - d->type = SPA_DATA_MemPtr; - d->maxsize = data_sizes[i]; - SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_READWRITE); - } else { - /* client allocates memory. Set the mask of possible - * types in the type field */ - d->type = data_types[i]; - d->maxsize = 0; - } + d->type = data_types[i]; + d->maxsize = data_sizes[i]; if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_DYNAMIC)) SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_DYNAMIC); + /* if we alloc, we know it will be READWRITE */ + if (!SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_NO_MEM)) + SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_READWRITE); } + /* propagate NO_MEM flag to NO_DATA for the buffer alloc. This ensures, + * it does not try to set the data pointer in spa_data. */ + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_NO_MEM)) + SPA_FLAG_SET(info.flags, SPA_BUFFER_ALLOC_FLAG_NO_DATA); + + /* if we don't share buffers, we can inline all meta/chunk/data with the + * skeleton */ + if (!SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) + SPA_FLAG_SET(info.flags, SPA_BUFFER_ALLOC_FLAG_INLINE_ALL); spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns); + /* allocate the skeleton, depending on SHARED flag, meta/chunk/data is included */ buffers = calloc(1, info.max_align + n_buffers * (sizeof(struct spa_buffer *) + info.skel_size)); if (buffers == NULL) return -errno; @@ -79,7 +79,7 @@ static int alloc_buffers(struct pw_mempool *pool, skel = SPA_PTR_ALIGN(skel, info.max_align, void); if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) { - /* pointer to buffer structures */ + /* For shared data we use MemFd for meta/chunk/data */ m = pw_mempool_alloc(pool, PW_MEMBLOCK_FLAG_READWRITE | PW_MEMBLOCK_FLAG_SEAL | @@ -90,7 +90,6 @@ static int alloc_buffers(struct pw_mempool *pool, free(buffers); return -errno; } - data = m->map->ptr; } else { m = NULL; @@ -99,8 +98,23 @@ static int alloc_buffers(struct pw_mempool *pool, pw_log_debug("%p: layout buffers skel:%p data:%p n_buffers:%d buffers:%p", allocation, skel, data, n_buffers, buffers); + spa_buffer_alloc_layout_array(&info, n_buffers, buffers, skel, data); + if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED) && + !SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_NO_MEM)) { + /* set the fd and mappoffset into our shared memory */ + for (i = 0; i < n_buffers; i++) { + struct spa_buffer *buf = buffers[i]; + for (j = 0; j < buf->n_datas; j++) { + struct spa_data *d = &buf->datas[j]; + d->fd = m->fd; + d->mapoffset = SPA_PTRDIFF(d->data, data); + SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_MAPPABLE); + } + } + } + allocation->mem = m; allocation->n_buffers = n_buffers; allocation->buffers = buffers; @@ -317,15 +331,32 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, max_buffers = SPA_MAX(min_buffers, max_buffers); - if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED_MEM)) { - if (types != SPA_ID_INVALID) - SPA_FLAG_CLEAR(types, 1<sc_pagesize); + } + else + return -ENOTSUP; + } data_sizes = alloca(sizeof(uint32_t) * blocks); data_strides = alloca(sizeof(int32_t) * blocks); diff --git a/src/pipewire/buffers.h b/src/pipewire/buffers.h index 6796b2b39..ff39b8be4 100644 --- a/src/pipewire/buffers.h +++ b/src/pipewire/buffers.h @@ -29,9 +29,6 @@ extern "C" { * the data types on the buffers don't contain * any MemPtr, only fd based memory. */ #define PW_BUFFERS_FLAG_DYNAMIC (1<<2) /**< buffers have dynamic data */ -#define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory. This will - * allocate the metadata, chunks and possibly - * data in shared mem. */ #define PW_BUFFERS_FLAG_IN_PRIORITY (1<<4) /**< input parameters have priority */ #define PW_BUFFERS_FLAG_ASYNC (1<<5) /**< one of the nodes is async */ diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index f810f597b..cdbfc2faa 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -698,13 +698,11 @@ static int do_allocation(struct pw_impl_link *this) uint32_t in_port, out_port; flags = 0; - /* always shared buffers for the link */ - alloc_flags = PW_BUFFERS_FLAG_SHARED; /* always enable async mode */ - alloc_flags |= PW_BUFFERS_FLAG_ASYNC; + alloc_flags = PW_BUFFERS_FLAG_ASYNC; if (output->node->remote || input->node->remote) - alloc_flags |= PW_BUFFERS_FLAG_SHARED_MEM; + alloc_flags |= PW_BUFFERS_FLAG_SHARED; if (output->node->driver) alloc_flags |= PW_BUFFERS_FLAG_IN_PRIORITY; From b66d49702f548edad33162fbe2ee8837434af6e5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 11:17:08 +0200 Subject: [PATCH 0910/1014] examples: avoid mmap in the example We don't need to do this ourselves, the MAP_BUFFERS port flag already makes sure this is done for use. We used to have to do this here to ensure the mixer could find the data pointer and not error out. Now that the mixer can MMAP, this can go. See #4918 --- src/examples/audio-dsp-sink.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/examples/audio-dsp-sink.c b/src/examples/audio-dsp-sink.c index 3f18adb66..caaac3b66 100644 --- a/src/examples/audio-dsp-sink.c +++ b/src/examples/audio-dsp-sink.c @@ -87,7 +87,6 @@ static void on_remove_buffer(void *_data, void *_port_data, struct pw_buffer *bu d = buf->datas; pw_log_info("remove buffer %p", buffer); - munmap(d[0].data, d[0].maxsize); close(d[0].fd); } @@ -127,13 +126,6 @@ static void on_add_buffer(void *_data, void *_port_data, struct pw_buffer *buffe pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } - /* now mmap so we can read it in the process function above */ - d[0].data = mmap(NULL, d[0].maxsize, PROT_READ | PROT_WRITE, - MAP_SHARED, d[0].fd, d[0].mapoffset); - if (d[0].data == MAP_FAILED) { - pw_log_error("can't mmap memory: %m"); - return; - } } #endif From 984b2d296e246d69451e593a6cf8d21c30078292 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 11:48:40 +0200 Subject: [PATCH 0911/1014] context: only make active nodes runnable Only try to make active nodes runnable. This can happen when the node is destroyed. Avoids a -EIO error when destroying nodes from pavucontrol. --- src/pipewire/context.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/context.c b/src/pipewire/context.c index 8956c9c03..f1d5e01b4 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -1114,7 +1114,7 @@ static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, * made the driver active. If the node is a leaf it can not be activated in any other * way and we will also make it, and all its peers, runnable */ spa_list_for_each(n, collect, sort_link) { - if (!n->driver && n->driver_node->runnable && !n->runnable && n->leaf) { + if (!n->driver && n->driver_node->runnable && !n->runnable && n->leaf && n->active) { n->runnable = true; run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); From e7bc261830d9624ac35fd4306d7a9e867a38c151 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 13:16:32 +0200 Subject: [PATCH 0912/1014] filter: don't lock the Buffers param It should be possible to override the Buffers params in the filter. --- src/pipewire/filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index b78b827ed..d51338b32 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1743,7 +1743,7 @@ static void add_audio_dsp_port_params(struct filter *impl, struct port *port) SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32))); spa_pod_builder_init(&b, buffer, sizeof(buffer)); - add_param(impl, port, SPA_PARAM_Buffers, PARAM_FLAG_LOCKED, + add_param(impl, port, SPA_PARAM_Buffers, 0, spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), From 5ccaf29793100c00ed16d54dabc5651cfbf782cc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 13:17:00 +0200 Subject: [PATCH 0913/1014] stream: only mmap buffers when not already mapped Don't just blindly mmap the buffer but only when the data pointer is NULL. If it was mapped already by the peer or the adapter or the buffer allocation, we don't want to mmap it again and override the buffer data pointer. Also mmap with the permissions on the data. There is not much point in limiting the permissions for an input port (to read only). We could do this but then we would not be allowed to modify the existing data pointer. The problem is that when the stream mmaps the data as READ only and set the data pointer, if it is then handed to the mixer, it would assume it is mapped with the permissions and then segfault when it tries to write to the memory. It's just better to only mmap when the data is NULL. --- src/pipewire/filter.c | 11 +++++++---- src/pipewire/stream.c | 12 +++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index d51338b32..ea3e74cd2 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -885,8 +885,7 @@ static int impl_port_use_buffers(void *object, struct port *port; struct pw_filter *filter = &impl->this; uint32_t i, j, impl_flags; - int prot, res; - int size = 0; + int res, size = 0; pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl, direction, port_id, n_buffers, impl->disconnecting); @@ -900,7 +899,6 @@ static int impl_port_use_buffers(void *object, clear_buffers(port); impl_flags = port->flags; - prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0); if (n_buffers > MAX_BUFFERS) return -ENOSPC; @@ -915,7 +913,12 @@ static int impl_port_use_buffers(void *object, if (SPA_FLAG_IS_SET(impl_flags, PW_FILTER_PORT_FLAG_MAP_BUFFERS)) { for (j = 0; j < buffers[i]->n_datas; j++) { struct spa_data *d = &buffers[i]->datas[j]; - if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_MAPPABLE)) { + if (d->data == NULL && SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_MAPPABLE)) { + int prot = 0; + if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_READABLE)) + prot |= PROT_READ; + if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_WRITABLE)) + prot |= PROT_WRITE; if ((res = map_data(impl, d, prot)) < 0) return res; SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index c4b785017..46c0822a6 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -979,8 +979,7 @@ static int impl_port_use_buffers(void *object, struct stream *impl = object; struct pw_stream *stream = &impl->this; uint32_t i, j, impl_flags = impl->flags; - int prot, res; - int size = 0; + int res, size = 0; pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl, direction, port_id, n_buffers, impl->disconnecting); @@ -988,8 +987,6 @@ static int impl_port_use_buffers(void *object, if (impl->disconnecting && n_buffers > 0) return -EIO; - prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0); - clear_buffers(stream); if (n_buffers > MAX_BUFFERS) @@ -1005,7 +1002,12 @@ static int impl_port_use_buffers(void *object, if (SPA_FLAG_IS_SET(impl_flags, PW_STREAM_FLAG_MAP_BUFFERS)) { for (j = 0; j < buffers[i]->n_datas; j++) { struct spa_data *d = &buffers[i]->datas[j]; - if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_MAPPABLE)) { + if (d->data == NULL && SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_MAPPABLE)) { + int prot = 0; + if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_READABLE)) + prot |= PROT_READ; + if (SPA_FLAG_IS_SET(d->flags, SPA_DATA_FLAG_WRITABLE)) + prot |= PROT_WRITE; if ((res = map_data(impl, d, prot)) < 0) return res; SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); From e9aef9196fa7b3a4fdab73f6bc494177a67b50b5 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 13:37:59 +0200 Subject: [PATCH 0914/1014] examples: add example MemFd sink Add an example of a filter sink that requires MemFd memory on the input port. Show that it gets automatically mapped and that it contains MemFd memory. Fixes #4918 --- src/examples/audio-dsp-sink2.c | 186 +++++++++++++++++++++++++++++++++ src/examples/meson.build | 1 + 2 files changed, 187 insertions(+) create mode 100644 src/examples/audio-dsp-sink2.c diff --git a/src/examples/audio-dsp-sink2.c b/src/examples/audio-dsp-sink2.c new file mode 100644 index 000000000..7ced1a553 --- /dev/null +++ b/src/examples/audio-dsp-sink2.c @@ -0,0 +1,186 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Audio sink using \ref pw_filter "pw_filter" + [title] + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +struct data; + +struct port { + struct data *data; +}; + +struct data { + struct pw_main_loop *loop; + struct pw_filter *filter; + struct port *in_port; + bool move; + uint32_t quantum_limit; +}; + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * out = pw_filter_dequeue_buffer(filter, in_port); + * + * .. consume data in the buffer ... + * + * pw_filter_queue_buffer(filter, in_port, out); + * + * For DSP ports, there is a shortcut to directly dequeue, get + * the data and requeue the buffer with pw_filter_get_dsp_buffer(). + */ +static void on_process(void *userdata, struct spa_io_position *position) +{ + struct data *data = userdata; + float *in, max; + struct port *in_port = data->in_port; + uint32_t i, n_samples = position->clock.duration, peak; + + pw_log_trace("do process %d", n_samples); + + in = pw_filter_get_dsp_buffer(in_port, n_samples); + if (in == NULL) + return; + + /* move cursor up */ + if (data->move) + fprintf(stdout, "%c[%dA", 0x1b, 2); + fprintf(stdout, "captured %d samples\n", n_samples); + max = 0.0f; + for (i = 0; i < n_samples; i++) + max = fmaxf(max, fabsf(in[i])); + + peak = (uint32_t)SPA_CLAMPF(max * 30, 0.f, 39.f); + + fprintf(stdout, "input: |%*s%*s| peak:%f\n", peak+1, "*", 40 - peak, "", max); + data->move = true; + fflush(stdout); +} + +/* Check the buffer memory */ +static void on_add_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) +{ + struct spa_buffer *buf = buffer->buffer; + struct spa_data *d; + + pw_log_info("add buffer %p", buffer); + d = buf->datas; + + if ((d[0].type != SPA_DATA_MemFd)) { + pw_log_error("unsupported data type %08x", d[0].type); + return; + } +} + +static const struct pw_filter_events filter_events = { + PW_VERSION_FILTER_EVENTS, + .process = on_process, + .add_buffer = on_add_buffer, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + uint32_t flags, n_params = 0; + const struct spa_pod *params[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b; + + pw_init(&argc, &argv); + + data.quantum_limit = 8192; + + /* make a main loop. If you already have another main loop, you can add + * the fd of this pipewire mainloop to it. */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* Create a simple filter, the simple filter manages the core and remote + * objects for you if you don't need to deal with them. + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the filter state. The most important event + * you need to listen to is the process event where you need to process + * the data. + */ + data.filter = pw_filter_new_simple( + pw_main_loop_get_loop(data.loop), + "audio-dsp-sink2", + pw_properties_new( + PW_KEY_MEDIA_TYPE, "Audio", + PW_KEY_MEDIA_CATEGORY, "Sink", + PW_KEY_MEDIA_ROLE, "DSP", + PW_KEY_MEDIA_CLASS, "Stream/Input/Audio", + PW_KEY_NODE_AUTOCONNECT, "true", + NULL), + &filter_events, + &data); + + flags = PW_FILTER_PORT_FLAG_MAP_BUFFERS; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 16), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(sizeof(float) * data.quantum_limit), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(sizeof(float)), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1< Date: Sun, 5 Oct 2025 16:00:54 +0200 Subject: [PATCH 0915/1014] pw-cat: do not use deprecated `FF_PROFILE_*` These macros were deprecated in 2023[0], and are absent in the latest ffmpeg 8.0[1]. So use the new names. [0]: https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/8238bc0b5e3dba271217b1223a901b3f9713dc6e [1]: https://code.ffmpeg.org/FFmpeg/FFmpeg/commit/822432769868da325ba03774df1084aa78b9a5a0 --- src/tools/pw-cat.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 6e9326642..cc1a3bda2 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -40,6 +40,15 @@ #include #include #include + +#ifndef AV_PROFILE_DTS_HD_MA +#define AV_PROFILE_DTS_HD_MA FF_PROFILE_DTS_HD_MA +#endif + +#ifndef AV_PROFILE_DTS_HD_HRA +#define AV_PROFILE_DTS_HD_HRA FF_PROFILE_DTS_HD_HRA +#endif + #endif #include "midifile.h" @@ -475,10 +484,10 @@ static int av_codec_params_to_audio_info(struct data *data, AVCodecParameters *c info->info.dts.rate = data->rate; info->info.dts.channels = data->channels; switch (codec_params->profile) { - case FF_PROFILE_DTS_HD_MA: + case AV_PROFILE_DTS_HD_MA: info->info.dts.ext_type = SPA_AUDIO_DTS_EXT_HD_MA; break; - case FF_PROFILE_DTS_HD_HRA: + case AV_PROFILE_DTS_HD_HRA: info->info.dts.ext_type = SPA_AUDIO_DTS_EXT_HD_HRA; break; default: From e4480cf804dcae1a095a93e191563f267fae8544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Sun, 5 Oct 2025 16:21:20 +0200 Subject: [PATCH 0916/1014] ci: enable ffmpeg in `build_on_debian` The dependencies are already installed, but since the ffmpeg related meson options are set to `disabled`, they were not used. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f52e5fa1..222a0ce87 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -343,6 +343,8 @@ build_on_debian: -D systemd-system-service=disabled -D onnxruntime=disabled -D vulkan=enabled + -D ffmpeg=enabled + -D pw-cat-ffmpeg=enabled .build_on_fedora: extends: From 7d781e696f63c14758fbea8d3639869e7878cf05 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 6 Oct 2025 14:34:15 +0200 Subject: [PATCH 0917/1014] profiler: avoid null dereference Avoid shadowing some variables from the parent block. The node of a target can be NULL when the target is running in another instance. We already do some checks for this but make sure we never deref the NULL pointer. Fixes #4922 --- src/modules/module-profiler.c | 66 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index d6ed9914c..be1da5724 100644 --- a/src/modules/module-profiler.c +++ b/src/modules/module-profiler.c @@ -266,54 +266,58 @@ static void context_do_profile(void *data) SPA_POD_Int(a->xrun_count)); spa_list_for_each(t, &node->rt.target_list, link) { - struct pw_impl_node *n = t->node; - struct pw_node_activation *na; + struct pw_impl_node *tn = t->node; + struct pw_node_activation *ta = t->activation; struct spa_fraction latency; - struct pw_node_activation *a = n->rt.target.activation; - struct spa_io_position *pos = &a->position; + bool async; + int64_t prev_signal_time; if (t->id == id) continue; - if (n != NULL) { - latency = n->latency; - if (n->force_quantum != 0) - latency.num = n->force_quantum; - if (n->force_rate != 0) - update_denom(&latency, n->force_rate); - else if (n->rate.denom != 0) - update_denom(&latency, n->rate.denom); + if (tn != NULL) { + latency = tn->latency; + if (tn->force_quantum != 0) + latency.num = tn->force_quantum; + if (tn->force_rate != 0) + update_denom(&latency, tn->force_rate); + else if (tn->rate.denom != 0) + update_denom(&latency, tn->rate.denom); + async = tn->async; + prev_signal_time = tn->rt.target.activation->prev_signal_time; } else { spa_zero(latency); + async = false; + prev_signal_time = ta->prev_signal_time; } - na = t->activation; spa_pod_builder_prop(&b, SPA_PROFILER_followerBlock, 0); spa_pod_builder_add_struct(&b, SPA_POD_Int(t->id), SPA_POD_String(t->name), - SPA_POD_Long(a->prev_signal_time), - SPA_POD_Long(n->async ? na->prev_signal_time : na->signal_time), - SPA_POD_Long(n->async ? na->prev_awake_time : na->awake_time), - SPA_POD_Long(n->async ? na->prev_finish_time : na->finish_time), - SPA_POD_Int(na->status), + SPA_POD_Long(prev_signal_time), + SPA_POD_Long(async ? ta->prev_signal_time : ta->signal_time), + SPA_POD_Long(async ? ta->prev_awake_time : ta->awake_time), + SPA_POD_Long(async ? ta->prev_finish_time : ta->finish_time), + SPA_POD_Int(ta->status), SPA_POD_Fraction(&latency), - SPA_POD_Int(na->xrun_count), - SPA_POD_Bool(n->async)); + SPA_POD_Int(ta->xrun_count), + SPA_POD_Bool(async)); - if (n->driver) { + if (tn && tn->driver) { + struct spa_io_position *tpos = &tn->rt.target.activation->position; spa_pod_builder_prop(&b, SPA_PROFILER_followerClock, 0); spa_pod_builder_add_struct(&b, - SPA_POD_Int(pos->clock.id), - SPA_POD_String(pos->clock.name), - SPA_POD_Long(pos->clock.nsec), - SPA_POD_Fraction(&pos->clock.rate), - SPA_POD_Long(pos->clock.position), - SPA_POD_Long(pos->clock.duration), - SPA_POD_Long(pos->clock.delay), - SPA_POD_Double(pos->clock.rate_diff), - SPA_POD_Long(pos->clock.next_nsec), - SPA_POD_Long(pos->clock.xrun)); + SPA_POD_Int(tpos->clock.id), + SPA_POD_String(tpos->clock.name), + SPA_POD_Long(tpos->clock.nsec), + SPA_POD_Fraction(&tpos->clock.rate), + SPA_POD_Long(tpos->clock.position), + SPA_POD_Long(tpos->clock.duration), + SPA_POD_Long(tpos->clock.delay), + SPA_POD_Double(tpos->clock.rate_diff), + SPA_POD_Long(tpos->clock.next_nsec), + SPA_POD_Long(tpos->clock.xrun)); } } spa_pod_builder_pop(&b, &f[0]); From 4fb0a0aeea1d759114f6c668f2a87b926f009f51 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 8 Oct 2025 12:53:30 +0200 Subject: [PATCH 0918/1014] example: add sync timeline example Add an example producer and consumer using the SyncTimeline metadata. The syncobj are just eventfd for the purpose of the example. Also demonstrate the RELEASE feature when negotiated. Add some switches to tweak the SyncTimeline and features support. See #4885 --- src/examples/meson.build | 3 + src/examples/video-play-sync.c | 530 +++++++++++++++++++++++++++++++++ src/examples/video-src-sync.c | 437 +++++++++++++++++++++++++++ 3 files changed, 970 insertions(+) create mode 100644 src/examples/video-play-sync.c create mode 100644 src/examples/video-src-sync.c diff --git a/src/examples/meson.build b/src/examples/meson.build index 27743d4f7..ad40f0069 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -10,10 +10,12 @@ examples = [ 'audio-capture', 'video-play', 'video-src', + 'video-src-sync', 'video-dsp-play', 'video-dsp-src', 'video-play-pull', 'video-play-reneg', + 'video-play-sync', 'video-src-alloc', 'video-src-reneg', 'video-src-fixate', @@ -39,6 +41,7 @@ examples_extra_deps = { 'video-play-reneg': [sdl_dep], 'video-play-fixate': [sdl_dep, drm_dep], 'video-play-pull': [sdl_dep], + 'video-play-sync': [sdl_dep], 'video-dsp-play': [sdl_dep], 'local-v4l2': [sdl_dep], 'export-sink': [sdl_dep], diff --git a/src/examples/video-play-sync.c b/src/examples/video-play-sync.c new file mode 100644 index 000000000..4b16e494f --- /dev/null +++ b/src/examples/video-play-sync.c @@ -0,0 +1,530 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Video input stream using \ref pw_stream "pw_stream" and sync timeline. + [title] + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define WIDTH 1920 +#define HEIGHT 1080 +#define RATE 30 + +#define MAX_BUFFERS 64 + +#include "sdl.h" + +struct pixel { + float r, g, b, a; +}; + +struct data { + const char *path; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + struct pw_main_loop *loop; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + struct spa_io_position *position; + + struct spa_video_info format; + int32_t stride; + struct spa_rectangle size; + + int counter; + SDL_Rect rect; + bool is_yuv; + + bool with_synctimeline; + bool with_synctimeline_release; +}; + +static void handle_events(struct data *data) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + pw_main_loop_quit(data->loop); + break; + } + } +} + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * b = pw_stream_dequeue_buffer(stream); + * + * .. do stuff with buffer ... + * + * pw_stream_queue_buffer(stream, b); + */ +static void +on_process(void *_data) +{ + struct data *data = _data; + struct pw_stream *stream = data->stream; + struct pw_buffer *b; + struct spa_buffer *buf; + void *sdata, *ddata; + int sstride, dstride, ostride; + struct spa_meta_header *h; + struct spa_meta_sync_timeline *stl = NULL; + uint32_t i, j; + uint8_t *src, *dst; + uint64_t cmd; + + b = NULL; + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(stream)) == NULL) + break; + if (b) + pw_stream_queue_buffer(stream, b); + b = t; + } + if (b == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + + pw_log_trace("new buffer %p", buf); + + handle_events(data); + + if ((sdata = buf->datas[0].data) == NULL) + goto done; + + if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { + uint64_t now = pw_stream_get_nsec(stream); + pw_log_debug("now:%"PRIu64" pts:%"PRIu64" diff:%"PRIi64, + now, h->pts, now - h->pts); + } + if ((stl = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*stl))) && + stl->acquire_point) { + /* wait before we can use the buffer */ + if (read(buf->datas[1].fd, &cmd, sizeof(cmd)) < 0) + pw_log_warn("acquire_point wait error %m"); + pw_log_debug("acquire_point:%"PRIu64, stl->acquire_point); + } + + /* copy video image in texture */ + if (data->is_yuv) { + void *datas[4]; + sstride = data->stride; + if (buf->n_datas == 1) { + SDL_UpdateTexture(data->texture, NULL, + sdata, sstride); + } else { + datas[0] = sdata; + datas[1] = buf->datas[1].data; + datas[2] = buf->datas[2].data; + SDL_UpdateYUVTexture(data->texture, NULL, + datas[0], sstride, + datas[1], sstride / 2, + datas[2], sstride / 2); + } + } + else { + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + } + + sstride = buf->datas[0].chunk->stride; + if (sstride == 0) + sstride = buf->datas[0].chunk->size / data->size.height; + ostride = SPA_MIN(sstride, dstride); + + src = sdata; + dst = ddata; + + if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { + for (i = 0; i < data->size.height; i++) { + struct pixel *p = (struct pixel *) src; + for (j = 0; j < data->size.width; j++) { + dst[j * 4 + 0] = SPA_CLAMP((uint8_t)(p[j].r * 255.0f), 0u, 255u); + dst[j * 4 + 1] = SPA_CLAMP((uint8_t)(p[j].g * 255.0f), 0u, 255u); + dst[j * 4 + 2] = SPA_CLAMP((uint8_t)(p[j].b * 255.0f), 0u, 255u); + dst[j * 4 + 3] = SPA_CLAMP((uint8_t)(p[j].a * 255.0f), 0u, 255u); + } + src += sstride; + dst += dstride; + } + } else { + for (i = 0; i < data->size.height; i++) { + memcpy(dst, src, ostride); + src += sstride; + dst += dstride; + } + } + SDL_UnlockTexture(data->texture); + } + + SDL_RenderClear(data->renderer); + /* now render the video */ + SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); + SDL_RenderPresent(data->renderer); + + done: + pw_stream_queue_buffer(stream, b); + + if (stl != NULL && stl->release_point) { + /* we promise to signal the release point */ + if (data->with_synctimeline_release) + SPA_FLAG_CLEAR(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE); + cmd = 1; + /* signal buffer release point */ + write(buf->datas[2].fd, &cmd, sizeof(cmd)); + pw_log_debug("release:%"PRIu64, stl->release_point); + } +} + +static void on_stream_state_changed(void *_data, enum pw_stream_state old, + enum pw_stream_state state, const char *error) +{ + struct data *data = _data; + fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + pw_main_loop_quit(data->loop); + break; + case PW_STREAM_STATE_PAUSED: + /* because we started inactive, activate ourselves now */ + pw_stream_set_active(data->stream, true); + break; + default: + break; + } +} + +static void +on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size) +{ + struct data *data = _data; + + switch (id) { + case SPA_IO_Position: + data->position = area; + break; + } +} + +/* Be notified when the stream param changes. We're only looking at the + * format changes. + * + * We are now supposed to call pw_stream_finish_format() with success or + * failure, depending on if we can support the format. Because we gave + * a list of supported formats, this should be ok. + * + * As part of pw_stream_finish_format() we can provide parameters that + * will control the buffer memory allocation. This includes the metadata + * that we would like on our buffer, the size, alignment, etc. + */ +static void +on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) +{ + struct data *data = _data; + struct pw_stream *stream = data->stream; + uint8_t params_buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); + struct spa_pod_frame f; + const struct spa_pod *params[5]; + uint32_t n_params = 0; + Uint32 sdl_format; + void *d; + int32_t mult, size, blocks; + + if (param != NULL && id == SPA_PARAM_Tag) { + spa_debug_pod(0, NULL, param); + return; + } + if (param != NULL && id == SPA_PARAM_Latency) { + struct spa_latency_info info; + if (spa_latency_parse(param, &info) >= 0) + fprintf(stderr, "got latency: %"PRIu64"\n", (info.min_ns + info.max_ns) / 2); + return; + } + /* NULL means to clear the format */ + if (param == NULL || id != SPA_PARAM_Format) + return; + + fprintf(stderr, "got format:\n"); + spa_debug_format(2, NULL, param); + + if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) + return; + + if (data->format.media_type != SPA_MEDIA_TYPE_video) + return; + + switch (data->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + /* call a helper function to parse the format for us. */ + spa_format_video_raw_parse(param, &data->format.info.raw); + sdl_format = id_to_sdl_format(data->format.info.raw.format); + data->size = SPA_RECTANGLE(data->format.info.raw.size.width, + data->format.info.raw.size.height); + mult = 1; + break; + case SPA_MEDIA_SUBTYPE_dsp: + spa_format_video_dsp_parse(param, &data->format.info.dsp); + if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) + return; + sdl_format = SDL_PIXELFORMAT_RGBA32; + data->size = SPA_RECTANGLE(data->position->video.size.width, + data->position->video.size.height); + mult = 4; + break; + default: + sdl_format = SDL_PIXELFORMAT_UNKNOWN; + break; + } + + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { + pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); + return; + } + if (data->size.width == 0 || data->size.height == 0) { + pw_stream_set_error(stream, -EINVAL, "invalid size"); + return; + } + + data->texture = SDL_CreateTexture(data->renderer, + sdl_format, + SDL_TEXTUREACCESS_STREAMING, + data->size.width, + data->size.height); + switch(sdl_format) { + case SDL_PIXELFORMAT_YV12: + case SDL_PIXELFORMAT_IYUV: + data->stride = data->size.width; + size = (data->stride * data->size.height) * 3 / 2; + data->is_yuv = true; + blocks = 3; + break; + case SDL_PIXELFORMAT_YUY2: + data->is_yuv = true; + data->stride = data->size.width * 2; + size = (data->stride * data->size.height); + blocks = 1; + break; + default: + if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + data->stride = data->size.width * 2; + } else + SDL_UnlockTexture(data->texture); + size = data->stride * data->size.height; + blocks = 1; + break; + } + + data->rect.x = 0; + data->rect.y = 0; + data->rect.w = data->size.width; + data->rect.h = data->size.height; + + if (data->with_synctimeline) { + /* first add Buffer with 3 blocks (1 data, 2 sync fds). */ + spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); + spa_pod_builder_add(&b, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(3), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<with_synctimeline_release) { + /* drop features flags if not provided by both sides */ + spa_pod_builder_prop(&b, SPA_PARAM_META_features, SPA_POD_PROP_FLAG_DROP); + spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); + } + params[n_params++] = spa_pod_builder_pop(&b, &f); + } + + /* fallback for when the synctimeline is not negotiated */ + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); + params[n_params++] = sdl_build_formats(&info, b); + + fprintf(stderr, "supported SDL formats:\n"); + spa_debug_format(2, NULL, params[0]); + + params[n_params++] = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); + + fprintf(stderr, "supported DSP formats:\n"); + spa_debug_format(2, NULL, params[1]); + + return n_params; +} + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + const struct spa_pod *params[3]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct pw_properties *props; + int res, n_params; + + pw_init(&argc, &argv); + + data.with_synctimeline = true; + data.with_synctimeline_release = true; + + /* create a main loop */ + data.loop = pw_main_loop_new(NULL); + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + /* create a simple stream, the simple stream manages to core and remote + * objects for you if you don't need to deal with them + * + * If you plan to autoconnect your stream, you need to provide at least + * media, category and role properties + * + * Pass your events and a user_data pointer as the last arguments. This + * will inform you about the stream state. The most important event + * you need to listen to is the process event where you need to consume + * the data provided to you. + */ + props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", + PW_KEY_MEDIA_CATEGORY, "Capture", + PW_KEY_MEDIA_ROLE, "Camera", + NULL), + data.path = argc > 1 ? argv[1] : NULL; + if (data.path) + pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); + + data.stream = pw_stream_new_simple( + pw_main_loop_get_loop(data.loop), + "video-play", + props, + &stream_events, + &data); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); + return -1; + } + + if (SDL_CreateWindowAndRenderer + (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + fprintf(stderr, "can't create window: %s\n", SDL_GetError()); + return -1; + } + + /* build the extra parameters to connect with. To connect, we can provide + * a list of supported formats. We use a builder that writes the param + * object to the stack. */ + n_params = build_format(&data, &b, params); + + /* now connect the stream, we need a direction (input/output), + * an optional target node to connect to, some flags and parameters + */ + if ((res = pw_stream_connect(data.stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ + PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */ + PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ + params, n_params)) /* extra parameters, see above */ < 0) { + fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); + return -1; + } + + /* do things until we quit the mainloop */ + pw_main_loop_run(data.loop); + + pw_stream_destroy(data.stream); + pw_main_loop_destroy(data.loop); + + SDL_DestroyTexture(data.texture); + SDL_DestroyRenderer(data.renderer); + SDL_DestroyWindow(data.window); + pw_deinit(); + + return 0; +} diff --git a/src/examples/video-src-sync.c b/src/examples/video-src-sync.c new file mode 100644 index 000000000..b34d779ec --- /dev/null +++ b/src/examples/video-src-sync.c @@ -0,0 +1,437 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Video source using \ref pw_stream and sync_timeline. + [title] + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define BPP 4 +#define CURSOR_WIDTH 64 +#define CURSOR_HEIGHT 64 +#define CURSOR_BPP 4 + +#define MAX_BUFFERS 64 + +#define M_PI_M2 ( M_PI + M_PI ) + +struct data { + struct pw_main_loop *loop; + struct spa_source *timer; + + struct pw_context *context; + struct pw_core *core; + + struct pw_stream *stream; + struct spa_hook stream_listener; + + struct spa_video_info_raw format; + int32_t stride; + + int counter; + uint32_t seq; + + int res; + + bool with_synctimeline; + bool with_synctimeline_release; +}; + +static void on_process(void *userdata) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + uint32_t i, j; + uint8_t *p; + struct spa_meta_header *h; + struct spa_meta_sync_timeline *stl; + uint64_t cmd; + + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if ((p = buf->datas[0].data) == NULL) + return; + + if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { +#if 0 + h->pts = pw_stream_get_nsec(data->stream); +#else + h->pts = -1; +#endif + h->flags = 0; + h->seq = data->seq++; + h->dts_offset = 0; + } + if ((stl = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*stl))) && + stl->release_point) { + if (!SPA_FLAG_IS_SET(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE)) { + /* The other end promised to schedule the release point, wait before we + * can use the buffer */ + if (read(buf->datas[2].fd, &cmd, sizeof(cmd)) < 0) + pw_log_warn("release_point wait error %m"); + pw_log_debug("release_point:%"PRIu64, stl->release_point); + } else if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, + SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { + /* this happens when the other end did not get the buffer or + * will not trigger the release point, There is no point waiting, + * we can use the buffer right away */ + pw_log_warn("release_point not scheduled:%"PRIu64, stl->release_point); + } else { + /* The other end does not support the RELEASE flag, we don't + * know if the buffer was used or not or if the release point will + * ever be scheduled, we must assume we can reuse the buffer */ + pw_log_debug("assume buffer was released:%"PRIu64, stl->release_point); + } + } + + for (i = 0; i < data->format.size.height; i++) { + for (j = 0; j < data->format.size.width * BPP; j++) + p[j] = data->counter + j * i; + p += data->stride; + data->counter += 13; + } + + buf->datas[0].chunk->offset = 0; + buf->datas[0].chunk->size = data->format.size.height * data->stride; + buf->datas[0].chunk->stride = data->stride; + + if (stl) { + /* set the UNSCHEDULED_RELEASE flag, the consumer will clear this if + * it promises to signal the release point */ + SPA_FLAG_SET(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE); + cmd = 1; + stl->acquire_point = data->seq; + stl->release_point = data->seq; + /* write the acquire point */ + write(buf->datas[1].fd, &cmd, sizeof(cmd)); + } + pw_stream_queue_buffer(data->stream, b); +} + +static void on_timeout(void *userdata, uint64_t expirations) +{ + struct data *data = userdata; + pw_log_trace("timeout"); + pw_stream_trigger_process(data->stream); +} + +static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, + const char *error) +{ + struct data *data = _data; + + printf("stream state: \"%s\" %s\n", pw_stream_state_as_string(state), error ? error : ""); + + switch (state) { + case PW_STREAM_STATE_ERROR: + case PW_STREAM_STATE_UNCONNECTED: + pw_main_loop_quit(data->loop); + break; + + case PW_STREAM_STATE_PAUSED: + printf("node id: %d\n", pw_stream_get_node_id(data->stream)); + pw_loop_update_timer(pw_main_loop_get_loop(data->loop), + data->timer, NULL, NULL, false); + break; + case PW_STREAM_STATE_STREAMING: + { + struct timespec timeout, interval; + + timeout.tv_sec = 0; + timeout.tv_nsec = 1; + interval.tv_sec = 0; + interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; + + printf("driving:%d\n", pw_stream_is_driving(data->stream)); + + if (pw_stream_is_driving(data->stream)) + pw_loop_update_timer(pw_main_loop_get_loop(data->loop), + data->timer, &timeout, &interval, false); + break; + } + default: + break; + } +} + +static void +on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) +{ + struct data *data = _data; + struct pw_stream *stream = data->stream; + uint8_t params_buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); + const struct spa_pod *params[5]; + uint32_t n_params = 0; + struct spa_pod_frame f; + + if (param != NULL && id == SPA_PARAM_Tag) { + spa_debug_pod(0, NULL, param); + return; + } + if (param == NULL || id != SPA_PARAM_Format) + return; + + fprintf(stderr, "got format:\n"); + spa_debug_format(2, NULL, param); + + spa_format_video_raw_parse(param, &data->format); + + data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); + + /* first add Buffer with 3 blocks (1 data, 2 sync fds). */ + if (data->with_synctimeline) { + spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); + spa_pod_builder_add(&b, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(3), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<with_synctimeline_release) { + /* drop features flags if not provided by both sides */ + spa_pod_builder_prop(&b, SPA_PARAM_META_features, SPA_POD_PROP_FLAG_DROP); + spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); + } + params[n_params++] = spa_pod_builder_pop(&b, &f); + } + + /* fallback for when the synctimeline is not negotiated */ + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), + SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<buffer; + struct spa_data *d; +#ifdef HAVE_MEMFD_CREATE + unsigned int seals; +#endif + struct spa_meta_sync_timeline *s; + + d = buf->datas; + + pw_log_debug("add buffer %p", buffer); + if ((d[0].type & (1<stride * data->format.size.height; + + /* truncate to the right size before we set seals */ + if (ftruncate(d[0].fd, d[0].maxsize) < 0) { + pw_log_error("can't truncate to %d: %m", d[0].maxsize); + return; + } +#ifdef HAVE_MEMFD_CREATE + /* not enforced yet but server might require SEAL_SHRINK later */ + seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; + if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) { + pw_log_warn("Failed to add seals: %m"); + } +#endif + + /* now mmap so we can write to it in the process function above */ + d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE, + MAP_SHARED, d[0].fd, d[0].mapoffset); + if (d[0].data == MAP_FAILED) { + pw_log_error("can't mmap memory: %m"); + return; + } + + if ((s = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*s))) && buf->n_datas >= 3) { + pw_log_debug("got sync timeline"); + /* acquire fd (just an example, not really syncobj here) */ + d[1].type = SPA_DATA_SyncObj; + d[1].flags = SPA_DATA_FLAG_READWRITE; + d[1].fd = eventfd(0, EFD_CLOEXEC); + d[1].mapoffset = 0; + d[1].maxsize = 0; + if (d[1].fd == -1) { + pw_log_error("can't create acquire fd: %m"); + return; + } + /* release fd (just an example, not really syncobj here) */ + d[2].type = SPA_DATA_SyncObj; + d[2].flags = SPA_DATA_FLAG_READWRITE; + d[2].fd = eventfd(0, EFD_CLOEXEC); + d[2].mapoffset = 0; + d[2].maxsize = 0; + if (d[2].fd == -1) { + pw_log_error("can't create release fd: %m"); + return; + } + } + if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, + SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { + pw_log_debug("got sync timeline release"); + } +} + +/* close the memfd we set on the buffers here */ +static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) +{ + struct spa_buffer *buf = buffer->buffer; + struct spa_data *d; + + d = buf->datas; + pw_log_debug("remove buffer %p", buffer); + + munmap(d[0].data, d[0].maxsize); + close(d[0].fd); + if (buf->n_datas >= 3) { + close(d[1].fd); + close(d[2].fd); + } + } + + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .process = on_process, + .state_changed = on_stream_state_changed, + .param_changed = on_stream_param_changed, + .add_buffer = on_stream_add_buffer, + .remove_buffer = on_stream_remove_buffer, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->loop); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + const struct spa_pod *params[2]; + uint32_t n_params = 0; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + pw_init(&argc, &argv); + + data.loop = pw_main_loop_new(NULL); + + data.with_synctimeline = true; + data.with_synctimeline_release = true; + + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); + pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); + + data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); + + data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); + + data.core = pw_context_connect(data.context, NULL, 0); + if (data.core == NULL) { + fprintf(stderr, "can't connect: %m\n"); + data.res = -errno; + goto cleanup; + } + + data.stream = pw_stream_new(data.core, "video-src-sync", + pw_properties_new( + PW_KEY_MEDIA_CLASS, "Video/Source", + NULL)); + + params[n_params++] = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_BGRA), + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(320, 240), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(4096, 4096)), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); + + pw_stream_add_listener(data.stream, + &data.stream_listener, + &stream_events, + &data); + + pw_stream_connect(data.stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + PW_STREAM_FLAG_DRIVER | + PW_STREAM_FLAG_ALLOC_BUFFERS | + PW_STREAM_FLAG_MAP_BUFFERS, + params, n_params); + + pw_main_loop_run(data.loop); + +cleanup: + pw_context_destroy(data.context); + pw_main_loop_destroy(data.loop); + pw_deinit(); + + return data.res; +} From dcdb88d7b7fa3da1f72effa818c5b189d743f505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Tue, 30 Sep 2025 13:36:34 +0200 Subject: [PATCH 0919/1014] spa: libcamera: device: adapt to libcamera change The interface of string typed controls has recently been changed in libcamera[0], which affects `properties::Model`, so adapt to that change in such a way that is compatible with both the new and old versions. [0]: https://gitlab.freedesktop.org/camera/libcamera/-/commit/f84522d7cd208b5123f0bd249ed1e160d35fdfb8 --- spa/plugins/libcamera/libcamera-device.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 49ef957e1..2d6532f82 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -60,10 +60,7 @@ const libcamera::Span cameraDevice(const Camera& camera) std::string cameraModel(const Camera& camera) { - if (auto model = camera.properties().get(properties::Model)) - return std::move(model.value()); - - return camera.id(); + return std::string(camera.properties().get(properties::Model).value_or(camera.id())); } const char *cameraLoc(const Camera& camera) From a4ec02f9d79d41e86e5702efe5411b25e5b844c6 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 6 Oct 2025 17:51:16 -0700 Subject: [PATCH 0920/1014] spa: tests: Add an offline AEC benchmark --- spa/tests/benchmark-aec.c | 390 ++++++++++++++++++++++++++++++++++++++ spa/tests/meson.build | 19 +- 2 files changed, 403 insertions(+), 6 deletions(-) create mode 100644 spa/tests/benchmark-aec.c diff --git a/spa/tests/benchmark-aec.c b/spa/tests/benchmark-aec.c new file mode 100644 index 000000000..38f190261 --- /dev/null +++ b/spa/tests/benchmark-aec.c @@ -0,0 +1,390 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static SPA_LOG_IMPL(default_log); + +struct data { + const char *plugin_dir; + + struct spa_log *log; + struct spa_system *system; + struct spa_loop *loop; + struct spa_loop_control *control; + struct spa_loop_utils *loop_utils; + struct spa_plugin_loader *plugin_loader; + + struct spa_support support[6]; + uint32_t n_support; + + struct spa_audio_aec *aec; + struct spa_handle *aec_handle; + uint32_t aec_samples; +}; + +static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) +{ + int res; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + uint32_t i; + + char *path = NULL; + + if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { + return -ENOMEM; + } + if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { + printf("can't load %s: %s\n", path, dlerror()); + free(path); + return -errno; + } + free(path); + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + printf("can't find enum function\n"); + return -errno; + } + + for (i = 0;;) { + const struct spa_handle_factory *factory; + + if ((res = enum_func(&factory, &i)) <= 0) { + if (res != 0) + printf("can't enumerate factories: %s\n", spa_strerror(res)); + break; + } + if (!spa_streq(factory->name, name)) + continue; + + *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + if ((res = spa_handle_factory_init(factory, *handle, + NULL, data->support, + data->n_support)) < 0) { + printf("can't make factory instance: %d\n", res); + return res; + } + return 0; + } + return -EBADF; +} + +static int init(struct data *data) +{ + int res; + const char *str; + struct spa_handle *handle = NULL; + void *iface; + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + data->plugin_dir = str; + + if ((res = load_handle(data, &handle, + "support/libspa-support.so", + SPA_NAME_SUPPORT_SYSTEM)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { + printf("can't get System interface %d\n", res); + return res; + } + data->system = iface; + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data->system); + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data->system); + + if ((res = load_handle(data, &handle, + "support/libspa-support.so", + SPA_NAME_SUPPORT_LOOP)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data->loop = iface; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data->control = iface; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopUtils, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data->loop_utils = iface; + + data->log = &default_log.log; + + if ((str = getenv("SPA_DEBUG"))) + data->log->level = atoi(str); + + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data->log); + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data->loop); + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data->loop); + data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, data->loop_utils); + + /* Use webrtc as default */ + if ((res = load_handle(data, &handle, + "aec/libspa-aec-webrtc.so", + SPA_NAME_AEC)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_AUDIO_AEC, &iface)) < 0) { + spa_log_error(data->log, "can't get %s interface %d", SPA_TYPE_INTERFACE_AUDIO_AEC, res); + return res; + } + + data->aec = iface; + data->aec_handle = handle; + + return 0; +} + +static int spa_dict_from_json(struct spa_dict_item *items, uint32_t n_items, const char *str) +{ + struct spa_json it; + int res; + char key[1024]; + const char *value; + uint32_t i, len; + struct spa_error_location loc; + + if ((res = spa_json_begin_object_relax(&it, str, strlen(str))) < 0) { + return res; + } + + i = 0; + while ((len = spa_json_object_next(&it, key, sizeof(key), &value)) > 0) { + if (i > n_items) + return -ENOSPC; + + char *k = malloc(strlen(key) + 1); + char *v = malloc(len + 1); + + memcpy(k, key, strlen(key) + 1); + spa_json_parse_stringn(value, len, v, len + 1); + + items[i++] = SPA_DICT_ITEM_INIT(k, v); + } + + if (spa_json_get_error(&it, str, &loc)) { + struct spa_debug_context *c = NULL; + spa_debugc(c, "Invalid JSON: %s", loc.reason); + spa_debugc_error_location(c, &loc); + return -EINVAL; + } + + return i; +} + +static const struct format_info { + const char *name; + int sf_format; + uint32_t spa_format; + uint32_t width; +} format_info[] = { + { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 }, + { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 }, + { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 }, + { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 }, + { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 }, + { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 }, + { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, + { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, + { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, +}; + +static SNDFILE* open_file_read(const struct data *data, const char *name, struct spa_audio_info_raw *info) +{ + SF_INFO sf_info = { 0, }; + + SNDFILE *file = sf_open(name, SFM_READ, &sf_info); + + if (!file) { + spa_log_error(data->log, "Could not open file: %s", sf_strerror(NULL)); + exit(255); + } + + for (unsigned long i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if ((sf_info.format & SF_FORMAT_SUBMASK) == format_info[i].sf_format) { + info->format = format_info[i].spa_format; + break; + } + } + + info->rate = sf_info.samplerate; + info->channels = sf_info.channels; + + return file; +} + +static SNDFILE* open_file_write(const struct data *data, const char *name, struct spa_audio_info_raw *info) +{ + SF_INFO sf_info = { 0, }; + + for (unsigned long i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (info->format == format_info[i].spa_format) { + sf_info.format = SF_FORMAT_WAV | format_info[i].sf_format; + break; + } + } + + sf_info.samplerate = info->rate; + sf_info.channels = info->channels; + + SNDFILE *file = sf_open(name, SFM_WRITE, &sf_info); + + if (!file) { + spa_log_error(data->log, "Could not open file: %s", sf_strerror(NULL)); + exit(255); + } + + return file; +} + +static void deinterleave(float *data, uint32_t channels, uint32_t samples) +{ + float temp[channels * samples]; + + for (uint32_t i = 0; i < channels; i++) { + for (uint32_t j = 0; j < samples; j++) { + temp[i * samples + j] = data[j * channels + i]; + } + } + + memcpy(data, temp, sizeof(temp)); +} + +static void interleave(float *data, uint32_t channels, uint32_t samples) +{ + float temp[channels * samples]; + + for (uint32_t i = 0; i < samples; i++) { + for (uint32_t j = 0; j < channels; j++) { + temp[i * channels + j] = data[j * samples + i]; + } + } + + memcpy(data, temp, sizeof(temp)); +} + +static void usage(const char *exe) +{ + printf("Usage: %s rec_file play_file out_file <\"aec args\">\n", basename(exe)); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + struct spa_dict_item items[16] = { 0, }; + int n_items = 0, res; + + if ((res = init(&data)) < 0) + return res; + + if (argc < 4 || argc > 5) { + usage(argv[0]); + return -1; + } + + if (argc == 5) { + if ((res = spa_dict_from_json(items, SPA_N_ELEMENTS(items), argv[4])) < 0) + return res; + n_items = res; + } + + struct spa_dict aec_args = SPA_DICT(items, n_items); + + struct spa_audio_info_raw rec_info = { 0, }; + struct spa_audio_info_raw play_info = { 0, }; + + SNDFILE *rec_file = open_file_read(&data, argv[1], &rec_info); + SNDFILE *play_file = open_file_read(&data, argv[2], &play_info); + SNDFILE *out_file = open_file_write(&data, argv[3], &rec_info); + + if ((res = spa_audio_aec_init2(data.aec, &aec_args, &rec_info, &play_info, &rec_info)) < 0) { + spa_log_error(data.log, "Could not initialise AEC engine: %s", spa_strerror(res)); + return -1; + } + + if (data.aec->latency) { + unsigned int num, denom; + sscanf(data.aec->latency, "%u/%u", &num, &denom); + data.aec_samples = rec_info.rate * num / denom; + + } else { + /* Implementation doesn't care about the block size */ + data.aec_samples = 1024; + } + + float rec_data[rec_info.channels * data.aec_samples]; + float play_data[play_info.channels * data.aec_samples]; + float out_data[rec_info.channels * data.aec_samples]; + + const float *rec[rec_info.channels]; + const float *play[play_info.channels]; + float *out[rec_info.channels]; + + for (uint32_t i = 0; i < rec_info.channels; i++) { + rec[i] = &rec_data[i * data.aec_samples]; + out[i] = &out_data[i * data.aec_samples]; + } + + for (uint32_t i = 0; i < play_info.channels; i++) { + play[i] = &play_data[i * data.aec_samples]; + } + + while (1) { + res = sf_readf_float(rec_file, (float *)rec_data, data.aec_samples); + if (res != (int) data.aec_samples) + break; + + res = sf_readf_float(play_file, (float *)play_data, data.aec_samples); + if (res != (int) data.aec_samples) + break; + + deinterleave((float *)rec_data, rec_info.channels, data.aec_samples); + deinterleave((float *)play_data, play_info.channels, data.aec_samples); + + spa_audio_aec_run(data.aec, rec, play, out, data.aec_samples); + + interleave((float *)out_data, rec_info.channels, data.aec_samples); + + res = sf_writef_float(out_file, (const float *)out_data, data.aec_samples); + if (res != (int) data.aec_samples) { + spa_log_error(data.log, "Failed to write: %s", spa_strerror(res)); + break; + } + } + + sf_close(rec_file); + sf_close(play_file); + sf_close(out_file); + + return 0; +} diff --git a/spa/tests/meson.build b/spa/tests/meson.build index c73c887f4..c701ce9ba 100644 --- a/spa/tests/meson.build +++ b/spa/tests/meson.build @@ -28,15 +28,22 @@ if find.found() endif benchmark_apps = [ - 'stress-ringbuffer', - 'benchmark-pod', - 'benchmark-dict', + ['stress-ringbuffer', []], + ['benchmark-pod', []], + ['benchmark-dict', []], ] +if sndfile_dep.found() + benchmark_apps += [ + ['benchmark-aec', [sndfile_dep]] + ] +endif + foreach a : benchmark_apps - benchmark('spa-' + a, - executable('spa-' + a, a + '.c', - dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib ], + benchmark('spa-' + a[0], + executable('spa-' + a[0], a[0] + '.c', + dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib ] + a[1], + include_directories : [configinc], install : installed_tests_enabled, install_dir : installed_tests_execdir, ), From b7b9e7dc6e2a255253dc0eca011ec42ffec76fa6 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 9 Oct 2025 09:22:07 +0200 Subject: [PATCH 0921/1014] tests: fix compilation --- spa/tests/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/tests/meson.build b/spa/tests/meson.build index c701ce9ba..81635dea9 100644 --- a/spa/tests/meson.build +++ b/spa/tests/meson.build @@ -54,10 +54,10 @@ foreach a : benchmark_apps if installed_tests_enabled test_conf = configuration_data() - test_conf.set('exec', installed_tests_execdir / 'spa-' + a) + test_conf.set('exec', installed_tests_execdir / 'spa-' + a[0]) configure_file( input: installed_tests_template, - output: 'spa-' + a + '.test', + output: 'spa-' + a[0] + '.test', install_dir: installed_tests_metadir, configuration: test_conf, ) From bd6081018f26a740b5f5c763ea63c98e33232187 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 9 Oct 2025 09:29:32 +0200 Subject: [PATCH 0922/1014] tests: fix warning --- spa/tests/benchmark-aec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/tests/benchmark-aec.c b/spa/tests/benchmark-aec.c index 38f190261..3ac0fc62e 100644 --- a/spa/tests/benchmark-aec.c +++ b/spa/tests/benchmark-aec.c @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -293,7 +294,7 @@ static void interleave(float *data, uint32_t channels, uint32_t samples) memcpy(data, temp, sizeof(temp)); } -static void usage(const char *exe) +static void usage(char *exe) { printf("Usage: %s rec_file play_file out_file <\"aec args\">\n", basename(exe)); } From d268b6e1047c06cc1eb62d200f5d56ba4b4b4fbd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 10 Oct 2025 10:41:53 +0200 Subject: [PATCH 0923/1014] examples: add some options to enable features --- src/examples/video-play-sync.c | 60 +++++++++++++++++++++++++++++-- src/examples/video-src-sync.c | 64 +++++++++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/examples/video-play-sync.c b/src/examples/video-play-sync.c index 4b16e494f..44b9b2cd6 100644 --- a/src/examples/video-play-sync.c +++ b/src/examples/video-play-sync.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -439,6 +440,22 @@ static void do_quit(void *userdata, int signal_number) pw_main_loop_quit(data->loop); } +static void show_help(struct data *data, const char *name, bool is_error) +{ + FILE *fp; + + fp = is_error ? stderr : stdout; + + fprintf(fp, + "%s [options]\n" + " -h, --help Show this help\n" + " --version Show version\n" + " -r, --remote Remote daemon name\n" + " -S, --sync Enable SyncTimeline\n" + " -R, --release Enable RELEASE feature\n" + "\n", name); +} + int main(int argc, char *argv[]) { struct data data = { 0, }; @@ -447,11 +464,46 @@ int main(int argc, char *argv[]) struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "remote", required_argument, NULL, 'r' }, + { "sync", no_argument, NULL, 'S' }, + { "release", no_argument, NULL, 'R' }, + { NULL, 0, NULL, 0} + }; + char *opt_remote = NULL; + int c; pw_init(&argc, &argv); - data.with_synctimeline = true; - data.with_synctimeline_release = true; + while ((c = getopt_long(argc, argv, "hVr:SR", long_options, NULL)) != -1) { + switch (c) { + case 'h': + show_help(&data, argv[0], false); + return 0; + case 'V': + printf("%s\n" + "Compiled with libpipewire %s\n" + "Linked with libpipewire %s\n", + argv[0], + pw_get_headers_version(), + pw_get_library_version()); + return 0; + case 'r': + opt_remote = optarg; + break; + case 'S': + data.with_synctimeline = true; + break; + case 'R': + data.with_synctimeline_release = true; + break; + default: + show_help(&data, argv[0], true); + return -1; + } + } /* create a main loop */ data.loop = pw_main_loop_new(NULL); @@ -473,8 +525,10 @@ int main(int argc, char *argv[]) props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", + PW_KEY_REMOTE_NAME, opt_remote, NULL), - data.path = argc > 1 ? argv[1] : NULL; + + data.path = optind < argc ? argv[optind++] : "video-src-sync"; if (data.path) pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); diff --git a/src/examples/video-src-sync.c b/src/examples/video-src-sync.c index b34d779ec..890b2d8d9 100644 --- a/src/examples/video-src-sync.c +++ b/src/examples/video-src-sync.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -368,6 +369,22 @@ static void do_quit(void *userdata, int signal_number) pw_main_loop_quit(data->loop); } +static void show_help(struct data *data, const char *name, bool is_error) +{ + FILE *fp; + + fp = is_error ? stderr : stdout; + + fprintf(fp, + "%s [options]\n" + " -h, --help Show this help\n" + " --version Show version\n" + " -r, --remote Remote daemon name\n" + " -S, --sync Enable SyncTimeline\n" + " -R, --release Enable RELEASE feature\n" + "\n", name); +} + int main(int argc, char *argv[]) { struct data data = { 0, }; @@ -375,13 +392,48 @@ int main(int argc, char *argv[]) uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + static const struct option long_options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "remote", required_argument, NULL, 'r' }, + { "sync", no_argument, NULL, 'S' }, + { "release", no_argument, NULL, 'R' }, + { NULL, 0, NULL, 0} + }; + char *opt_remote = NULL; + int c; pw_init(&argc, &argv); - data.loop = pw_main_loop_new(NULL); + while ((c = getopt_long(argc, argv, "hVr:SR", long_options, NULL)) != -1) { + switch (c) { + case 'h': + show_help(&data, argv[0], false); + return 0; + case 'V': + printf("%s\n" + "Compiled with libpipewire %s\n" + "Linked with libpipewire %s\n", + argv[0], + pw_get_headers_version(), + pw_get_library_version()); + return 0; + case 'r': + opt_remote = optarg; + break; + case 'S': + data.with_synctimeline = true; + break; + case 'R': + data.with_synctimeline_release = true; + break; + default: + show_help(&data, argv[0], true); + return -1; + } + } - data.with_synctimeline = true; - data.with_synctimeline_release = true; + data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); @@ -390,7 +442,11 @@ int main(int argc, char *argv[]) data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); - data.core = pw_context_connect(data.context, NULL, 0); + data.core = pw_context_connect(data.context, + pw_properties_new( + PW_KEY_REMOTE_NAME, opt_remote, + NULL), + 0); if (data.core == NULL) { fprintf(stderr, "can't connect: %m\n"); data.res = -errno; From 91e2f184e2d14cabebcd78abf30426894f2f9462 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Wed, 8 Oct 2025 16:38:51 -0700 Subject: [PATCH 0924/1014] spa: alsa: Read and expose channel count and position from ELD The next step will be to propagate this to the correct node. --- spa/plugins/alsa/acp/acp.c | 69 ++++++++++++++++++++++++++++++-- spa/plugins/alsa/acp/acp.h | 12 ++++++ spa/plugins/alsa/acp/alsa-util.c | 10 +++++ spa/plugins/alsa/acp/alsa-util.h | 2 + 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 7fc749a7e..a30e59490 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -10,6 +10,7 @@ #include #include #include +#include int _acp_log_level = 1; acp_log_func _acp_log_func; @@ -1064,8 +1065,8 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; - int device, i; - const char *old_monitor_name, *old_iec958_codec_list; + int device; + const char *old_monitor_name, *old_iec958_codec_list, *old_channels, *old_position; pa_device_port *p; pa_hdmi_eld eld; bool changed = false; @@ -1087,7 +1088,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) memset(&eld, 0, sizeof(eld)); // Strip trailing whitespace from monitor_name (primarily an NVidia driver bug for now) - for (i = strlen(eld.monitor_name) - 1; i >= 0; i--) { + for (int i = strlen(eld.monitor_name) - 1; i >= 0; i--) { if (eld.monitor_name[i] == '\n' || eld.monitor_name[i] == '\r' || eld.monitor_name[i] == '\t' || eld.monitor_name[i] == ' ') eld.monitor_name[i] = 0; @@ -1115,6 +1116,68 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) pa_proplist_sets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED, codecs); } + old_channels = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); + if (eld.lpcm_channels == 0) { + changed |= old_channels != NULL; + pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); + } else { + char channels[4]; + snprintf(channels, sizeof(channels), "%u", eld.lpcm_channels); + changed |= (old_channels == NULL) || (!spa_streq(old_channels, channels)); + pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED, channels); + } + + old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); + if (eld.speakers == 0) { + changed |= old_position != NULL; + pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); + } else { + uint32_t positions[eld.lpcm_channels]; + char position[64]; + int i = 0, pos = 0; + + if (eld.speakers & 0x01) { + positions[i++] = ACP_CHANNEL_FL; + positions[i++] = ACP_CHANNEL_FR; + } + if (eld.speakers & 0x02) { + positions[i++] = ACP_CHANNEL_LFE; + } + if (eld.speakers & 0x04) { + positions[i++] = ACP_CHANNEL_FC; + } + if (eld.speakers & 0x08) { + positions[i++] = ACP_CHANNEL_RL; + positions[i++] = ACP_CHANNEL_RR; + } + /* The rest are out of order in order of what channels we would prefer to use/expose first */ + if (eld.speakers & 0x40) { + /* Use SL/SR instead of RLC/RRC */ + positions[i++] = ACP_CHANNEL_SL; + positions[i++] = ACP_CHANNEL_SR; + } + if (eld.speakers & 0x20) { + positions[i++] = ACP_CHANNEL_RLC; + positions[i++] = ACP_CHANNEL_RRC; + } + if (eld.speakers & 0x10) { + positions[i++] = ACP_CHANNEL_RC; + } + + while (i < eld.lpcm_channels) + positions[i++] = ACP_CHANNEL_UNKNOWN; + + for (i = 0, pos = 0; i < eld.lpcm_channels; i++) { + pos += snprintf(&position[pos], sizeof(position) - pos, "%s,", channel_names[positions[i]]); + } + + /* Overwrite trailing , */ + position[pos - 1] = 0; + + changed |= (old_position == NULL) || (!spa_streq(old_position, position)); + pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position); + } + pa_proplist_as_dict(p->proplist, &p->port.props); if (changed && mask != 0 && impl->events && impl->events->props_changed) diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index ff5f360d7..5fb5b96f7 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -153,6 +153,18 @@ const char *acp_available_str(enum acp_available status); * values may be incorrect and/or might change, e.g. when external devices such * as receivers are powered on or off. */ +#define ACP_KEY_AUDIO_CHANNELS_DETECTED "audio.channels.detected" + /**< The number of channels detected detected via EDID-like data read from a device + * connected via HDMI/DisplayPort. This only serves as a hint, as the auto-detected + * values may be incorrect and/or might change, e.g. when external devices such + * as receivers are powered on or off. + */ +#define ACP_KEY_AUDIO_POSITION_DETECTED "audio.position.detected" + /**< The channel positions detected detected via EDID-like data read from a device + * connected via HDMI/DisplayPort. This only serves as a hint, as the auto-detected + * values may be incorrect and/or might change, e.g. when external devices such + * as receivers are powered on or off. + */ struct acp_device; diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index 96d6020cd..52b63eb87 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -2020,14 +2020,24 @@ int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { sad_count = 0; } + /* Look up speaker presence in Speaker Allocation Data Block */ + eld->speakers = elddata[7] & 0x7f; + + eld->lpcm_channels = 0; eld->iec958_codecs = 0; + for (unsigned i = 0; i < sad_count; i++) { uint8_t *sad = &elddata[20 + mnl + 3 * i]; + uint8_t lpcm_channels; /* https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#Audio_Data_Blocks */ switch ((sad[0] & 0x78) >> 3) { case 1: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; + /* Lowest 3 bits are channel count - 1 */ + lpcm_channels = (sad[0] & 0x07) + 1; + if (lpcm_channels > eld->lpcm_channels) + eld->lpcm_channels = lpcm_channels; break; case 2: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_AC3; diff --git a/spa/plugins/alsa/acp/alsa-util.h b/spa/plugins/alsa/acp/alsa-util.h index 26c2698c9..f8c8622b5 100644 --- a/spa/plugins/alsa/acp/alsa-util.h +++ b/spa/plugins/alsa/acp/alsa-util.h @@ -175,7 +175,9 @@ void pa_alsa_mixer_free(pa_alsa_mixer *mixer); typedef struct pa_hdmi_eld pa_hdmi_eld; struct pa_hdmi_eld { char monitor_name[17]; + uint8_t speakers; uint64_t iec958_codecs; + uint8_t lpcm_channels; }; int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld); From 154ab33607b26509737e3a87b815e8a35b0c243d Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Thu, 9 Oct 2025 20:24:14 -0700 Subject: [PATCH 0925/1014] spa: alsa: Add option to use ELD-detected channels --- spa/plugins/alsa/acp/acp.c | 42 ++++++++++++++++++++++++++++++++++--- spa/plugins/alsa/acp/card.h | 1 + 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index a30e59490..364d71eb0 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -147,6 +147,15 @@ char *acp_channel_str(char *buf, size_t len, enum acp_channel ch) return buf; } +static enum acp_channel acp_channel_from_str(const char *buf, size_t len) +{ + for (unsigned long i = 0; i < ACP_N_ELEMENTS(channel_names); i++) { + if (strncmp(channel_names[i], buf, len) == 0) + return i; + } + + return ACP_CHANNEL_UNKNOWN; +} const char *acp_available_str(enum acp_available status) { @@ -1682,7 +1691,6 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device { const char *mod_name; uint32_t i, port_index; - const char *codecs; pa_device_port *p; void *state = NULL; int res; @@ -1721,6 +1729,29 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device if (dev->active_port) dev->active_port->port.flags |= ACP_PORT_ACTIVE; + if (impl->use_eld_channels) { + while ((p = pa_hashmap_iterate(dev->ports, &state, NULL))) { + const char *channels = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); + const char *positions = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); + + if (channels && positions) { + const char *position, *split_state = NULL; + size_t i = 0, n; + + dev->device.format.channels = atoi(channels); + free(dev->device.format.map); + dev->device.format.map = calloc(dev->device.format.channels, sizeof(uint32_t)); + + while ((position = pa_split_in_place(positions, ",", &n, &split_state)) != NULL && + i < dev->device.format.channels) { + dev->device.format.map[i++] = acp_channel_from_str(position, n); + } + + break; + } + } + } + if ((res = setup_mixer(impl, dev, impl->ignore_dB)) < 0) return res; @@ -1735,13 +1766,16 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device else dev->muted = false; + state = NULL; while ((p = pa_hashmap_iterate(dev->ports, &state, NULL))) { - codecs = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); + const char *codecs = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); if (codecs) { dev->device.n_codecs = acp_iec958_codecs_from_json(codecs, dev->device.codecs, ACP_N_ELEMENTS(dev->device.codecs)); - break; } + + if (codecs) + break; } return 0; @@ -1933,6 +1967,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->ucm.split_enable = spa_atob(s); if ((s = acp_dict_lookup(props, "api.acp.disable-pro-audio")) != NULL) impl->disable_pro_audio = spa_atob(s); + if ((s = acp_dict_lookup(props, "api.acp.use-eld-channels")) != NULL) + impl->use_eld_channels = spa_atob(s); } #if SND_LIB_VERSION < 0x10207 diff --git a/spa/plugins/alsa/acp/card.h b/spa/plugins/alsa/acp/card.h index 0ef1a1ce1..f75222dbf 100644 --- a/spa/plugins/alsa/acp/card.h +++ b/spa/plugins/alsa/acp/card.h @@ -49,6 +49,7 @@ struct pa_card { bool auto_port; bool ignore_dB; bool disable_pro_audio; + bool use_eld_channels; uint32_t rate; uint32_t pro_channels; From 2aa725e4fe16cc7d47f4c4e4bada7b93b898d1de Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 10 Oct 2025 12:23:43 +0300 Subject: [PATCH 0926/1014] audioconvert: accept prop params that are encoded as Long in the pod --- spa/plugins/audioconvert/audioconvert.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 70affb04f..d2e22d603 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1506,6 +1506,9 @@ static int parse_prop_params(struct impl *this, struct spa_pod *params) } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); + } else if (spa_pod_is_long(pod)) { + snprintf(value, sizeof(value), "%"PRIi64, + SPA_POD_VALUE(struct spa_pod_long, pod)); } else if (spa_pod_is_bool(pod)) { snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? From 16ce5a2ccf705957e4c98beb984bbafd5632f697 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Fri, 10 Oct 2025 11:50:13 +0200 Subject: [PATCH 0927/1014] filter-graph: Accept params of type Long Integer numbers in lua are sent over protocol-native as Longs so make sure to handle them. --- spa/plugins/filter-graph/filter-graph.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 36d0c1ec1..2d837e95d 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -618,6 +618,7 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) double dbl_val; bool bool_val; int32_t int_val; + int64_t long_val; if (spa_pod_parser_get_string(&prs, &name) < 0) break; @@ -629,6 +630,9 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) } else if (spa_pod_parser_get_int(&prs, &int_val) >= 0) { value = int_val; val = &value; + } else if (spa_pod_parser_get_long(&prs, &long_val) >= 0) { + value = long_val; + val = &value; } else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) { value = bool_val ? 1.0f : 0.0f; val = &value; From 4da25df986e7c06e96a4d78c184d647d6c4648b3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 10 Oct 2025 15:03:04 +0200 Subject: [PATCH 0928/1014] filter-graph: accept String param values We parse the string as a float and if that works, set the value. --- spa/plugins/filter-graph/filter-graph.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 2d837e95d..d32e4a37f 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -613,7 +613,7 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) return 0; while (true) { - const char *name; + const char *name, *str_val; float value, *val = NULL; double dbl_val; bool bool_val; @@ -636,6 +636,9 @@ static int parse_params(struct graph *graph, const struct spa_pod *pod) } else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) { value = bool_val ? 1.0f : 0.0f; val = &value; + } else if (spa_pod_parser_get_string(&prs, &str_val) >= 0 && + spa_json_parse_float(str_val, strlen(str_val), &value) >= 0) { + val = &value; } else { struct spa_pod *pod; spa_pod_parser_get_pod(&prs, &pod); From c65e70fce04a72f651498c2d1f1c6d932f339f71 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Fri, 10 Oct 2025 18:30:26 +0300 Subject: [PATCH 0929/1014] doc: document missing api.acp.* properties --- doc/dox/config/pipewire-props.7.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 1319297ff..4db52b94b 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -822,6 +822,13 @@ some devices. \copydoc SPA_KEY_API_ALSA_SPLIT_ENABLE \endparblock +@PAR@ device-prop api.acp.disable-pro-audio = false # boolean +Disable the "Pro Audio" profile for this device. + +@PAR@ device-prop api.acp.use-eld-channels = false # boolean +Use the channel count and mapping the connected HDMI device +provides via ELD information. + ## Node properties @PAR@ node-prop audio.channels # integer From 126d61db1bf7430530cff15058d81a45a1259f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 10 Oct 2025 17:37:21 +0200 Subject: [PATCH 0930/1014] ci: build_on_debian: set test timeout multiplier Some tests - for example test-fmt-ops - are compute heavy. Since tests in non-x86 builds are run inside qemu, they can be significantly slower, exceeding the default 30 second timeout. So set the timeout multiplier to 2 to allow for slower execution. --- .gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 222a0ce87..ac10fb674 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -240,7 +240,8 @@ include: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - - meson test -C "$BUILD_DIR" --no-rebuild + - echo "Running tests with meson options $MESON_TEST_OPTIONS" + - meson test -C "$BUILD_DIR" --no-rebuild $MESON_TEST_OPTIONS - meson install -C "$BUILD_DIR" --no-rebuild artifacts: name: pipewire-$CI_COMMIT_SHA @@ -345,6 +346,8 @@ build_on_debian: -D vulkan=enabled -D ffmpeg=enabled -D pw-cat-ffmpeg=enabled + MESON_TEST_OPTIONS: >- + --timeout-multiplier=2 .build_on_fedora: extends: From 8bf8600e5956e7b708c8cb3775e1203d95c1d8e3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 11 Oct 2025 20:40:48 +0300 Subject: [PATCH 0931/1014] bluez5: if Acquire results to NoReply, try to clean up with Release If BlueZ doesn't reply, it may consider the operation still active. Try to Release the transport to get to a known state. This can happen if device doesn't respond to operations in reasonable time and BlueZ doesn't have its own timeout which is the case for BAP currently (which is a bug there). --- spa/plugins/bluez5/bluez5-dbus.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 07b122c63..4b5ca1d65 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -3868,6 +3868,21 @@ static void transport_acquire_reply(DBusPendingCall *pending, void *user_data) spa_log_error(monitor->log, "Acquire %s returned error: %s", transport->path, dbus_message_get_error_name(r)); + + /* If no reply, BlueZ may consider operation still active, so release to + * try to get to a known state. + */ + if (spa_streq(dbus_message_get_error_name(r), DBUS_ERROR_NO_REPLY)) { + spa_autoptr(DBusMessage) m = NULL; + + spa_log_info(monitor->log, "Releasing transport %s (clean up NoReply)", + transport->path); + m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, + BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"); + if (m) + dbus_connection_send(monitor->conn, m, NULL); + } + ret = -EIO; goto finish; } From 4ca1d70979a2ca0129ee4493ee72e82ce6cf2ee4 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 11 Oct 2025 18:38:38 +0300 Subject: [PATCH 0932/1014] bluez5: media-sink: fix silence padding for ISO stream resync Silence padding larger than ISO packet may be needed for resync when quantum is large. We can't insert silence by adding data to encoding buffer, as the encoding buffer may be then too small and it may also be partially filled. Fix by inserting silence from flush_data() just before buffers would be consumed. Fixes ISO stream alignment at playback start. --- spa/plugins/bluez5/media-sink.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 271cc419c..98fa6c4b0 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -206,6 +206,7 @@ struct impl { uint8_t tmp_buffer[BUFFER_SIZE]; uint32_t tmp_buffer_used; uint32_t fd_buffer_size; + uint32_t silence_frames; struct spa_bt_asha *asha; struct spa_list asha_link; @@ -604,6 +605,8 @@ static uint32_t get_queued_frames(struct impl *this) else bytes = 0; + bytes += this->silence_frames * this->block_size; + /* Count (partially) encoded packet */ bytes += this->tmp_buffer_used; bytes += this->block_count * this->block_size; @@ -929,6 +932,20 @@ again: } } + while (this->silence_frames && !this->need_flush) { + static const uint8_t empty[1024] = {}; + uint32_t avail = SPA_MIN(this->silence_frames, sizeof(empty) / port->frame_size) + * port->frame_size; + + written = add_data(this, empty, avail); + if (written <= 0) + break; + + this->silence_frames -= written / port->frame_size; + spa_log_trace(this->log, "%p: written %d silence frames", this, + written / port->frame_size); + } + while (!spa_list_is_empty(&port->ready) && !this->need_flush) { uint8_t *src; uint32_t n_bytes, n_frames; @@ -1165,6 +1182,14 @@ static void drop_frames(struct impl *this, uint32_t req) { struct port *port = &this->port; + if (this->silence_frames > req) { + this->silence_frames -= req; + req = 0; + } else { + req -= this->silence_frames; + this->silence_frames = 0; + } + while (req > 0 && !spa_list_is_empty(&port->ready)) { struct buffer *b; struct spa_data *d; @@ -1246,12 +1271,10 @@ static void media_iso_rate_match(struct impl *this) spa_log_debug(this->log, "%p: ISO sync skip frames:%u", this, req); } else if (iso_io->resync && -err >= 0) { unsigned int req = (unsigned int)(-err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC); - static const uint8_t empty[8192] = {0}; if (req > 0) { spa_bt_rate_control_init(&port->ratectl, 0); - req = SPA_MIN(req, sizeof(empty) / port->frame_size); - add_data(this, empty, req * port->frame_size); + this->silence_frames += req; } spa_log_debug(this->log, "%p: ISO sync pad frames:%u", this, req); } else if (err > max_err || -err > max_err) { From 4e3a5d9e6f37a87ca788d36c7da316500fb50c3a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 11 Oct 2025 19:20:07 +0300 Subject: [PATCH 0933/1014] bluez5: iso-io: initialize stream->size, now when setting cb Ensure size and now have valid values after exiting spa_bt_iso_io_set_cb(), so data may be provided already on first cycle. --- spa/plugins/bluez5/iso-io.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c index 8015a81de..2f991b8ca 100644 --- a/spa/plugins/bluez5/iso-io.c +++ b/spa/plugins/bluez5/iso-io.c @@ -613,12 +613,10 @@ void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *this, spa_bt_iso_io_pull_t pull, } stream->idle = true; - stream->this.resync = true; - if (pull == NULL) { - stream->this.size = 0; - return; - } + stream->this.resync = true; + stream->this.size = 0; + stream->this.now = stream->group->next; } /** Must be called from data thread */ From 91702975f7d0a1068fec4d7bdf38ecf14640c333 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 11 Oct 2025 18:53:31 +0300 Subject: [PATCH 0934/1014] bluez5: media-sink: cleanup ISO rate matching Move accounting for pending ISO packet to the reference time. Make sure rate matching is reset on start, and reset matching on resync properly. Allow resync on first cycle, ok since iso_io->now is valid immediately. --- spa/plugins/bluez5/media-sink.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 98fa6c4b0..3d3f2ec75 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -652,6 +652,9 @@ static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) if (this->process_rate_diff > 0) t = (int64_t)(t / this->process_rate_diff); + if (this->transport && this->transport->iso_io && this->transport->iso_io->size) + t -= this->transport->iso_io->duration; + return this->process_time + t; } @@ -1233,6 +1236,7 @@ static void media_iso_rate_match(struct impl *this) if (this->resync || !this->position) { spa_bt_rate_control_init(&port->ratectl, 0); + setup_matching(this); return; } @@ -1249,8 +1253,6 @@ static void media_iso_rate_match(struct impl *this) */ ref_time = get_reference_time(this, &duration_ns); - if (iso_io->size) - ref_time -= iso_io->duration; value = (int64_t)iso_io->now - (int64_t)ref_time; if (this->process_rate) @@ -1644,7 +1646,7 @@ static int transport_start(struct impl *this) spa_loop_add_source(this->data_loop, &this->flush_source); } - this->resync = RESYNC_CYCLES; + this->resync = 0; this->flush_pending = false; this->iso_pending = false; @@ -1689,6 +1691,7 @@ fail: static int do_start(struct impl *this) { + struct port *port = &this->port; int res; if (this->started) @@ -1717,6 +1720,7 @@ static int do_start(struct impl *this) this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); + spa_bt_rate_control_init(&port->ratectl, 0); setup_matching(this); set_timers(this); From c89acd3e1c4255e5437aa2f6ef0b0d494e3af072 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 10 Sep 2025 18:57:27 +0300 Subject: [PATCH 0935/1014] alsa: acp: fix volume rounding down causing mute Some ALSA devices have minimum HW volume value that is muted. ALSA indicates it with SND_CTL_TLV_DB_GAIN_MUTE = -9999999 dB/100 volume dB. When rounding down to HW volume, we may get this muted value. When determining splitting of volumes to mixers and soft volume, we don't want HW mixers to set volume to muted, unless the target volume is actually muted. Fix by adding element_ask_unmuted_dB_vol() that rounds up if the asked rounding mode resulted to mute. This fixes mic getting muted at low volume despite ALSA reporting the dB values correctly. Fixes #4890 --- spa/plugins/alsa/acp/alsa-mixer.c | 100 ++++++++++++++++++++++++------ 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index 1f494ee0b..ec9726d8c 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -30,6 +30,16 @@ #include "alsa-mixer.h" #include "alsa-util.h" +/** + * ALSA (1/100) dB volume equal or below this is considered muted. + * The actual ALSA value is SND_CTL_TLV_DB_GAIN_MUTE = -9999999 + */ +#ifdef SND_CTL_TLV_DB_GAIN_MUTE +#define ALSA_DB_MUTED (SND_CTL_TLV_DB_GAIN_MUTE) +#else +#define ALSA_DB_MUTED (-9999999) +#endif + static int setting_select(pa_alsa_setting *s, snd_mixer_t *m); struct description_map { @@ -1046,6 +1056,52 @@ static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, in return i + db_fix->min_step; } +/* Same as snd_mixer_selem_ask_playback/capture_dB_vol(), but if the result is muted, + * round volume up instead. + */ +static int element_ask_unmuted_dB_vol(snd_mixer_elem_t *me, pa_alsa_direction_t d, long value_dB, int rounding, long *alsa_val) { + int r = -1; + long val, dB, base_val; + + pa_assert(me); + + if (d == PA_ALSA_DIRECTION_OUTPUT) { + if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value_dB, rounding, &val)) < 0) + return r; + + if ((r = snd_mixer_selem_ask_playback_vol_dB(me, val, &dB)) < 0) + return r; + + base_val = val; + + if (dB <= ALSA_DB_MUTED && value_dB > ALSA_DB_MUTED) + if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value_dB, +1, &val)) < 0) + return r; + + *alsa_val = val; + } else { + if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value_dB, rounding, &val)) < 0) + return r; + + if ((r = snd_mixer_selem_ask_capture_vol_dB(me, val, &dB)) < 0) + return r; + + base_val = val; + + if (dB <= ALSA_DB_MUTED && value_dB > ALSA_DB_MUTED) + if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value_dB, +1, &val)) < 0) + return r; + + *alsa_val = val; + } + + if (val != base_val) + pa_log_debug("Volume rounded to mute: %ld -> %ld (dB/100) rounding:%d, correcting vol:%ld -> %ld", + value_dB, dB, rounding, base_val, val); + + return r; +} + /* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument, * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above". * But even with accurate nearest dB volume step is not selected, so that is why we need @@ -1164,17 +1220,19 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann } } else { + long alsa_val; + if (write_to_hw) { if (deferred_volume) { if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0) r = snd_mixer_selem_set_playback_dB(me, c, value, 0); } else { - if ((r = snd_mixer_selem_set_playback_dB(me, c, value, rounding)) >= 0) - r = snd_mixer_selem_get_playback_dB(me, c, &value); + if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) + if ((r = snd_mixer_selem_set_playback_volume(me, c, alsa_val)) >= 0) + r = snd_mixer_selem_get_playback_dB(me, c, &value); } } else { - long alsa_val; - if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, rounding, &alsa_val)) >= 0) + if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value); } } @@ -1192,17 +1250,19 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann } } else { + long alsa_val; + if (write_to_hw) { if (deferred_volume) { if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0) r = snd_mixer_selem_set_capture_dB(me, c, value, 0); } else { - if ((r = snd_mixer_selem_set_capture_dB(me, c, value, rounding)) >= 0) - r = snd_mixer_selem_get_capture_dB(me, c, &value); + if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) + if ((r = snd_mixer_selem_set_capture_volume(me, c, alsa_val)) >= 0) + r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value); } } else { - long alsa_val; - if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, rounding, &alsa_val)) >= 0) + if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value); } } @@ -1398,13 +1458,18 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { else r = snd_mixer_selem_set_capture_volume_all(me, volume); } else { + int rounding = (e->direction == PA_ALSA_DIRECTION_OUTPUT) ? +1 : -1; + pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO); pa_assert(!e->db_fix); - if (e->direction == PA_ALSA_DIRECTION_OUTPUT) - r = snd_mixer_selem_set_playback_dB_all(me, 0, +1); - else - r = snd_mixer_selem_set_capture_dB_all(me, 0, -1); + r = element_ask_unmuted_dB_vol(me, e->direction, 0, rounding, &volume); + if (r >= 0) { + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) + r = snd_mixer_selem_set_playback_volume_all(me, volume); + else + r = snd_mixer_selem_set_capture_volume_all(me, volume); + } } if (r < 0) { @@ -3558,9 +3623,9 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ a_limit = a->constant_volume; else if (a->volume_use == PA_ALSA_VOLUME_ZERO) { long dB = 0; + int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1); if (a->db_fix) { - int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1); a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding); } else { snd_mixer_selem_id_t *sid; @@ -3573,13 +3638,8 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ return false; } - if (a->direction == PA_ALSA_DIRECTION_OUTPUT) { - if (snd_mixer_selem_ask_playback_dB_vol(me, dB, +1, &a_limit) < 0) - return false; - } else { - if (snd_mixer_selem_ask_capture_dB_vol(me, dB, -1, &a_limit) < 0) - return false; - } + if (element_ask_unmuted_dB_vol(me, a->direction, dB, rounding, &a_limit) < 0) + return false; } } else if (a->volume_use == PA_ALSA_VOLUME_OFF) a_limit = a->min_volume; From 20d2a331be58a68abead4b99a95bf783c9797033 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 19 Sep 2025 09:48:13 +0200 Subject: [PATCH 0936/1014] impl-node: add a NODE_RELIABLE property Add a PW_KEY_NODE_RELIABLE and PW_KEY_PORT_RELIABLE property. the port property value is inherited from the parent when not explicitly set. Setting the property on a port will activate a more reliable tee, that actually only recycles buffers that were consumed. It will also activate a mode in stream that gives out new buffers only when the previous one was recycled and nothing else is queued. This is necessary to avoid queuing in the stream when the other side is not consuming. When a link is async but the output node is a driver of the input, we can avoid async io. This also removes a potential out-of-order buffer recycling when the node resumes at a different cycle. See #4885 --- src/pipewire/impl-link.c | 11 ++++++++- src/pipewire/impl-node.c | 1 + src/pipewire/impl-port.c | 49 ++++++++++++++++++++++++++++++++++++++++ src/pipewire/keys.h | 3 +++ src/pipewire/private.h | 2 ++ src/pipewire/stream.c | 10 ++++++++ 6 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index cdbfc2faa..99eea5a55 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -787,6 +787,7 @@ int pw_impl_link_activate(struct pw_impl_link *this) struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res; uint32_t io_type, io_size; + bool reliable_driver; pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated, pw_link_state_as_string(this->info.state)); @@ -795,7 +796,15 @@ int pw_impl_link_activate(struct pw_impl_link *this) !impl->input.node->runnable || !impl->output.node->runnable) return 0; - if (this->async) { + /* check if the output node is a driver for the input node and if + * it has reliable scheduling. Because it is a driver, it will always be + * scheduled before the input node and there will not be any concurrent access + * to the io, so we don't need async IO, even when the input is async. This + * avoid the problem of out-of-order buffers after a stall. */ + reliable_driver = (impl->output.node == impl->input.node->driver_node) && + impl->output.node->reliable; + + if (this->async && !reliable_driver) { io_type = SPA_IO_AsyncBuffers; io_size = sizeof(struct spa_io_async_buffers); } else { diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 87fbe58e2..a8f65b06c 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -1158,6 +1158,7 @@ static void check_properties(struct pw_impl_node *node) impl->cache_params = pw_properties_get_bool(node->properties, PW_KEY_NODE_CACHE_PARAMS, true); driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_DRIVER, false); node->exclusive = pw_properties_get_bool(node->properties, PW_KEY_NODE_EXCLUSIVE, false); + node->reliable = pw_properties_get_bool(node->properties, PW_KEY_NODE_RELIABLE, false); if (node->driver != driver) { pw_log_debug("%p: driver %d -> %d", node, node->driver, driver); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index aff08e4be..b6b06d87c 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -303,6 +303,37 @@ static int tee_process(void *object) return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } +static int tee_process_reliable(void *object) +{ + struct impl *impl = object; + struct pw_impl_port *this = &impl->this; + struct pw_impl_port_mix *mix; + struct spa_io_buffers *io = &this->rt.io; + uint32_t cycle = this->node->rt.position->clock.cycle & 1; + + if (io->status == SPA_STATUS_HAVE_DATA) { + uint32_t buffer_id = io->buffer_id; + + pw_log_trace_fp("%p: tee input status:%d id:%d cycle:%d", this, io->status, buffer_id, cycle); + + spa_list_for_each(mix, &impl->rt.mix_list, rt.link) { + struct spa_io_buffers *mio = mix->io[cycle]; + + pw_log_trace_fp("%p: port %d %p->%p status:%d id:%d", this, + mix->port.port_id, io, mio, mio->status, mio->buffer_id); + + if (mio->status != SPA_STATUS_HAVE_DATA) { + io->buffer_id = mio->buffer_id; + io->status = SPA_STATUS_NEED_DATA; + mio->buffer_id = buffer_id; + mio->status = SPA_STATUS_HAVE_DATA; + break; + } + } + } + return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; +} + static int tee_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *impl = object; @@ -322,6 +353,15 @@ static const struct spa_node_methods schedule_tee_node = { .process = tee_process, }; +static const struct spa_node_methods schedule_tee_node_reliable = { + SPA_VERSION_NODE_METHODS, + .add_listener = mix_add_listener, + .port_enum_params = mix_port_enum_params, + .port_set_io = port_set_io, + .port_reuse_buffer = tee_reuse_buffer, + .process = tee_process_reliable, +}; + static int schedule_mix_input(void *object) { struct impl *impl = object; @@ -456,6 +496,7 @@ int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix static int check_properties(struct pw_impl_port *port) { + struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); struct pw_impl_node *node = port->node; bool is_control, is_network, is_monitor, is_device, is_duplex, is_virtual; const char *media_class, *override_device_prefix, *channel_names; @@ -480,6 +521,14 @@ static int check_properties(struct pw_impl_port *port) port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); + port->reliable = pw_properties_get_bool(port->properties, PW_KEY_PORT_RELIABLE, node->reliable); + + if (port->direction == PW_DIRECTION_OUTPUT) { + if (port->reliable) + impl->mix_node.iface.cb.funcs = &schedule_tee_node_reliable; + else + impl->mix_node.iface.cb.funcs = &schedule_tee_node; + } /* inherit passive state from parent node */ port->passive = pw_properties_get_bool(port->properties, PW_KEY_PORT_PASSIVE, diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 3d119fefb..13694bc29 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -232,6 +232,8 @@ extern "C" { #define PW_KEY_NODE_PHYSICAL "node.physical" /**< ports from the node are physical */ #define PW_KEY_NODE_TERMINAL "node.terminal" /**< ports from the node are terminal */ +#define PW_KEY_NODE_RELIABLE "node.reliable" /**< node uses reliable transport 1.6.0 */ + /** Port keys */ #define PW_KEY_PORT_ID "port.id" /**< port id */ #define PW_KEY_PORT_NAME "port.name" /**< port name */ @@ -249,6 +251,7 @@ extern "C" { #define PW_KEY_PORT_IGNORE_LATENCY "port.ignore-latency" /**< latency ignored by peers, since 0.3.71 */ #define PW_KEY_PORT_GROUP "port.group" /**< the port group of the port 1.2.0 */ #define PW_KEY_PORT_EXCLUSIVE "port.exclusive" /**< link port only once 1.6.0 */ +#define PW_KEY_PORT_RELIABLE "port.reliable" /**< port uses reliable transport 1.6.0 */ /** link properties */ #define PW_KEY_LINK_ID "link.id" /**< a link id */ diff --git a/src/pipewire/private.h b/src/pipewire/private.h index ebe0c6d5d..36f85bd9d 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -785,6 +785,7 @@ struct pw_impl_node { unsigned int lazy:1; /**< the graph is lazy scheduling */ unsigned int exclusive:1; /**< ports can only be linked once */ unsigned int leaf:1; /**< node only produces/consumes data */ + unsigned int reliable:1; /**< ports need reliable tee */ uint32_t transport; /**< latest transport request */ @@ -970,6 +971,7 @@ struct pw_impl_port { unsigned int ignore_latency:1; unsigned int have_latency:1; unsigned int exclusive:1; /**< port can only be linked once */ + unsigned int reliable:1; /**< port needs reliable tee */ unsigned int have_tag_param:1; struct spa_pod *tag[2]; /**< tags */ diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 46c0822a6..4085e0885 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -2507,6 +2507,16 @@ struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream) struct buffer *b; int res; + /* For reliable output streams, only give buffers when both queue AND output IO are clear */ + if (impl->direction == SPA_DIRECTION_OUTPUT && stream->node->reliable) { + struct spa_io_buffers *io = impl->io; + + if (!queue_is_empty(impl, &impl->queued) || io->status == SPA_STATUS_HAVE_DATA) { + errno = EAGAIN; + return NULL; + } + } + if ((b = queue_pop(impl, &impl->dequeued)) == NULL) { res = -errno; pw_log_trace_fp("%p: no more buffers: %m", stream); From 9f2d8737608518dc2a330ec2a11cd0023a30dc09 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 14 Oct 2025 11:37:24 +0200 Subject: [PATCH 0937/1014] examples: set exclusive and reliable flags We need exclusive port use if we negotiated SyncTimeline because there can only be one consumer of the syncobj. We also need to enable reliable transport if synctimeline is supported but the release flag isn't. Add some more logging to the port when the exclusive and reliable states changed. Fixes #4885 --- src/examples/video-play-sync.c | 38 +++++++++++--------- src/examples/video-src-sync.c | 64 ++++++++++++++++++++++++++-------- src/pipewire/impl-port.c | 21 ++++++----- 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/src/examples/video-play-sync.c b/src/examples/video-play-sync.c index 44b9b2cd6..3ef4ed619 100644 --- a/src/examples/video-play-sync.c +++ b/src/examples/video-play-sync.c @@ -59,6 +59,7 @@ struct data { bool with_synctimeline; bool with_synctimeline_release; + bool force_synctimeline_release; }; static void handle_events(struct data *data) @@ -371,8 +372,13 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_sync_timeline)), 0); if (data->with_synctimeline_release) { - /* drop features flags if not provided by both sides */ - spa_pod_builder_prop(&b, SPA_PARAM_META_features, SPA_POD_PROP_FLAG_DROP); + uint32_t flags = data->force_synctimeline_release ? + /* both sides need compatible features */ + SPA_POD_PROP_FLAG_MANDATORY : + /* drop features flags if not provided by both sides */ + SPA_POD_PROP_FLAG_DROP; + + spa_pod_builder_prop(&b, SPA_PARAM_META_features, flags); spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); } params[n_params++] = spa_pod_builder_pop(&b, &f); @@ -397,10 +403,6 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) pw_stream_update_params(stream, params, n_params); } -static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer) -{ -} - /* these are the stream events we listen for */ static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, @@ -408,7 +410,6 @@ static const struct pw_stream_events stream_events = { .io_changed = on_stream_io_changed, .param_changed = on_stream_param_changed, .process = on_process, - .add_buffer = on_stream_add_buffer, }; static int build_format(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params) @@ -453,6 +454,7 @@ static void show_help(struct data *data, const char *name, bool is_error) " -r, --remote Remote daemon name\n" " -S, --sync Enable SyncTimeline\n" " -R, --release Enable RELEASE feature\n" + " -F, --force-release RELEASE feature needs to be present\n" "\n", name); } @@ -465,11 +467,12 @@ int main(int argc, char *argv[]) struct pw_properties *props; int res, n_params; static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "remote", required_argument, NULL, 'r' }, - { "sync", no_argument, NULL, 'S' }, - { "release", no_argument, NULL, 'R' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "remote", required_argument, NULL, 'r' }, + { "sync", no_argument, NULL, 'S' }, + { "release", no_argument, NULL, 'R' }, + { "force-release", no_argument, NULL, 'F' }, { NULL, 0, NULL, 0} }; char *opt_remote = NULL; @@ -477,7 +480,7 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); - while ((c = getopt_long(argc, argv, "hVr:SR", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:SRF", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); @@ -493,11 +496,14 @@ int main(int argc, char *argv[]) case 'r': opt_remote = optarg; break; - case 'S': - data.with_synctimeline = true; - break; + case 'F': + data.force_synctimeline_release = true; + SPA_FALLTHROUGH; case 'R': data.with_synctimeline_release = true; + SPA_FALLTHROUGH; + case 'S': + data.with_synctimeline = true; break; default: show_help(&data, argv[0], true); diff --git a/src/examples/video-src-sync.c b/src/examples/video-src-sync.c index 890b2d8d9..873a27407 100644 --- a/src/examples/video-src-sync.c +++ b/src/examples/video-src-sync.c @@ -51,11 +51,13 @@ struct data { int counter; uint32_t seq; + uint32_t n_buffers; int res; bool with_synctimeline; bool with_synctimeline_release; + bool force_synctimeline_release; }; static void on_process(void *userdata) @@ -227,8 +229,13 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_sync_timeline)), 0); if (data->with_synctimeline_release) { - /* drop features flags if not provided by both sides */ - spa_pod_builder_prop(&b, SPA_PARAM_META_features, SPA_POD_PROP_FLAG_DROP); + uint32_t flags = data->force_synctimeline_release ? + /* both sides need compatible features */ + SPA_POD_PROP_FLAG_MANDATORY : + /* drop features flags if not provided by both sides */ + SPA_POD_PROP_FLAG_DROP; + + spa_pod_builder_prop(&b, SPA_PARAM_META_features, flags); spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); } params[n_params++] = spa_pod_builder_pop(&b, &f); @@ -330,15 +337,38 @@ static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer) return; } } - if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, - SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { - pw_log_debug("got sync timeline release"); + + if (data->n_buffers++ == 0) { + struct spa_dict_item items[2]; + uint32_t n_items = 0; + bool reliable = false, exclusive = false; + + if (s != NULL) { + /* sync timeline is always exclusive */ + exclusive = true; + if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, + SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { + pw_log_info("got sync timeline with release"); + } else { + pw_log_info("got sync timeline"); + /* we need reliable transport without release */ + reliable = true; + } + } + else { + pw_log_info("did not get sync timeline"); + } + items[n_items++] = SPA_DICT_ITEM(PW_KEY_NODE_EXCLUSIVE, exclusive ? "true" : "false"); + items[n_items++] = SPA_DICT_ITEM(PW_KEY_NODE_RELIABLE, reliable ? "true" : "false"); + + pw_stream_update_properties(data->stream, &SPA_DICT(items, n_items)); } } /* close the memfd we set on the buffers here */ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) { + struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; @@ -351,6 +381,7 @@ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) close(d[1].fd); close(d[2].fd); } + data->n_buffers--; } @@ -382,6 +413,7 @@ static void show_help(struct data *data, const char *name, bool is_error) " -r, --remote Remote daemon name\n" " -S, --sync Enable SyncTimeline\n" " -R, --release Enable RELEASE feature\n" + " -F, --force-release RELEASE feature needs to be present\n" "\n", name); } @@ -393,11 +425,12 @@ int main(int argc, char *argv[]) uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "remote", required_argument, NULL, 'r' }, - { "sync", no_argument, NULL, 'S' }, - { "release", no_argument, NULL, 'R' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "remote", required_argument, NULL, 'r' }, + { "sync", no_argument, NULL, 'S' }, + { "release", no_argument, NULL, 'R' }, + { "force-release", no_argument, NULL, 'F' }, { NULL, 0, NULL, 0} }; char *opt_remote = NULL; @@ -405,7 +438,7 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); - while ((c = getopt_long(argc, argv, "hVr:SR", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:SRF", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); @@ -421,11 +454,14 @@ int main(int argc, char *argv[]) case 'r': opt_remote = optarg; break; - case 'S': - data.with_synctimeline = true; - break; + case 'F': + data.force_synctimeline_release = true; + SPA_FALLTHROUGH; case 'R': data.with_synctimeline_release = true; + SPA_FALLTHROUGH; + case 'S': + data.with_synctimeline = true; break; default: show_help(&data, argv[0], true); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index b6b06d87c..27c771446 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -498,7 +498,7 @@ static int check_properties(struct pw_impl_port *port) { struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this); struct pw_impl_node *node = port->node; - bool is_control, is_network, is_monitor, is_device, is_duplex, is_virtual; + bool is_control, is_network, is_monitor, is_device, is_duplex, is_virtual, new_bool; const char *media_class, *override_device_prefix, *channel_names; const char *str, *prefix, *path, *desc, *nick, *name; const struct pw_properties *nprops; @@ -520,14 +520,19 @@ static int check_properties(struct pw_impl_port *port) } port->ignore_latency = pw_properties_get_bool(port->properties, PW_KEY_PORT_IGNORE_LATENCY, false); - port->exclusive = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); - port->reliable = pw_properties_get_bool(port->properties, PW_KEY_PORT_RELIABLE, node->reliable); + new_bool = pw_properties_get_bool(port->properties, PW_KEY_PORT_EXCLUSIVE, node->exclusive); + if (new_bool != port->exclusive) { + pw_log_info("%p: exclusive %d->%d", port, port->exclusive, new_bool); + port->exclusive = new_bool; + } - if (port->direction == PW_DIRECTION_OUTPUT) { - if (port->reliable) - impl->mix_node.iface.cb.funcs = &schedule_tee_node_reliable; - else - impl->mix_node.iface.cb.funcs = &schedule_tee_node; + new_bool = pw_properties_get_bool(port->properties, PW_KEY_PORT_RELIABLE, node->reliable); + if (new_bool != port->reliable && port->direction == PW_DIRECTION_OUTPUT) { + pw_log_info("%p: reliable %d->%d", port, port->reliable, new_bool); + port->reliable = new_bool; + impl->mix_node.iface.cb.funcs = new_bool ? + &schedule_tee_node_reliable : + &schedule_tee_node; } /* inherit passive state from parent node */ From 9b507d3210bfb368df93f3dc6f2e1dd67058e730 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 15 Oct 2025 09:26:36 +0200 Subject: [PATCH 0938/1014] context: add support for rlimit. = Add support for rlimit. = in the context.properties to adjust the rlimits of the process. A value of -1 sets the max limit. This can be used to increase the number of file descriptors in a pipewire process when select() is not used, for example. Other resource limits might be interesting as well maybe. Fixes #4047 --- doc/dox/config/pipewire.conf.5.md | 14 ++++++ src/daemon/pipewire-pulse.conf.in | 1 + src/daemon/pipewire.conf.in | 1 + src/pipewire/context.c | 84 +++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/doc/dox/config/pipewire.conf.5.md b/doc/dox/config/pipewire.conf.5.md index 57ea8c3de..617f04751 100644 --- a/doc/dox/config/pipewire.conf.5.md +++ b/doc/dox/config/pipewire.conf.5.md @@ -275,6 +275,20 @@ Warn about failures to lock memory. @PAR@ pipewire.conf mem.mlock-all = false Try to mlock all current and future memory by the process. +@PAR@ pipewire.conf rlimit.nofile = 4096 +Try to set the max file descriptor number resource limit of the process. +A value of -1 raises the limit to the system defined hard maximum value. +The file resource limit is usually 1024 and should only be raised if the +program does not use the select() system call. PipeWire does normally not +use select(). + +@PAR@ pipewire.conf rlimit.*resource* = *value* +Set resource limits. *resource* can be one of: as, core, cpu, +data, fsize, locks, memlock, msgqueue, nice, nofile, nproc, rss, rtprio, +rttime, sigpending or stack. See the documentation of setrlimit to get the +meaning of these resources. A value of -1 will set the maximum allowed +limit. + @PAR@ pipewire.conf settings.check-quantum = false Check if the quantum in the settings metadata update is compatible with the configured limits. diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index affb993dd..8d018606d 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -14,6 +14,7 @@ context.properties = { #mem.allow-mlock = true #mem.mlock-all = false #log.level = 2 + #rlimit.nofile = -1 #default.clock.quantum-limit = 8192 } diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 14f7c6ea0..c3eb7120f 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -21,6 +21,7 @@ context.properties = { #clock.power-of-two-quantum = true #log.level = 2 #cpu.zero.denormals = false + #rlimit.nofile = -1 #loop.rt-prio = -1 # -1 = use module-rt prio, 0 disable rt #loop.class = data.rt diff --git a/src/pipewire/context.c b/src/pipewire/context.c index f1d5e01b4..ddcdd2cba 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -300,6 +301,88 @@ static void data_loop_stop(struct impl *impl, struct data_loop *loop) loop->started = false; } +static int adjust_rlimit(int resource, const char *name, int value) +{ + struct rlimit rlim, highest, fixed; + + rlim = (struct rlimit) { .rlim_cur = value, .rlim_max = value, }; + + if (setrlimit(resource, &rlim) >= 0) { + pw_log_info("set rlimit %s to %d", name, value); + return 0; + } + if (errno != EPERM) + return -errno; + + /* So we failed to set the desired setrlimit, then let's try + * to get as close as we can */ + if (getrlimit(resource, &highest) < 0) + return -errno; + + /* If the hard limit is unbounded anyway, then the EPERM had other reasons, + * let's propagate the original EPERM then */ + if (highest.rlim_max == RLIM_INFINITY) + return -EPERM; + + fixed = (struct rlimit) { + .rlim_cur = SPA_MIN(rlim.rlim_cur, highest.rlim_max), + .rlim_max = SPA_MIN(rlim.rlim_max, highest.rlim_max), + }; + + /* Shortcut things if we wouldn't change anything. */ + if (fixed.rlim_cur == highest.rlim_cur && + fixed.rlim_max == highest.rlim_max) + return 0; + + pw_log_info("set rlimit %s to %d/%d instead of %d", name, + (int)fixed.rlim_cur, (int)fixed.rlim_max, value); + if (setrlimit(resource, &fixed) < 0) + return -errno; + + return 0; +} + +static int adjust_rlimits(const struct spa_dict *dict) +{ + const struct spa_dict_item *it; + static const char* rlimit_table[] = { + [RLIMIT_AS] = "as", + [RLIMIT_CORE] = "core", + [RLIMIT_CPU] = "cpu", + [RLIMIT_DATA] = "data", + [RLIMIT_FSIZE] = "fsize", + [RLIMIT_LOCKS] = "locks", + [RLIMIT_MEMLOCK] = "memlock", + [RLIMIT_MSGQUEUE] = "msgqueue", + [RLIMIT_NICE] = "nice", + [RLIMIT_NOFILE] = "nofile", + [RLIMIT_NPROC] = "nproc", + [RLIMIT_RSS] = "rss", + [RLIMIT_RTPRIO] = "rtprio", + [RLIMIT_RTTIME] = "rttime", + [RLIMIT_SIGPENDING] = "sigpending", + [RLIMIT_STACK] = "stack", + }; + int res; + spa_dict_for_each(it, dict) { + if (!spa_strstartswith(it->key, "rlimit.")) + continue; + for (size_t i = 0; i < SPA_N_ELEMENTS(rlimit_table); i++) { + const char *name = rlimit_table[i]; + int64_t val; + if (!spa_streq(it->key+7, name)) + continue; + if (!spa_atoi64(it->value, &val, 0)) { + pw_log_warn("invalid number %s", it->value); + } else if ((res = adjust_rlimit(i, name, val)) < 0) + pw_log_warn("can't set rlimit %s to %s: %s", + name, it->value, spa_strerror(res)); + break; + } + } + return 0; +} + /** Create a new context object * * \param main_loop the main loop to use @@ -420,6 +503,7 @@ struct pw_context *pw_context_new(struct pw_loop *main_loop, else pw_log_info("%p: mlockall succeeded", impl); } + adjust_rlimits(&properties->dict); pw_settings_init(this); this->settings = this->defaults; From f91aed5eebd7028d79cee6e254a16f2242461c14 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 15 Oct 2025 11:20:01 +0200 Subject: [PATCH 0939/1014] impl-link: pass error codes in link error state Pass the error code when setting the link in the error state. Add some more debug. --- src/pipewire/impl-client.c | 3 ++- src/pipewire/impl-link.c | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index 36b4370b2..a13a40a5a 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -139,7 +139,8 @@ static int client_error(void *object, uint32_t id, int res, const char *error) goto error_no_id; } - pw_log_debug("%p: sender %p: error for global %u", client, sender, id); + pw_log_debug("%p: sender %p: error for global %u: %s (%d)", + client, sender, id, error, res); pw_map_for_each(&client->objects, error_resource, &d); return 0; diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 99eea5a55..3760cbe04 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -104,8 +104,8 @@ static void link_update_state(struct pw_impl_link *link, enum pw_link_state stat pw_link_state_as_string(state), error); if (state == PW_LINK_STATE_ERROR) { - pw_log_error("(%s) %s -> error (%s) (%s-%s)", link->name, - pw_link_state_as_string(old), error, + pw_log_error("(%s) %s -> error %s (%d) (%s-%s)", link->name, + pw_link_state_as_string(old), error, res, pw_impl_port_state_as_string(link->output->state), pw_impl_port_state_as_string(link->input->state)); } else { @@ -174,7 +174,7 @@ static void complete_ready(void *obj, void *data, int res, uint32_t id) this->output->state >= PW_IMPL_PORT_STATE_READY) link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL); } else { - link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("Format negotiation failed")); + link_update_state(this, PW_LINK_STATE_ERROR, res, strdup("Format negotiation failed")); } } @@ -192,8 +192,8 @@ static void complete_paused(void *obj, void *data, int res, uint32_t id) return; } - pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, - port->state, spa_strerror(res)); + pw_log_debug("%p: obj:%p port %p complete state:%d: %s (%d)", this, obj, port, + port->state, spa_strerror(res), res); if (SPA_RESULT_IS_OK(res)) { if (port->state < PW_IMPL_PORT_STATE_PAUSED) @@ -205,7 +205,7 @@ static void complete_paused(void *obj, void *data, int res, uint32_t id) link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); } else { mix->have_buffers = false; - link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("Buffer allocation failed")); + link_update_state(this, PW_LINK_STATE_ERROR, res, strdup("Buffer allocation failed")); } } From c40e0d1d981d71c1e5d5a1cca7ec9a39f71fbc05 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 15 Oct 2025 14:05:29 +0200 Subject: [PATCH 0940/1014] module-rtp: unset ptp_fd or else we might close it --- src/modules/module-rtp-sap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 79af04f2e..6634dab6a 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -1873,6 +1873,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) return -errno; impl->sap_fd = -1; + impl->ptp_fd = -1; spa_list_init(&impl->sessions); if (args == NULL) From b220f85790d09deaffc61a032bf5bc883828ea4a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 15 Oct 2025 14:08:24 +0200 Subject: [PATCH 0941/1014] module-rtp-sap: reorganize generation of SDP Move the code to recalculate the hash and version into make_sdp. Add a boolean argument to the make_sdp function. Recalculate the hash and version when we are making a new SDP and leave the old values if we are making an SDP to compare against the current one. --- src/modules/module-rtp-sap.c | 95 ++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 6634dab6a..4dd824211 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -662,15 +662,24 @@ static bool update_ts_refclk(struct impl *impl) return gmid_changed; } -static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_t buffer_size) +static uint16_t generate_hash(uint16_t prev) +{ + uint16_t hash = pw_rand32(); + if (hash == prev) hash++; + if (hash == 0) hash++; + return hash; +} + +static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_t buffer_size, bool new) { char src_addr[64], dst_addr[64], dst_ttl[8], ptime[32]; struct sdp_info *sdp = &sess->info; bool src_ip4, dst_ip4; bool multicast; - const char *user_name; + const char *user_name, *str; struct spa_strbuf buf; int res; + struct pw_properties *props = sess->props; if ((res = pw_net_get_ip(&impl->src_addr, src_addr, sizeof(src_addr), &src_ip4, NULL)) < 0) return res; @@ -678,6 +687,30 @@ static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_ if ((res = pw_net_get_ip(&sdp->dst_addr, dst_addr, sizeof(dst_addr), &dst_ip4, NULL)) < 0) return res; + if (new) { + /* update the version and hash */ + sdp->hash = generate_hash(sdp->hash); + if ((str = pw_properties_get(props, "sess.id")) != NULL) { + if (!spa_atou32(str, &sdp->session_id, 10)) { + pw_log_error("Invalid session id: %s (must be a uint32)", str); + return -EINVAL; + } + sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", + (uint32_t) time(NULL) + 2208988800U + impl->n_sessions); + } else { + sdp->session_id = (uint32_t) time(NULL) + 2208988800U + impl->n_sessions; + sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", sdp->session_id); + } + if ((str = pw_properties_get(props, "sess.version")) != NULL) { + if (!spa_atou32(str, &sdp->session_version, 10)) { + pw_log_error("Invalid session version: %s (must be a uint32)", str); + return -EINVAL; + } + } else { + sdp->session_version = sdp->t_ntp; + } + } + if ((user_name = pw_get_user_name()) == NULL) user_name = "-"; @@ -839,7 +872,7 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) * socket needs to be open for us to get the interface address (which * happens above. So let's create the SDP now, if needed. */ if (!sess->has_sdp) { - res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp), true); if (res != 0) { pw_log_error("Failed to create SDP: %s", spa_strerror(res)); return res; @@ -887,20 +920,13 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) return res; } -static uint16_t generate_hash(uint16_t prev) -{ - uint16_t hash = pw_rand32(); - if (hash == prev) hash++; - if (hash == 0) hash++; - return hash; -} - static void on_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; struct session *sess, *tmp; uint64_t timestamp, interval; bool clk_changed; + int res; timestamp = get_time_nsec(impl); interval = impl->cleanup_interval * SPA_NSEC_PER_SEC; @@ -911,12 +937,12 @@ static void on_timer_event(void *data, uint64_t expirations) if (clk_changed) { // The clock has changed: Send bye and create new SDP. send_sap(impl, sess, 1); - sess->info.hash = generate_hash(sess->info.hash); - int res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); - if (res != 0) { + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp), true); + if (res != 0) pw_log_error("Failed to create SDP: %s", spa_strerror(res)); - } + else + sess->has_sdp = true; } send_sap(impl, sess, 0); } else { @@ -1045,44 +1071,19 @@ static struct session *session_new_announce(struct impl *impl, struct node *node /* see if we can make an SDP, will fail for the first session because we * haven't got the SAP socket open yet */ - res = make_sdp(impl, sess, buffer, sizeof(buffer)); + res = make_sdp(impl, sess, buffer, sizeof(buffer), false); /* we had no sdp or something changed */ if (!sess->has_sdp || ((res == 0) && strcmp(buffer, sess->sdp) != 0)) { /* send bye on the old session */ send_sap(impl, sess, 1); - /* update the version and hash */ - sdp->hash = generate_hash(sdp->hash); - if ((str = pw_properties_get(props, "sess.id")) != NULL) { - if (!spa_atou32(str, &sdp->session_id, 10)) { - pw_log_error("Invalid session id: %s (must be a uint32)", str); - goto error_free; - } - sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", - (uint32_t) time(NULL) + 2208988800U + impl->n_sessions); - } else { - sdp->session_id = (uint32_t) time(NULL) + 2208988800U + impl->n_sessions; - sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", sdp->session_id); - } - if ((str = pw_properties_get(props, "sess.version")) != NULL) { - if (!spa_atou32(str, &sdp->session_version, 10)) { - pw_log_error("Invalid session version: %s (must be a uint32)", str); - goto error_free; - } - } else { - sdp->session_version = sdp->t_ntp; - } - - if (res == 0) { - /* make an updated SDP for sending, this should not actually fail */ - res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); - - if (res == 0) - sess->has_sdp = true; - else - pw_log_error("Failed to create SDP: %s", spa_strerror(res)); - } + /* make an updated SDP for sending, this should not actually fail */ + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp), true); + if (res != 0) + pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + else + sess->has_sdp = true; } send_sap(impl, sess, 0); From a75cea96fbad4be3de516a6710f7c09defafd374 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 15 Oct 2025 16:57:24 +0200 Subject: [PATCH 0942/1014] modules: port modules to timer-queue Instead of using timerfd, use the context timer-queue to schedule timeouts. This saves fds and removes some redundant code. Make the rtp-source timeout and standby code a bit better by using atomic operations. --- src/modules/module-avb/avb.c | 1 + src/modules/module-avb/avdecc.c | 28 ++--- src/modules/module-avb/internal.h | 3 +- src/modules/module-netjack2-driver.c | 28 ++--- .../modules/module-combine-sink.c | 18 ++- src/modules/module-pulse-tunnel.c | 26 ++--- src/modules/module-raop-sink.c | 29 +++-- src/modules/module-rtp-sap.c | 27 ++--- src/modules/module-rtp-session.c | 101 +++++----------- src/modules/module-rtp-source.c | 108 ++++++++---------- src/modules/module-vban-recv.c | 26 ++--- 11 files changed, 157 insertions(+), 238 deletions(-) diff --git a/src/modules/module-avb/avb.c b/src/modules/module-avb/avb.c index 7bfa85eb9..eeacb7b7c 100644 --- a/src/modules/module-avb/avb.c +++ b/src/modules/module-avb/avb.c @@ -41,6 +41,7 @@ struct pw_avb *pw_avb_new(struct pw_context *context, impl->context = context; impl->loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); impl->props = props; impl->core = pw_context_get_object(context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c index fd80ec8ac..0e2b33c34 100644 --- a/src/modules/module-avb/avdecc.c +++ b/src/modules/module-avb/avdecc.c @@ -14,6 +14,7 @@ #include #include +#include #include @@ -39,12 +40,18 @@ #define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) #define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f) -static void on_timer_event(void *data, uint64_t expirations) +static void on_timer_event(void *data) { struct server *server = data; + struct impl *impl = server->impl; struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now)); + + pw_timer_queue_add(impl->timer_queue, &server->timer, + &server->timer.timeout, DEFAULT_INTERVAL * SPA_NSEC_PER_SEC, + on_timer_event, server); } static void on_socket_data(void *data, int fd, uint32_t mask) @@ -201,7 +208,6 @@ static int setup_socket(struct server *server) struct impl *impl = server->impl; int fd, res; static const uint8_t bmac[6] = AVB_BROADCAST_MAC; - struct timespec value, interval; fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); if (fd < 0) @@ -215,18 +221,13 @@ static int setup_socket(struct server *server) pw_log_error("server %p: can't create server source: %m", impl); goto error_no_source; } - server->timer = pw_loop_add_timer(impl->loop, on_timer_event, server); - if (server->timer == NULL) { - res = -errno; - pw_log_error("server %p: can't create timer source: %m", impl); + + if ((res = pw_timer_queue_add(impl->timer_queue, &server->timer, + NULL, DEFAULT_INTERVAL * SPA_NSEC_PER_SEC, + on_timer_event, server)) < 0) { + pw_log_error("server %p: can't create timer: %s", impl, spa_strerror(res)); goto error_no_timer; } - value.tv_sec = 0; - value.tv_nsec = 1; - interval.tv_sec = DEFAULT_INTERVAL; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, server->timer, &value, &interval, false); - return 0; error_no_timer: @@ -310,8 +311,7 @@ void avdecc_server_free(struct server *server) spa_list_remove(&server->link); if (server->source) pw_loop_destroy_source(impl->loop, server->source); - if (server->timer) - pw_loop_destroy_source(impl->loop, server->timer); + pw_timer_queue_cancel(&server->timer); spa_hook_list_clean(&server->listener_list); free(server); } diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h index 4a108a486..90222511a 100644 --- a/src/modules/module-avb/internal.h +++ b/src/modules/module-avb/internal.h @@ -19,6 +19,7 @@ struct avb_mrp; struct impl { struct pw_loop *loop; + struct pw_timer_queue *timer_queue; struct pw_context *context; struct spa_hook context_listener; struct pw_core *core; @@ -61,7 +62,7 @@ struct server { int ifindex; struct spa_source *source; - struct spa_source *timer; + struct pw_timer timer; struct spa_hook_list listener_list; diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 5cd93b253..cacdf9368 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -225,6 +225,7 @@ struct impl { struct pw_loop *main_loop; struct pw_loop *data_loop; struct spa_system *system; + struct pw_timer_queue *timer_queue; #define MODE_SINK (1<<0) #define MODE_SOURCE (1<<1) @@ -263,7 +264,7 @@ struct impl { struct spa_source *setup_socket; struct spa_source *socket; - struct spa_source *timer; + struct pw_timer timer; int32_t init_retry; struct netjack2_peer peer; @@ -801,14 +802,14 @@ error: return res; } +static void on_timer_event(void *data); + static void update_timer(struct impl *impl, uint64_t timeout) { - struct timespec value, interval; - value.tv_sec = 0; - value.tv_nsec = timeout ? 1 : 0; - interval.tv_sec = timeout; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->main_loop, impl->timer, &value, &interval, false); + pw_timer_queue_cancel(&impl->timer); + pw_timer_queue_add(impl->timer_queue, &impl->timer, + NULL, timeout * SPA_NSEC_PER_SEC, + on_timer_event, impl); } static bool encoding_supported(uint32_t encoder) @@ -1132,7 +1133,7 @@ static void restart_netjack2_socket(struct impl *impl) create_netjack2_socket(impl); } -static void on_timer_event(void *data, uint64_t expirations) +static void on_timer_event(void *data) { struct impl *impl = data; @@ -1193,8 +1194,7 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->main_loop, impl->timer); + pw_timer_queue_cancel(&impl->timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -1283,6 +1283,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } impl->main_loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); impl->system = impl->main_loop->system; impl->source.impl = impl; @@ -1370,13 +1371,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->timer = pw_loop_add_timer(impl->main_loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); - goto error; - } - if ((res = create_netjack2_socket(impl)) < 0) goto error; diff --git a/src/modules/module-protocol-pulse/modules/module-combine-sink.c b/src/modules/module-protocol-pulse/modules/module-combine-sink.c index aa5881b73..962c22293 100644 --- a/src/modules/module-protocol-pulse/modules/module-combine-sink.c +++ b/src/modules/module-protocol-pulse/modules/module-combine-sink.c @@ -71,7 +71,7 @@ struct module_combine_sink_data { struct pw_properties *combine_props; struct pw_properties *stream_props; - struct spa_source *sinks_timeout; + struct pw_timer sinks_timeout; unsigned int sinks_pending; unsigned int load_emitted:1; @@ -128,7 +128,7 @@ static const struct pw_manager_events manager_events = { .added = manager_added, }; -static void on_sinks_timeout(void *d, uint64_t count) +static void on_sinks_timeout(void *d) { struct module_combine_sink_data *data = d; @@ -214,13 +214,10 @@ static int module_combine_sink_load(struct module *module) pw_manager_add_listener(data->manager, &data->manager_listener, &manager_events, data); - data->sinks_timeout = pw_loop_add_timer(module->impl->main_loop, on_sinks_timeout, data); - if (data->sinks_timeout) { - struct timespec timeout = {0}; - timeout.tv_sec = TIMEOUT_SINKS_MSEC / 1000; - timeout.tv_nsec = (TIMEOUT_SINKS_MSEC % 1000) * SPA_NSEC_PER_MSEC; - pw_loop_update_timer(module->impl->main_loop, data->sinks_timeout, &timeout, NULL, false); - } + pw_timer_queue_add(module->impl->timer_queue, &data->sinks_timeout, + NULL, TIMEOUT_SINKS_MSEC * SPA_NSEC_PER_MSEC, + on_sinks_timeout, data); + return data->load_emitted ? 0 : SPA_RESULT_RETURN_ASYNC(0); } @@ -228,8 +225,7 @@ static int module_combine_sink_unload(struct module *module) { struct module_combine_sink_data *d = module->user_data; - if (d->sinks_timeout != NULL) - pw_loop_destroy_source(module->impl->main_loop, d->sinks_timeout); + pw_timer_queue_cancel(&d->sinks_timeout); if (d->mod != NULL) { spa_hook_remove(&d->mod_listener); diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 6e6a7f2e1..c9b9e5f9d 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -148,6 +148,7 @@ static const struct spa_dict_item module_props[] = { struct impl { struct pw_context *context; struct pw_loop *main_loop; + struct pw_timer_queue *timer_queue; #define MODE_SINK 0 #define MODE_SOURCE 1 @@ -194,7 +195,7 @@ struct impl { bool do_disconnect:1; bool stopping; - struct spa_source *timer; + struct pw_timer timer; uint32_t reconnect_interval_ms; bool recovering; }; @@ -525,7 +526,7 @@ static void cleanup_streams(struct impl *impl) pw_stream_destroy(impl->stream); } -static void on_timer_event(void *data, uint64_t expirations) +static void on_timer_event(void *data) { struct impl *impl = data; cleanup_streams(impl); @@ -538,13 +539,9 @@ do_schedule_recovery(struct spa_loop *loop, { struct impl *impl = user_data; if (impl->reconnect_interval_ms > 0) { - struct timespec value; - uint64_t timestamp; - - timestamp = impl->reconnect_interval_ms * SPA_NSEC_PER_MSEC; - value.tv_sec = timestamp / SPA_NSEC_PER_SEC; - value.tv_nsec = timestamp % SPA_NSEC_PER_SEC; - pw_loop_update_timer(impl->main_loop, impl->timer, &value, NULL, false); + pw_timer_queue_add(impl->timer_queue, &impl->timer, + NULL, impl->reconnect_interval_ms * SPA_NSEC_PER_MSEC, + on_timer_event, impl); } else { if (impl->module) pw_impl_module_schedule_destroy(impl->module); @@ -1029,8 +1026,7 @@ static void impl_destroy(struct impl *impl) pw_properties_free(impl->stream_props); pw_properties_free(impl->props); - if (impl->timer) - pw_loop_destroy_source(impl->main_loop, impl->timer); + pw_timer_queue_cancel(&impl->timer); free(impl->buffer); free(impl); @@ -1144,6 +1140,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; impl->main_loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); spa_ringbuffer_init(&impl->ring); impl->buffer = calloc(1, RINGBUFFER_SIZE); @@ -1165,13 +1162,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->reconnect_interval_ms = pw_properties_get_uint32(props, "reconnect.interval.ms", 0); - impl->timer = pw_loop_add_timer(impl->main_loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); - goto error; - } - impl->latency_msec = pw_properties_get_uint32(props, "pulse.latency", DEFAULT_LATENCY_MSEC); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 5f464628c..11948807b 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -212,6 +212,7 @@ struct impl { struct pw_impl_module *module; struct pw_loop *loop; + struct pw_timer_queue *timer_queue; struct spa_hook module_listener; @@ -245,7 +246,7 @@ struct impl { uint16_t control_port; int control_fd; struct spa_source *control_source; - struct spa_source *feedback_timer; + struct pw_timer feedback_timer; uint16_t timing_port; int timing_fd; @@ -836,12 +837,16 @@ static int rtsp_send_volume(struct impl *impl) return rtsp_send(impl, "SET_PARAMETER", "text/parameters", header, rtsp_log_reply_status); } -static void rtsp_do_post_feedback(void *data, uint64_t expirations) +static void rtsp_do_post_feedback(void *data) { struct impl *impl = data; pw_rtsp_client_url_send(impl->rtsp, "/feedback", "POST", &impl->headers->dict, NULL, NULL, 0, rtsp_log_reply_status, impl); + + pw_timer_queue_add(impl->timer_queue, &impl->feedback_timer, + &impl->feedback_timer.timeout, 2 * SPA_NSEC_PER_SEC, + rtsp_do_post_feedback, impl); } static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) @@ -854,7 +859,6 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head struct impl *impl = data; const char *str; char progress[128]; - struct timespec timeout, interval; struct spa_process_latency_info process_latency; pw_log_info("record status: %d", status); @@ -866,16 +870,12 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head return 0; } - timeout.tv_sec = 2; - timeout.tv_nsec = 0; - interval.tv_sec = 2; - interval.tv_nsec = 0; - // feedback timer is only needed for auth_setup encryption if (impl->encryption == CRYPTO_FP_SAP25) { - if (!impl->feedback_timer) - impl->feedback_timer = pw_loop_add_timer(impl->loop, rtsp_do_post_feedback, impl); - pw_loop_update_timer(impl->loop, impl->feedback_timer, &timeout, &interval, false); + pw_timer_queue_cancel(&impl->feedback_timer); + pw_timer_queue_add(impl->timer_queue, &impl->feedback_timer, + NULL, 2 * SPA_NSEC_PER_SEC, + rtsp_do_post_feedback, impl); } if ((str = spa_dict_lookup(headers, "Audio-Latency")) != NULL) { @@ -1476,10 +1476,8 @@ static void connection_cleanup(struct impl *impl) close(impl->timing_fd); impl->timing_fd = -1; } - if (impl->feedback_timer != NULL) { - pw_loop_destroy_source(impl->loop, impl->feedback_timer); - impl->feedback_timer = NULL; - } + pw_timer_queue_cancel(&impl->feedback_timer); + free(impl->auth_method); impl->auth_method = NULL; free(impl->realm); @@ -1792,6 +1790,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; impl->loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); ip = pw_properties_get(props, "raop.ip"); port = pw_properties_get(props, "raop.port"); diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 4dd824211..0c00beb61 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -251,6 +251,7 @@ struct impl { struct pw_properties *props; struct pw_loop *loop; + struct pw_timer_queue *timer_queue; struct pw_impl_module *module; struct spa_hook module_listener; @@ -263,7 +264,7 @@ struct impl { struct pw_registry *registry; struct spa_hook registry_listener; - struct spa_source *timer; + struct pw_timer timer; char *ifname; uint32_t ttl; @@ -920,7 +921,7 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) return res; } -static void on_timer_event(void *data, uint64_t expirations) +static void on_timer_event(void *data) { struct impl *impl = data; struct session *sess, *tmp; @@ -954,6 +955,9 @@ static void on_timer_event(void *data, uint64_t expirations) } } + pw_timer_queue_add(impl->timer_queue, &impl->timer, + &impl->timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, + on_timer_event, impl); } static struct session *session_find(struct impl *impl, const struct sdp_info *info) @@ -1647,22 +1651,15 @@ on_sap_io(void *data, int fd, uint32_t mask) static int start_sap(struct impl *impl) { int fd = -1, res; - struct timespec value, interval; char addr[128] = "invalid"; pw_log_info("starting SAP timer"); - impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); + if ((res = pw_timer_queue_add(impl->timer_queue, &impl->timer, + NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, + on_timer_event, impl)) < 0) { + pw_log_error("can't add timer: %s", spa_strerror(res)); goto error; } - value.tv_sec = 0; - value.tv_nsec = 1; - interval.tv_sec = SAP_INTERVAL_SEC; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); - if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) return fd; @@ -1809,8 +1806,7 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->loop, impl->timer); + pw_timer_queue_cancel(&impl->timer); if (impl->sap_source) pw_loop_destroy_source(impl->loop, impl->sap_source); @@ -1890,6 +1886,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); str = pw_properties_get(props, "local.ifname"); impl->ifname = str ? strdup(str) : NULL; diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index c77912fc5..2b69a0be7 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -204,7 +204,7 @@ struct session { #define SESSION_STATE_ESTABLISHED 4 int state; int ck_count; - uint64_t next_time; + struct pw_timer timer; uint32_t ctrl_initiator; uint32_t data_initiator; @@ -237,15 +237,13 @@ struct impl { struct pw_loop *loop; struct pw_loop *data_loop; + struct pw_timer_queue *timer_queue; struct pw_core *core; struct spa_hook core_listener; struct spa_hook core_proxy_listener; unsigned int do_disconnect:1; - struct spa_source *timer; - uint64_t next_time; - struct spa_source *ctrl_source; struct spa_source *data_source; @@ -276,34 +274,19 @@ static ssize_t send_packet(int fd, struct msghdr *msg) return n; } -static uint64_t current_time_ns(void) +static uint64_t current_time_ns(struct timespec *ts) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return SPA_TIMESPEC_TO_NSEC(&ts); + clock_gettime(CLOCK_MONOTONIC, ts); + return SPA_TIMESPEC_TO_NSEC(ts); } -static void set_timeout(struct impl *impl, uint64_t time) -{ - struct itimerspec ts; - ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; - ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; - ts.it_interval.tv_sec = 0; - ts.it_interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &ts.it_value, &ts.it_interval, true); - impl->next_time = time; -} +static void send_apple_midi_cmd_ck0(struct session *sess); -static void schedule_timeout(struct impl *impl) +static void on_timer_event(void *data) { - struct session *sess; - uint64_t next_time = 0; - spa_list_for_each(sess, &impl->sessions, link) { - if (next_time == 0 || - (sess->next_time != 0 && sess->next_time < next_time)) - next_time = sess->next_time; - } - set_timeout(impl, next_time); + struct session *sess = data; + pw_log_debug("timeout"); + send_apple_midi_cmd_ck0(sess); } static void send_apple_midi_cmd_ck0(struct session *sess) @@ -312,14 +295,14 @@ static void send_apple_midi_cmd_ck0(struct session *sess) struct iovec iov[3]; struct msghdr msg; struct rtp_apple_midi_ck hdr; - uint64_t current_time, ts; + struct timespec now; + uint64_t timeout, ts; spa_zero(hdr); hdr.cmd = htonl(APPLE_MIDI_CMD_CK); hdr.ssrc = htonl(sess->ssrc); - current_time = current_time_ns(); - ts = current_time / 10000; + ts = current_time_ns(&now) / 10000; hdr.ts1_h = htonl(ts >> 32); hdr.ts1_l = htonl(ts); @@ -335,11 +318,15 @@ static void send_apple_midi_cmd_ck0(struct session *sess) send_packet(impl->data_source->fd, &msg); if (sess->ck_count++ < 8) - sess->next_time = current_time + SPA_NSEC_PER_SEC; + timeout = 1; else if (sess->ck_count++ < 16) - sess->next_time = current_time + 2 * SPA_NSEC_PER_SEC; + timeout = 2; else - sess->next_time = current_time + 5 * SPA_NSEC_PER_SEC; + timeout = 5; + + pw_timer_queue_add(impl->timer_queue, &sess->timer, + &now, timeout * SPA_NSEC_PER_SEC, + on_timer_event, sess); } static void session_update_state(struct session *sess, int state) @@ -355,12 +342,10 @@ static void session_update_state(struct session *sess, int state) if (sess->we_initiated) { sess->ck_count = 0; send_apple_midi_cmd_ck0(sess); - schedule_timeout(sess->impl); } break; case SESSION_STATE_INIT: - sess->next_time = 0; - schedule_timeout(sess->impl); + pw_timer_queue_cancel(&sess->timer); break; default: break; @@ -601,6 +586,7 @@ static void free_session(struct session *sess) pw_loop_locked(impl->data_loop, do_unlink_session, 1, NULL, 0, sess); sess->impl->n_sessions--; + pw_timer_queue_cancel(&sess->timer); if (sess->send) rtp_stream_destroy(sess->send); @@ -859,8 +845,9 @@ static void parse_apple_midi_cmd_ck(struct impl *impl, bool ctrl, uint8_t *buffe struct msghdr msg; struct rtp_apple_midi_ck reply; struct session *sess; - uint64_t now, t1, t2, t3; + uint64_t ts, t1, t2, t3; uint32_t ssrc = ntohl(hdr->ssrc); + struct timespec now; sess = find_session_by_ssrc(impl, ssrc); if (sess == NULL) { @@ -870,7 +857,7 @@ static void parse_apple_midi_cmd_ck(struct impl *impl, bool ctrl, uint8_t *buffe pw_log_trace("got CK count %d", hdr->count); - now = current_time_ns() / 10000; + ts = current_time_ns(&now) / 10000; reply = *hdr; reply.ssrc = htonl(sess->ssrc); reply.count++; @@ -882,11 +869,11 @@ static void parse_apple_midi_cmd_ck(struct impl *impl, bool ctrl, uint8_t *buffe switch (hdr->count) { case 0: - t2 = now; + t2 = ts; break; case 1: t2 = ((uint64_t)ntohl(hdr->ts2_h) << 32) | ntohl(hdr->ts2_l); - t3 = now; + t3 = ts; break; case 2: t3 = ((uint64_t)ntohl(hdr->ts3_h) << 32) | ntohl(hdr->ts3_l); @@ -1223,8 +1210,6 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->loop, impl->timer); if (impl->ctrl_source) pw_loop_destroy_source(impl->loop, impl->ctrl_source); if (impl->data_source) @@ -1646,24 +1631,6 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda } } -static void on_timer_event(void *data, uint64_t expirations) -{ - struct impl *impl = data; - struct session *sess; - uint64_t current_time = impl->next_time; - - pw_log_debug("timeout"); - spa_list_for_each(sess, &impl->sessions, link) { - if (sess->state != SESSION_STATE_ESTABLISHED) - continue; - if (sess->next_time < current_time) - continue; - - send_apple_midi_cmd_ck0(sess); - } - schedule_timeout(impl); -} - static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; @@ -1681,7 +1648,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) struct pw_properties *props = NULL, *stream_props = NULL; uint16_t port; const char *str; - struct timespec value, interval; int res = 0; PW_LOG_TOPIC_INIT(mod_topic); @@ -1719,6 +1685,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); + impl->timer_queue = pw_context_get_timer_queue(context); pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); @@ -1820,18 +1787,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); - goto out; - } - value.tv_sec = 0; - value.tv_nsec = 1; - interval.tv_sec = 1; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); - if ((res = setup_apple_session(impl)) < 0) goto out; diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index cf497e354..63f1f399d 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -188,6 +188,7 @@ struct impl { struct pw_loop *main_loop; struct pw_loop *data_loop; + struct pw_timer_queue *timer_queue; struct pw_core *core; struct spa_hook core_listener; @@ -200,10 +201,10 @@ struct impl { bool always_process; uint32_t cleanup_interval; - struct spa_source *standby_timer; + struct pw_timer standby_timer; /* This timer is used when the first stream_start() call fails because * of an ENODEV error (see the stream_start() code for details) */ - struct spa_source *stream_start_retry_timer; + struct pw_timer stream_start_retry_timer; struct pw_properties *stream_props; struct rtp_stream *stream; @@ -216,7 +217,11 @@ struct impl { uint8_t *buffer; size_t buffer_size; - bool receiving; +#define STATE_IDLE 0 +#define STATE_PROBE 1 +#define STATE_RECEIVING 2 +#define STATE_STOPPING 3 + int state; bool may_pause; bool standby; bool waiting; @@ -269,9 +274,11 @@ on_rtp_io(void *data, int fd, uint32_t mask) goto receive_error; } - if (!impl->receiving) { - impl->receiving = true; - pw_loop_invoke(impl->main_loop, do_start, 1, NULL, 0, false, impl); + if (SPA_ATOMIC_LOAD(impl->state) != STATE_RECEIVING) { + if (!SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_RECEIVING)) { + if (SPA_ATOMIC_CAS(impl->state, STATE_IDLE, STATE_RECEIVING)) + pw_loop_invoke(impl->main_loop, do_start, 1, NULL, 0, false, impl); + } } } return; @@ -385,23 +392,6 @@ error: return res; } -static void stream_open_connection(void *data, int *result); - -static void on_open_connection_retry_timer_event(void *data, uint64_t expirations) -{ - struct impl *impl = data; - pw_log_debug("trying again to open connection after previous attempt failed with ENODEV"); - stream_open_connection(impl, NULL); -} - -static void destroy_stream_start_retry_timer(struct impl *impl) -{ - if (impl->stream_start_retry_timer != NULL) { - pw_loop_destroy_source(impl->main_loop, impl->stream_start_retry_timer); - impl->stream_start_retry_timer = NULL; - } -} - static void stream_report_error(void *data, const char *error) { struct impl *impl = data; @@ -411,6 +401,15 @@ static void stream_report_error(void *data, const char *error) } } +static void stream_open_connection(void *data, int *result); + +static void on_open_connection_retry_timer_event(void *data) +{ + struct impl *impl = data; + pw_log_debug("trying again to open connection after previous attempt failed with ENODEV"); + stream_open_connection(impl, NULL); +} + static void stream_open_connection(void *data, int *result) { int res = 0; @@ -438,21 +437,12 @@ static void stream_open_connection(void *data, int *result) pw_log_warn("failed to create socket because network device is not ready " "and present yet; will try again"); - if (impl->stream_start_retry_timer == NULL) { - struct timespec value, interval; - - impl->stream_start_retry_timer = pw_loop_add_timer(impl->main_loop, - on_open_connection_retry_timer_event, impl); - /* Use a 1-second retry interval. The network interfaces - * are likely to be up and running then. */ - value.tv_sec = 1; - value.tv_nsec = 0; - interval.tv_sec = 1; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->main_loop, impl->stream_start_retry_timer, &value, - &interval, false); - } - /* Do nothing if the timer is already up. */ + pw_timer_queue_cancel(&impl->stream_start_retry_timer); + /* Use a 1-second retry interval. The network interfaces + * are likely to be up and running then. */ + pw_timer_queue_add(impl->timer_queue, &impl->stream_start_retry_timer, + NULL, 1 * SPA_NSEC_PER_SEC, + on_open_connection_retry_timer_event, impl); /* It is important to return 0 in this case. Otherwise, the nonzero return * value will later be propagated through the core as an error. */ @@ -463,7 +453,7 @@ static void stream_open_connection(void *data, int *result) /* If ENODEV was returned earlier, and the stream_start_retry_timer * was consequently created, but then a non-ENODEV error occurred, * the timer must be stopped and removed. */ - destroy_stream_start_retry_timer(impl); + pw_timer_queue_cancel(&impl->stream_start_retry_timer); res = -errno; goto finish; } @@ -471,7 +461,7 @@ static void stream_open_connection(void *data, int *result) /* Cleanup the timer in case ENODEV occurred earlier, and this time, * the socket creation succeeded. */ - destroy_stream_start_retry_timer(impl); + pw_timer_queue_cancel(&impl->stream_start_retry_timer); impl->source = pw_loop_add_io(impl->data_loop, fd, SPA_IO_IN, true, on_rtp_io, impl); @@ -504,7 +494,7 @@ static void stream_close_connection(void *data, int *result) pw_log_info("stopping RTP listener"); - destroy_stream_start_retry_timer(impl); + pw_timer_queue_cancel(&impl->stream_start_retry_timer); pw_loop_destroy_source(impl->data_loop, impl->source); impl->source = NULL; @@ -586,14 +576,14 @@ static const struct rtp_stream_events stream_events = { .param_changed = stream_param_changed, }; -static void on_standby_timer_event(void *data, uint64_t expirations) +static void on_standby_timer_event(void *data) { struct impl *impl = data; - pw_log_debug("standby timer event; receiving: %d standby: %d waiting: %d", - impl->receiving, impl->standby, impl->waiting); + pw_log_debug("standby timer event; state: %d standby: %d waiting: %d", + impl->state, impl->standby, impl->waiting); - if (!impl->receiving) { + if (SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_STOPPING)) { if (!impl->standby) { struct spa_dict_item item[1]; @@ -608,10 +598,15 @@ static void on_standby_timer_event(void *data, uint64_t expirations) rtp_stream_set_active(impl->stream, false); } //pw_impl_module_schedule_destroy(impl->module); + SPA_ATOMIC_STORE(impl->state, STATE_IDLE); } else { pw_log_debug("timeout, keeping active RTP source"); + SPA_ATOMIC_CAS(impl->state, STATE_RECEIVING, STATE_PROBE); } - impl->receiving = false; + + pw_timer_queue_add(impl->timer_queue, &impl->standby_timer, + &impl->standby_timer.timeout, impl->cleanup_interval * SPA_NSEC_PER_SEC, + on_standby_timer_event, impl); } static void core_destroy(void *d) @@ -636,10 +631,8 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->standby_timer) - pw_loop_destroy_source(impl->main_loop, impl->standby_timer); - - destroy_stream_start_retry_timer(impl); + pw_timer_queue_cancel(&impl->standby_timer); + pw_timer_queue_cancel(&impl->stream_start_retry_timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -695,7 +688,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; const char *str, *sess_name; - struct timespec value, interval; struct pw_properties *props, *stream_props; int64_t ts_offset; char addr[128]; @@ -722,6 +714,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; impl->main_loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; @@ -830,17 +823,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->standby_timer = pw_loop_add_timer(impl->main_loop, on_standby_timer_event, impl); - if (impl->standby_timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); + if ((res = pw_timer_queue_add(impl->timer_queue, &impl->standby_timer, + NULL, impl->cleanup_interval * SPA_NSEC_PER_SEC, + on_standby_timer_event, impl)) < 0) { + pw_log_error("can't add timer: %s", spa_strerror(res)); goto out; } - value.tv_sec = impl->cleanup_interval; - value.tv_nsec = 0; - interval.tv_sec = impl->cleanup_interval; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->main_loop, impl->standby_timer, &value, &interval, false); impl->stream = rtp_stream_new(impl->core, PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props), diff --git a/src/modules/module-vban-recv.c b/src/modules/module-vban-recv.c index 53bad5252..26dc7b507 100644 --- a/src/modules/module-vban-recv.c +++ b/src/modules/module-vban-recv.c @@ -168,6 +168,7 @@ struct impl { struct pw_loop *main_loop; struct pw_loop *data_loop; + struct pw_timer_queue *timer_queue; struct pw_core *core; struct spa_hook core_listener; @@ -180,7 +181,7 @@ struct impl { struct pw_properties *stream_props; - struct spa_source *timer; + struct pw_timer timer; uint16_t src_port; struct sockaddr_storage src_addr; @@ -562,7 +563,7 @@ static void destroy_stream(struct stream *s) free(s); } -static void on_timer_event(void *data, uint64_t expirations) +static void on_timer_event(void *data) { struct impl *impl = data; struct stream *s; @@ -576,6 +577,9 @@ static void on_timer_event(void *data, uint64_t expirations) } s->receiving = false; } + pw_timer_queue_add(impl->timer_queue, &impl->timer, + &impl->timer.timeout, impl->cleanup_interval * SPA_NSEC_PER_SEC, + on_timer_event, impl); } static void core_destroy(void *d) @@ -602,8 +606,7 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - if (impl->timer) - pw_loop_destroy_source(impl->main_loop, impl->timer); + pw_timer_queue_cancel(&impl->timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -658,7 +661,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; const char *str; - struct timespec value, interval; struct pw_properties *props, *stream_props; int res = 0; @@ -682,6 +684,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; impl->main_loop = pw_context_get_main_loop(context); + impl->timer_queue = pw_context_get_timer_queue(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); spa_list_init(&impl->streams); @@ -747,17 +750,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->timer = pw_loop_add_timer(impl->main_loop, on_timer_event, impl); - if (impl->timer == NULL) { - res = -errno; - pw_log_error("can't create timer source: %m"); + if ((res = pw_timer_queue_add(impl->timer_queue, &impl->timer, + NULL, impl->cleanup_interval * SPA_NSEC_PER_SEC, + on_timer_event, impl)) < 0) { + pw_log_error("can't add timer: %s", spa_strerror(res)); goto out; } - value.tv_sec = impl->cleanup_interval; - value.tv_nsec = 0; - interval.tv_sec = impl->cleanup_interval; - interval.tv_nsec = 0; - pw_loop_update_timer(impl->main_loop, impl->timer, &value, &interval, false); if ((res = listen_start(impl)) < 0) { pw_log_error("failed to start VBAN stream: %s", spa_strerror(res)); From da2cecf07484521540a28c680c4a19392d93d833 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 15 Oct 2025 20:11:36 +0300 Subject: [PATCH 0943/1014] alsa: acp: don't disable dB if negative max unless range is small Disabling dB volumes for max_dB < 0 was added in Pulseaudio in 2021, based on a device which had -128..-127.07 range. However, negative max_dB is valid value for USB devices, and there are devices that have it. Eg. Microsoft LifeChat LX-3000 has numid=6,iface=MIXER,name='Speaker Playback Volume' ; type=INTEGER,access=rw---R--,values=2,min=0,max=151,step=0 : values=150,150 | dBminmax-min=-28.37dB,max=-0.06dB and the dB range seems to be OK. Web search for "The decibel volume range for element" also gives other hits with seemingly OK looking ranges. Don't disable dB volume unless both the max is negative and the range is suspiciously small. This should still disable it for the device this check was originally added for. Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/447 Link: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/commit/10ac01a2066b3d0a58ecc3e3db98dd43c284a209 --- spa/plugins/alsa/acp/alsa-mixer.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index ec9726d8c..b18d7c6c9 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -1719,11 +1719,15 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { else e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; - /* Assume decibel data to be incorrect if max_dB is negative. */ - if (e->has_dB && max_dB < 0 && !e->db_fix) { + /* Assume decibel data to be incorrect if max_dB is negative and dB range is + * suspiciously small (< 10 dB). This can happen eg. if USB device is using volume + * values as arbitrary scale ignoring USB standard on their meaning. + */ + if (e->has_dB && max_dB < 0 && SPA_ABS(max_dB - min_dB) < 10*100 && !e->db_fix) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); - pa_log_warn("The decibel volume range for element %s (%li dB - %li dB) has negative maximum. " - "Disabling the decibel range.", buf, min_dB, max_dB); + pa_log_warn("The decibel volume range for element %s (%0.2f dB to %0.2f dB) has negative maximum " + "and suspiciously small range. " + "Disabling the decibel range.", buf, min_dB/100.0, max_dB/100.0); e->has_dB = false; } From 0b78a2d97c8950db8739c762cf195fef410efba8 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 15 Oct 2025 23:06:29 +0300 Subject: [PATCH 0944/1014] ci: bump Fedora version to Fedora 42 This brings newer Doxygen. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac10fb674..9cfde68d0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,8 +38,8 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-07-17.0' - FDO_DISTRIBUTION_VERSION: '41' + FDO_DISTRIBUTION_TAG: '2025-10-15.0' + FDO_DISTRIBUTION_VERSION: '42' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel avahi-devel From 453ca31214315ddba9bd4d08f2120cdd2612294e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 15 Oct 2025 23:16:21 +0300 Subject: [PATCH 0945/1014] doc: update doxygen-awesome.css Update doxygen-awesome.css to v2.4.1 (1f3620084ff7573) from upstream. Link: https://github.com/jothepro/doxygen-awesome-css/ --- doc/custom.css | 4 +- doc/doxygen-awesome.css | 2224 ++++++++++++++++++++++++++++++++++----- 2 files changed, 1942 insertions(+), 286 deletions(-) diff --git a/doc/custom.css b/doc/custom.css index 46e58ddae..59a2a94d4 100644 --- a/doc/custom.css +++ b/doc/custom.css @@ -1,4 +1,4 @@ -:root { +html { /* --page-background-color: #729fcf; */ --primary-color: #729fcf; --primary-dark-color: #729fcf; @@ -8,7 +8,7 @@ } @media (prefers-color-scheme: light) { - :root { + html { --code-background: #f5f5f5; --code-foreground: #333333; --fragment-background: #f5f5f5; diff --git a/doc/doxygen-awesome.css b/doc/doxygen-awesome.css index 6000d2f56..f992c2329 100644 --- a/doc/doxygen-awesome.css +++ b/doc/doxygen-awesome.css @@ -1,101 +1,92 @@ +/* SPDX-License-Identifier: MIT */ /** Doxygen Awesome https://github.com/jothepro/doxygen-awesome-css -MIT License - -Copyright (c) 2021 jothepro - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +Copyright (c) 2021 - 2025 jothepro */ -:root { +html { /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ - --primary-color: #1982d2; - --primary-dark-color: #00559f; - --primary-light-color: #7aabd6; - --primary-lighter-color: #cae1f1; - --primary-lightest-color: #e9f1f8; + --primary-color: #1779c4; + --primary-dark-color: #335c80; + --primary-light-color: #70b1e9; + --on-primary-color: #ffffff; + + --link-color: var(--primary-color); /* page base colors */ - --page-background-color: white; - --page-foreground-color: #2c3e50; - --page-secondary-foreground-color: #67727e; + --page-background-color: #ffffff; + --page-foreground-color: #2f4153; + --page-secondary-foreground-color: #6f7e8e; /* color for all separators on the website: hr, borders, ... */ --separator-color: #dedede; /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ - --border-radius-large: 8px; - --border-radius-small: 4px; - --border-radius-medium: 6px; + --border-radius-large: 10px; + --border-radius-small: 5px; + --border-radius-medium: 8px; - /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ + /* default spacings. Most components reference these values for spacing, to provide uniform spacing on the page. */ --spacing-small: 5px; --spacing-medium: 10px; --spacing-large: 16px; + --spacing-xlarge: 20px; - /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ - --box-shadow: 0 2px 10px 0 rgba(0,0,0,.1); + /* default box shadow used for raising an element above the normal content. Used in dropdowns, search result, ... */ + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); - --odd-color: rgba(0,0,0,.03); + --odd-color: rgba(0,0,0,.028); /* font-families. will affect all text on the website * font-family: the normal font for text, headlines, menus * font-family-monospace: used for preformatted text in memtitle, code, fragments */ --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; - --font-family-monospace: source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace; + --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; /* font sizes */ --page-font-size: 15.6px; --navigation-font-size: 14.4px; - --code-font-size: 14.4px; /* affects code, fragment */ + --toc-font-size: 13.4px; + --code-font-size: 14px; /* affects code, fragment */ --title-font-size: 22px; /* content text properties. These only affect the page content, not the navigation or any other ui elements */ --content-line-height: 27px; /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ - --content-maxwidth: 900px; + --content-maxwidth: 1050px; + --table-line-height: 24px; + --toc-sticky-top: var(--spacing-medium); + --toc-width: 200px; + --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 85px); /* colors for various content boxes: @warning, @note, @deprecated @bug */ - --warning-color: #fca49b; - --warning-color-dark: #b61825; - --warning-color-darker: #75070f; - --note-color: rgba(255,229,100,.3); - --note-color-dark: #c39900; - --note-color-darker: #8d7400; - --deprecated-color: rgb(214, 216, 224); + --warning-color: #faf3d8; + --warning-color-dark: #f3a600; + --warning-color-darker: #5f4204; + --note-color: #e4f3ff; + --note-color-dark: #1879C4; + --note-color-darker: #274a5c; + --todo-color: #e4dafd; + --todo-color-dark: #5b2bdd; + --todo-color-darker: #2a0d72; + --deprecated-color: #ecf0f3; --deprecated-color-dark: #5b6269; --deprecated-color-darker: #43454a; - --bug-color: rgb(246, 208, 178); - --bug-color-dark: #a53a00; - --bug-color-darker: #5b1d00; - --invariant-color: #b7f8d0; - --invariant-color-dark: #00ba44; - --invariant-color-darker: #008622; + --bug-color: #f8d1cc; + --bug-color-dark: #b61825; + --bug-color-darker: #75070f; + --invariant-color: #d8f1e3; + --invariant-color-dark: #44b86f; + --invariant-color-darker: #265532; /* blockquote colors */ - --blockquote-background: #f5f5f5; - --blockquote-foreground: #727272; + --blockquote-background: #f8f9fa; + --blockquote-foreground: #636568; /* table colors */ --tablehead-background: #f1f1f1; @@ -107,7 +98,7 @@ SOFTWARE. */ --menu-display: block; - --menu-focus-foreground: var(--page-background-color); + --menu-focus-foreground: var(--on-primary-color); --menu-focus-background: var(--primary-color); --menu-selected-background: rgba(0,0,0,.05); @@ -124,14 +115,168 @@ SOFTWARE. * on smaller screens the searchbar will always fill the entire screen width) */ --searchbar-height: 33px; --searchbar-width: 210px; + --searchbar-border-radius: var(--searchbar-height); /* code block colors */ --code-background: #f5f5f5; --code-foreground: var(--page-foreground-color); /* fragment colors */ + --fragment-background: #F8F9FA; + --fragment-foreground: #37474F; + --fragment-keyword: #bb6bb2; + --fragment-keywordtype: #8258b3; + --fragment-keywordflow: #d67c3b; + --fragment-token: #438a59; + --fragment-comment: #969696; + --fragment-link: #5383d6; + --fragment-preprocessor: #46aaa5; + --fragment-linenumber-color: #797979; + --fragment-linenumber-background: #f4f4f5; + --fragment-linenumber-border: #e3e5e7; + --fragment-lineheight: 20px; + + /* sidebar navigation (treeview) colors */ + --side-nav-background: #fbfbfb; + --side-nav-foreground: var(--page-foreground-color); + --side-nav-arrow-opacity: 0; + --side-nav-arrow-hover-opacity: 0.9; + + --toc-background: var(--side-nav-background); + --toc-foreground: var(--side-nav-foreground); + + /* height of an item in any tree / collapsible table */ + --tree-item-height: 30px; + + --memname-font-size: var(--code-font-size); + --memtitle-font-size: 18px; + + --webkit-scrollbar-size: 7px; + --webkit-scrollbar-padding: 4px; + --webkit-scrollbar-color: var(--separator-color); + + --animation-duration: .12s +} + +@media screen and (max-width: 767px) { + html { + --page-font-size: 16px; + --navigation-font-size: 16px; + --toc-font-size: 15px; + --code-font-size: 15px; /* affects code, fragment */ + --title-font-size: 22px; + } +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #3b2e04; + --warning-color-dark: #f1b602; + --warning-color-darker: #ceb670; + --note-color: #163750; + --note-color-dark: #1982D2; + --note-color-darker: #dcf0fa; + --todo-color: #2a2536; + --todo-color-dark: #7661b3; + --todo-color-darker: #ae9ed6; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2e1917; + --bug-color-dark: #ad2617; + --bug-color-darker: #f5b1aa; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + + --fragment-background: #282c34; + --fragment-foreground: #dbe4eb; + --fragment-keyword: #cc99cd; + --fragment-keywordtype: #ab99cd; + --fragment-keywordflow: #e08000; + --fragment-token: #7ec699; + --fragment-comment: #999999; + --fragment-link: #98c0e3; + --fragment-preprocessor: #65cabe; + --fragment-linenumber-color: #cccccc; + --fragment-linenumber-background: #35393c; + --fragment-linenumber-border: #1f1f1f; + } +} + +/* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ +html.dark-mode { + color-scheme: dark; + + --primary-color: #1982d2; + --primary-dark-color: #86a9c4; + --primary-light-color: #4779ac; + + --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); + + --odd-color: rgba(100,100,100,.06); + + --menu-selected-background: rgba(0,0,0,.4); + + --page-background-color: #1C1D1F; + --page-foreground-color: #d2dbde; + --page-secondary-foreground-color: #859399; + --separator-color: #38393b; + --side-nav-background: #252628; + + --code-background: #2a2c2f; + + --tablehead-background: #2a2c2f; + + --blockquote-background: #222325; + --blockquote-foreground: #7e8c92; + + --warning-color: #3b2e04; + --warning-color-dark: #f1b602; + --warning-color-darker: #ceb670; + --note-color: #163750; + --note-color-dark: #1982D2; + --note-color-darker: #dcf0fa; + --todo-color: #2a2536; + --todo-color-dark: #7661b3; + --todo-color-darker: #ae9ed6; + --deprecated-color: #2e323b; + --deprecated-color-dark: #738396; + --deprecated-color-darker: #abb0bd; + --bug-color: #2e1917; + --bug-color-dark: #ad2617; + --bug-color-darker: #f5b1aa; + --invariant-color: #303a35; + --invariant-color-dark: #76ce96; + --invariant-color-darker: #cceed5; + --fragment-background: #282c34; - --fragment-foreground: #ffffff; + --fragment-foreground: #dbe4eb; --fragment-keyword: #cc99cd; --fragment-keywordtype: #ab99cd; --fragment-keywordflow: #e08000; @@ -142,66 +287,6 @@ SOFTWARE. --fragment-linenumber-color: #cccccc; --fragment-linenumber-background: #35393c; --fragment-linenumber-border: #1f1f1f; - --fragment-lineheight: 20px; - - /* sidebar navigation (treeview) colors */ - --side-nav-background: #fbfbfb; - --side-nav-foreground: var(--page-foreground-color); - --side-nav-arrow-color: var(--page-background-color); - - /* height of an item in any tree / collapsible table */ - --tree-item-height: 30px; -} - -@media screen and (max-width: 767px) { - :root { - --page-font-size: 16px; - --navigation-font-size: 16px; - --code-font-size: 15px; /* affects code, fragment */ - --title-font-size: 22px; - } -} - -@media (prefers-color-scheme: dark) { - :root { - --primary-color: #00559f; - --primary-dark-color: #1982d2; - --primary-light-color: #4779ac; - --primary-lighter-color: #191e21; - --primary-lightest-color: #191a1c; - - --box-shadow: 0 2px 10px 0 rgba(0,0,0,.35); - - --odd-color: rgba(0,0,0,.1); - - --menu-selected-background: rgba(0,0,0,.4); - - --page-background-color: #1C1D1F; - --page-foreground-color: #d2dbde; - --page-secondary-foreground-color: #859399; - --separator-color: #000000; - --side-nav-background: #252628; - - --code-background: #2a2c2f; - - --tablehead-background: #2a2c2f; - - --blockquote-background: #1f2022; - --blockquote-foreground: #77848a; - - --warning-color: #b61825; - --warning-color-dark: #510a02; - --warning-color-darker: #f5b1aa; - --note-color: rgb(255, 183, 0); - --note-color-dark: #9f7300; - --note-color-darker: #fff6df; - --deprecated-color: rgb(88, 90, 96); - --deprecated-color-dark: #262e37; - --deprecated-color-darker: #a0a5b0; - --bug-color: rgb(248, 113, 0); - --bug-color-dark: #812a00; - --bug-color-darker: #ffd3be; - } } body { @@ -210,22 +295,41 @@ body { font-size: var(--page-font-size); } -body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { +body, table, div, p, dl, #nav-tree .label, #nav-tree a, .title, +.sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, +.SelectItem, #MSearchField, .navpath li.navelem a, +.navpath li.navelem a:hover, p.reference, p.definition, div.toc li, div.toc h3, +#page-nav ul.page-outline li a { font-family: var(--font-family); } h1, h2, h3, h4, h5 { - margin-top: .9em; + margin-top: 1em; font-weight: 600; line-height: initial; } -p, div, table, dl { +p, div, table, dl, p.reference, p.definition { font-size: var(--page-font-size); } -a, a.el:visited, a.el:hover, a.el:focus, a.el:active { - color: var(--primary-dark-color); +p.reference, p.definition { + color: var(--page-secondary-foreground-color); +} + +a:link, a:visited, a:hover, a:focus, a:active { + color: var(--link-color) !important; + font-weight: 500; + background: none; +} + +a:hover { + text-decoration: underline; +} + +a.anchor { + scroll-margin-top: var(--spacing-large); + display: block; } /* @@ -235,6 +339,8 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { #top { background: var(--header-background); border-bottom: 1px solid var(--separator-color); + position: relative; + z-index: 99; } @media screen and (min-width: 768px) { @@ -249,6 +355,7 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { #main-nav { flex-grow: 5; padding: var(--spacing-small) var(--spacing-medium); + border-bottom: 0; } #titlearea { @@ -303,10 +410,23 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { margin-bottom: -1px; } +.main-menu-btn-icon, .main-menu-btn-icon:before, .main-menu-btn-icon:after { + background: var(--page-secondary-foreground-color); +} + @media screen and (max-width: 767px) { .sm-dox a span.sub-arrow { background: var(--code-background); } + + #main-menu a.has-submenu span.sub-arrow { + color: var(--page-secondary-foreground-color); + border-radius: var(--border-radius-medium); + } + + #main-menu a.has-submenu:hover span.sub-arrow { + color: var(--page-foreground-color); + } } @media screen and (min-width: 768px) { @@ -315,19 +435,36 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { } .sm-dox a span.sub-arrow { - border-color: var(--header-foreground) transparent transparent transparent; + top: 15px; + right: 10px; + box-sizing: content-box; + padding: 0; + margin: 0; + display: inline-block; + width: 5px; + height: 5px; + transform: rotate(45deg); + border-width: 0; + border-right: 2px solid var(--header-foreground); + border-bottom: 2px solid var(--header-foreground); + background: none; } .sm-dox a:hover span.sub-arrow { - border-color: var(--menu-focus-foreground) transparent transparent transparent; + border-color: var(--menu-focus-foreground); + background: none; } .sm-dox ul a span.sub-arrow { - border-color: transparent transparent transparent var(--header-foreground); + transform: rotate(-45deg); + border-width: 0; + border-right: 2px solid var(--header-foreground); + border-bottom: 2px solid var(--header-foreground); } .sm-dox ul a:hover span.sub-arrow { - border-color: transparent transparent transparent var(--menu-focus-foreground); + border-color: var(--menu-focus-foreground); + background: none; } } @@ -353,8 +490,8 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { } .sm-dox ul a { - color: var(--page-foreground-color); - background: var(--page-background-color); + color: var(--page-foreground-color) !important; + background: none; font-size: var(--navigation-font-size); } @@ -367,8 +504,8 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { } .sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { - font-size: var(--navigation-font-size); - color: var(--menu-focus-foreground); + font-size: var(--navigation-font-size) !important; + color: var(--menu-focus-foreground) !important; text-shadow: none; background-color: var(--menu-focus-background); border-radius: var(--border-radius-small) !important; @@ -378,9 +515,10 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { text-shadow: none; background: transparent; background-image: none !important; - color: var(--header-foreground); + color: var(--header-foreground) !important; font-weight: normal; font-size: var(--navigation-font-size); + border-radius: var(--border-radius-small) !important; } .sm-dox a:focus { @@ -391,7 +529,7 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { text-shadow: none; font-weight: normal; background: var(--menu-focus-background); - color: var(--menu-focus-foreground); + color: var(--menu-focus-foreground) !important; border-radius: var(--border-radius-small) !important; font-size: var(--navigation-font-size); } @@ -417,7 +555,7 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { #MSearchBox { height: var(--searchbar-height); background: var(--searchbar-background); - border-radius: var(--searchbar-height); + border-radius: var(--searchbar-border-radius); border: 1px solid var(--separator-color); overflow: hidden; width: var(--searchbar-width); @@ -425,10 +563,47 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { box-shadow: none; display: block; margin-top: 0; + margin-right: 0; } -.left #MSearchSelect { +@media (min-width: 768px) { + .sm-dox li { + padding: 0; + } +} + +/* until Doxygen 1.9.4 */ +.left img#MSearchSelect { left: 0; + user-select: none; + padding-left: 8px; +} + +/* Doxygen 1.9.5 */ +.left span#MSearchSelect { + left: 0; + user-select: none; + margin-left: 8px; + padding: 0; +} + +.left #MSearchSelect[src$=".png"] { + padding-left: 0 +} + +/* Doxygen 1.14.0 */ +.search-icon::before { + background: none; + top: 5px; +} + +.search-icon::after { + background: none; + top: 12px; +} + +.SelectionMark { + user-select: none; } .tabs .left #MSearchSelect { @@ -483,16 +658,15 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { top: calc(calc(var(--searchbar-height) / 2) - 11px); } -.left #MSearchSelect { - padding-left: 8px; -} - #MSearchBox span.left, #MSearchBox span.right { background: none; + background-image: none; } #MSearchBox span.right { padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); + position: absolute; + right: var(--spacing-small); } .tabs #MSearchBox span.right { @@ -523,15 +697,39 @@ a, a.el:visited, a.el:hover, a.el:focus, a.el:active { } iframe#MSearchResults { - background: var(--page-background-color); margin: 4px; } +iframe { + color-scheme: normal; +} + +@media (prefers-color-scheme: dark) { + html:not(.light-mode) iframe#MSearchResults { + filter: invert() hue-rotate(180deg); + } +} + +html.dark-mode iframe#MSearchResults { + filter: invert() hue-rotate(180deg); +} + +#MSearchResults .SRPage { + background-color: transparent; +} + +#MSearchResults .SRPage .SREntry { + font-size: 10pt; + padding: var(--spacing-small) var(--spacing-medium); +} + #MSearchSelectWindow { border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); box-shadow: var(--box-shadow); background: var(--page-background-color); + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); } #MSearchSelectWindow a.SelectItem { @@ -539,12 +737,13 @@ iframe#MSearchResults { line-height: var(--content-line-height); margin: 0 var(--spacing-small); border-radius: var(--border-radius-small); - color: var(--page-foreground-color); + color: var(--page-foreground-color) !important; + font-weight: normal; } #MSearchSelectWindow a.SelectItem:hover { background: var(--menu-focus-background); - color: var(--menu-focus-foreground); + color: var(--menu-focus-foreground) !important; } @media screen and (max-width: 767px) { @@ -559,7 +758,7 @@ iframe#MSearchResults { } #MSearchField { - width: calc(100vw - 95px); + width: calc(100vw - 110px); } @keyframes slideInSearchResultsMobile { @@ -580,6 +779,24 @@ iframe#MSearchResults { overflow: auto; transform: translate(0, 20px); animation: ease-out 280ms slideInSearchResultsMobile; + width: auto !important; + } + + /* + * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 + */ + label.main-menu-btn ~ #searchBoxPos1 { + top: 3px !important; + right: 6px !important; + left: 45px; + display: flex; + } + + label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { + margin-top: 0; + margin-bottom: 0; + flex-grow: 2; + float: left; } } @@ -588,8 +805,13 @@ iframe#MSearchResults { */ #side-nav { - padding: 0 !important; - background: var(--side-nav-background); + min-width: 8px; + max-width: 50vw; +} + + +#nav-tree, #top { + border-right: 1px solid var(--separator-color); } @media screen and (max-width: 767px) { @@ -599,67 +821,168 @@ iframe#MSearchResults { #doc-content { margin-left: 0 !important; - height: auto !important; - padding-bottom: calc(2 * var(--spacing-large)); + } + + #top { + border-right: none; } } #nav-tree { - background: transparent; + background: var(--side-nav-background); + margin-right: -1px; + padding: 0; } #nav-tree .label { font-size: var(--navigation-font-size); + line-height: var(--tree-item-height); +} + +#nav-tree span.label a:hover { + background: none; } #nav-tree .item { height: var(--tree-item-height); line-height: var(--tree-item-height); + overflow: hidden; + text-overflow: ellipsis; + margin: 0; + padding: 0; +} + +#nav-tree-contents { + margin: 0; +} + +#main-menu > li:last-child { + height: auto; +} + +#nav-tree .item > a:focus { + outline: none; } #nav-sync { - top: 12px !important; - right: 12px; + bottom: var(--spacing-medium); + right: var(--spacing-medium) !important; + top: auto !important; + user-select: none; +} + +div.nav-sync-icon { + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + background: var(--page-background-color); + width: 30px; + height: 20px; +} + +div.nav-sync-icon:hover { + background: var(--page-background-color); +} + +span.sync-icon-left, div.nav-sync-icon:hover span.sync-icon-left { + border-left: 2px solid var(--primary-color); + border-top: 2px solid var(--primary-color); + top: 5px; + left: 6px; +} +span.sync-icon-right, div.nav-sync-icon:hover span.sync-icon-right { + border-right: 2px solid var(--primary-color); + border-bottom: 2px solid var(--primary-color); + top: 5px; + left: initial; + right: 6px; +} + +div.nav-sync-icon.active::after, div.nav-sync-icon.active:hover::after { + border-top: 2px solid var(--primary-color); + top: 9px; + left: 6px; + width: 19px; } #nav-tree .selected { text-shadow: none; background-image: none; background-color: transparent; - box-shadow: inset 4px 0 0 0 var(--primary-dark-color); + position: relative; + color: var(--primary-color) !important; + font-weight: 500; } +#nav-tree .selected::after { + content: ""; + position: absolute; + top: 1px; + bottom: 1px; + left: 0; + width: 4px; + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + background: var(--primary-color); +} + + #nav-tree a { - color: var(--side-nav-foreground); + color: var(--side-nav-foreground) !important; + font-weight: normal; } #nav-tree a:focus { outline-style: auto; } -.arrow { - color: var(--primary-light-color); - font-family: serif; - height: auto; - text-align: right; +#nav-tree .arrow { + opacity: var(--side-nav-arrow-opacity); + background: none; } -#nav-tree .arrow { +#nav-tree span.arrowhead { + margin: 0 0 1px 2px; +} + +span.arrowhead { + border-color: var(--primary-light-color); +} + +.selected span.arrowhead { + border-color: var(--primary-color); +} + +#nav-tree-contents > ul > li:first-child > div > a { opacity: 0; + pointer-events: none; +} + +.contents .arrow { + color: inherit; + cursor: pointer; + font-size: 45%; + vertical-align: middle; + margin-right: 2px; + font-family: serif; + height: auto; + padding-bottom: 4px; } #nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { - opacity: 1; + opacity: var(--side-nav-arrow-hover-opacity); } #nav-tree .selected a { - color: var(--primary-dark-color); + color: var(--primary-color) !important; font-weight: bolder; + font-weight: 600; } .ui-resizable-e { + background: none; +} + +.ui-resizable-e:hover { background: var(--separator-color); - width: 1px; } /* @@ -668,10 +991,25 @@ iframe#MSearchResults { div.header { border-bottom: 1px solid var(--separator-color); - background-color: var(--page-background-color); + background: none; background-image: none; } +@media screen and (min-width: 1000px) { + #doc-content > div > div.contents, + .PageDoc > div.contents { + display: flex; + flex-direction: row-reverse; + flex-wrap: nowrap; + align-items: flex-start; + } + + div.contents .textblock { + min-width: 200px; + flex-grow: 1; + } +} + div.contents, div.header .title, div.header .summary { max-width: var(--content-maxwidth); } @@ -691,8 +1029,8 @@ div.headertitle { div.header .title { font-weight: 600; - font-size: 210%; - padding: var(--spacing-medium) var(--spacing-large); + font-size: 225%; + padding: var(--spacing-medium) var(--spacing-xlarge); word-break: break-word; } @@ -707,19 +1045,12 @@ td.memSeparator { border-color: var(--separator-color); } -.mdescLeft, .mdescRight, .memItemLeft, .memItemRight, .memTemplItemLeft, .memTemplItemRight, .memTemplParams { - background: var(--code-background); -} - -.mdescRight { - color: var(--page-secondary-foreground-color); -} - span.mlabel { background: var(--primary-color); + color: var(--on-primary-color); border: none; padding: 4px 9px; - border-radius: 12px; + border-radius: var(--border-radius-large); margin-right: var(--spacing-medium); } @@ -728,7 +1059,7 @@ span.mlabel:last-of-type { } div.contents { - padding: 0 var(--spacing-large); + padding: 0 var(--spacing-xlarge); } div.contents p, div.contents li { @@ -739,88 +1070,345 @@ div.contents div.dyncontent { margin: var(--spacing-medium) 0; } -@media (prefers-color-scheme: dark) { - div.contents div.dyncontent img { - filter: hue-rotate(180deg) invert(); +@media screen and (max-width: 767px) { + div.contents { + padding: 0 var(--spacing-large); + } + + div.header .title { + padding: var(--spacing-medium) var(--spacing-large); } } -h2.groupheader { - border-bottom: 1px solid var(--separator-color); +@media (prefers-color-scheme: dark) { + html:not(.light-mode) div.contents div.dyncontent img, + html:not(.light-mode) div.contents center img, + html:not(.light-mode) div.contents > table img, + html:not(.light-mode) div.contents div.dyncontent iframe, + html:not(.light-mode) div.contents center iframe, + html:not(.light-mode) div.contents table iframe, + html:not(.light-mode) div.contents .dotgraph iframe { + filter: brightness(89%) hue-rotate(180deg) invert(); + } +} + +html.dark-mode div.contents div.dyncontent img, +html.dark-mode div.contents center img, +html.dark-mode div.contents > table img, +html.dark-mode div.contents div.dyncontent iframe, +html.dark-mode div.contents center iframe, +html.dark-mode div.contents table iframe, +html.dark-mode div.contents .dotgraph iframe + { + filter: brightness(89%) hue-rotate(180deg) invert(); +} + +td h2.groupheader, h2.groupheader { + border-bottom: 0px; color: var(--page-foreground-color); + box-shadow: + 100px 0 var(--page-background-color), + -100px 0 var(--page-background-color), + 100px 0.75px var(--separator-color), + -100px 0.75px var(--separator-color), + 500px 0 var(--page-background-color), + -500px 0 var(--page-background-color), + 500px 0.75px var(--separator-color), + -500px 0.75px var(--separator-color), + 900px 0 var(--page-background-color), + -900px 0 var(--page-background-color), + 900px 0.75px var(--separator-color), + -900px 0.75px var(--separator-color), + 1400px 0 var(--page-background-color), + -1400px 0 var(--page-background-color), + 1400px 0.75px var(--separator-color), + -1400px 0.75px var(--separator-color), + 1900px 0 var(--page-background-color), + -1900px 0 var(--page-background-color), + 1900px 0.75px var(--separator-color), + -1900px 0.75px var(--separator-color); } blockquote { - padding: var(--spacing-small) var(--spacing-medium); + margin: 0 var(--spacing-medium) 0 var(--spacing-medium); + padding: var(--spacing-small) var(--spacing-large); background: var(--blockquote-background); color: var(--blockquote-foreground); - border-left: 2px solid var(--blockquote-foreground); - margin: 0; + border-left: 0; + overflow: visible; + border-radius: var(--border-radius-medium); + overflow: visible; + position: relative; +} + +blockquote::before, blockquote::after { + font-weight: bold; + font-family: serif; + font-size: 360%; + opacity: .15; + position: absolute; +} + +blockquote::before { + content: "“"; + left: -10px; + top: 4px; +} + +blockquote::after { + content: "”"; + right: -8px; + bottom: -25px; } blockquote p { margin: var(--spacing-small) 0 var(--spacing-medium) 0; } -.paramname { +.paramname, .paramname em { + font-weight: 600; color: var(--primary-dark-color); } -.glow { - text-shadow: 0 0 15px var(--primary-light-color) !important; +.paramname > code { + border: 0; +} + +table.params .paramname { + font-weight: 600; + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + padding-right: var(--spacing-small); + line-height: var(--table-line-height); +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--primary-light-color); } .alphachar a { color: var(--page-foreground-color); } +.dotgraph { + max-width: 100%; + overflow-x: scroll; +} + +.dotgraph .caption { + position: sticky; + left: 0; +} + +/* Wrap Graphviz graphs with the `interactive_dotgraph` class if `INTERACTIVE_SVG = YES` */ +.interactive_dotgraph .dotgraph iframe { + max-width: 100%; +} + /* Table of Contents */ -div.toc { - background-color: var(--side-nav-background); - border: 1px solid var(--separator-color); - border-radius: var(--border-radius-medium); - box-shadow: var(--box-shadow); +div.contents .toc { + max-height: var(--toc-max-height); + min-width: var(--toc-width); + border: 0; + border-left: 1px solid var(--separator-color); + border-radius: 0; + background-color: var(--page-background-color); + box-shadow: none; + position: sticky; + top: var(--toc-sticky-top); padding: 0 var(--spacing-large); - margin: 0 0 var(--spacing-medium) var(--spacing-medium); + margin: var(--spacing-small) 0 var(--spacing-large) var(--spacing-large); } div.toc h3 { - color: var(--side-nav-foreground); + color: var(--toc-foreground); font-size: var(--navigation-font-size); - margin: var(--spacing-large) 0; + margin: var(--spacing-large) 0 var(--spacing-medium) 0; } div.toc li { - font-size: var(--navigation-font-size); padding: 0; background: none; + line-height: var(--toc-font-size); + margin: var(--toc-font-size) 0 0 0; } -div.toc li:before { - content: '↓'; - font-weight: 800; - font-family: var(--font-family); - margin-right: var(--spacing-small); - color: var(--side-nav-foreground); - opacity: .4; +div.toc li::before { + display: none; } -div.toc ul li.level1 { - margin: 0; +div.toc ul { + margin-top: 0 } -div.toc ul li.level2, div.toc ul li.level3 { - margin-top: 0; +div.toc li a { + font-size: var(--toc-font-size); + color: var(--page-foreground-color) !important; + text-decoration: none; +} + +div.toc li a:hover, div.toc li a.active { + color: var(--primary-color) !important; +} + +div.toc li a.aboveActive { + color: var(--page-secondary-foreground-color) !important; } -@media screen and (max-width: 767px) { - div.toc { +@media screen and (max-width: 999px) { + div.contents .toc { + max-height: 45vh; float: none; width: auto; margin: 0 0 var(--spacing-medium) 0; + position: relative; + top: 0; + position: relative; + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + background-color: var(--toc-background); + box-shadow: var(--box-shadow); + } + + div.contents .toc.interactive { + max-height: calc(var(--navigation-font-size) + 2 * var(--spacing-large)); + overflow: hidden; + } + + div.contents .toc > h3 { + -webkit-tap-highlight-color: transparent; + cursor: pointer; + position: sticky; + top: 0; + background-color: var(--toc-background); + margin: 0; + padding: var(--spacing-large) 0; + display: block; + } + + div.contents .toc.interactive > h3::before { + content: ""; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--primary-color); + display: inline-block; + margin-right: var(--spacing-small); + margin-bottom: calc(var(--navigation-font-size) / 4); + transform: rotate(-90deg); + transition: transform var(--animation-duration) ease-out; + } + + div.contents .toc.interactive.open > h3::before { + transform: rotate(0deg); + } + + div.contents .toc.interactive.open { + max-height: 45vh; + overflow: auto; + transition: max-height 0.2s ease-in-out; + } + + div.contents .toc a, div.contents .toc a.active { + color: var(--primary-color) !important; + } + + div.contents .toc a:hover { + text-decoration: underline; + } +} + +/* + Page Outline (Doxygen >= 1.14.0) +*/ + +#page-nav { + background: var(--page-background-color); + border-left: 1px solid var(--separator-color); +} + +#page-nav #page-nav-resize-handle { + background: var(--separator-color); +} + +#page-nav #page-nav-resize-handle::after { + border-left: 1px solid var(--primary-color); + border-right: 1px solid var(--primary-color); +} + +#page-nav #page-nav-tree #page-nav-contents { + top: var(--spacing-large); +} + +#page-nav ul.page-outline { + margin: 0; + padding: 0; +} + +#page-nav ul.page-outline li a { + font-size: var(--toc-font-size) !important; + color: var(--page-secondary-foreground-color) !important; + display: inline-block; + line-height: calc(2 * var(--toc-font-size)); +} + +#page-nav ul.page-outline li a a.anchorlink { + display: none; +} + +#page-nav ul.page-outline li.vis ~ * a { + color: var(--page-foreground-color) !important; +} + +#page-nav ul.page-outline li.vis:not(.vis ~ .vis) a, #page-nav ul.page-outline li a:hover { + color: var(--primary-color) !important; +} + +#page-nav ul.page-outline .vis { + background: var(--page-background-color); + position: relative; +} + +#page-nav ul.page-outline .vis::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 4px; + background: var(--page-secondary-foreground-color); +} + +#page-nav ul.page-outline .vis:not(.vis ~ .vis)::after { + top: 1px; + border-top-right-radius: var(--border-radius-small); +} + +#page-nav ul.page-outline .vis:not(:has(~ .vis))::after { + bottom: 1px; + border-bottom-right-radius: var(--border-radius-small); +} + + +#page-nav ul.page-outline .arrow { + display: inline-block; +} + +#page-nav ul.page-outline .arrow span { + display: none; +} + +@media screen and (max-width: 767px) { + #container { + grid-template-columns: initial !important; + } + + #page-nav { + display: none; } } @@ -828,23 +1416,23 @@ div.toc ul li.level2, div.toc ul li.level3 { Code & Fragments */ -code, div.fragment, pre.fragment { - border-radius: var(--border-radius-small); - border: none; +code, div.fragment, pre.fragment, span.tt { + border: 1px solid var(--separator-color); overflow: hidden; } -code { +code, span.tt { display: inline; background: var(--code-background); color: var(--code-foreground); padding: 2px 6px; - word-break: break-word; + border-radius: var(--border-radius-small); } div.fragment, pre.fragment { + border-radius: var(--border-radius-medium); margin: var(--spacing-medium) 0; - padding: 14px 16px; + padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); background: var(--fragment-background); color: var(--fragment-foreground); overflow-x: auto; @@ -854,28 +1442,53 @@ div.fragment, pre.fragment { div.fragment, pre.fragment { border-top-right-radius: 0; border-bottom-right-radius: 0; + border-right: 0; } - .contents > div.fragment, .textblock > div.fragment, .textblock > pre.fragment { + .contents > div.fragment, + .textblock > div.fragment, + .textblock > pre.fragment, + .textblock > .tabbed > ul > li > div.fragment, + .textblock > .tabbed > ul > li > pre.fragment, + .contents > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment, + .textblock > .tabbed > ul > li > .doxygen-awesome-fragment-wrapper > div.fragment, + .textblock > .tabbed > ul > li > .doxygen-awesome-fragment-wrapper > pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); border-radius: 0; + border-left: 0; } - .textblock li > .fragment { + .textblock li > .fragment, + .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); } - .memdoc li > .fragment { + .memdoc li > .fragment, + .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); } - .memdoc > div.fragment, .memdoc > pre.fragment, dl dd > div.fragment, dl dd pre.fragment { + .textblock ul, .memdoc ul { + overflow: initial; + } + + .memdoc > div.fragment, + .memdoc > pre.fragment, + dl dd > div.fragment, + dl dd pre.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, + .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, + dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, + dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); border-radius: 0; + border-left: 0; } } -code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { +code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span, span.tt { font-family: var(--font-family-monospace); font-size: var(--code-font-size) !important; } @@ -911,7 +1524,7 @@ div.fragment span.comment { } div.fragment a.code { - color: var(--fragment-link); + color: var(--fragment-link) !important; } div.fragment span.preprocessor { @@ -928,18 +1541,36 @@ div.fragment span.lineno { div.fragment span.lineno a { background: none; - color: var(--fragment-link); + color: var(--fragment-link) !important; } -div.fragment .line:first-child .lineno { +div.fragment > .line:first-child .lineno { box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); + background-color: var(--fragment-linenumber-background) !important; +} + +div.line { + border-radius: var(--border-radius-small); +} + +div.line.glow { + background-color: var(--primary-light-color); + box-shadow: none; } /* dl warning, attention, note, deprecated, bug, ... */ -dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre { +dl { + line-height: calc(1.65 * var(--page-font-size)); +} + +dl.bug dt a, dl.deprecated dt a, dl.todo dt a { + font-weight: bold !important; +} + +dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.post, dl.todo, dl.remark { padding: var(--spacing-medium); margin: var(--spacing-medium) 0; color: var(--page-background-color); @@ -962,16 +1593,30 @@ dl.warning dt, dl.attention dt { color: var(--warning-color-dark); } -dl.note { +dl.note, dl.remark { background: var(--note-color); border-left: 8px solid var(--note-color-dark); color: var(--note-color-darker); } -dl.note dt { +dl.note dt, dl.remark dt { color: var(--note-color-dark); } +dl.todo { + background: var(--todo-color); + border-left: 8px solid var(--todo-color-dark); + color: var(--todo-color-darker); +} + +dl.todo dt a { + color: var(--todo-color-dark) !important; +} + +dl.bug dt a { + color: var(--todo-color-dark) !important; +} + dl.bug { background: var(--bug-color); border-left: 8px solid var(--bug-color-dark); @@ -992,16 +1637,20 @@ dl.deprecated dt a { color: var(--deprecated-color-dark) !important; } -dl.section dd, dl.bug dd, dl.deprecated dd { +dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { margin-inline-start: 0px; } -dl.invariant, dl.pre { +dl.invariant, dl.pre, dl.post { background: var(--invariant-color); border-left: 8px solid var(--invariant-color-dark); color: var(--invariant-color-darker); } +dl.invariant dt, dl.pre dt, dl.post dt { + color: var(--invariant-color-dark); +} + /* memitem */ @@ -1019,38 +1668,70 @@ div.memdoc { h2.memtitle, div.memitem { border: 1px solid var(--separator-color); + box-shadow: var(--box-shadow); +} + +h2.memtitle { + box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); +} + +div.memitem { + transition: none; } div.memproto, h2.memtitle { - background: var(--code-background); - text-shadow: none; + background: var(--fragment-background); } h2.memtitle { font-weight: 500; - font-family: monospace, fixed; + font-size: var(--memtitle-font-size); + font-family: var(--font-family-monospace); border-bottom: none; border-top-left-radius: var(--border-radius-medium); border-top-right-radius: var(--border-radius-medium); word-break: break-all; + position: relative; +} + +h2.memtitle:after { + content: ""; + display: block; + background: var(--fragment-background); + height: var(--spacing-medium); + bottom: calc(0px - var(--spacing-medium)); + left: 0; + right: -14px; + position: absolute; + border-top-right-radius: var(--border-radius-medium); +} + +h2.memtitle > span.permalink { + font-size: inherit; +} + +h2.memtitle > span.permalink > a { + text-decoration: none; + padding-left: 3px; + margin-right: -4px; + user-select: none; + display: inline-block; + margin-top: -6px; +} + +h2.memtitle > span.permalink > a:hover { + color: var(--primary-dark-color) !important; } a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { border-color: var(--primary-light-color); } -a:target + h2.memtitle { - box-shadow: -3px -3px 3px 0 var(--primary-lightest-color), 3px -3px 3px 0 var(--primary-lightest-color); -} - -a:target + h2.memtitle + div.memitem { - box-shadow: 0 0 10px 0 var(--primary-lighter-color); -} - div.memitem { border-top-right-radius: var(--border-radius-medium); border-bottom-right-radius: var(--border-radius-medium); border-bottom-left-radius: var(--border-radius-medium); + border-top-left-radius: 0; overflow: hidden; display: block !important; } @@ -1073,8 +1754,18 @@ div.memtitle { } div.memproto table.memname { - font-family: monospace, fixed; + font-family: var(--font-family-monospace); color: var(--page-foreground-color); + font-size: var(--memname-font-size); + text-shadow: none; +} + +div.memproto div.memtemplate { + font-family: var(--font-family-monospace); + color: var(--primary-dark-color); + font-size: var(--memname-font-size); + margin-left: 2px; + text-shadow: none; } table.mlabels, table.mlabels > tbody { @@ -1085,6 +1776,12 @@ td.mlabels-left { width: auto; } +td.mlabels-right { + margin-top: 3px; + position: sticky; + left: 0; +} + table.mlabels > tbody > tr:first-child { display: flex; justify-content: space-between; @@ -1100,6 +1797,7 @@ table.mlabels > tbody > tr:first-child { */ dl.reflist { + box-shadow: var(--box-shadow); border-radius: var(--border-radius-medium); border: 1px solid var(--separator-color); overflow: hidden; @@ -1117,6 +1815,7 @@ dl.reflist dt, dl.reflist dd { dl.reflist dt { + font-weight: 500; border-radius: 0; background: var(--code-background); border-bottom: 1px solid var(--separator-color); @@ -1132,55 +1831,426 @@ dl.reflist dd { Table */ -table.markdownTable, table.fieldtable { - width: 100%; - border: 1px solid var(--separator-color); - margin: var(--spacing-medium) 0; +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname), +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody { + display: inline-block; + max-width: 100%; } -table.fieldtable { - box-shadow: none; +.contents > table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):not(.classindex) { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); +} + +table.fieldtable, +table.markdownTable tbody, +table.doxtable tbody { + border: none; + margin: var(--spacing-medium) 0; + box-shadow: 0 0 0 1px var(--separator-color); border-radius: var(--border-radius-small); } -th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { +table.markdownTable, table.doxtable, table.fieldtable { + padding: 1px; +} + +table.doxtable caption { + display: block; +} + +table.fieldtable { + border-collapse: collapse; + width: 100%; +} + +th.markdownTableHeadLeft, +th.markdownTableHeadRight, +th.markdownTableHeadCenter, +th.markdownTableHeadNone, +table.doxtable th { background: var(--tablehead-background); color: var(--tablehead-foreground); font-weight: 600; + font-size: var(--page-font-size); } -table.markdownTable td, table.markdownTable th, table.fieldtable dt { +th.markdownTableHeadLeft:first-child, +th.markdownTableHeadRight:first-child, +th.markdownTableHeadCenter:first-child, +th.markdownTableHeadNone:first-child, +table.doxtable tr th:first-child { + border-top-left-radius: var(--border-radius-small); +} + +th.markdownTableHeadLeft:last-child, +th.markdownTableHeadRight:last-child, +th.markdownTableHeadCenter:last-child, +th.markdownTableHeadNone:last-child, +table.doxtable tr th:last-child { + border-top-right-radius: var(--border-radius-small); +} + +table.markdownTable td, +table.markdownTable th, +table.fieldtable td, +table.fieldtable th, +table.doxtable td, +table.doxtable th { border: 1px solid var(--separator-color); padding: var(--spacing-small) var(--spacing-medium); } +table.markdownTable td:last-child, +table.markdownTable th:last-child, +table.fieldtable td:last-child, +table.fieldtable th:last-child, +table.doxtable td:last-child, +table.doxtable th:last-child { + border-right: none; +} + +table.markdownTable td:first-child, +table.markdownTable th:first-child, +table.fieldtable td:first-child, +table.fieldtable th:first-child, +table.doxtable td:first-child, +table.doxtable th:first-child { + border-left: none; +} + +table.markdownTable tr:first-child td, +table.markdownTable tr:first-child th, +table.fieldtable tr:first-child td, +table.fieldtable tr:first-child th, +table.doxtable tr:first-child td, +table.doxtable tr:first-child th { + border-top: none; +} + +table.markdownTable tr:last-child td, +table.markdownTable tr:last-child th, +table.fieldtable tr:last-child td, +table.fieldtable tr:last-child th, +table.doxtable tr:last-child td, +table.doxtable tr:last-child th { + border-bottom: none; +} + +table.markdownTable tr, table.doxtable tr { + border-bottom: 1px solid var(--separator-color); +} + +table.markdownTable tr:last-child, table.doxtable tr:last-child { + border-bottom: none; +} + +.full_width_table table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { + display: block; +} + +.full_width_table table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody { + display: table; + width: 100%; +} + table.fieldtable th { font-size: var(--page-font-size); font-weight: 600; background-image: none; background-color: var(--tablehead-background); color: var(--tablehead-foreground); - border-bottom: 1px solid var(--separator-color); } -.fieldtable td.fieldtype, .fieldtable td.fieldname { +table.fieldtable td.fieldtype, .fieldtable td.fieldname, .fieldtable td.fieldinit, .fieldtable td.fielddoc, .fieldtable th { border-bottom: 1px solid var(--separator-color); border-right: 1px solid var(--separator-color); } -.fieldtable td.fielddoc { - border-bottom: 1px solid var(--separator-color); +table.fieldtable tr:last-child td:first-child { + border-bottom-left-radius: var(--border-radius-small); +} + +table.fieldtable tr:last-child td:last-child { + border-bottom-right-radius: var(--border-radius-small); } .memberdecls td.glow, .fieldtable tr.glow { background-color: var(--primary-light-color); - box-shadow: 0 0 15px var(--primary-lighter-color); + box-shadow: none; } table.memberdecls { display: block; - overflow-x: auto; - overflow-y: hidden; + -webkit-tap-highlight-color: transparent; +} + +table.memberdecls tr[class^='memitem'] { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); +} + +table.memberdecls tr[class^='memitem'] .memTemplParams { + font-family: var(--font-family-monospace); + font-size: var(--code-font-size); + color: var(--primary-dark-color); + white-space: normal; +} + +table.memberdecls tr.heading + tr[class^='memitem'] td.memItemLeft, +table.memberdecls tr.heading + tr[class^='memitem'] td.memItemRight, +table.memberdecls td.memItemLeft, +table.memberdecls td.memItemRight, +table.memberdecls .memTemplItemLeft, +table.memberdecls .memTemplItemRight, +table.memberdecls .memTemplParams { + transition: none; + padding-top: var(--spacing-small); + padding-bottom: var(--spacing-small); + border-top: 1px solid var(--separator-color); + border-bottom: 1px solid var(--separator-color); + background-color: var(--fragment-background); +} + +@media screen and (min-width: 768px) { + + tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { + border-top-right-radius: var(--border-radius-small); + } + + table.memberdecls tr:last-child td.memItemRight, table.memberdecls tr:last-child td.mdescRight, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemRight, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemRight, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescRight, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescRight { + border-bottom-right-radius: var(--border-radius-small); + } + + table.memberdecls tr:last-child td.memItemLeft, table.memberdecls tr:last-child td.mdescLeft, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemLeft, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescLeft { + border-bottom-left-radius: var(--border-radius-small); + } + + tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft { + border-top-left-radius: var(--border-radius-small); + } + +} + +table.memname td.memname { + font-size: var(--memname-font-size); +} + +table.memberdecls .memTemplItemLeft, +table.memberdecls .template .memItemLeft, +table.memberdecls .memTemplItemRight, +table.memberdecls .template .memItemRight { + padding-top: 2px; +} + +table.memberdecls .memTemplParams { + border-bottom: 0; + border-left: 1px solid var(--separator-color); + border-right: 1px solid var(--separator-color); + border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; + padding-bottom: var(--spacing-small); +} + +table.memberdecls .memTemplItemLeft, table.memberdecls .template .memItemLeft { + border-radius: 0 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + border-top: 0; +} + +table.memberdecls .memTemplItemRight, table.memberdecls .template .memItemRight { + border-radius: 0 0 var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-left: 0; + border-top: 0; +} + +table.memberdecls .memItemLeft { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); + border-left: 1px solid var(--separator-color); + padding-left: var(--spacing-medium); + padding-right: 0; +} + +table.memberdecls .memItemRight { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; + border-right: 1px solid var(--separator-color); + padding-right: var(--spacing-medium); + padding-left: 0; + +} + +table.memberdecls .mdescLeft, table.memberdecls .mdescRight { + background: none; + color: var(--page-foreground-color); + padding: var(--spacing-small) 0; + border: 0; +} + +table.memberdecls [class^="memdesc"] { + box-shadow: none; +} + + +table.memberdecls .memItemLeft, +table.memberdecls .memTemplItemLeft { + padding-right: var(--spacing-medium); +} + +table.memberdecls .memSeparator { + background: var(--page-background-color); + height: var(--spacing-large); + border: 0; + transition: none; +} + +table.memberdecls .groupheader { + margin-bottom: var(--spacing-large); +} + +table.memberdecls .inherit_header td { + padding: 0 0 var(--spacing-medium) 0; + text-indent: -12px; + color: var(--page-secondary-foreground-color); +} + +table.memberdecls span.dynarrow { + left: 10px; +} + +table.memberdecls img[src="closed.png"], +table.memberdecls img[src="open.png"], +div.dynheader img[src="open.png"], +div.dynheader img[src="closed.png"] { + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--primary-color); + margin-top: 8px; + display: block; + float: left; + margin-left: -10px; + transition: transform var(--animation-duration) ease-out; +} + +tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft, tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { + border-top: 1px solid var(--separator-color); +} + +table.memberdecls img { + margin-right: 10px; +} + +table.memberdecls img[src="closed.png"], +div.dynheader img[src="closed.png"] { + transform: rotate(-90deg); + +} + +.compoundTemplParams { + font-family: var(--font-family-monospace); + color: var(--primary-dark-color); + font-size: var(--code-font-size); +} + +@media screen and (max-width: 767px) { + + table.memberdecls .memItemLeft, + table.memberdecls .memItemRight, + table.memberdecls .mdescLeft, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemLeft, + table.memberdecls .memTemplItemRight, + table.memberdecls .memTemplParams, + table.memberdecls .template .memItemLeft, + table.memberdecls .template .memItemRight, + table.memberdecls .template .memParams { + display: block; + text-align: left; + padding-left: var(--spacing-large); + margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); + border-right: none; + border-left: none; + border-radius: 0; + white-space: normal; + } + + table.memberdecls .memItemLeft, + table.memberdecls .mdescLeft, + table.memberdecls .memTemplItemLeft, + table.memberdecls .template .memItemLeft { + border-bottom: 0 !important; + padding-bottom: 0 !important; + } + + table.memberdecls .memTemplItemLeft, + table.memberdecls .template .memItemLeft { + padding-top: 0; + } + + table.memberdecls .mdescLeft { + margin-bottom: calc(0px - var(--page-font-size)); + } + + table.memberdecls .memItemRight, + table.memberdecls .mdescRight, + table.memberdecls .memTemplItemRight, + table.memberdecls .template .memItemRight { + border-top: 0 !important; + padding-top: 0 !important; + padding-right: var(--spacing-large); + padding-bottom: var(--spacing-medium); + overflow-x: auto; + } + + table.memberdecls tr[class^='memitem']:not(.inherit) { + display: block; + width: calc(100vw - 2 * var(--spacing-large)); + } + + table.memberdecls .mdescRight { + color: var(--page-foreground-color); + } + + table.memberdecls tr.inherit { + visibility: hidden; + } + + table.memberdecls tr[style="display: table-row;"] { + display: block !important; + visibility: visible; + width: calc(100vw - 2 * var(--spacing-large)); + animation: fade .5s; + } + + @keyframes fade { + 0% { + opacity: 0; + max-height: 0; + } + + 100% { + opacity: 1; + max-height: 200px; + } + } + + tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { + border-top-right-radius: 0; + } + + table.memberdecls tr:last-child td.memItemRight, table.memberdecls tr:last-child td.mdescRight, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemRight, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemRight, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescRight, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescRight { + border-bottom-right-radius: 0; + } + + table.memberdecls tr:last-child td.memItemLeft, table.memberdecls tr:last-child td.mdescLeft, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemLeft, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescLeft { + border-bottom-left-radius: 0; + } + + tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft { + border-top-left-radius: 0; + } } @@ -1191,15 +2261,35 @@ table.memberdecls { hr { margin-top: var(--spacing-large); margin-bottom: var(--spacing-large); - border-top:1px solid var(--separator-color); + height: 1px; + background-color: var(--separator-color); + border: 0; } .contents hr { - box-shadow: var(--content-maxwidth) 0 0 0 var(--separator-color), calc(0px - var(--content-maxwidth)) 0 0 0 var(--separator-color); + box-shadow: 100px 0 var(--separator-color), + -100px 0 var(--separator-color), + 500px 0 var(--separator-color), + -500px 0 var(--separator-color), + 900px 0 var(--separator-color), + -900px 0 var(--separator-color), + 1400px 0 var(--separator-color), + -1400px 0 var(--separator-color), + 1900px 0 var(--separator-color), + -1900px 0 var(--separator-color); } -.contents img { +.contents img, .contents .center, .contents center, .contents div.image object { max-width: 100%; + overflow: auto; +} + +@media screen and (max-width: 767px) { + .contents .dyncontent > .center, .contents > center { + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); + max-width: calc(100% + 2 * var(--spacing-large)); + } } /* @@ -1215,18 +2305,42 @@ table.directory { font-family: var(--font-family); font-size: var(--page-font-size); font-weight: normal; + width: 100%; } -.directory td.entry { - padding: var(--spacing-small); - display: flex; - align-items: center; +table.directory td.entry, table.directory td.desc { + padding: calc(var(--spacing-small) / 2) var(--spacing-small); + line-height: var(--table-line-height); } -.directory tr.even { +table.directory tr.even td:last-child { + border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; +} + +table.directory tr.even td:first-child { + border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); +} + +table.directory tr.even:last-child td:last-child { + border-radius: 0 var(--border-radius-small) 0 0; +} + +table.directory tr.even:last-child td:first-child { + border-radius: var(--border-radius-small) 0 0 0; +} + +table.directory td.desc { + min-width: 250px; +} + +table.directory tr.even { background-color: var(--odd-color); } +table.directory tr.odd { + background-color: transparent; +} + .icona { width: auto; height: auto; @@ -1234,15 +2348,21 @@ table.directory { } .icon { - background: var(--primary-dark-color); - width: 18px; - height: 18px; - line-height: 18px; + background: var(--primary-color); + border-radius: var(--border-radius-small); + font-size: var(--page-font-size); + padding: calc(var(--page-font-size) / 5); + line-height: var(--page-font-size); + transform: scale(0.8); + height: auto; + width: var(--page-font-size); + user-select: none; } .iconfopen, .icondoc, .iconfclosed { background-position: center; margin-bottom: 0; + height: var(--table-line-height); } .icondoc { @@ -1251,17 +2371,21 @@ table.directory { @media screen and (max-width: 767px) { div.directory { - margin-left: calc(0px - var(--spacing-medium)); - margin-right: calc(0px - var(--spacing-medium)); + margin-left: calc(0px - var(--spacing-large)); + margin-right: calc(0px - var(--spacing-large)); } } @media (prefers-color-scheme: dark) { - .iconfopen, .iconfclosed { + html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { filter: hue-rotate(180deg) invert(); } } +html.dark-mode .iconfopen, html.dark-mode .iconfclosed { + filter: hue-rotate(180deg) invert(); +} + /* Class list */ @@ -1271,10 +2395,35 @@ table.directory { border-radius: var(--border-radius-small); } -@media screen and (max-width: 767px) { - .classindex { - margin: 0 calc(0px - var(--spacing-small)); - } +.classindex dl.even { + background-color: transparent; +} + +/* + Class Index Doxygen 1.8 +*/ + +table.classindex { + margin-left: 0; + margin-right: 0; + width: 100%; +} + +table.classindex table div.ah { + background-image: none; + background-color: initial; + border-color: var(--separator-color); + color: var(--page-foreground-color); + box-shadow: var(--box-shadow); + border-radius: var(--border-radius-large); + padding: var(--spacing-small); +} + +div.qindex { + background-color: var(--odd-color); + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + padding: var(--spacing-small) 0; } /* @@ -1282,7 +2431,6 @@ table.directory { */ #nav-path { - margin-bottom: -1px; width: 100%; } @@ -1291,7 +2439,7 @@ table.directory { background: var(--page-background-color); border: none; border-top: 1px solid var(--separator-color); - border-bottom: 1px solid var(--separator-color); + border-bottom: 0; font-size: var(--navigation-font-size); } @@ -1304,6 +2452,7 @@ img.footer { } address.footer { + color: var(--page-secondary-foreground-color); margin-bottom: var(--spacing-large); } @@ -1316,7 +2465,16 @@ address.footer { .navpath li.navelem a { text-shadow: none; display: inline-block; - color: var(--primary-dark-color) + color: var(--primary-color) !important; +} + +.navpath li.navelem a:hover { + text-shadow: none; +} + +.navpath li.navelem b { + color: var(--primary-dark-color); + font-weight: 500; } li.navelem { @@ -1332,33 +2490,531 @@ li.navelem:first-child:before { display: none; } -#nav-path li.navelem:after { +#nav-path ul { + padding-left: 0; +} + +#nav-path li.navelem:has(.el):after { content: ''; border: 5px solid var(--page-background-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; - transform: scaleY(4.2); + transform: translateY(-1px) scaleY(4.2); z-index: 10; margin-left: 6px; } -#nav-path li.navelem:before { +#nav-path li.navelem:not(:has(.el)):after { + background: var(--page-background-color); + box-shadow: 1px -1px 0 1px var(--separator-color); + border-radius: 0 var(--border-radius-medium) 0 50px; +} + +#nav-path li.navelem:not(:has(.el)) { + margin-left: 0; +} + +#nav-path li.navelem:not(:has(.el)):hover, #nav-path li.navelem:not(:has(.el)):hover:after { + background-color: var(--separator-color); +} + +#nav-path li.navelem:has(.el):before { content: ''; border: 5px solid var(--separator-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; - transform: scaleY(3.2); + transform: translateY(-1px) scaleY(3.2); margin-right: var(--spacing-small); } -@media (prefers-color-scheme: dark) { - #nav-path li.navelem:after { - text-shadow: 3px 0 0 var(--separator-color), 8px 0 6px rgba(0,0,0,0.4); - } -} - .navpath li.navelem a:hover { color: var(--primary-color); } + +/* + Scrollbars for Webkit +*/ + +#nav-tree::-webkit-scrollbar, +div.fragment::-webkit-scrollbar, +pre.fragment::-webkit-scrollbar, +div.memproto::-webkit-scrollbar, +.contents center::-webkit-scrollbar, +.contents .center::-webkit-scrollbar, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar, +div.contents .toc::-webkit-scrollbar, +.contents .dotgraph::-webkit-scrollbar, +.contents .tabs-overview-container::-webkit-scrollbar { + background: transparent; + width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); +} + +#nav-tree::-webkit-scrollbar-thumb, +div.fragment::-webkit-scrollbar-thumb, +pre.fragment::-webkit-scrollbar-thumb, +div.memproto::-webkit-scrollbar-thumb, +.contents center::-webkit-scrollbar-thumb, +.contents .center::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-thumb, +div.contents .toc::-webkit-scrollbar-thumb, +.contents .dotgraph::-webkit-scrollbar-thumb, +.contents .tabs-overview-container::-webkit-scrollbar-thumb { + background-color: transparent; + border: var(--webkit-scrollbar-padding) solid transparent; + border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); + background-clip: padding-box; +} + +#nav-tree:hover::-webkit-scrollbar-thumb, +div.fragment:hover::-webkit-scrollbar-thumb, +pre.fragment:hover::-webkit-scrollbar-thumb, +div.memproto:hover::-webkit-scrollbar-thumb, +.contents center:hover::-webkit-scrollbar-thumb, +.contents .center:hover::-webkit-scrollbar-thumb, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody:hover::-webkit-scrollbar-thumb, +div.contents .toc:hover::-webkit-scrollbar-thumb, +.contents .dotgraph:hover::-webkit-scrollbar-thumb, +.contents .tabs-overview-container:hover::-webkit-scrollbar-thumb { + background-color: var(--webkit-scrollbar-color); +} + +#nav-tree::-webkit-scrollbar-track, +div.fragment::-webkit-scrollbar-track, +pre.fragment::-webkit-scrollbar-track, +div.memproto::-webkit-scrollbar-track, +.contents center::-webkit-scrollbar-track, +.contents .center::-webkit-scrollbar-track, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-track, +div.contents .toc::-webkit-scrollbar-track, +.contents .dotgraph::-webkit-scrollbar-track, +.contents .tabs-overview-container::-webkit-scrollbar-track { + background: transparent; +} + +#nav-tree::-webkit-scrollbar-corner { + background-color: var(--side-nav-background); +} + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, +div.contents .toc { + overflow-x: auto; + overflow-x: overlay; +} + +#nav-tree { + overflow-x: auto; + overflow-y: auto; + overflow-y: overlay; +} + +/* + Scrollbars for Firefox +*/ + +#nav-tree, +div.fragment, +pre.fragment, +div.memproto, +.contents center, +.contents .center, +.contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, +div.contents .toc, +.contents .dotgraph, +.contents .tabs-overview-container { + scrollbar-width: thin; +} + +/* + Optional Dark mode toggle button +*/ + +doxygen-awesome-dark-mode-toggle { + display: inline-block; + margin: 0 0 0 var(--spacing-small); + padding: 0; + width: var(--searchbar-height); + height: var(--searchbar-height); + background: none; + border: none; + border-radius: var(--searchbar-border-radius); + vertical-align: middle; + text-align: center; + line-height: var(--searchbar-height); + font-size: 22px; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; +} + +doxygen-awesome-dark-mode-toggle > svg { + transition: transform var(--animation-duration) ease-in-out; +} + +doxygen-awesome-dark-mode-toggle:active > svg { + transform: scale(.5); +} + +doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.03); +} + +html.dark-mode doxygen-awesome-dark-mode-toggle:hover { + background-color: rgba(0,0,0,.18); +} + +/* + Optional fragment copy button +*/ +.doxygen-awesome-fragment-wrapper { + position: relative; +} + +doxygen-awesome-fragment-copy-button { + opacity: 0; + background: var(--fragment-background); + width: 28px; + height: 28px; + position: absolute; + right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); + border: 1px solid var(--fragment-foreground); + cursor: pointer; + border-radius: var(--border-radius-small); + display: flex; + justify-content: center; + align-items: center; +} + +.doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { + opacity: .28; +} + +doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { + opacity: 1 !important; +} + +doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { + transform: scale(.91); +} + +doxygen-awesome-fragment-copy-button svg { + fill: var(--fragment-foreground); + width: 18px; + height: 18px; +} + +doxygen-awesome-fragment-copy-button.success svg { + fill: rgb(14, 168, 14); +} + +doxygen-awesome-fragment-copy-button.success { + border-color: rgb(14, 168, 14); +} + +@media screen and (max-width: 767px) { + .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, + dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { + right: 0; + } +} + +/* + Optional paragraph link button +*/ + +a.anchorlink { + font-size: 90%; + margin-left: var(--spacing-small); + color: var(--page-foreground-color) !important; + text-decoration: none; + opacity: .15; + display: none; + transition: opacity var(--animation-duration) ease-in-out, color var(--animation-duration) ease-in-out; +} + +a.anchorlink svg { + fill: var(--page-foreground-color); +} + +h3 a.anchorlink svg, h4 a.anchorlink svg { + margin-bottom: -3px; + margin-top: -4px; +} + +a.anchorlink:hover { + opacity: .45; +} + +h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { + display: inline-block; +} + +/* + Optional tab feature +*/ + +.tabbed > ul { + padding-inline-start: 0px; + margin: 0; + padding: var(--spacing-small) 0; +} + +.tabbed > ul > li { + display: none; +} + +.tabbed > ul > li.selected { + display: block; +} + +.tabs-overview-container { + overflow-x: auto; + display: block; + overflow-y: visible; +} + +.tabs-overview { + border-bottom: 1px solid var(--separator-color); + display: flex; + flex-direction: row; +} + +@media screen and (max-width: 767px) { + .tabs-overview-container { + margin: 0 calc(0px - var(--spacing-large)); + } + .tabs-overview { + padding: 0 var(--spacing-large) + } +} + +.tabs-overview button.tab-button { + color: var(--page-foreground-color); + margin: 0; + border: none; + background: transparent; + padding: calc(var(--spacing-large) / 2) 0; + display: inline-block; + font-size: var(--page-font-size); + cursor: pointer; + box-shadow: 0 1px 0 0 var(--separator-color); + position: relative; + + -webkit-tap-highlight-color: transparent; +} + +.tabs-overview button.tab-button .tab-title::before { + display: block; + content: attr(title); + font-weight: 600; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.tabs-overview button.tab-button .tab-title { + float: left; + white-space: nowrap; + font-weight: normal; + font-family: var(--font-family); + padding: calc(var(--spacing-large) / 2) var(--spacing-large); + border-radius: var(--border-radius-medium); + transition: background-color var(--animation-duration) ease-in-out, font-weight var(--animation-duration) ease-in-out; +} + +.tabs-overview button.tab-button:not(:last-child) .tab-title { + box-shadow: 8px 0 0 -7px var(--separator-color); +} + +.tabs-overview button.tab-button:hover .tab-title { + background: var(--separator-color); + box-shadow: none; +} + +.tabs-overview button.tab-button.active .tab-title { + font-weight: 600; +} + +.tabs-overview button.tab-button::after { + content: ''; + display: block; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 0; + width: 0%; + margin: 0 auto; + border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; + background-color: var(--primary-color); + transition: width var(--animation-duration) ease-in-out, height var(--animation-duration) ease-in-out; +} + +.tabs-overview button.tab-button.active::after { + width: 100%; + box-sizing: border-box; + height: 3px; +} + + +/* + Navigation Buttons +*/ + +.section_buttons:not(:empty) { + margin-top: calc(var(--spacing-large) * 3); +} + +.section_buttons table.markdownTable { + display: block; + width: 100%; +} + +.section_buttons table.markdownTable tbody { + display: table !important; + width: 100%; + box-shadow: none; + border-spacing: 10px; +} + +.section_buttons table.markdownTable td { + padding: 0; +} + +.section_buttons table.markdownTable th { + display: none; +} + +.section_buttons table.markdownTable tr.markdownTableHead { + border: none; +} + +.section_buttons tr th, .section_buttons tr td { + background: none; + border: none; + padding: var(--spacing-large) 0 var(--spacing-small); +} + +.section_buttons a { + display: inline-block; + border: 1px solid var(--separator-color); + border-radius: var(--border-radius-medium); + color: var(--page-secondary-foreground-color) !important; + text-decoration: none; + transition: color var(--animation-duration) ease-in-out, background-color var(--animation-duration) ease-in-out; +} + +.section_buttons a:hover { + color: var(--page-foreground-color) !important; + background-color: var(--odd-color); +} + +.section_buttons tr td.markdownTableBodyLeft a { + padding: var(--spacing-medium) var(--spacing-large) var(--spacing-medium) calc(var(--spacing-large) / 2); +} + +.section_buttons tr td.markdownTableBodyRight a { + padding: var(--spacing-medium) calc(var(--spacing-large) / 2) var(--spacing-medium) var(--spacing-large); +} + +.section_buttons tr td.markdownTableBodyLeft a::before, +.section_buttons tr td.markdownTableBodyRight a::after { + color: var(--page-secondary-foreground-color) !important; + display: inline-block; + transition: color .08s ease-in-out, transform .09s ease-in-out; +} + +.section_buttons tr td.markdownTableBodyLeft a::before { + content: '〈'; + padding-right: var(--spacing-large); +} + + +.section_buttons tr td.markdownTableBodyRight a::after { + content: '〉'; + padding-left: var(--spacing-large); +} + + +.section_buttons tr td.markdownTableBodyLeft a:hover::before { + color: var(--page-foreground-color) !important; + transform: translateX(-3px); +} + +.section_buttons tr td.markdownTableBodyRight a:hover::after { + color: var(--page-foreground-color) !important; + transform: translateX(3px); +} + +@media screen and (max-width: 450px) { + .section_buttons a { + width: 100%; + box-sizing: border-box; + } + + .section_buttons tr td:nth-of-type(1).markdownTableBodyLeft a { + border-radius: var(--border-radius-medium) 0 0 var(--border-radius-medium); + border-right: none; + } + + .section_buttons tr td:nth-of-type(2).markdownTableBodyRight a { + border-radius: 0 var(--border-radius-medium) var(--border-radius-medium) 0; + } +} + +/* + Bordered image +*/ + +html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */ +html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { + filter: brightness(89%) hue-rotate(180deg) invert(); +} + +.bordered_image { + border-radius: var(--border-radius-small); + border: 1px solid var(--separator-color); + display: inline-block; + overflow: hidden; +} + +.bordered_image:empty { + border: none; +} + +html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */ +html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { + border-radius: var(--border-radius-small); +} + +/* + Button +*/ + +.primary-button { + display: inline-block; + cursor: pointer; + background: var(--primary-color); + color: var(--page-background-color) !important; + border-radius: var(--border-radius-medium); + padding: var(--spacing-small) var(--spacing-medium); + text-decoration: none; +} + +.primary-button:hover { + background: var(--primary-dark-color); +} \ No newline at end of file From 3263e2497e989eabf63f64cfb9b7e607be85f445 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 5 Sep 2025 18:10:53 +0200 Subject: [PATCH 0946/1014] 1.5.81 --- NEWS | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++- meson.build | 2 +- 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 66ce22fcb..016ebfa89 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,201 @@ +# PipeWire 1.5.81 (2025-10-16) + +This is the first 1.6 release candidate that is API and ABI +compatible with previous 1.4.x, 1.2.x and 1.0.x releases. + +In addition to all the changes backported to 1.4.x, this release +also contains some new features: + +## Highlights + - The link negotiation code was refactored and improved. + Applications now have more options for selecting the default + values and restricting the available options. The default + negotiation code will now attempt to better match the application + suggested values. + - The loop now has support for locking with priority inversion. Most + of the code was updated to use the locks instead of invoke to + get proper concurrent updates with the loop. The Thread loop + functionality of locks, signal and wait was moved to the SPA loop. + This guarantees better real-time behaviour because inter-thread + synchronization does not have to pass eventfd/epoll. + - The control stream parser was rewritten to be safe against concurrent + updates while parsing, which can occur when parsing shared memory. + It also has extra checks to avoid integer overflows and undefined + behaviour. + - MIDI 2.0 clip support was added to the tools. + - Bluetooth ASHA (Audio Streaming for Hearing Aid) support was added. + - The ALSA node setup was tweaked to provide low latency with the ALSA + Firewire driver. + - Better support for explicit sync. It is now possible to negotiate + extra features to know if a consumer will signal the sync objects and + implement a fallback using a reliable transport. + - Many bug fixes and improvements. + + +## PipeWire + - Avoid process calls in disconnect in pw-stream. (#3314) + - Disable PipeWire services for root. + - The link negotiation was refactored and improved. Drivers now + always have a lower priority in deciding the final format. + - Backwards compatibility with the v0 protocol was removed. + - pw-stream and pw-filter will now refuse to queue a buffer that + was not dequeued before. + - Object properties will now be updated on the global as well. + - The priority of config overrides is correct now. (#4816) + - Async links now correctly report 1 extra quantum of latency. + - node.exclusive and the new port.exclusive flag are now enforced + by PipeWire itself. + - A new timer-queue helper was added to schedule timeouts. + - node.terminal and node.physical properties are now copied to the + ports to make it possible to create virtual sources and sinks + for JACK applications. + - Port properties will now be dynamically updated when the node + properties they depend on are updated. + - Passive leaf nodes are now handled better. Now they will also + run when the peer is active. (#4915) + - Reliable transport has been added for output ports. This can be + used in some cases if the producer wants to ensure buffers are + consumed by a consumer. (#4885) + - Context properties now support rlimit. properties to + configure rlimits. (#4047) + +## Modules + - Close SyncObj fds. + - module-combine-stream has better Latency reporting. + - The JACK tunnel can now optionally connect ports. + - module-loopback has better Latency reporting. + - A Dolby Surround and Dolby Pro Logic II example filter config + was added. + - Filter-chain can now resample to a specific rate before running the + filters. This is useful when the filter-graph needs to run at a + specific rate. + - Avahi-poll now uses the timer-queue to schedule timeouts. + - Modules are ported to timer-queue instead of using timerfd directly + for non-realtime timers. + +## SPA + - The loop now has support for locking with priority inversion. Most + of the code was updated to use the locks instead of invoke to + get proper concurrent updates with the loop. The Thread loop + functionality of locks, signal and wait was moved to the SPA loop. + - UMP to Midi 1.0 conversion was improved, some UMP events are now + converted to multiple Midi 1.0 messages. (#4839) + - The POD filter was refactored and improved. It is now possible to + use the default value of the output by specifying an invalid input + default value. + - The POD parser was made safe for concurrent updates of the memory + it is parsing. This is important when the POD is in shared memory + and the parser should not access invalid memory. + - Some hardcoded channel limits were removed and now use the global + channel limit. More things can dynamically adapt to this global + limit. The max number of channels was then bumped to 128. + - The POD builder is safe to use on shared memory now and tries to + avoid many integer overflows. + - Most debug functions are safe to be used on shared memory. + - User specified Commands and Events are now possible. + - The SPA_IO_CLOCK_FLAG_DISCONT was added to spa_io_clock to signal + a discont in the clock due to clock change. + - AC3, DTS, EAC3, TRUEHD and MPEGH now have helper parser functions. + - H265 was added as a video format. (#4674) + - SPA_PARAM_PeerFormats was added to let a port know about its peer + formats in order to better filter possible conversions. + - More color matrices, transfer functions and color primaries. + - The echo-canceler is enabled now. + - Pro-Audio mode now uses 3 periods by default. This lowers the + latency on some drivers (Firewire). The latency of Firewire is + also reported correctly now. + - The ALSA DLL bandwidth is configurable now. + - The resampler now uses fixed point for the phases and is a little + faster when updating adaptive rates. + - The convolver is a little faster by swapping buffers instead of + copying samples. + - Latency and ProcessLatency support was added to filter-graph. + (#4678) + - Audio channel position support was added to filter-graph. + - A new ffmpeg avfilter plugin was added to filter-graph. + - A new ONNX filter was added to filter-graph. + - A debug, pipe, zeroramp and noisegate filter was added to the + filter-graph. (#4745) + - The filter-graph lv2 plugin now supports options and state. + - videoconvert was greatly improved. + - The v4l2 plugin can negotiate DMABUF with modifiers. + - Colorimetry information was added to v4l2 and libcamera. + - Audioconvert can handle empty buffers more efficiently. + - Improve the POD compare functions for Rectangle. + - There is now a SPA_POD_PROP_FLAG_DROP flag to drop the property when + the property is missing from one side. + - A new FEATURE choice was added that is basically a flags choice with + a FLAG_DROP property. + - Metadata features were added. This is a way to negotiate new features + for the metadata. (#4885) + - DSD playback with pw-cat has been improved. + - Compatibility and xrun prevention for the SOF driver has been + improved. (#4489) + - The filter-graph max plugin can now have 8 input channels. + - Buffer Negotiation between the mixer port and the node ports is much + improved. (#4918) + - An offline AEC benchmark was added. + - Channel positions are now read from HDMI ELD when possible. + - Audioconvert and filter-graph now also support properties of Long + and String types. + +## ACP + - It's possible to disable the pro-audio profile. + - Support for Logitech Z407 PC Speakers was improved. + - Support for Razer BlackShark v3. + - Fix volume rounding down causing mute. (#4890) + +## Tools + - pw-cat can now play and record MIDI 2.0 Clips, which is the + official format for storing MIDI 2.0 UMP data. pw-midi2play + and pw-midi2record were added as aliases. + - pw-cat can now upload sysex files. The pw-sysex alias was + added for this. + - The pw-link tool now has a -t option to list port latencies. + It also has better monitor support. + - pw-top can now clear the ERR column with the c key. + - pw-cli now keeps the types of the variables it stores and avoid + using wrongly typed variables that can crash things. It can now + also list the available variables. + - pw-dump can now output raw JSON and SPA JSON. + - pw-dump has configurable indentation level. + - pw-mididump can be forced to output MIDI 1.0 messages. + - pw-profiler now uses doubles for extra precision. + - pw-top now marks the async nodes with =. + +## Bluetooth + - Telephony improvements. + - ASHA support was added. + - Add packet loss correction with spandsp for some codecs. + - Synchronisation between ISO streams in the same group. + +## Pulse-server + - The SUSPEND event is now correctly generated. fail-on-suspend is + now implemented. + - PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND is now implemented. (#4255) + (#4726) + - RTP streams now have stream.properties for extra configuration. + - Timed out streams are now destroyed instead of lingering. (#4901) + - A new help and pipewire-pulse:list-modules core message was added. + +## JACK + - Port rename callbacks are now emitted correctly. + - Use safe POD parsing for the control sequences. + +## V4l2 + - The wrapper now avoids a race while initializing PipeWire. (#4859) + +## GStreamer + - Colorimetry support was added. + - Cursor metadata is now exposed as ROI metadata. + - Many more updates. + +## Docs + - Document the client-node flow a bit more. + + +Older versions: + # PipeWire 1.4.0 (2025-03-06) This is the 1.4 release that is API and ABI compatible with previous @@ -99,9 +297,6 @@ the 1.2 release last year, including: ## JACK - Add an option to disable the MIDI2 port flags. (#4584) - -Older versions: - # PipeWire 1.3.83 (2025-02-20) This is the third and hopefully last 1.4 release candidate that diff --git a/meson.build b/meson.build index 83e964a07..e0a333a8f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.5.0', + version : '1.5.81', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', From f70b0892ea50fdc5fafe6968f140acf305873fca Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 17 Oct 2025 12:28:15 +0200 Subject: [PATCH 0947/1014] doc: swap the name and id of the device.product Fixes #4935 --- doc/dox/config/pipewire-props.7.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 4db52b94b..97b758a2a 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -174,12 +174,12 @@ ie. for example `device.Param.Props = { ... }` to set `Props`. @PAR@ device-prop device.product.id # integer \parblock -\copydoc PW_KEY_DEVICE_PRODUCT_NAME +\copydoc PW_KEY_DEVICE_PRODUCT_ID \endparblock @PAR@ device-prop device.product.name # string \parblock -\copydoc PW_KEY_DEVICE_PRODUCT_ID +\copydoc PW_KEY_DEVICE_PRODUCT_NAME \endparblock @PAR@ device-prop device.class # string From fb49759d1fdf54ddab7c725f723318c57e8fd49a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 17 Oct 2025 14:51:14 +0200 Subject: [PATCH 0948/1014] module-echo-cancel: drop samples when source not ready When we can't dequeue a buffer from the source stream, drop the samples instead of leaving them queued in the ringbuffer. --- src/modules/module-echo-cancel.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 113a89cea..d241369c7 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -431,23 +431,22 @@ static void process(struct impl *impl) avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex); while (avail >= 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++) { + dd = &cout->buffer->datas[i]; + spa_ringbuffer_read_data(&impl->out_ring, impl->out_buffer[i], + impl->out_ringsize, oindex % impl->out_ringsize, + (void *)dd->data, size); + dd->chunk->offset = 0; + dd->chunk->size = size; + dd->chunk->stride = sizeof(float); + } + pw_stream_queue_buffer(impl->source, cout); + } else { + /* drop data as to not cause delay */ pw_log_debug("out of source buffers: %m"); - break; } - for (i = 0; i < impl->out_info.channels; i++) { - dd = &cout->buffer->datas[i]; - spa_ringbuffer_read_data(&impl->out_ring, impl->out_buffer[i], - impl->out_ringsize, oindex % impl->out_ringsize, - (void *)dd->data, size); - dd->chunk->offset = 0; - dd->chunk->size = size; - dd->chunk->stride = sizeof(float); - } - - pw_stream_queue_buffer(impl->source, cout); - oindex += size; spa_ringbuffer_read_update(&impl->out_ring, oindex); avail -= size; From c94aff8cae158def64e20a320dcc1b1020d1d795 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 09:17:14 +0200 Subject: [PATCH 0949/1014] Revert "audio: bump max channels to 128" This reverts commit c91f75ae2e68e601e5f90790704776cd1b3f755c. This change causes a subtle ABI change and also breaks the Rust bindings. --- spa/include/spa/param/audio/raw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 2fd7fcfe3..804e5df1d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,7 +18,7 @@ extern "C" { * \{ */ -#define SPA_AUDIO_MAX_CHANNELS 128u +#define SPA_AUDIO_MAX_CHANNELS 64u enum spa_audio_format { SPA_AUDIO_FORMAT_UNKNOWN, From f453b1545dc229f992a8da9dbb31a2fab634c6c1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 15:28:51 +0200 Subject: [PATCH 0950/1014] audio: don't use SPA_AUDIO_MAX_CHANNELS in some places When we know the max size of the array, just use this instead of the SPA_AUDIO_MAX_CHANNELS constant. --- spa/plugins/bluez5/a2dp-codec-aac.c | 2 +- spa/plugins/bluez5/a2dp-codec-aptx.c | 2 +- spa/plugins/bluez5/a2dp-codec-faststream.c | 2 +- spa/plugins/bluez5/a2dp-codec-lc3plus.c | 2 +- spa/plugins/bluez5/a2dp-codec-ldac.c | 2 +- spa/plugins/bluez5/a2dp-codec-opus-g.c | 2 +- spa/plugins/bluez5/a2dp-codec-sbc.c | 2 +- spa/plugins/bluez5/bap-codec-lc3.c | 11 ++++++----- spa/plugins/bluez5/hfp-codec-cvsd.c | 2 +- spa/plugins/bluez5/hfp-codec-lc3-a127.c | 2 +- spa/plugins/bluez5/hfp-codec-lc3-swb.c | 2 +- spa/plugins/bluez5/hfp-codec-msbc.c | 2 +- src/gst/gstpipewireformat.c | 2 +- src/modules/module-rtp/opus.c | 5 ++++- 14 files changed, 22 insertions(+), 18 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 505066c3e..6d4db7fc4 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -200,7 +200,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, a2dp_aac_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index 7ebdc99d8..75ceb229f 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -205,7 +205,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, a2dp_aptx_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index 5b78fb38e..edc833292 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -114,7 +114,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, a2dp_faststream_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index c5a191e29..ee25fef0c 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -175,7 +175,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, a2dp_lc3plus_hr_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index cf1526faf..11185800e 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -158,7 +158,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t i = 0; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; if (caps_size < sizeof(conf)) return -EINVAL; diff --git a/spa/plugins/bluez5/a2dp-codec-opus-g.c b/spa/plugins/bluez5/a2dp-codec-opus-g.c index 2fe34bf5e..130dea433 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus-g.c +++ b/spa/plugins/bluez5/a2dp-codec-opus-g.c @@ -164,7 +164,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, { a2dp_opus_g_t conf; struct spa_pod_frame f[1]; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; int channels; if (caps_size < sizeof(conf)) diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index f2bc3e7ef..a91109e52 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -346,7 +346,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t i = 0; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[2]; if (caps_size < sizeof(conf)) return -EINVAL; diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 818f4ee36..10d9eecc8 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -901,12 +901,12 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f return conf_cmp(&conf1, res1, &conf2, res2); } -static uint8_t channels_to_positions(uint32_t channels, uint32_t *position) +static uint8_t channels_to_positions(uint32_t channels, uint32_t *position, uint32_t max_position) { uint32_t n_channels = get_channel_count(channels); uint8_t n_positions = 0; - spa_assert(n_channels <= SPA_AUDIO_MAX_CHANNELS); + spa_assert(n_channels <= max_position); if (channels == 0) { position[0] = SPA_AUDIO_CHANNEL_MONO; @@ -932,7 +932,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, bap_lc3_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[LC3_MAX_CHANNELS]; uint32_t i = 0; uint8_t res; @@ -990,7 +990,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, if (i == 0) return -EINVAL; - res = channels_to_positions(conf.channels, position); + res = channels_to_positions(conf.channels, position, SPA_N_ELEMENTS(position)); if (res == 0) return -EINVAL; spa_pod_builder_add(b, @@ -1044,7 +1044,8 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags return -EINVAL; } - res = channels_to_positions(conf.channels, info->info.raw.position); + res = channels_to_positions(conf.channels, info->info.raw.position, + SPA_N_ELEMENTS(info->info.raw.position)); if (res == 0) return -EINVAL; info->info.raw.channels = res; diff --git a/spa/plugins/bluez5/hfp-codec-cvsd.c b/spa/plugins/bluez5/hfp-codec-cvsd.c index 66b801ecc..19f908188 100644 --- a/spa/plugins/bluez5/hfp-codec-cvsd.c +++ b/spa/plugins/bluez5/hfp-codec-cvsd.c @@ -34,7 +34,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; - const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); diff --git a/spa/plugins/bluez5/hfp-codec-lc3-a127.c b/spa/plugins/bluez5/hfp-codec-lc3-a127.c index 703e153a8..b10bba874 100644 --- a/spa/plugins/bluez5/hfp-codec-lc3-a127.c +++ b/spa/plugins/bluez5/hfp-codec-lc3-a127.c @@ -39,7 +39,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; - const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); diff --git a/spa/plugins/bluez5/hfp-codec-lc3-swb.c b/spa/plugins/bluez5/hfp-codec-lc3-swb.c index 685806286..1cd679958 100644 --- a/spa/plugins/bluez5/hfp-codec-lc3-swb.c +++ b/spa/plugins/bluez5/hfp-codec-lc3-swb.c @@ -42,7 +42,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; - const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c index d2aabe2b0..5175a68d7 100644 --- a/spa/plugins/bluez5/hfp-codec-msbc.c +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -49,7 +49,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; - const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 20b93065a..fa97d3e01 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -649,7 +649,7 @@ handle_video_fields (ConvertData *d) static void set_default_channels (struct spa_pod_builder *b, uint32_t channels) { - uint32_t position[SPA_AUDIO_MAX_CHANNELS] = {0}; + uint32_t position[8] = {0}; gboolean ok = TRUE; switch (channels) { diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 6f27bd2bd..7eeda7f43 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -334,9 +334,12 @@ static void rtp_opus_deinit(struct impl *impl, enum spa_direction direction) static int rtp_opus_init(struct impl *impl, enum spa_direction direction) { int err; - unsigned char mapping[SPA_AUDIO_MAX_CHANNELS]; + unsigned char mapping[255]; uint32_t i; + if (impl->info.info.opus.channels > 255) + return -EINVAL; + if (impl->psamples >= 2880) impl->psamples = 2880; else if (impl->psamples >= 1920) From ede13a8cb5cee59a984da64428555476b9269f31 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 18:11:42 +0200 Subject: [PATCH 0951/1014] spa: don't add more channels than we have positions --- spa/include/spa/param/audio/raw-utils.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index c36491445..8dd816026 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -69,7 +69,8 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, - info->channels, info->position), 0); + SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position)), + info->position), 0); } } return (struct spa_pod*)spa_pod_builder_pop(builder, &f); From eb096bfb622e7ffb692c773d6ba72b3560643788 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 21 Oct 2025 09:39:15 +0200 Subject: [PATCH 0952/1014] spa: provide information about channels > SPA_AUDIO_MAX_CHANNELS Define some rules for how the position information works for channels > SPA_AUDIO_MAX_CHANNELS. We basically wrap around and incrementing the AUX channel counters. Make a function to implement this. --- spa/include/spa/param/audio/raw-utils.h | 14 ++++++++++++++ spa/include/spa/param/audio/raw.h | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 8dd816026..505965972 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -28,6 +28,20 @@ extern "C" { #endif #endif +SPA_API_AUDIO_RAW_UTILS uint32_t +spa_format_audio_get_position(struct spa_audio_info_raw *info, uint32_t idx) +{ + uint32_t pos; + if (idx < SPA_AUDIO_MAX_CHANNELS) { + pos = info->position[idx]; + } else { + pos = info->position[idx % SPA_AUDIO_MAX_CHANNELS]; + if (SPA_AUDIO_CHANNEL_IS_AUX(pos)) + pos += (idx / SPA_AUDIO_MAX_CHANNELS) * SPA_AUDIO_MAX_CHANNELS; + } + return pos; +} + SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) { diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 804e5df1d..6defe5020 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,6 +18,7 @@ extern "C" { * \{ */ +/* this is the max number of channels for position info */ #define SPA_AUDIO_MAX_CHANNELS 64u enum spa_audio_format { @@ -259,6 +260,8 @@ enum spa_audio_channel { SPA_AUDIO_CHANNEL_START_Custom = 0x10000, }; +#define SPA_AUDIO_CHANNEL_IS_AUX(ch) ((ch)>=SPA_AUDIO_CHANNEL_START_Aux && (ch)<=SPA_AUDIO_CHANNEL_LAST_Aux) + enum spa_audio_volume_ramp_scale { SPA_AUDIO_VOLUME_RAMP_INVALID, SPA_AUDIO_VOLUME_RAMP_LINEAR, @@ -274,7 +277,9 @@ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels */ + uint32_t channels; /*< number of channels. This can be larger than + * SPA_AUDIO_MAX_CHANNELS, the position is taken + * (index % SPA_AUDIO_MAX_CHANNELS) */ uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; From 13b8c237679d9c9bef06a879ebc6af7f841c9a5f Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 15:33:17 +0200 Subject: [PATCH 0953/1014] Don't use SPA_AUDIO_MAX_CHANNELS directly Make a MAX_CHANNELS define and use that one in code. This makes it easier to change the constant later. --- pipewire-alsa/alsa-plugins/ctl_pipewire.c | 8 +++-- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 5 +-- spa/plugins/alsa/alsa-acp-device.c | 7 ++-- spa/plugins/alsa/alsa-pcm.c | 10 +++--- spa/plugins/alsa/alsa-pcm.h | 3 +- spa/plugins/audioconvert/audioconvert.c | 25 +++++++------- spa/plugins/audioconvert/channelmix-ops.c | 36 ++++++++++---------- spa/plugins/audioconvert/channelmix-ops.h | 7 ++-- spa/plugins/audioconvert/test-audioconvert.c | 3 +- spa/plugins/avb/avb-pcm.h | 3 +- spa/plugins/bluez5/a2dp-codec-opus.c | 14 ++++---- spa/plugins/bluez5/bluez5-dbus.c | 2 +- spa/plugins/bluez5/bluez5-device.c | 20 +++++------ spa/plugins/bluez5/defs.h | 4 ++- spa/plugins/bluez5/media-sink.c | 2 +- spa/plugins/bluez5/media-source.c | 2 +- spa/plugins/filter-graph/filter-graph.c | 25 +++++++------- spa/plugins/support/null-audio-sink.c | 3 +- src/modules/module-combine-stream.c | 10 +++--- src/modules/module-echo-cancel.c | 7 ++-- src/modules/module-ffado-driver.c | 11 +++--- src/modules/module-jack-tunnel.c | 5 +-- src/modules/module-netjack2-driver.c | 2 +- src/modules/module-netjack2-manager.c | 2 +- src/modules/module-netjack2/peer.c | 4 ++- src/modules/module-protocol-pulse/format.c | 2 ++ src/modules/module-pulse-tunnel.c | 10 +++--- src/modules/module-raop-sink.c | 5 +-- src/modules/module-rtp-sap.c | 3 +- src/tools/pw-cat.c | 4 ++- 30 files changed, 136 insertions(+), 108 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/ctl_pipewire.c b/pipewire-alsa/alsa-plugins/ctl_pipewire.c index 7fcfd5726..459355c8f 100644 --- a/pipewire-alsa/alsa-plugins/ctl_pipewire.c +++ b/pipewire-alsa/alsa-plugins/ctl_pipewire.c @@ -22,9 +22,11 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.ctl"); #define VOLUME_MIN ((uint32_t) 0U) #define VOLUME_MAX ((uint32_t) 0x10000U) +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + struct volume { uint32_t channels; - long values[SPA_AUDIO_MAX_CHANNELS]; + long values[MAX_CHANNELS]; }; typedef struct { @@ -498,7 +500,7 @@ static struct spa_pod *build_volume_mute(struct spa_pod_builder *b, struct volum spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); if (volume) { - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; uint32_t i, n_volumes = 0; n_volumes = volume->channels; @@ -850,7 +852,7 @@ static void parse_props(struct global *g, const struct spa_pod *param, bool devi break; case SPA_PROP_channelVolumes: { - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; uint32_t n_volumes, i; n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 4b92bbb13..4bd55c2a8 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -31,6 +31,7 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define MAX_BUFFERS 64u #define MAX_RATE (48000*8) +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MIN_PERIOD 64 @@ -642,7 +643,7 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) +static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS]) { switch (channels) { case 8: @@ -1097,7 +1098,7 @@ struct param_info infos[] = { { "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX, { 1, MAX_RATE }, 2, collect_int }, { "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX, - { 1, SPA_AUDIO_MAX_CHANNELS }, 2, collect_int }, + { 1, MAX_CHANNELS }, 2, collect_int }, { "alsa.buffer-bytes", SND_PCM_IOPLUG_HW_BUFFER_BYTES, TYPE_MIN_MAX, { MIN_BUFFER_BYTES, MAX_BUFFER_BYTES }, 2, collect_int }, { "alsa.period-bytes", SND_PCM_IOPLUG_HW_PERIOD_BYTES, TYPE_MIN_MAX, diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index b2e06f67f..bdcb40fa5 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -38,6 +38,7 @@ extern struct spa_i18n *acp_i18n; #define MAX_POLL 16 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_DEVICE "hw:0" #define DEFAULT_AUTO_PROFILE true @@ -156,7 +157,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) uint32_t n_items, i; char device_name[128], path[210], channels[16], ch[12], routes[16]; char card_index[16], card_name[64], *p; - char positions[SPA_AUDIO_MAX_CHANNELS * 12]; + char positions[MAX_CHANNELS * 12]; char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; @@ -673,8 +674,8 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) props; int changed = 0; - float volumes[SPA_AUDIO_MAX_CHANNELS]; - uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; + uint32_t channels[MAX_CHANNELS]; uint32_t n_volumes = 0; if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 138a36cda..bad6da9fe 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -163,10 +163,10 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) int fmt_change = 0; if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { state->default_channels = atoi(s); - if (state->default_channels > SPA_AUDIO_MAX_CHANNELS) { + if (state->default_channels > MAX_CHANNELS) { spa_log_warn(state->log, "%p: %s: %s > %d, clamping", - state, k, s, SPA_AUDIO_MAX_CHANNELS); - state->default_channels = SPA_AUDIO_MAX_CHANNELS; + state, k, s, MAX_CHANNELS); + state->default_channels = MAX_CHANNELS; } fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { @@ -1584,8 +1584,8 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", min, max, state->default_channels, all); - min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS); - max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS); + min = SPA_MIN(min, MAX_CHANNELS); + max = SPA_MIN(max, MAX_CHANNELS); if (state->default_channels != 0 && !all) { if (min > state->default_channels || diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index bc96a3924..5bad22159 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -36,6 +36,7 @@ extern "C" { #endif #define MAX_RATES 16 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_PERIOD 1024u #define DEFAULT_RATE 48000u @@ -72,7 +73,7 @@ struct buffer { struct channel_map { uint32_t channels; - uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; + uint32_t pos[MAX_CHANNELS]; }; struct card { diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index d2e22d603..0658dea1c 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -47,10 +47,11 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert"); #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_ALIGN FMT_OPS_MAX_ALIGN #define MAX_BUFFERS 32 -#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS -#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) +#define MAX_DATAS MAX_CHANNELS +#define MAX_PORTS (MAX_CHANNELS+1) #define MAX_STAGES 64 #define MAX_GRAPH 9 /* 8 active + 1 replacement slot */ @@ -62,7 +63,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert"); struct volumes { bool mute; uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; }; static void init_volumes(struct volumes *vol) @@ -70,7 +71,7 @@ static void init_volumes(struct volumes *vol) uint32_t i; vol->mute = DEFAULT_MUTE; vol->n_volumes = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) vol->volumes[i] = DEFAULT_VOLUME; } @@ -91,7 +92,7 @@ struct props { float max_volume; float prev_volume; uint32_t n_channels; - uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channel_map[MAX_CHANNELS]; struct volumes channel; struct volumes soft; struct volumes monitor; @@ -112,7 +113,7 @@ static void props_reset(struct props *props) props->min_volume = DEFAULT_MIN_VOLUME; props->max_volume = DEFAULT_MAX_VOLUME; props->n_channels = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; init_volumes(&props->channel); init_volumes(&props->soft); @@ -241,9 +242,9 @@ struct filter_graph { struct spa_filter_graph *graph; struct spa_hook listener; uint32_t n_inputs; - uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t inputs_position[MAX_CHANNELS]; uint32_t n_outputs; - uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t outputs_position[MAX_CHANNELS]; uint32_t latency; bool removing; bool setup; @@ -1966,7 +1967,7 @@ static int node_set_param_port_config(struct impl *this, uint32_t flags, return -EINVAL; if (info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > MAX_CHANNELS) return -EINVAL; infop = &info; @@ -2153,7 +2154,7 @@ static void set_volume(struct impl *this) { struct volumes *vol; uint32_t i; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; struct dir *dir = &this->dir[this->direction]; spa_log_debug(this->log, "%p set volume %f have_format:%d", this, this->props.volume, dir->have_format); @@ -2671,7 +2672,7 @@ static int port_param_enum_formats(struct impl *impl, struct port *port, uint32_ } spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( - DEFAULT_CHANNELS, 1, SPA_AUDIO_MAX_CHANNELS), + DEFAULT_CHANNELS, 1, MAX_CHANNELS), 0); *param = spa_pod_builder_pop(b, &f[0]); } @@ -3064,7 +3065,7 @@ static int port_set_format(void *object, if (info.info.raw.format == 0 || (!this->props.resample_disabled && info.info.raw.rate == 0) || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) { + info.info.raw.channels > MAX_CHANNELS) { spa_log_error(this->log, "invalid format:%d rate:%d channels:%d", info.info.raw.format, info.info.raw.rate, info.info.raw.channels); diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 792cf7e09..ce67042e1 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -142,29 +142,29 @@ static uint32_t mask_to_ch(struct channelmix *mix, uint64_t mask) } static void distribute_mix(struct channelmix *mix, - float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS], + float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t mask) { uint32_t i, ch = mask_to_ch(mix, mask); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[i][ch]= 1.0f; } static void average_mix(struct channelmix *mix, - float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS], + float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t mask) { uint32_t i, ch = mask_to_ch(mix, mask); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[ch][i]= 1.0f; } -static void pair_mix(float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS]) +static void pair_mix(float matrix[MAX_CHANNELS][MAX_CHANNELS]) { uint32_t i; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[i][i]= 1.0f; } static bool match_mix(struct channelmix *mix, - float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS], + float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t src_mask, uint64_t dst_mask) { bool matched = false; @@ -181,7 +181,7 @@ static bool match_mix(struct channelmix *mix, static int make_matrix(struct channelmix *mix) { - float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }}; + float matrix[MAX_CHANNELS][MAX_CHANNELS] = {{ 0.0f }}; uint64_t src_mask = mix->src_mask, src_paired; uint64_t dst_mask = mix->dst_mask, dst_paired; uint32_t src_chan = mix->src_chan; @@ -293,7 +293,7 @@ static int make_matrix(struct channelmix *mix) keep &= ~STEREO; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign FC to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(FC)]= 1.0f; normalize = true; } else { @@ -313,7 +313,7 @@ static int make_matrix(struct channelmix *mix) keep &= ~FRONT; } else if ((dst_mask & _MASK(MONO))){ spa_log_info(mix->log, "assign STEREO to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(FL)]= 1.0f; matrix[i][_CH(FR)]= 1.0f; } @@ -352,7 +352,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FC,RC) += slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign RC to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(RC)]= 1.0f; normalize = true; } else { @@ -398,7 +398,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FC,RR)+= slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign RL+RR to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(RL)]= 1.0f; matrix[i][_CH(RR)]= 1.0f; } @@ -450,7 +450,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FC,SR) += slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign SL+SR to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(SL)]= 1.0f; matrix[i][_CH(SR)]= 1.0f; } @@ -471,7 +471,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FC,FRC)+= SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign FLC+FRC to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(FLC)]= 1.0f; matrix[i][_CH(FRC)]= 1.0f; } @@ -492,7 +492,7 @@ static int make_matrix(struct channelmix *mix) _MATRIX(FR,LFE) += llev * SQRT1_2; } else if ((dst_mask & _MASK(MONO))){ spa_log_info(mix->log, "assign LFE to MONO (%f)", 1.0f); - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(LFE)]= 1.0f; normalize = true; } else { @@ -690,7 +690,7 @@ done: static void impl_channelmix_set_volume(struct channelmix *mix, float volume, bool mute, uint32_t n_channel_volumes, float *channel_volumes) { - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; float vol = mute ? 0.0f : volume, t; uint32_t i, j; uint32_t src_chan = mix->src_chan; @@ -760,8 +760,8 @@ int channelmix_init(struct channelmix *mix) { const struct channelmix_info *info; - if (mix->src_chan > SPA_AUDIO_MAX_CHANNELS || - mix->dst_chan > SPA_AUDIO_MAX_CHANNELS) + if (mix->src_chan > MAX_CHANNELS || + mix->dst_chan > MAX_CHANNELS) return -EINVAL; info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask, diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index 26e2efc3a..6ea2b9451 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -24,6 +24,7 @@ #define BUFFER_SIZE 4096 #define MAX_TAPS 255u +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define CHANNELMIX_OPS_MAX_ALIGN 16 @@ -50,8 +51,8 @@ struct channelmix { #define CHANNELMIX_FLAG_EQUAL (1<<2) /**< all values are equal */ #define CHANNELMIX_FLAG_COPY (1<<3) /**< 1 on diagonal, can be nxm */ uint32_t flags; - float matrix_orig[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS]; - float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS]; + float matrix_orig[MAX_CHANNELS][MAX_CHANNELS]; + float matrix[MAX_CHANNELS][MAX_CHANNELS]; float freq; /* sample frequency */ float lfe_cutoff; /* in Hz, 0 is disabled */ @@ -59,7 +60,7 @@ struct channelmix { float rear_delay; /* in ms, 0 is disabled */ float widen; /* stereo widen. 0 is disabled */ uint32_t hilbert_taps; /* to phase shift, 0 disabled */ - struct lr4 lr4[SPA_AUDIO_MAX_CHANNELS]; + struct lr4 lr4[MAX_CHANNELS]; float buffer_mem[2 * BUFFER_SIZE*2 + CHANNELMIX_OPS_MAX_ALIGN/4]; float *buffer[2]; diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c index 0546e629a..de3ebb8b5 100644 --- a/spa/plugins/audioconvert/test-audioconvert.c +++ b/spa/plugins/audioconvert/test-audioconvert.c @@ -25,7 +25,8 @@ SPA_LOG_IMPL(logger); extern const struct spa_handle_factory test_source_factory; -#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS +#define MAX_PORTS (MAX_CHANNELS+1) struct context { struct spa_handle *convert_handle; diff --git a/spa/plugins/avb/avb-pcm.h b/spa/plugins/avb/avb-pcm.h index 7e026741d..41f803d4c 100644 --- a/spa/plugins/avb/avb-pcm.h +++ b/spa/plugins/avb/avb-pcm.h @@ -109,6 +109,7 @@ static inline char *format_streamid(char *str, size_t size, const uint64_t strea return str; } +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_BUFFERS 32 struct buffer { @@ -127,7 +128,7 @@ struct buffer { struct channel_map { uint32_t channels; - uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; + uint32_t pos[MAX_CHANNELS]; }; struct port { diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 21ede7d34..d9cdc9a0e 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -58,6 +58,8 @@ static struct spa_log *log; #define BITRATE_DUPLEX_BIDI 160000 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + #define OPUS_05_MAX_BYTES (15 * 1024) struct props { @@ -313,14 +315,14 @@ static void parse_settings(struct props *props, const struct spa_dict *settings) return; if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.channels"), &v, 0)) - props->channels = SPA_CLAMP(v, 1u, SPA_AUDIO_MAX_CHANNELS); + props->channels = SPA_CLAMP(v, 1u, MAX_CHANNELS); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.max-bitrate"), &v, 0)) props->max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.coupled-streams"), &v, 0)) props->coupled_streams = SPA_CLAMP(v, 0u, props->channels / 2); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.channels"), &v, 0)) - props->bidi_channels = SPA_CLAMP(v, 0u, SPA_AUDIO_MAX_CHANNELS); + props->bidi_channels = SPA_CLAMP(v, 0u, MAX_CHANNELS); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.max-bitrate"), &v, 0)) props->bidi_max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.coupled-streams"), &v, 0)) @@ -503,7 +505,7 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc const uint8_t *permutation = NULL; size_t i, j; - if (channels > SPA_AUDIO_MAX_CHANNELS) + if (channels > MAX_CHANNELS) return -EINVAL; if (2 * coupled_streams > channels) return -EINVAL; @@ -561,7 +563,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, a2dp_opus_05_t a2dp_opus_05 = { .info = codec->vendor, .main = { - .channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS), + .channels = SPA_MIN(255u, MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | @@ -571,7 +573,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, OPUS_05_INIT_BITRATE(0) }, .bidi = { - .channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS), + .channels = SPA_MIN(255u, MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | @@ -771,7 +773,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, a2dp_opus_05_t conf; a2dp_opus_05_direction_t *dir; struct spa_pod_frame f[1]; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[MAX_CHANNELS]; if (caps_size < sizeof(conf)) return -EINVAL; diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 4b5ca1d65..b3af4615b 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -6850,7 +6850,7 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di const char *key, uint32_t *value) { const char *str; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[MAX_CHANNELS]; uint32_t n_channels; uint32_t locations; unsigned int i, j; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index dde098ace..64d7064f7 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -38,7 +38,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.device"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic -#define MAX_NODES (2*SPA_AUDIO_MAX_CHANNELS) +#define MAX_NODES (2*MAX_CHANNELS) #define DEVICE_ID_SOURCE 0 #define DEVICE_ID_SINK 1 @@ -99,9 +99,9 @@ struct node { unsigned int offload_acquired:1; uint32_t n_channels; int64_t latency_offset; - uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; - float volumes[SPA_AUDIO_MAX_CHANNELS]; - float soft_volumes[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels[MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; + float soft_volumes[MAX_CHANNELS]; }; struct dynamic_node @@ -129,8 +129,8 @@ struct device_set { bool leader; uint32_t sinks; uint32_t sources; - struct device_set_member sink[SPA_AUDIO_MAX_CHANNELS]; - struct device_set_member source[SPA_AUDIO_MAX_CHANNELS]; + struct device_set_member sink[MAX_CHANNELS]; + struct device_set_member source[MAX_CHANNELS]; }; struct impl { @@ -182,7 +182,7 @@ static void init_node(struct impl *this, struct node *node, uint32_t id) spa_zero(*node); node->id = id; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) { + for (i = 0; i < MAX_CHANNELS; i++) { node->volumes[i] = 1.0f; node->soft_volumes[i] = 1.0f; } @@ -546,7 +546,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id) if (node->channels[k] == t->channels[j]) break; } - if (k == node->n_channels && node->n_channels < SPA_AUDIO_MAX_CHANNELS) + if (k == node->n_channels && node->n_channels < MAX_CHANNELS) node->channels[node->n_channels++] = t->channels[j]; } } @@ -2946,8 +2946,8 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) props; int changed = 0; - float volumes[SPA_AUDIO_MAX_CHANNELS]; - uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; + uint32_t channels[MAX_CHANNELS]; uint32_t n_volumes = 0, SPA_UNUSED n_channels = 0; int64_t latency_offset = 0; diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 45754c19a..0e25592e2 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -157,6 +157,8 @@ extern "C" { #define SPA_BT_NO_BATTERY ((uint8_t)255) +#define MAX_CHANNELS (SPA_AUDIO_MAX_CHANNELS) + enum spa_bt_media_direction { SPA_BT_MEDIA_SOURCE, SPA_BT_MEDIA_SINK, @@ -678,7 +680,7 @@ struct spa_bt_transport { struct spa_list bap_transport_linked; uint32_t n_channels; - uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels[MAX_CHANNELS]; struct spa_bt_transport_volume volumes[SPA_BT_VOLUME_ID_TERM]; diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 3d3f2ec75..bac1e84ab 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -2139,7 +2139,7 @@ static int port_set_format(struct impl *this, struct port *port, if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > MAX_CHANNELS) return -EINVAL; if (this->transport && this->transport->iso_io) { diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 4fab044a0..dc52a09b4 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -1439,7 +1439,7 @@ static int port_set_format(struct impl *this, struct port *port, if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > MAX_CHANNELS) return -EINVAL; port->frame_size = info.info.raw.channels; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index d32e4a37f..871eda4da 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -39,6 +39,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.filter-graph"); #define MAX_HNDL 64 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_RATE 48000 @@ -154,15 +155,15 @@ struct graph_hndl { struct volume { bool mute; uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; uint32_t n_ports; - struct port *ports[SPA_AUDIO_MAX_CHANNELS]; - float min[SPA_AUDIO_MAX_CHANNELS]; - float max[SPA_AUDIO_MAX_CHANNELS]; + struct port *ports[MAX_CHANNELS]; + float min[MAX_CHANNELS]; + float max[MAX_CHANNELS]; #define SCALE_LINEAR 0 #define SCALE_CUBIC 1 - int scale[SPA_AUDIO_MAX_CHANNELS]; + int scale[MAX_CHANNELS]; }; struct graph { @@ -194,9 +195,9 @@ struct graph { uint32_t n_inputs; uint32_t n_outputs; - uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t inputs_position[MAX_CHANNELS]; uint32_t n_inputs_position; - uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t outputs_position[MAX_CHANNELS]; uint32_t n_outputs_position; float min_latency; @@ -256,8 +257,8 @@ static void emit_filter_graph_info(struct impl *impl, bool full) char n_inputs[64], n_outputs[64], latency[64]; struct spa_dict_item items[6]; struct spa_dict dict = SPA_DICT(items, 0); - char in_pos[SPA_AUDIO_MAX_CHANNELS * 8]; - char out_pos[SPA_AUDIO_MAX_CHANNELS * 8]; + char in_pos[MAX_CHANNELS * 8]; + char out_pos[MAX_CHANNELS * 8]; snprintf(n_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs); snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); @@ -745,7 +746,7 @@ static int impl_set_props(void *object, enum spa_direction direction, const stru case SPA_PROP_channelVolumes: { uint32_t i, n_vols; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { @@ -772,7 +773,7 @@ static int impl_set_props(void *object, enum spa_direction direction, const stru } } if (do_volume && vol->n_ports != 0) { - float soft_vols[SPA_AUDIO_MAX_CHANNELS]; + float soft_vols[MAX_CHANNELS]; uint32_t i; for (i = 0; i < vol->n_volumes; i++) @@ -1264,7 +1265,7 @@ static int parse_volume(struct graph *graph, struct spa_json *json, enum spa_dir spa_log_error(impl->log, "unknown control port %s", control); return -ENOENT; } - if (vol->n_ports >= SPA_AUDIO_MAX_CHANNELS) { + if (vol->n_ports >= MAX_CHANNELS) { spa_log_error(impl->log, "too many volume controls"); return -ENOSPC; } diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 70edd4ff2..0943fbb91 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -35,12 +35,13 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.null-audio-sink"); #define DEFAULT_CLOCK_NAME "clock.system.monotonic" +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS struct props { uint32_t format; uint32_t channels; uint32_t rate; - uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; + uint32_t pos[MAX_CHANNELS]; char clock_name[64]; unsigned int debug:1; unsigned int driver:1; diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 842138688..6166f161f 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -231,9 +231,9 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( stream.props= ) " \ "( stream.rules= ) " +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DELAYBUF_MAX_SIZE (20 * sizeof(float) * 96000) - static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Combine multiple streams into a single stream" }, @@ -312,10 +312,10 @@ struct stream { struct spa_latency_info latency; struct spa_audio_info_raw info; - uint32_t remap[SPA_AUDIO_MAX_CHANNELS]; + uint32_t remap[MAX_CHANNELS]; void *delaybuf; - struct ringbuffer delay[SPA_AUDIO_MAX_CHANNELS]; + struct ringbuffer delay[MAX_CHANNELS]; int64_t delay_samples; /* for main loop */ int64_t data_delay_samples; /* for data loop */ @@ -509,7 +509,7 @@ static void update_latency(struct impl *impl) struct replace_delay_info { struct stream *stream; void *buf; - struct ringbuffer delay[SPA_AUDIO_MAX_CHANNELS]; + struct ringbuffer delay[MAX_CHANNELS]; }; static int do_replace_delay(struct spa_loop *loop, bool async, uint32_t seq, @@ -1228,7 +1228,7 @@ static void combine_output_process(void *d) struct pw_buffer *in, *out; struct stream *s; bool delay_changed = false; - bool mix[SPA_AUDIO_MAX_CHANNELS]; + bool mix[MAX_CHANNELS]; if ((out = pw_stream_dequeue_buffer(impl->combine)) == NULL) { pw_log_debug("%p: out of output buffers: %m", impl); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index d241369c7..bb9ea3adb 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -154,6 +154,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_RATE 48000 #define DEFAULT_POSITION "[ FL FR ]" +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS /* Hopefully this is enough for any combination of AEC engine and resampler * input requirement for rate matching */ @@ -203,7 +204,7 @@ struct impl { struct spa_hook source_listener; struct spa_audio_info_raw source_info; - void *rec_buffer[SPA_AUDIO_MAX_CHANNELS]; + void *rec_buffer[MAX_CHANNELS]; uint32_t rec_ringsize; struct spa_ringbuffer rec_ring; @@ -215,13 +216,13 @@ struct impl { struct pw_properties *sink_props; struct pw_stream *sink; struct spa_hook sink_listener; - void *play_buffer[SPA_AUDIO_MAX_CHANNELS]; + void *play_buffer[MAX_CHANNELS]; uint32_t play_ringsize; struct spa_ringbuffer play_ring; struct spa_ringbuffer play_delayed_ring; struct spa_audio_info_raw sink_info; - void *out_buffer[SPA_AUDIO_MAX_CHANNELS]; + void *out_buffer[MAX_CHANNELS]; uint32_t out_ringsize; struct spa_ringbuffer out_ring; diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 8c94edcdd..e4ad5cab8 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -112,6 +112,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS 128 #define FFADO_RT_PRIORITY_PACKETIZER_RELATIVE 5 @@ -179,7 +180,7 @@ struct port { struct volume { bool mute; uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; }; struct stream { @@ -760,7 +761,7 @@ static int make_stream_ports(struct stream *s) struct port *port = s->ports[i]; char channel[32]; - snprintf(channel, sizeof(channel), "AUX%u", n_channels % SPA_AUDIO_MAX_CHANNELS); + snprintf(channel, sizeof(channel), "AUX%u", n_channels % MAX_CHANNELS); switch (port->stream_type) { case ffado_stream_type_audio: @@ -873,7 +874,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) case SPA_PROP_channelVolumes: { uint32_t n; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { s->volume.n_volumes = n; @@ -1228,7 +1229,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->source.info.channels != n_channels) { impl->source.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + for (i = 0; i < SPA_MIN(impl->source.info.channels, MAX_CHANNELS); i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1254,7 +1255,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->sink.info.channels != n_channels) { impl->sink.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + for (i = 0; i < SPA_MIN(impl->sink.info.channels, MAX_CHANNELS); i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 226caf6fe..2c88a5cbc 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -115,6 +115,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS 128 #define DEFAULT_CLIENT_NAME "PipeWire" @@ -157,7 +158,7 @@ struct port { struct volume { bool mute; uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; }; struct stream { @@ -624,7 +625,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) case SPA_PROP_channelVolumes: { uint32_t n; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { s->volume.n_volumes = n; diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index cacdf9368..cfc48f69b 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -512,7 +512,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) case SPA_PROP_channelVolumes: { uint32_t n; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { s->volume.n_volumes = n; diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index c5d8d308b..aa61013ea 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -677,7 +677,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) case SPA_PROP_channelVolumes: { uint32_t n; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { s->volume.n_volumes = n; diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 37f7854db..eacc1c95b 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -7,10 +7,12 @@ #include #endif +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + struct volume { bool mute; uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; + float volumes[MAX_CHANNELS]; }; static inline float bswap_f32(float f) diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index e3d438c93..0bea31bc0 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -12,6 +12,8 @@ #include "format.h" +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + static const struct format audio_formats[] = { [SAMPLE_U8] = { SAMPLE_U8, SPA_AUDIO_FORMAT_U8, "u8", 1 }, [SAMPLE_ALAW] = { SAMPLE_ALAW, SPA_AUDIO_FORMAT_ALAW, "alaw", 1 }, diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index c9b9e5f9d..26af5aa20 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -117,6 +117,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + #define MODULE_USAGE "( remote.name= ] " \ "( node.latency= ] " \ "( node.name= ] " \ @@ -295,7 +297,7 @@ static void stream_param_changed(void *d, uint32_t id, const struct spa_pod *par { struct pa_cvolume volume; uint32_t n; - float vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { @@ -832,10 +834,10 @@ do_stream_sync_volumes(struct spa_loop *loop, struct spa_pod_frame f[1]; struct spa_pod *param; uint32_t i, channels; - float vols[SPA_AUDIO_MAX_CHANNELS]; - float soft_vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS]; + float soft_vols[MAX_CHANNELS]; - channels = SPA_MIN(impl->volume.channels, SPA_AUDIO_MAX_CHANNELS); + channels = SPA_MIN(impl->volume.channels, MAX_CHANNELS); for (i = 0; i < channels; i++) { vols[i] = (float)pa_sw_volume_to_linear(impl->volume.values[i]); soft_vols[i] = 1.0f; diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 11948807b..2fdb80618 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -158,6 +158,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); #define RAOP_LATENCY_MS 250 #define DEFAULT_LATENCY_MS 1500 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define VOLUME_MAX 0.0 #define VOLUME_MIN -30.0 #define VOLUME_MUTE -144.0 @@ -1612,8 +1613,8 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp case SPA_PROP_channelVolumes: { uint32_t i, n_vols; - float vols[SPA_AUDIO_MAX_CHANNELS], volume; - float soft_vols[SPA_AUDIO_MAX_CHANNELS]; + float vols[MAX_CHANNELS], volume; + float soft_vols[MAX_CHANNELS]; if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 0c00beb61..22fe89b68 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -156,6 +156,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_LOOP false #define MAX_SDP 2048 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define USAGE "( local.ifname= ) " \ "( sap.ip= ) " \ @@ -1404,7 +1405,7 @@ static int parse_sdp_i(struct impl *impl, char *c, struct sdp_info *info) c[strcspn(c, " ")] = '\0'; uint32_t channels; - if (sscanf(c, "%u", &channels) != 1 || channels <= 0 || channels > SPA_AUDIO_MAX_CHANNELS) + if (sscanf(c, "%u", &channels) != 1 || channels <= 0 || channels > MAX_CHANNELS) return 0; c += strcspn(c, "\0"); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index cc1a3bda2..199046e80 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -70,6 +70,8 @@ #define DEFAULT_VOLUME 1.0 #define DEFAULT_QUALITY 4 +#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS + enum mode { mode_none, mode_playback, @@ -91,7 +93,7 @@ typedef int (*fill_fn)(struct data *d, void *dest, unsigned int n_frames, bool * struct channelmap { uint32_t n_channels; - uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels[MAX_CHANNELS]; }; struct data { From 9e7cae13df10382574ed405d67cbda6257056aee Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 18:09:11 +0200 Subject: [PATCH 0954/1014] alsa: use the amount of positions we will write --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 4bd55c2a8..4b91e82c0 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -643,7 +643,7 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS]) +static int set_default_channels(uint32_t channels, uint32_t position[8]) { switch (channels) { case 8: From 8bbca3b8f36e7aea04ce07998c30acf28b23c48a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 20 Oct 2025 15:16:54 +0200 Subject: [PATCH 0955/1014] spa: add spa_audio_parse_position_n Add a function that accepts the size of the position array when reading the audio positions. This makes it possible to decouple the position array size from SPA_AUDIO_MAX_CHANNELS. Also use SPA_N_ELEMENTS to pass the number of array elements to functions instead of a fixed constant. This makes it easier to change the array size later to a different constant without having to patch up all the places where the size is used. --- pipewire-alsa/alsa-plugins/ctl_pipewire.c | 2 +- spa/include/spa/param/audio/dsd-utils.h | 2 +- spa/include/spa/param/audio/raw-json.h | 17 ++++++++++++----- spa/include/spa/param/audio/raw-utils.h | 2 +- spa/plugins/alsa/alsa-acp-device.c | 4 ++-- spa/plugins/alsa/alsa-pcm.h | 2 +- spa/plugins/audioconvert/audioconvert.c | 21 +++++++++++---------- spa/plugins/avb/avb-pcm.c | 3 ++- spa/plugins/bluez5/bluez5-dbus.c | 3 ++- spa/plugins/bluez5/bluez5-device.c | 4 ++-- spa/plugins/filter-graph/filter-graph.c | 12 +++++++----- spa/plugins/support/null-audio-sink.c | 5 +++-- spa/plugins/volume/volume.c | 2 +- src/examples/export-source.c | 2 +- src/modules/module-combine-stream.c | 6 ++++-- src/modules/module-echo-cancel.c | 16 ++++++++-------- src/modules/module-example-filter.c | 2 +- src/modules/module-ffado-driver.c | 2 +- src/modules/module-jack-tunnel.c | 2 +- src/modules/module-loopback.c | 2 +- src/modules/module-netjack2-driver.c | 10 ++++++---- src/modules/module-netjack2-manager.c | 8 +++++--- src/modules/module-protocol-pulse/format.c | 5 +++-- src/modules/module-protocol-pulse/volume.c | 6 +++--- src/modules/module-pulse-tunnel.c | 2 +- src/modules/module-raop-sink.c | 2 +- src/tools/pw-cat.c | 3 ++- 27 files changed, 84 insertions(+), 63 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/ctl_pipewire.c b/pipewire-alsa/alsa-plugins/ctl_pipewire.c index 459355c8f..36bc4be5e 100644 --- a/pipewire-alsa/alsa-plugins/ctl_pipewire.c +++ b/pipewire-alsa/alsa-plugins/ctl_pipewire.c @@ -856,7 +856,7 @@ static void parse_props(struct global *g, const struct spa_pod *param, bool devi uint32_t n_volumes, i; n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - volumes, SPA_AUDIO_MAX_CHANNELS); + volumes, SPA_N_ELEMENTS(volumes)); g->node.channel_volume.channels = n_volumes; for (i = 0; i < n_volumes; i++) diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h index 980bdf971..2d99e79f2 100644 --- a/spa/include/spa/param/audio/dsd-utils.h +++ b/spa/include/spa/param/audio/dsd-utils.h @@ -42,7 +42,7 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) + !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_N_ELEMENTS(info->position))) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); return res; diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 92d7a1aad..50bd63181 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -28,8 +28,8 @@ extern "C" { #endif SPA_API_AUDIO_RAW_JSON int -spa_audio_parse_position(const char *str, size_t len, - uint32_t *position, uint32_t *n_channels) +spa_audio_parse_position_n(const char *str, size_t len, + uint32_t *position, uint32_t max_channels, uint32_t *n_channels) { struct spa_json iter; char v[256]; @@ -39,12 +39,18 @@ spa_audio_parse_position(const char *str, size_t len, return 0; while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && - channels < SPA_AUDIO_MAX_CHANNELS) { + channels < max_channels) { position[channels++] = spa_type_audio_channel_from_short_name(v); } *n_channels = channels; return channels; } +SPA_API_AUDIO_RAW_JSON int +spa_audio_parse_position(const char *str, size_t len, + uint32_t *position, uint32_t *n_channels) +{ + return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); +} 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) @@ -58,10 +64,11 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) - info->channels = SPA_MIN(v, SPA_AUDIO_MAX_CHANNELS); + info->channels = SPA_MIN(v, SPA_N_ELEMENTS(info->position)); } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { - if (spa_audio_parse_position(val, strlen(val), info->position, &info->channels) > 0) + if (spa_audio_parse_position_n(val, strlen(val), info->position, + SPA_N_ELEMENTS(info->position), &info->channels) > 0) SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } } diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 505965972..1f1dfe45d 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -55,7 +55,7 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS)) + !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_N_ELEMENTS(info->position))) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); return res; diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index bdcb40fa5..6ab41c8d2 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -697,13 +697,13 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct break; case SPA_PROP_channelVolumes: if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + volumes, SPA_N_ELEMENTS(volumes))) > 0) { changed++; } break; case SPA_PROP_channelMap: if (spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - channels, SPA_AUDIO_MAX_CHANNELS) > 0) { + channels, SPA_N_ELEMENTS(channels)) > 0) { changed++; } break; diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 5bad22159..09b5140b9 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -315,7 +315,7 @@ void spa_alsa_emit_port_info(struct state *state, bool full); static inline void spa_alsa_parse_position(struct channel_map *map, const char *val, size_t len) { - spa_audio_parse_position(val, len, map->pos, &map->channels); + spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->channels); } static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 0658dea1c..2ff270fb1 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1104,11 +1104,11 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) else if (spa_streq(k, "n_outputs")) spa_atou32(s, &g->n_outputs, 0); else if (spa_streq(k, "inputs.audio.position")) - spa_audio_parse_position(s, strlen(s), - g->inputs_position, &g->n_inputs); + spa_audio_parse_position_n(s, strlen(s), g->inputs_position, + SPA_N_ELEMENTS(g->inputs_position), &g->n_inputs); else if (spa_streq(k, "outputs.audio.position")) - spa_audio_parse_position(s, strlen(s), - g->outputs_position, &g->n_outputs); + spa_audio_parse_position_n(s, strlen(s), g->outputs_position, + SPA_N_ELEMENTS(g->outputs_position), &g->n_outputs); else if (spa_streq(k, "latency")) { double latency; if (spa_atod(s, &latency)) @@ -1749,7 +1749,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) case SPA_PROP_channelVolumes: if (!p->lock_volumes && (n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->channel.volumes, SPA_N_ELEMENTS(p->channel.volumes))) > 0) { have_channel_volume = true; p->channel.n_volumes = n; changed++; @@ -1757,7 +1757,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) break; case SPA_PROP_channelMap: if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->channel_map, SPA_N_ELEMENTS(p->channel_map))) > 0) { p->n_channels = n; changed++; } @@ -1772,7 +1772,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) case SPA_PROP_softVolumes: if (!p->lock_volumes && (n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->soft.volumes, SPA_N_ELEMENTS(p->soft.volumes))) > 0) { have_soft_volume = true; p->soft.n_volumes = n; changed++; @@ -1784,7 +1784,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) break; case SPA_PROP_monitorVolumes: if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + p->monitor.volumes, SPA_N_ELEMENTS(p->monitor.volumes))) > 0) { p->monitor.n_volumes = n; changed++; } @@ -4297,8 +4297,9 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { if (s != NULL) - spa_audio_parse_position(s, strlen(s), this->props.channel_map, - &this->props.n_channels); + spa_audio_parse_position_n(s, strlen(s), + this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), + &this->props.n_channels); } else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY)) this->port_ignore_latency = spa_atob(s); diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index 59b05a3a5..f54e9dabf 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -40,7 +40,8 @@ static int avb_set_param(struct state *state, const char *k, const char *s) state->default_format = spa_type_audio_format_from_short_name(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { - spa_audio_parse_position(s, strlen(s), state->default_pos.pos, + spa_audio_parse_position_n(s, strlen(s), state->default_pos.pos, + SPA_N_ELEMENTS(state->default_pos.pos), &state->default_pos.channels); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index b3af4615b..d4305c535 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -6861,7 +6861,8 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di if (spa_atou32(str, value, 0)) return; - if (!spa_audio_parse_position(str, strlen(str), position, &n_channels)) { + if (!spa_audio_parse_position_n(str, strlen(str), position, + SPA_N_ELEMENTS(position), &n_channels)) { spa_log_error(this->log, "property %s '%s' is not valid position array", key, str); return; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 64d7064f7..aee6d8cab 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -2972,11 +2972,11 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p break; case SPA_PROP_channelVolumes: n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - volumes, SPA_AUDIO_MAX_CHANNELS); + volumes, SPA_N_ELEMENTS(volumes)); break; case SPA_PROP_channelMap: n_channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - channels, SPA_AUDIO_MAX_CHANNELS); + channels, SPA_N_ELEMENTS(channels)); break; case SPA_PROP_latencyOffsetNsec: if (spa_pod_get_long(&prop->value, &latency_offset) == 0) { diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 871eda4da..bb7f24831 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -749,7 +749,7 @@ static int impl_set_props(void *object, enum spa_direction direction, const stru float vols[MAX_CHANNELS]; if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, - SPA_AUDIO_MAX_CHANNELS)) > 0) { + SPA_N_ELEMENTS(vols))) > 0) { if (vol->n_volumes != n_vols) do_volume = true; vol->n_volumes = n_vols; @@ -2119,8 +2119,9 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } - spa_audio_parse_position(val, len, graph->inputs_position, - &graph->n_inputs_position); + spa_audio_parse_position_n(val, len, graph->inputs_position, + SPA_N_ELEMENTS(graph->inputs_position), + &graph->n_inputs_position); impl->info.n_inputs = graph->n_inputs_position; } else if (spa_streq("outputs.audio.position", key)) { @@ -2129,8 +2130,9 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } - spa_audio_parse_position(val, len, graph->outputs_position, - &graph->n_outputs_position); + spa_audio_parse_position_n(val, len, graph->outputs_position, + SPA_N_ELEMENTS(graph->outputs_position), + &graph->n_outputs_position); impl->info.n_outputs = graph->n_outputs_position; } else if (spa_streq("nodes", key)) { diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 0943fbb91..27ae8d0f0 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -637,7 +637,7 @@ port_set_format(struct impl *this, if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) return -EINVAL; if (this->props.format != 0) { @@ -950,7 +950,8 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, SPA_KEY_NODE_DRIVER)) { this->props.driver = spa_atob(s); } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { - spa_audio_parse_position(s, strlen(s), this->props.pos, &this->props.channels); + spa_audio_parse_position_n(s, strlen(s), this->props.pos, + SPA_N_ELEMENTS(this->props.pos), &this->props.channels); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index 164e46a6a..7881703db 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -455,7 +455,7 @@ static int port_set_format(void *object, if (info.info.raw.format != SPA_AUDIO_FORMAT_S16 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) return -EINVAL; this->bpf = 2 * info.info.raw.channels; diff --git a/src/examples/export-source.c b/src/examples/export-source.c index 85af5b727..3d3859273 100644 --- a/src/examples/export-source.c +++ b/src/examples/export-source.c @@ -269,7 +269,7 @@ static int port_set_format(void *object, return -EINVAL; if (d->format.rate == 0 || d->format.channels == 0 || - d->format.channels > SPA_AUDIO_MAX_CHANNELS) + d->format.channels > SPA_N_ELEMENTS(d->format.position)) return -EINVAL; } diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 6166f161f..54a98970b 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -866,13 +866,15 @@ static int create_stream(struct stream_info *info) s->info = impl->info; if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) - spa_audio_parse_position(str, strlen(str), s->info.position, &s->info.channels); + spa_audio_parse_position_n(str, strlen(str), s->info.position, + SPA_N_ELEMENTS(s->info.position), &s->info.channels); if (s->info.channels == 0) s->info = impl->info; spa_zero(remap_info); if ((str = pw_properties_get(info->stream_props, "combine.audio.position")) != NULL) - spa_audio_parse_position(str, strlen(str), remap_info.position, &remap_info.channels); + spa_audio_parse_position_n(str, strlen(str), remap_info.position, + SPA_N_ELEMENTS(remap_info.position), &remap_info.channels); if (remap_info.channels == 0) remap_info = s->info; diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index bb9ea3adb..0d640f162 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -1371,21 +1371,21 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - spa_audio_parse_position(str, strlen(str), - impl->capture_info.position, &impl->capture_info.channels); + spa_audio_parse_position_n(str, strlen(str), impl->capture_info.position, + SPA_N_ELEMENTS(impl->capture_info.position), &impl->capture_info.channels); } if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - spa_audio_parse_position(str, strlen(str), - impl->source_info.position, &impl->source_info.channels); + spa_audio_parse_position_n(str, strlen(str), impl->source_info.position, + SPA_N_ELEMENTS(impl->source_info.position), &impl->source_info.channels); } if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - spa_audio_parse_position(str, strlen(str), - impl->sink_info.position, &impl->sink_info.channels); + spa_audio_parse_position_n(str, strlen(str), impl->sink_info.position, + SPA_N_ELEMENTS(impl->sink_info.position), &impl->sink_info.channels); impl->playback_info = impl->sink_info; } if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - spa_audio_parse_position(str, strlen(str), - impl->playback_info.position, &impl->playback_info.channels); + spa_audio_parse_position_n(str, strlen(str), impl->playback_info.position, + SPA_N_ELEMENTS(impl->playback_info.position), &impl->playback_info.channels); if (impl->playback_info.channels != impl->sink_info.channels) impl->playback_info = impl->sink_info; } diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index ec9cdbd19..92f1f38cb 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -299,7 +299,7 @@ static void capture_param_changed(void *data, uint32_t id, const struct spa_pod return; if (info.rate == 0 || info.channels == 0 || - info.channels > SPA_AUDIO_MAX_CHANNELS) + info.channels > SPA_N_ELEMENTS(info.position)) return; break; } diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index e4ad5cab8..db6fe8b71 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -876,7 +876,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) uint32_t n; float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { s->volume.n_volumes = n; for (n = 0; n < s->volume.n_volumes; n++) s->volume.volumes[n] = vols[n]; diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 2c88a5cbc..f5066abc4 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -627,7 +627,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) uint32_t n; float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { s->volume.n_volumes = n; for (n = 0; n < s->volume.n_volumes; n++) s->volume.volumes[n] = vols[n]; diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index b23d167c1..ce1da189d 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -539,7 +539,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, if (param != NULL) { if (spa_format_audio_raw_parse(param, &info) < 0 || info.channels == 0 || - info.channels > SPA_AUDIO_MAX_CHANNELS) + info.channels > SPA_N_ELEMENTS(info.position)) return; if ((impl->info.format != 0 && impl->info.format != info.format) || diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index cfc48f69b..f1aefaa35 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -442,7 +442,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, - s->info.position[i % SPA_AUDIO_MAX_CHANNELS]); + s->info.position[i % SPA_N_ELEMENTS(s->info.position)]); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", @@ -514,7 +514,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) uint32_t n; float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { s->volume.n_volumes = n; for (n = 0; n < s->volume.n_volumes; n++) s->volume.volumes[n] = vols[n]; @@ -863,7 +863,8 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p } impl->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { - impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, + (int)SPA_N_ELEMENTS(impl->sink.info.position)); for (i = 0; i < impl->sink.info.channels; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -874,7 +875,8 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p } impl->source.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { - impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, + (int)SPA_N_ELEMENTS(impl->source.info.position)); for (i = 0; i < impl->source.info.channels; i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index aa61013ea..6b3285305 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -679,7 +679,7 @@ static void parse_props(struct stream *s, const struct spa_pod *param) uint32_t n; float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { s->volume.n_volumes = n; for (n = 0; n < s->volume.n_volumes; n++) s->volume.volumes[n] = vols[n]; @@ -1026,14 +1026,16 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param follower->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; follower->source.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { - follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, + (int)SPA_N_ELEMENTS(follower->source.info.position)); for (i = 0; i < follower->source.info.channels; i++) follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { - follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, (int)SPA_AUDIO_MAX_CHANNELS); + follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, + (int)SPA_N_ELEMENTS(follower->sink.info.position)); for (i = 0; i < follower->sink.info.channels; i++) follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 0bea31bc0..189e60f17 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -451,7 +451,8 @@ void channel_map_parse(const char *str, struct channel_map *map) void channel_map_parse_position(const char *str, struct channel_map *map) { uint32_t channels = 0, position[SPA_AUDIO_MAX_CHANNELS]; - spa_audio_parse_position(str, strlen(str), position, &channels); + spa_audio_parse_position_n(str, strlen(str), position, + SPA_N_ELEMENTS(position), &channels); positions_to_channel_map(position, channels, map); } @@ -535,7 +536,7 @@ int format_parse_param(const struct spa_pod *param, bool collect, if (info.info.raw.format == 0 || info.info.raw.rate == 0 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) return -ENOTSUP; } break; diff --git a/src/modules/module-protocol-pulse/volume.c b/src/modules/module-protocol-pulse/volume.c index de71baa53..e53f967ec 100644 --- a/src/modules/module-protocol-pulse/volume.c +++ b/src/modules/module-protocol-pulse/volume.c @@ -53,7 +53,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo if (monitor) continue; info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - info->volume.values, CHANNELS_MAX); + info->volume.values, SPA_N_ELEMENTS(info->volume.values)); SPA_FLAG_UPDATE(info->flags, VOLUME_HW_VOLUME, prop->flags & SPA_POD_PROP_FLAG_HARDWARE); break; @@ -68,7 +68,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo if (!monitor) continue; info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - info->volume.values, CHANNELS_MAX); + info->volume.values, SPA_N_ELEMENTS(info->volume.values)); SPA_FLAG_CLEAR(info->flags, VOLUME_HW_VOLUME); break; case SPA_PROP_volumeBase: @@ -84,7 +84,7 @@ int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bo } case SPA_PROP_channelMap: info->map.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, - info->map.map, CHANNELS_MAX); + info->map.map, SPA_N_ELEMENTS(info->map.map)); break; default: break; diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 26af5aa20..afda6f484 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -300,7 +300,7 @@ static void stream_param_changed(void *d, uint32_t id, const struct spa_pod *par float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { volume.channels = SPA_MIN(PA_CHANNELS_MAX, n); for (n = 0; n < volume.channels; n++) volume.values[n] = pa_sw_volume_from_linear(vols[n]); diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 2fdb80618..accc2a9da 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1617,7 +1617,7 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp float soft_vols[MAX_CHANNELS]; if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - vols, SPA_AUDIO_MAX_CHANNELS)) > 0) { + vols, SPA_N_ELEMENTS(vols))) > 0) { volume = 0.0f; for (i = 0; i < n_vols; i++) { volume += vols[i]; diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 199046e80..0fefa2377 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -704,7 +704,8 @@ static int parse_channelmap(const char *channel_map, struct channelmap *map) } } - spa_audio_parse_position(channel_map, strlen(channel_map), map->channels, &map->n_channels); + spa_audio_parse_position_n(channel_map, strlen(channel_map), + map->channels, SPA_N_ELEMENTS(map->channels), &map->n_channels); return 0; } From 818d1435cea73b12e0fcc36c601bffb4b8a145ec Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 21 Oct 2025 13:06:25 +0200 Subject: [PATCH 0956/1014] treewide: access the position information using helpers Make sure we don't access out of bounds and that we use the helpers wherever we can to access the position information. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 28 +++++--- spa/include/spa/param/audio/raw-utils.h | 17 ++++- spa/plugins/audioconvert/audioadapter.c | 5 +- spa/plugins/audioconvert/audioconvert.c | 78 ++++++++++++---------- spa/plugins/bluez5/a2dp-codec-opus.c | 21 +++--- spa/plugins/bluez5/bluez5-dbus.c | 5 +- spa/plugins/bluez5/bluez5-device.c | 10 +-- spa/plugins/support/null-audio-sink.c | 2 +- spa/plugins/volume/volume.c | 3 +- src/examples/export-source.c | 3 +- src/modules/module-combine-stream.c | 5 +- src/modules/module-example-filter.c | 3 +- src/modules/module-ffado-driver.c | 4 +- src/modules/module-filter-chain.c | 2 +- src/modules/module-jack-tunnel.c | 2 +- src/modules/module-loopback.c | 5 +- src/modules/module-netjack2-driver.c | 14 ++-- src/modules/module-netjack2-manager.c | 14 ++-- src/modules/module-protocol-pulse/format.c | 13 ++-- src/modules/module-protocol-pulse/format.h | 2 +- src/modules/module-protocol-pulse/module.c | 10 +-- src/modules/module-pulse-tunnel.c | 3 +- src/modules/module-vban/stream.c | 10 +-- src/modules/module-zeroconf-discover.c | 2 +- src/tools/pw-cat.c | 8 ++- 25 files changed, 155 insertions(+), 114 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 4b91e82c0..27495a296 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -643,8 +643,11 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(uint32_t channels, uint32_t position[8]) +static int set_default_channels(uint32_t channels, uint32_t *position, uint32_t max_position) { + if (max_position < 8) + return -ENOSPC; + switch (channels) { case 8: position[6] = SPA_AUDIO_CHANNEL_SL; @@ -772,7 +775,8 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = io->channels; pw->requested.info.raw.rate = io->rate; - set_default_channels(io->channels, pw->requested.info.raw.position); + 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); pw->format = pw->requested; break; @@ -780,7 +784,8 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb; pw->requested.info.dsd.channels = io->channels; pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); - set_default_channels(io->channels, pw->requested.info.dsd.position); + set_default_channels(io->channels, pw->requested.info.dsd.position, + SPA_N_ELEMENTS(pw->requested.info.dsd.position)); pw->format = pw->requested; /* we need to let the server decide these values */ pw->format.info.dsd.bitorder = 0; @@ -902,26 +907,29 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; unsigned int i; - uint32_t *position; + uint32_t *position, max_position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = map->channels; position = pw->requested.info.raw.position; + max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: pw->requested.info.dsd.channels = map->channels; position = pw->requested.info.dsd.position; + max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return -EINVAL; } for (i = 0; i < map->channels; i++) { - position[i] = chmap_to_channel(map->pos[i]); + uint32_t pos = chmap_to_channel(map->pos[i]); + if (i < max_position) + position[i] = pos; pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - position[i])); + spa_debug_type_find_short_name(spa_type_audio_channel, pos)); } return 1; } @@ -930,16 +938,18 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_chmap_t *map; - uint32_t i, channels, *position; + uint32_t i, channels, *position, max_position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: channels = pw->requested.info.raw.channels; position = pw->requested.info.raw.position; + max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: channels = pw->requested.info.dsd.channels; position = pw->requested.info.dsd.position; + max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return NULL; @@ -949,7 +959,7 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) channels * sizeof(unsigned int)); map->channels = channels; for (i = 0; i < channels; i++) - map->pos[i] = channel_to_chmap(position[i]); + map->pos[i] = channel_to_chmap(position[i % max_position]); return map; } diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 1f1dfe45d..03d6e3ac9 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -29,7 +29,7 @@ extern "C" { #endif SPA_API_AUDIO_RAW_UTILS uint32_t -spa_format_audio_get_position(struct spa_audio_info_raw *info, uint32_t idx) +spa_format_audio_raw_get_position(const struct spa_audio_info_raw *info, uint32_t idx) { uint32_t pos; if (idx < SPA_AUDIO_MAX_CHANNELS) { @@ -41,6 +41,21 @@ spa_format_audio_get_position(struct spa_audio_info_raw *info, uint32_t idx) } 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_AUDIO_MAX_CHANNELS) + info->position[idx] = position; +} + +SPA_API_AUDIO_RAW_UTILS uint32_t +spa_format_audio_raw_copy_positions(const struct spa_audio_info_raw *info, uint32_t *position, uint32_t max_position) +{ + uint32_t i, n_pos = SPA_MIN(info->channels, max_position); + for (i = 0; i < n_pos; i++) + position[i] = spa_format_audio_raw_get_position(info, i); + return n_pos; +} SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 0891462a2..26cb4af74 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -2046,11 +2046,12 @@ static int do_auto_port_config(struct impl *this, const char *str) return -ENOENT; if (format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { + uint32_t n_pos = SPA_MIN(SPA_AUDIO_MAX_CHANNELS, format.info.raw.channels); if (position == POSITION_AUX) { - for (i = 0; i < format.info.raw.channels; i++) + for (i = 0; i < n_pos; i++) format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i; } else if (position == POSITION_UNKNOWN) { - for (i = 0; i < format.info.raw.channels; i++) + for (i = 0; i < n_pos; i++) format.info.raw.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; } } diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 2ff270fb1..b91a46ff8 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1150,7 +1150,7 @@ struct spa_filter_graph_events graph_events = { }; static int setup_filter_graph(struct impl *this, struct filter_graph *g, - uint32_t channels, uint32_t *position) + uint32_t channels, uint32_t *position, uint32_t max_position) { int res; char rate_str[64], in_ports[64]; @@ -1165,8 +1165,9 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, snprintf(in_ports, sizeof(in_ports), "%d", channels); g->n_inputs = channels; if (position) { - memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); - memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); + uint32_t n_pos = SPA_MIN(channels, max_position); + memcpy(g->inputs_position, position, sizeof(uint32_t) * n_pos); + memcpy(g->outputs_position, position, sizeof(uint32_t) * n_pos); } } @@ -1181,7 +1182,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, return res; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position); static void free_tmp(struct impl *this) { @@ -1263,7 +1264,7 @@ static int ensure_tmp(struct impl *this) static int setup_filter_graphs(struct impl *impl, bool force) { int res; - uint32_t channels, *position; + uint32_t channels, *position, max_position; struct dir *in, *out; struct filter_graph *g, *t; @@ -1272,6 +1273,7 @@ static int setup_filter_graphs(struct impl *impl, bool force) channels = in->format.info.raw.channels; position = in->format.info.raw.position; + max_position = SPA_N_ELEMENTS(in->format.info.raw.position); impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); spa_list_for_each_safe(g, t, &impl->active_graphs, link) { @@ -1279,19 +1281,20 @@ static int setup_filter_graphs(struct impl *impl, bool force) continue; if (force) g->setup = false; - if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { + if ((res = setup_filter_graph(impl, g, channels, position, max_position)) < 0) { g->removing = true; spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, spa_strerror(res)); } else { channels = g->n_outputs; position = g->outputs_position; + max_position = SPA_N_ELEMENTS(g->outputs_position); impl->maxports = SPA_MAX(impl->maxports, channels); } } if ((res = ensure_tmp(impl)) < 0) return res; - if ((res = setup_channelmix(impl, channels, position)) < 0) + if ((res = setup_channelmix(impl, channels, position, max_position)) < 0) return res; return 0; @@ -1896,10 +1899,11 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; for (i = 0; i < dir->n_ports; i++) { - init_port(this, direction, i, info->info.raw.position[i], true, false, false); + uint32_t pos = spa_format_audio_raw_get_position(&info->info.raw, i); + init_port(this, direction, i, pos, true, false, false); if (this->monitor && direction == SPA_DIRECTION_INPUT) init_port(this, SPA_DIRECTION_OUTPUT, i+1, - info->info.raw.position[i], true, true, false); + pos, true, true, false); } break; } @@ -2056,24 +2060,25 @@ static int setup_in_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(dst_info.info.raw.position, dst_info.info.raw.channels, + qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS), sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { - if (src_info.info.raw.position[i] != - dst_info.info.raw.position[j]) + uint32_t pi, pj; + + pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); + pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + if (pi != pj) continue; in->remap[i] = j; if (i != j) remap = true; spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, in->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, - src_info.info.raw.position[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - dst_info.info.raw.position[j])); - dst_info.info.raw.position[j] = -1; + spa_debug_type_find_short_name(spa_type_audio_channel, pi), + spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; } } @@ -2121,9 +2126,10 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) for (i = 0; i < p->n_channels; i++) { for (j = i; j < target; j++) { + uint32_t pj = spa_format_audio_raw_get_position(&info->info.raw, j); spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, - p->channel_map[i], info->info.raw.position[j]); - if (p->channel_map[i] != info->info.raw.position[j]) + p->channel_map[i], pj); + if (p->channel_map[i] != pj) continue; if (i != j) { SPA_SWAP(p->channel_map[i], p->channel_map[j]); @@ -2136,7 +2142,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) } p->n_channels = target; for (i = 0; i < p->n_channels; i++) - p->channel_map[i] = info->info.raw.position[i]; + p->channel_map[i] = spa_format_audio_raw_get_position(&info->info.raw, i); if (target == 0) return 0; @@ -2182,17 +2188,17 @@ static void set_volume(struct impl *this) this->params[IDX_Props].user++; } -static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) +static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position, uint32_t max_position) { uint32_t i, idx = 0; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", spa_debug_type_find_short_name(spa_type_audio_channel, - position[i])); + position[i % max_position])); return str; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -2205,18 +2211,18 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { - p = position[i]; + p = position[i % max_position]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { - p = out->format.info.raw.position[i]; + p = spa_format_audio_raw_get_position(&out->format.info.raw, i); dst_mask |= 1ULL << (p < 64 ? p : 0); } spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), - src_chan, position), src_mask); + src_chan, position, max_position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), - dst_chan, out->format.info.raw.position), dst_mask); + dst_chan, out->format.info.raw.position, SPA_AUDIO_MAX_CHANNELS), dst_mask); spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), @@ -2344,13 +2350,16 @@ static int setup_out_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(src_info.info.raw.position, src_info.info.raw.channels, + qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS), sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { - if (src_info.info.raw.position[i] != - dst_info.info.raw.position[j]) + uint32_t pi, pj; + + pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); + pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + if (pi != pj) continue; out->remap[i] = j; if (i != j) @@ -2358,11 +2367,10 @@ static int setup_out_convert(struct impl *this) spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, out->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, - src_info.info.raw.position[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, - dst_info.info.raw.position[j])); - dst_info.info.raw.position[j] = -1; + spa_debug_type_find_short_name(spa_type_audio_channel, pi), + spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + + spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; } } diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index d9cdc9a0e..7a330e8e0 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -497,7 +497,7 @@ static void get_default_bitrates(const struct media_codec *codec, bool bidi, int static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direction_t *conf, bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, - const uint8_t **surround_mapping, uint32_t *positions) + const uint8_t **surround_mapping, uint32_t *positions, uint32_t max_positions) { const uint32_t channels = conf->channels; const uint32_t location = OPUS_05_GET_LOCATION(*conf); @@ -544,13 +544,13 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc const struct audio_location loc = audio_locations[i]; if (location & loc.mask) { - if (permutation) - positions[permutation[j++]] = loc.position; - else - positions[j++] = loc.position; + uint32_t idx = permutation ? permutation[j] : j; + if (idx < max_positions) + positions[idx] = loc.position; + j++; } } - for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j) + for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels && j < max_positions; ++i, ++j) positions[j] = i; } @@ -785,7 +785,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, dir = !is_duplex_codec(codec) ? &conf.main : &conf.bidi; - if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position) < 0) + if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position, MAX_CHANNELS) < 0) return -EINVAL; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); @@ -837,9 +837,10 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags } info->info.raw.channels = dir1->channels; - if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, info->info.raw.position) < 0) + if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, + info->info.raw.position, SPA_N_ELEMENTS(info->info.raw.position)) < 0) return -EINVAL; - if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0) + if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL, 0) < 0) return -EINVAL; return 0; @@ -930,7 +931,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, - &enc_mapping, NULL)) < 0) + &enc_mapping, NULL, 0)) < 0) goto error; if (config_info.info.raw.channels != info->info.raw.channels) { res = -EINVAL; diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index d4305c535..2e92696bc 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include "codec-loader.h" @@ -5038,8 +5039,8 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } transport->n_channels = info.info.raw.channels; - memcpy(transport->channels, info.info.raw.position, - transport->n_channels * sizeof(uint32_t)); + spa_format_audio_raw_copy_positions(&info.info.raw, + transport->channels, SPA_N_ELEMENTS(transport->channels)); } else { transport->n_channels = 2; transport->channels[0] = SPA_AUDIO_CHANNEL_FL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index aee6d8cab..838bb54f3 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -451,7 +452,8 @@ static int node_offload_set_active(struct node *node, bool active) return res; } -static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) +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; struct spa_audio_info info = { 0 }; @@ -473,9 +475,7 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t return; } - *n_channels = info.info.raw.channels; - memcpy(channels, info.info.raw.position, - info.info.raw.channels * sizeof(uint32_t)); + *n_channels = spa_format_audio_raw_copy_positions(&info.info.raw, channels, max_channels); } static const char *get_channel_name(uint32_t channel) @@ -686,7 +686,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, this->nodes[id].active = true; this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = a2dp_duplex; - get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels); + get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels, MAX_CHANNELS); if (this->nodes[id].transport) spa_hook_remove(&this->nodes[id].transport_listener); this->nodes[id].transport = t; diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 27ae8d0f0..acaec6f7b 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -637,7 +637,7 @@ port_set_format(struct impl *this, if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || - info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) + info.info.raw.channels > MAX_CHANNELS) return -EINVAL; if (this->props.format != 0) { diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c index 7881703db..0c750a96a 100644 --- a/spa/plugins/volume/volume.c +++ b/spa/plugins/volume/volume.c @@ -454,8 +454,7 @@ static int port_set_format(void *object, return -EINVAL; if (info.info.raw.format != SPA_AUDIO_FORMAT_S16 || - info.info.raw.channels == 0 || - info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) + info.info.raw.channels == 0) return -EINVAL; this->bpf = 2 * info.info.raw.channels; diff --git a/src/examples/export-source.c b/src/examples/export-source.c index 3d3859273..a0e983de1 100644 --- a/src/examples/export-source.c +++ b/src/examples/export-source.c @@ -268,8 +268,7 @@ static int port_set_format(void *object, d->format.format != SPA_AUDIO_FORMAT_F32) return -EINVAL; if (d->format.rate == 0 || - d->format.channels == 0 || - d->format.channels > SPA_N_ELEMENTS(d->format.position)) + d->format.channels == 0) return -EINVAL; } diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 54a98970b..a9dec8c2b 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -882,7 +882,10 @@ static int create_stream(struct stream_info *info) for (i = 0; i < remap_info.channels; i++) { s->remap[i] = i; for (j = 0; j < tmp_info.channels; j++) { - if (tmp_info.position[j] == remap_info.position[i]) { + uint32_t pj, pi; + pj = spa_format_audio_raw_get_position(&tmp_info, j); + pi = spa_format_audio_raw_get_position(&remap_info, i); + if (pj == pi) { s->remap[i] = j; break; } diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 92f1f38cb..6dcb155cc 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -298,8 +298,7 @@ static void capture_param_changed(void *data, uint32_t id, const struct spa_pod if (spa_format_audio_raw_parse(param, &info) < 0) return; if (info.rate == 0 || - info.channels == 0 || - info.channels > SPA_N_ELEMENTS(info.position)) + info.channels == 0) return; break; } diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index db6fe8b71..521899433 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -1229,7 +1229,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->source.info.channels != n_channels) { impl->source.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->source.info.channels, MAX_CHANNELS); i++) + for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1255,7 +1255,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->sink.info.channels != n_channels) { impl->sink.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->sink.info.channels, MAX_CHANNELS); i++) + for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 84b70d651..cdfcd0ecf 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1691,7 +1691,7 @@ static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio { if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && !SPA_FLAG_IS_SET(src->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - for (uint32_t i = 0; i < src->channels; i++) + for (uint32_t i = 0; i < SPA_MIN(src->channels, SPA_AUDIO_MAX_CHANNELS); i++) dst->position[i] = src->position[i]; SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index f5066abc4..0e1b3c3fb 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -524,7 +524,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, - s->info.position[i]); + spa_format_audio_raw_get_position(&s->info, i)); if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index ce1da189d..365e6c5b9 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -538,8 +538,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, spa_zero(info); if (param != NULL) { if (spa_format_audio_raw_parse(param, &info) < 0 || - info.channels == 0 || - info.channels > SPA_N_ELEMENTS(info.position)) + info.channels == 0) return; if ((impl->info.format != 0 && impl->info.format != info.format) || @@ -547,7 +546,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, (impl->info.channels != 0 && (impl->info.channels != info.channels || memcmp(impl->info.position, info.position, - info.channels * sizeof(uint32_t)) != 0))) { + SPA_MIN(info.channels, SPA_AUDIO_MAX_CHANNELS) * sizeof(uint32_t)) != 0))) { uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index f1aefaa35..5e3149b41 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -442,7 +442,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, - s->info.position[i % SPA_N_ELEMENTS(s->info.position)]); + spa_format_audio_raw_get_position(&s->info, i)); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", @@ -863,10 +863,10 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p } impl->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { - impl->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, - (int)SPA_N_ELEMENTS(impl->sink.info.position)); + impl->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < impl->sink.info.channels; i++) - impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + spa_format_audio_raw_set_position(&impl->sink.info, i, + SPA_AUDIO_CHANNEL_AUX0 + i); } impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; if (impl->source.n_ports > MAX_PORTS) { @@ -875,10 +875,10 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p } impl->source.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { - impl->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, - (int)SPA_N_ELEMENTS(impl->source.info.position)); + impl->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < impl->source.info.channels; i++) - impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + spa_format_audio_raw_set_position(&impl->source.info, i, + SPA_AUDIO_CHANNEL_AUX0 + i); } impl->samplerate = peer->params.sample_rate; impl->period_size = peer->params.period_size; diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 6b3285305..454632c8f 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -602,7 +602,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, - s->info.position[i]); + spa_format_audio_raw_get_position(&s->info, i)); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", @@ -1026,18 +1026,18 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param follower->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; follower->source.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { - follower->source.info.channels = SPA_MIN(peer->params.recv_audio_channels, - (int)SPA_N_ELEMENTS(follower->source.info.position)); + follower->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < follower->source.info.channels; i++) - follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + spa_format_audio_raw_set_position(&follower->source.info, i, + SPA_AUDIO_CHANNEL_AUX0 + i); } follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { - follower->sink.info.channels = SPA_MIN(peer->params.send_audio_channels, - (int)SPA_N_ELEMENTS(follower->sink.info.position)); + follower->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < follower->sink.info.channels; i++) - follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + spa_format_audio_raw_set_position(&follower->sink.info, i, + SPA_AUDIO_CHANNEL_AUX0 + i); } if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 189e60f17..8c417446f 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -348,9 +348,9 @@ uint32_t channel_paname2id(const char *name, size_t size) } -void channel_map_to_positions(const struct channel_map *map, uint32_t *pos) +void channel_map_to_positions(const struct channel_map *map, uint32_t *pos, uint32_t max_pos) { - uint32_t i, channels = SPA_MIN(map->channels, SPA_AUDIO_MAX_CHANNELS); + uint32_t i, channels = SPA_MIN(map->channels, max_pos); for (i = 0; i < channels; i++) pos[i] = map->map[i]; } @@ -535,8 +535,7 @@ int format_parse_param(const struct spa_pod *param, bool collect, info.info.raw.rate = 48000; if (info.info.raw.format == 0 || info.info.raw.rate == 0 || - info.info.raw.channels == 0 || - info.info.raw.channels > SPA_N_ELEMENTS(info.info.raw.position)) + info.info.raw.channels == 0) return -ENOTSUP; } break; @@ -586,7 +585,7 @@ int format_parse_param(const struct spa_pod *param, bool collect, if (info.info.raw.channels) { map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); for (i = 0; i < map->channels; i++) - map->map[i] = info.info.raw.position[i]; + map->map[i] = spa_format_audio_raw_get_position(&info.info.raw, i); } } return 0; @@ -634,8 +633,8 @@ const struct spa_pod *format_build_param(struct spa_pod_builder *b, uint32_t id, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(spec->channels), 0); if (map && map->channels == spec->channels) { - uint32_t positions[SPA_AUDIO_MAX_CHANNELS]; - channel_map_to_positions(map, positions); + uint32_t positions[spec->channels]; + channel_map_to_positions(map, positions, spec->channels); spa_pod_builder_add(b, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, spec->channels, positions), 0); diff --git a/src/modules/module-protocol-pulse/format.h b/src/modules/module-protocol-pulse/format.h index d564b1822..41cfb4f9d 100644 --- a/src/modules/module-protocol-pulse/format.h +++ b/src/modules/module-protocol-pulse/format.h @@ -196,7 +196,7 @@ enum channel_position channel_id2pa(uint32_t id, uint32_t *aux); const char *channel_id2paname(uint32_t id, uint32_t *aux); uint32_t channel_paname2id(const char *name, size_t size); -void channel_map_to_positions(const struct channel_map *map, uint32_t *pos); +void channel_map_to_positions(const struct channel_map *map, uint32_t *pos, uint32_t max_pos); void channel_map_parse(const char *str, struct channel_map *map); bool channel_map_valid(const struct channel_map *map); void channel_map_parse_position(const char *str, struct channel_map *map); diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index a0de01d0d..58bcce52b 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -226,14 +227,15 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props info->channels, map.channels); return -EINVAL; } - channel_map_to_positions(&map, info->position); + channel_map_to_positions(&map, info->position, SPA_N_ELEMENTS(info->position)); pw_properties_set(props, key_channel_map, NULL); } else { if (info->channels == 0) info->channels = impl->defs.sample_spec.channels; if (info->channels == impl->defs.channel_map.channels) { - channel_map_to_positions(&impl->defs.channel_map, info->position); + channel_map_to_positions(&impl->defs.channel_map, + info->position, SPA_N_ELEMENTS(info->position)); } else if (info->channels == 1) { info->position[0] = SPA_AUDIO_CHANNEL_MONO; } else if (info->channels == 2) { @@ -242,7 +244,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props } else { /* FIXME add more mappings */ for (i = 0; i < info->channels; i++) - info->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + spa_format_audio_raw_set_position(info, i, SPA_AUDIO_CHANNEL_UNKNOWN); } if (info->position[0] == SPA_AUDIO_CHANNEL_UNKNOWN) info->flags |= SPA_AUDIO_FLAG_UNPOSITIONED; @@ -288,7 +290,7 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti p = s = alloca(info->channels * 8); for (i = 0; i < info->channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", - channel_id2name(info->position[i])); + channel_id2name(spa_format_audio_raw_get_position(info, i))); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); } } diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index afda6f484..8849f7190 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -754,7 +754,8 @@ static int create_pulse_stream(struct impl *impl) map.channels = impl->info.channels; for (i = 0; i < map.channels; i++) - map.map[i] = (pa_channel_position_t)channel_id2pa(impl->info.position[i], &aux); + map.map[i] = (pa_channel_position_t)channel_id2pa( + spa_format_audio_raw_get_position(&impl->info, i), &aux); snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"), pw_get_user_name(), pw_get_host_name()); diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 3a7a31827..4ae0eacd1 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -215,16 +215,16 @@ static const struct spa_audio_layout_info layouts[] = { { SPA_AUDIO_LAYOUT_7_1 }, }; -static void default_layout(uint32_t channels, uint32_t *position) +static void default_layout(uint32_t channels, uint32_t *position, uint32_t max_position) { SPA_FOR_EACH_ELEMENT_VAR(layouts, l) { if (l->n_channels == channels) { - for (uint32_t i = 0; i < l->n_channels; i++) + for (uint32_t i = 0; i < l->n_channels && i < max_position; i++) position[i] = l->position[i]; return; } } - for (uint32_t i = 0; i < channels; i++) + for (uint32_t i = 0; i < channels && i < max_position; i++) position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -276,7 +276,9 @@ struct vban_stream *vban_stream_new(struct pw_core *core, case SPA_MEDIA_SUBTYPE_raw: parse_audio_info(props, &impl->info.info.raw); 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); + default_layout(impl->info.info.raw.channels, + impl->info.info.raw.position, + SPA_N_ELEMENTS(impl->info.info.raw.position)); impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index e165a1174..35e48279b 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -192,7 +192,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, spa_zero(channel_map); channel_map_parse(value, &channel_map); - channel_map_to_positions(&channel_map, pos); + channel_map_to_positions(&channel_map, pos, CHANNELS_MAX); p = s = alloca(4 + channel_map.channels * 8); p += spa_scnprintf(p, 2, "["); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 0fefa2377..52789d421 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -2347,9 +2347,11 @@ int main(int argc, char *argv[]) .rate = data.rate, .channels = data.channels); - if (data.channelmap.n_channels) - memcpy(info.position, data.channelmap.channels, data.channels * sizeof(int)); - + if (data.channelmap.n_channels) { + uint32_t i, n_pos = SPA_MIN(data.channels, SPA_N_ELEMENTS(info.position)); + for (i = 0; i < n_pos; i++) + info.position[i] = data.channelmap.channels[i]; + } params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info); break; } From dbc5c81e4a0b34da0450c55db87592b48b7d2e3c Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 21 Oct 2025 16:05:33 +0200 Subject: [PATCH 0957/1014] spa: avoid using SPA_AUDIO_MAX_CHANNELS Use SPA_N_ELEMENTS instead of the array we try to handle. --- spa/include/spa/param/audio/raw-utils.h | 10 +++++----- spa/plugins/audioconvert/audioadapter.c | 2 +- spa/plugins/audioconvert/audioconvert.c | 13 ++++++++----- src/modules/module-ffado-driver.c | 6 ++++-- src/modules/module-filter-chain.c | 3 ++- src/modules/module-loopback.c | 2 +- src/modules/module-protocol-pulse/format.c | 2 +- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 03d6e3ac9..8a9542c2e 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -31,20 +31,20 @@ extern "C" { 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; - if (idx < SPA_AUDIO_MAX_CHANNELS) { + uint32_t pos, max_position = SPA_N_ELEMENTS(info->position); + if (idx < max_position) { pos = info->position[idx]; } else { - pos = info->position[idx % SPA_AUDIO_MAX_CHANNELS]; + pos = info->position[idx % max_position]; if (SPA_AUDIO_CHANNEL_IS_AUX(pos)) - pos += (idx / SPA_AUDIO_MAX_CHANNELS) * SPA_AUDIO_MAX_CHANNELS; + 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_AUDIO_MAX_CHANNELS) + if (idx < SPA_N_ELEMENTS(info->position)) info->position[idx] = position; } diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 26cb4af74..01e94cf58 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -2046,7 +2046,7 @@ static int do_auto_port_config(struct impl *this, const char *str) return -ENOENT; if (format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { - uint32_t n_pos = SPA_MIN(SPA_AUDIO_MAX_CHANNELS, format.info.raw.channels); + uint32_t n_pos = SPA_MIN(SPA_N_ELEMENTS(format.info.raw.position), format.info.raw.channels); if (position == POSITION_AUX) { for (i = 0; i < n_pos; i++) format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index b91a46ff8..dd58ee9de 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -2060,8 +2060,9 @@ static int setup_in_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS), - sizeof(uint32_t), int32_cmp); + qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, + SPA_N_ELEMENTS(dst_info.info.raw.position)), + sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { @@ -2222,7 +2223,8 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, position, max_position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), - dst_chan, out->format.info.raw.position, SPA_AUDIO_MAX_CHANNELS), dst_mask); + dst_chan, out->format.info.raw.position, + 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_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), @@ -2350,8 +2352,9 @@ static int setup_out_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, SPA_AUDIO_MAX_CHANNELS), - sizeof(uint32_t), int32_cmp); + qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, + SPA_N_ELEMENTS(src_info.info.raw.position)), + sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 521899433..c71639b3b 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -1228,8 +1228,9 @@ static int probe_ffado_device(struct impl *impl) impl->source.ports[i] = port; } if (impl->source.info.channels != n_channels) { + uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->source.info.position)); impl->source.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->source.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + for (i = 0; i < n_pos; i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1254,8 +1255,9 @@ static int probe_ffado_device(struct impl *impl) impl->sink.ports[i] = port; } if (impl->sink.info.channels != n_channels) { + uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->sink.info.position)); impl->sink.info.channels = n_channels; - for (i = 0; i < SPA_MIN(impl->sink.info.channels, SPA_AUDIO_MAX_CHANNELS); i++) + for (i = 0; i < n_pos; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index cdfcd0ecf..d5b15d4ab 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1691,7 +1691,8 @@ static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio { if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && !SPA_FLAG_IS_SET(src->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - for (uint32_t i = 0; i < SPA_MIN(src->channels, SPA_AUDIO_MAX_CHANNELS); i++) + uint32_t i, n_pos = SPA_MIN(src->channels, SPA_N_ELEMENTS(dst->position)); + for (i = 0; i < n_pos; i++) dst->position[i] = src->position[i]; SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 365e6c5b9..64022b69d 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -546,7 +546,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, (impl->info.channels != 0 && (impl->info.channels != info.channels || memcmp(impl->info.position, info.position, - SPA_MIN(info.channels, SPA_AUDIO_MAX_CHANNELS) * sizeof(uint32_t)) != 0))) { + SPA_MIN(info.channels, SPA_N_ELEMENTS(info.position)) * sizeof(uint32_t)) != 0))) { uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 8c417446f..f92e29e78 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -450,7 +450,7 @@ void channel_map_parse(const char *str, struct channel_map *map) void channel_map_parse_position(const char *str, struct channel_map *map) { - uint32_t channels = 0, position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t channels = 0, position[CHANNELS_MAX]; spa_audio_parse_position_n(str, strlen(str), position, SPA_N_ELEMENTS(position), &channels); positions_to_channel_map(position, channels, map); From f19b075306900fb3650aa821ff2120b7804151a2 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 21 Oct 2025 16:08:24 +0200 Subject: [PATCH 0958/1014] spa: add SPA_AUDIO_MAX_POSITION Add a new SPA_AUDIO_MAX_POSITION constant with the maximum number of channel positions that can be kept in the various audio_info structures. Repurpose the SPA_AUDIO_MAX_CHANNELS as a suggestion for applications for the max allowed number of channels in the system. Make it possible to make this a compile time constant. --- spa/include/spa/param/audio/dsd.h | 2 +- spa/include/spa/param/audio/layout.h | 2 +- spa/include/spa/param/audio/raw-json.h | 2 +- spa/include/spa/param/audio/raw.h | 15 +++++++++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/param/audio/dsd.h b/spa/include/spa/param/audio/dsd.h index 73f3d4e8b..733823780 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -45,7 +45,7 @@ struct spa_audio_info_dsd { int32_t interleave; /*< interleave bytes */ uint32_t rate; /*< sample rate (in bytes per second) */ uint32_t channels; /*< channels */ - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ + uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ }; #define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 19e3a3c42..e5e59caf9 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -20,7 +20,7 @@ extern "C" { struct spa_audio_layout_info { uint32_t n_channels; - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t position[SPA_AUDIO_MAX_POSITION]; }; #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 50bd63181..edbb715d1 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -49,7 +49,7 @@ SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) { - return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); + return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_POSITION, n_channels); } SPA_API_AUDIO_RAW_JSON int diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 6defe5020..21cdb3c2d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,8 +18,15 @@ extern "C" { * \{ */ -/* this is the max number of channels for position info */ +/* This is the max number of position info, changing this will change the + * ABI */ +#define SPA_AUDIO_MAX_POSITION 64u + +/* The suggested number of max channels, can be a compile time constant and + * does not affect ABI or API */ +#ifndef SPA_AUDIO_MAX_CHANNELS #define SPA_AUDIO_MAX_CHANNELS 64u +#endif enum spa_audio_format { SPA_AUDIO_FORMAT_UNKNOWN, @@ -278,9 +285,9 @@ struct spa_audio_info_raw { uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels. This can be larger than - * SPA_AUDIO_MAX_CHANNELS, the position is taken - * (index % SPA_AUDIO_MAX_CHANNELS) */ - uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ + * SPA_AUDIO_MAX_POSITION, the position is taken + * (index % SPA_AUDIO_MAX_POSITION) */ + uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) From 99bbac9cbf40af8ba6b55fccf11b0f0a9a6c6537 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 21 Oct 2025 17:01:31 +0200 Subject: [PATCH 0959/1014] spa: increase SPA_AUDIO_MAX_CHANNELS to 128 This should now not change the ABI because the position array size is now controlled with the SPA_AUDIO_MAX_POSITION constant. --- spa/include/spa/param/audio/raw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 21cdb3c2d..5b57a5bf1 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -25,7 +25,7 @@ extern "C" { /* The suggested number of max channels, can be a compile time constant and * does not affect ABI or API */ #ifndef SPA_AUDIO_MAX_CHANNELS -#define SPA_AUDIO_MAX_CHANNELS 64u +#define SPA_AUDIO_MAX_CHANNELS 128u #endif enum spa_audio_format { From ae50bb5dc02f04d6adae37e2d06cc5e8293e58af Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 09:39:15 +0200 Subject: [PATCH 0960/1014] audio: don't limit channels to max positions We can have more channels than we have positions. --- spa/include/spa/param/audio/raw-json.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index edbb715d1..282e76ed5 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -64,7 +64,7 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) - info->channels = SPA_MIN(v, SPA_N_ELEMENTS(info->position)); + info->channels = v; } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, From 6465a63bf648caf827b9ce4e833d10beee9aafcf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 12:47:00 +0200 Subject: [PATCH 0961/1014] spa: parse the audio.position completetly Parse the audio.position spec completely so that we have the right number of channels but only store the first max_position channels. Also rename some field to make it clear that this is about the max number of channel positions. --- spa/include/spa/param/audio/raw-json.h | 9 +++++---- spa/plugins/alsa/alsa-pcm.c | 6 +++--- spa/plugins/alsa/alsa-pcm.h | 4 ++-- spa/plugins/filter-graph/filter-graph.c | 5 +++-- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 282e76ed5..7ef1b27f3 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -29,7 +29,7 @@ extern "C" { SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position_n(const char *str, size_t len, - uint32_t *position, uint32_t max_channels, uint32_t *n_channels) + uint32_t *position, uint32_t max_position, uint32_t *n_channels) { struct spa_json iter; char v[256]; @@ -38,9 +38,10 @@ spa_audio_parse_position_n(const char *str, size_t len, if (spa_json_begin_array_relax(&iter, str, len) <= 0) return 0; - while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && - channels < max_channels) { - position[channels++] = spa_type_audio_channel_from_short_name(v); + while (spa_json_get_string(&iter, v, sizeof(v)) > 0) { + if (channels < max_position) + position[channels] = spa_type_audio_channel_from_short_name(v); + channels++; } *n_channels = channels; return channels; diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index bad6da9fe..2d9492e95 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -243,7 +243,7 @@ static int position_to_string(struct channel_map *map, char *val, size_t len) uint32_t i, o = 0; int r; o += snprintf(val, len, "[ "); - for (i = 0; i < map->channels; i++) { + for (i = 0; i < map->n_pos; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", spa_debug_type_find_short_name(spa_type_audio_channel, map->pos[i])); @@ -1645,7 +1645,7 @@ skip_channels: } else { const struct channel_map *map = NULL; spa_pod_builder_int(b, min); - if (state->default_pos.channels == min) { + if (state->default_pos.n_pos == min) { map = &state->default_pos; spa_log_debug(state->log, "%p: using provided default", state); } else if (min <= 8) { @@ -1655,7 +1655,7 @@ skip_channels: if (map) { spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_push_array(b, &f[0]); - for (i = 0; i < map->channels; i++) { + for (i = 0; i < map->n_pos; i++) { spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]); spa_pod_builder_id(b, map->pos[i]); } diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 09b5140b9..8b8a07721 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -72,7 +72,7 @@ struct buffer { #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) struct channel_map { - uint32_t channels; + uint32_t n_pos; uint32_t pos[MAX_CHANNELS]; }; @@ -315,7 +315,7 @@ void spa_alsa_emit_port_info(struct state *state, bool full); static inline void spa_alsa_parse_position(struct channel_map *map, const char *val, size_t len) { - spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->channels); + spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->n_pos); } static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index bb7f24831..ce0765f83 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -232,16 +232,17 @@ struct impl { float *discard_data; }; -static inline void print_channels(char *buffer, size_t max_size, uint32_t n_channels, uint32_t *positions) +static inline void print_channels(char *buffer, size_t max_size, uint32_t n_positions, uint32_t *positions) { uint32_t i; struct spa_strbuf buf; spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_append(&buf, "["); - for (i = 0; i < n_channels; i++) { + for (i = 0; i < n_positions; i++) { spa_strbuf_append(&buf, "%s%s", i ? "," : "", spa_type_audio_channel_to_short_name(positions[i])); + } spa_strbuf_append(&buf, "]"); } From 7177f8269d7df4a063b59dfafe90bd9bc9a44562 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 12:54:30 +0200 Subject: [PATCH 0962/1014] bluez: use function to get the channel position from a name --- spa/plugins/bluez5/a2dp-codec-opus.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 7a330e8e0..0881c0255 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -253,14 +253,8 @@ static const struct surround_encoder_mapping surround_encoders[] = { static uint32_t bt_channel_from_name(const char *name) { size_t i; - enum spa_audio_channel position = SPA_AUDIO_CHANNEL_UNKNOWN; + enum spa_audio_channel position = spa_type_audio_channel_from_short_name(name); - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) { - position = spa_type_audio_channel[i].type; - break; - } - } for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) { if (position == audio_locations[i].position) return audio_locations[i].mask; From 11f1298f533bff1a2bc2caa4c0861e5273ed7916 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 13:04:53 +0200 Subject: [PATCH 0963/1014] spa: make a function to make a channel short name Make a function that can generate and parse a short name for the positions that are not in the type list, like the AUX channels. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 3 ++- spa/include/spa/param/audio/raw-types.h | 24 ++++++++++++++++++- spa/plugins/alsa/alsa-pcm.c | 5 ++-- spa/plugins/audioconvert/audioconvert.c | 19 ++++++++------- spa/plugins/avb/avb-pcm.c | 5 ++-- spa/plugins/filter-graph/filter-graph.c | 5 ++-- src/modules/module-jack-tunnel.c | 6 +++-- src/modules/module-netjack2-driver.c | 7 +++--- src/modules/module-netjack2-manager.c | 8 +++---- src/modules/module-protocol-pulse/format.c | 4 ++-- src/modules/module-protocol-pulse/format.h | 2 +- src/modules/module-protocol-pulse/message.c | 4 +++- src/modules/module-protocol-pulse/module.c | 5 ++-- .../modules/module-stream-restore.c | 4 +++- src/modules/module-zeroconf-discover.c | 4 ++-- src/tools/pw-cat.c | 7 +++--- 16 files changed, 74 insertions(+), 38 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 27495a296..bf7196898 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -925,11 +925,12 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, } for (i = 0; i < map->channels; i++) { uint32_t pos = chmap_to_channel(map->pos[i]); + char buf[8]; if (i < max_position) position[i] = pos; pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), - spa_debug_type_find_short_name(spa_type_audio_channel, pos)); + spa_type_audio_channel_make_short_name(pos, buf, sizeof(buf), "UNK")); } return 1; } diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index c862999d5..fbaa80abf 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -267,12 +267,34 @@ static const struct spa_type_info spa_type_audio_channel[] = { SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_channel_from_short_name(const char *name) { - return spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); + uint32_t res; + if (spa_strstartswith(name, "AUX")) { + if (spa_atou32(name+3, &res, 10) && res < 0x1000) + res = SPA_AUDIO_CHANNEL_AUX0 + res; + else + res = SPA_AUDIO_CHANNEL_UNKNOWN; + } else { + res = spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); + } + return res; } SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_audio_channel, "UNK"); } +SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_make_short_name(uint32_t type, + char *buf, size_t size, const char *unknown) +{ + if (SPA_AUDIO_CHANNEL_IS_AUX(type)) { + snprintf(buf, size, "AUX%u", type - SPA_AUDIO_CHANNEL_AUX0); + } else { + const char *str = spa_type_to_short_name(type, spa_type_audio_channel, NULL); + if (str == NULL) + return unknown; + snprintf(buf, size, "%.7s", str); + } + return buf; +} #define SPA_TYPE_INFO_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale" #define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":" diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 2d9492e95..676214685 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -241,12 +241,13 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i, o = 0; + char pos[8]; int r; o += snprintf(val, len, "[ "); for (i = 0; i < map->n_pos; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", - spa_debug_type_find_short_name(spa_type_audio_channel, - map->pos[i])); + spa_type_audio_channel_make_short_name(map->pos[i], + pos, sizeof(pos), "UNK")); if (r < 0 || o + r >= len) return -ENOSPC; o += r; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index dd58ee9de..1b4ae18fb 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -424,7 +424,6 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p uint32_t position, bool is_dsp, bool is_monitor, bool is_control) { struct port *port = GET_PORT(this, direction, port_id); - const char *name; spa_assert(port_id < MAX_PORTS); @@ -439,8 +438,7 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - name = spa_debug_type_find_short_name(spa_type_audio_channel, position); - snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK"); + spa_type_audio_channel_make_short_name(position, port->position, sizeof(port->position), "UNK"); port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | @@ -2067,6 +2065,7 @@ static int setup_in_convert(struct impl *this) for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; + char b1[8], b2[8]; pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); @@ -2077,8 +2076,8 @@ static int setup_in_convert(struct impl *this) remap = true; spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, in->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, pi), - spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), + spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; } @@ -2192,10 +2191,11 @@ static void set_volume(struct impl *this) static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position, uint32_t max_position) { uint32_t i, idx = 0; + char buf[8]; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", - spa_debug_type_find_short_name(spa_type_audio_channel, - position[i % max_position])); + spa_type_audio_channel_make_short_name(position[i % max_position], + buf, sizeof(buf), "UNK")); return str; } @@ -2359,6 +2359,7 @@ static int setup_out_convert(struct impl *this) for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; + char b1[8], b2[8]; pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); @@ -2370,8 +2371,8 @@ static int setup_out_convert(struct impl *this) spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, out->remap[i], j, - spa_debug_type_find_short_name(spa_type_audio_channel, pi), - spa_debug_type_find_short_name(spa_type_audio_channel, pj)); + spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), + spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); break; diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index f54e9dabf..0402b8c75 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -86,11 +86,12 @@ static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i, o = 0; int r; + char pos[8]; o += snprintf(val, len, "[ "); for (i = 0; i < map->channels; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", - spa_debug_type_find_short_name(spa_type_audio_channel, - map->pos[i])); + spa_type_audio_channel_make_short_name(map->pos[i], + pos, sizeof(pos), "UNK")); if (r < 0 || o + r >= len) return -ENOSPC; o += r; diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index ce0765f83..9271ace34 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -236,13 +236,14 @@ static inline void print_channels(char *buffer, size_t max_size, uint32_t n_posi { uint32_t i; struct spa_strbuf buf; + char pos[8]; spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_append(&buf, "["); for (i = 0; i < n_positions; i++) { spa_strbuf_append(&buf, "%s%s", i ? "," : "", - spa_type_audio_channel_to_short_name(positions[i])); - + spa_type_audio_channel_make_short_name(positions[i], + pos, sizeof(pos), "UNK")); } spa_strbuf_append(&buf, "]"); } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 0e1b3c3fb..4ca51bae8 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -514,6 +514,7 @@ static void make_stream_ports(struct stream *s) for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; char *link_port = NULL; + char pos[8]; if (port != NULL) { s->ports[i] = NULL; @@ -523,8 +524,9 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + pos, sizeof(pos), NULL); if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 5e3149b41..b71284841 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -441,11 +441,12 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_AUDIO_CHANNEL, str, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 454632c8f..e171b2785 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -601,12 +601,12 @@ static void make_stream_ports(struct stream *s) } if (i < s->info.channels) { - str = spa_debug_type_find_short_name(spa_type_audio_channel, - spa_format_audio_raw_get_position(&s->info, i)); - + str = spa_type_audio_channel_make_short_name( + spa_format_audio_raw_get_position(&s->info, i), + name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", - PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", + PW_KEY_AUDIO_CHANNEL, str, PW_KEY_PORT_PHYSICAL, "true", NULL); diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index f92e29e78..b34765641 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -298,9 +298,9 @@ uint32_t channel_pa2id(enum channel_position channel) return audio_channels[channel].channel; } -const char *channel_id2name(uint32_t channel) +const char *channel_id2name(uint32_t channel, char *buf, size_t size) { - return spa_type_audio_channel_to_short_name(channel); + return spa_type_audio_channel_make_short_name(channel, buf, size, "UNK"); } uint32_t channel_name2id(const char *name) diff --git a/src/modules/module-protocol-pulse/format.h b/src/modules/module-protocol-pulse/format.h index 41cfb4f9d..50bb8e2f8 100644 --- a/src/modules/module-protocol-pulse/format.h +++ b/src/modules/module-protocol-pulse/format.h @@ -190,7 +190,7 @@ void sample_spec_fix(struct sample_spec *ss, struct channel_map *map, struct spa_dict *props); uint32_t channel_pa2id(enum channel_position channel); -const char *channel_id2name(uint32_t channel); +const char *channel_id2name(uint32_t channel, char *buf, size_t size); uint32_t channel_name2id(const char *name); enum channel_position channel_id2pa(uint32_t id, uint32_t *aux); const char *channel_id2paname(uint32_t id, uint32_t *aux); diff --git a/src/modules/module-protocol-pulse/message.c b/src/modules/module-protocol-pulse/message.c index dbebc437e..19b4f82d6 100644 --- a/src/modules/module-protocol-pulse/message.c +++ b/src/modules/module-protocol-pulse/message.c @@ -761,11 +761,13 @@ int message_dump(enum spa_log_level level, const char *prefix, struct message *m case TAG_CHANNEL_MAP: { struct channel_map map; + char pos[8]; if ((res = read_channel_map(m, &map)) < 0) return res; pw_log(level, "%s %u: channelmap: channels:%u", prefix, o, map.channels); for (i = 0; i < map.channels; i++) - pw_log(level, "%s %d: %s", prefix, i, channel_id2name(map.map[i])); + pw_log(level, "%s %d: %s", prefix, i, + channel_id2name(map.map[i], pos, sizeof(pos))); break; } case TAG_CVOLUME: diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index 58bcce52b..fd997883e 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -283,14 +283,15 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti if (info->rate) pw_properties_setf(props, SPA_KEY_AUDIO_RATE, "%u", info->rate); if (info->channels) { - char *s, *p; + char *s, *p, pos[8]; pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels); p = s = alloca(info->channels * 8); for (i = 0; i < info->channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", - channel_id2name(spa_format_audio_raw_get_position(info, i))); + channel_id2name(spa_format_audio_raw_get_position(info, i), + pos, sizeof(pos))); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); } } diff --git a/src/modules/module-protocol-pulse/modules/module-stream-restore.c b/src/modules/module-protocol-pulse/modules/module-stream-restore.c index bdb9cfd7d..a33ab6fa2 100644 --- a/src/modules/module-protocol-pulse/modules/module-stream-restore.c +++ b/src/modules/module-protocol-pulse/modules/module-stream-restore.c @@ -295,9 +295,11 @@ static int do_extension_stream_restore_write(struct module *module, struct clien fprintf(f, " ]"); } if (map.channels > 0) { + char pos[8]; fprintf(f, ", \"channels\": ["); for (i = 0; i < map.channels; i++) - fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), channel_id2name(map.map[i])); + fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), + channel_id2name(map.map[i], pos, sizeof(pos))); fprintf(f, " ]"); } if (device_name != NULL && device_name[0] && diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 35e48279b..b324db403 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -188,7 +188,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, else if (spa_streq(key, "channel_map")) { struct channel_map channel_map; uint32_t i, pos[CHANNELS_MAX]; - char *p, *s; + char *p, *s, buf[8]; spa_zero(channel_map); channel_map_parse(value, &channel_map); @@ -198,7 +198,7 @@ static void pw_properties_from_avahi_string(const char *key, const char *value, p += spa_scnprintf(p, 2, "["); for (i = 0; i < channel_map.channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",", - channel_id2name(pos[i])); + channel_id2name(pos[i], buf, sizeof(buf))); p += spa_scnprintf(p, 2, "]"); pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s); } diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 52789d421..8210addb6 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -747,10 +747,11 @@ static int channelmap_default(struct channelmap *map, int n_channels) static void channelmap_print(struct channelmap *map) { uint32_t i; - + char pos[8]; for (i = 0; i < map->n_channels; i++) { - const char *name = spa_type_audio_channel_to_short_name(map->channels[i]); - fprintf(stderr, "%s%s", name, i + 1 < map->n_channels ? "," : ""); + fprintf(stderr, "%s%s", i ? "," : "", + spa_type_audio_channel_make_short_name(map->channels[i], + pos, sizeof(pos), "UNK")); } } From c5533b3c32775dc52c54c6d4a33531d2f03c36e1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 22 Oct 2025 13:26:52 +0200 Subject: [PATCH 0964/1014] spa: add all channel positions to the params Pass all the positions of all channels to the format param, even when there are more channels then positions. --- spa/include/spa/param/audio/raw-utils.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 8a9542c2e..7c0a92776 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -80,8 +80,8 @@ SPA_API_AUDIO_RAW_UTILS struct spa_pod * spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_raw *info) { - struct spa_pod_frame f; - spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); + struct spa_pod_frame f[2]; + spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -96,13 +96,16 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, - SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, - SPA_MIN(info->channels, SPA_N_ELEMENTS(info->position)), - info->position), 0); + uint32_t i; + spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); + spa_pod_builder_push_array(builder, &f[1]); + for (i = 0; i < info->channels; i++) + spa_pod_builder_id(builder, + spa_format_audio_raw_get_position(info, i)); + spa_pod_builder_pop(builder, &f[1]); } } - return (struct spa_pod*)spa_pod_builder_pop(builder, &f); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]); } /** From b8eeb2db45adfb4fa6df2f4c675c700d6b32570e Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 14:37:46 +0200 Subject: [PATCH 0965/1014] spa: make it possible to extend the spa_audio_info struct Add functions that take the size of the spa_audio_info struct in various functions. We can use this to determine how many channels and channel positions we can store. Error out if we try to use more channels than we can fit positions. This is probably the safest thing to do because most code will blindly try to get the positions without checking the channel count. Make sure we also propagate errors to the callers. --- spa/include/spa/param/audio/format-utils.h | 23 ++++-- spa/include/spa/param/audio/raw-json.h | 89 ++++++++++++++++------ spa/include/spa/param/audio/raw-utils.h | 48 ++++++++---- spa/include/spa/param/audio/raw.h | 10 ++- 4 files changed, 124 insertions(+), 46 deletions(-) diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index d608a4bdf..07f5d94e3 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -47,7 +47,7 @@ extern "C" { #endif SPA_API_AUDIO_FORMAT_UTILS int -spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) +spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; @@ -59,7 +59,7 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_parse(format, &info->info.raw); + return spa_format_audio_raw_ext_parse(format, &info->info.raw, size); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -98,13 +98,19 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info return -ENOTSUP; } +SPA_API_AUDIO_FORMAT_UTILS int +spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) +{ + return spa_format_audio_ext_parse(format, info, sizeof(*info)); +} + SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * -spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, - const struct spa_audio_info *info) +spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info *info, size_t size) { switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_build(builder, id, &info->info.raw); + return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, size); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -143,6 +149,13 @@ spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, errno = ENOTSUP; return NULL; } + +SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * +spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info *info) +{ + return spa_format_audio_ext_build(builder, id, info, sizeof(*info)); +} /** * \} */ diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 7ef1b27f3..7c8c11ca3 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -46,6 +46,7 @@ spa_audio_parse_position_n(const char *str, size_t len, *n_channels = channels; return channels; } + SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) @@ -54,9 +55,11 @@ spa_audio_parse_position(const char *str, size_t len, } SPA_API_AUDIO_RAW_JSON int -spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, const char *val, bool force) +spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, + const char *key, const char *val, bool force) { uint32_t v; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); @@ -64,42 +67,84 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons if (spa_atou32(val, &v, 0) && (force || info->rate == 0)) info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { - if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) + if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) { + if (v > max_position) + return -ECHRNG; info->channels = v; + } } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, - SPA_N_ELEMENTS(info->position), &info->channels) > 0) + max_position, &v) > 0) { + if (v > max_position) + return -ECHRNG; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } } } return 0; } +SPA_API_AUDIO_RAW_JSON int +spa_audio_info_raw_update(struct spa_audio_info_raw *info, + const char *key, const char *val, bool force) +{ + return spa_audio_info_raw_ext_update(info, sizeof(*info), key, val, force); +} + +SPA_API_AUDIO_RAW_JSON int +spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t size, + const struct spa_dict *defaults, + const struct spa_dict *dict, va_list args) +{ + int res; + + memset(info, 0, size); + SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + if (dict) { + const char *val, *key; + while ((key = va_arg(args, const char *))) { + if ((val = spa_dict_lookup(dict, key)) == NULL) + continue; + if ((res = spa_audio_info_raw_ext_update(info, size, + key, val, true)) < 0) + return res; + } + } + if (defaults) { + const struct spa_dict_item *it; + spa_dict_for_each(it, defaults) + if ((res = spa_audio_info_raw_ext_update(info, size, + it->key, it->value, false)) < 0) + return res; + } + return 0; +} + +SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL +spa_audio_info_raw_ext_init_dict_keys(struct spa_audio_info_raw *info, size_t size, + const struct spa_dict *defaults, + const struct spa_dict *dict, ...) +{ + va_list args; + int res; + va_start(args, dict); + res = spa_audio_info_raw_ext_init_dict_keys_va(info, size, defaults, dict, args); + va_end(args); + return res; +} + SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info, const struct spa_dict *defaults, const struct spa_dict *dict, ...) { - spa_zero(*info); - SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); - if (dict) { - const char *val, *key; - va_list args; - va_start(args, dict); - while ((key = va_arg(args, const char *))) { - if ((val = spa_dict_lookup(dict, key)) == NULL) - continue; - spa_audio_info_raw_update(info, key, val, true); - } - va_end(args); - } - if (defaults) { - const struct spa_dict_item *it; - spa_dict_for_each(it, defaults) - spa_audio_info_raw_update(info, it->key, it->value, false); - } - return 0; + va_list args; + int res; + va_start(args, dict); + res = spa_audio_info_raw_ext_init_dict_keys_va(info, sizeof(*info), defaults, dict, args); + va_end(args); + return res; } /** diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 7c0a92776..551f47af1 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -58,10 +58,12 @@ spa_format_audio_raw_copy_positions(const struct spa_audio_info_raw *info, uint3 } SPA_API_AUDIO_RAW_UTILS int -spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) +spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size) { struct spa_pod *position = NULL; int res; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, @@ -69,19 +71,29 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); + if (info->channels > max_position) + return -ENOTSUP; if (position == NULL || - !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_N_ELEMENTS(info->position))) + spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); return res; } -SPA_API_AUDIO_RAW_UTILS struct spa_pod * -spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, - const struct spa_audio_info_raw *info) +SPA_API_AUDIO_RAW_UTILS int +spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) { - struct spa_pod_frame f[2]; - spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, id); + return spa_format_audio_raw_ext_parse(format, info, sizeof(*info)); +} + +SPA_API_AUDIO_RAW_UTILS struct spa_pod * +spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_raw *info, size_t size) +{ + struct spa_pod_frame f; + uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), @@ -95,17 +107,21 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); - if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - uint32_t i; - spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); - spa_pod_builder_push_array(builder, &f[1]); - for (i = 0; i < info->channels; i++) - spa_pod_builder_id(builder, - spa_format_audio_raw_get_position(info, i)); - spa_pod_builder_pop(builder, &f[1]); + if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && + max_position > info->channels) { + spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, + SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, + info->channels, info->position), 0); } } - return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]); + return (struct spa_pod*)spa_pod_builder_pop(builder, &f); +} + +SPA_API_AUDIO_RAW_UTILS struct spa_pod * +spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, + const struct spa_audio_info_raw *info) +{ + return spa_format_audio_raw_ext_build(builder, id, info, sizeof(*info)); } /** diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 5b57a5bf1..1688595e1 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -284,14 +284,18 @@ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels. This can be larger than - * SPA_AUDIO_MAX_POSITION, the position is taken - * (index % SPA_AUDIO_MAX_POSITION) */ + uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_POSITION + * and you may assume there is enough padding for the extra + * channel positions. */ uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ + /* more channels can be added here */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) +#define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) + + #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ #define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string, From 5e1e3fca1ebf284e3d37a2e87fd1c866baed6bc1 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 18:01:35 +0200 Subject: [PATCH 0966/1014] modules: handle format parsing errors --- src/modules/module-combine-stream.c | 9 ++++++--- src/modules/module-echo-cancel.c | 9 ++++++--- src/modules/module-example-filter.c | 11 +++++++---- src/modules/module-example-sink.c | 9 ++++++--- src/modules/module-example-source.c | 9 ++++++--- src/modules/module-ffado-driver.c | 11 +++++++---- src/modules/module-filter-chain.c | 11 +++++++---- src/modules/module-jack-tunnel.c | 11 +++++++---- src/modules/module-loopback.c | 13 ++++++++----- src/modules/module-netjack2-driver.c | 11 +++++++---- src/modules/module-netjack2-manager.c | 13 ++++++++----- src/modules/module-pipe-tunnel.c | 9 ++++++--- src/modules/module-protocol-simple.c | 18 +++++++++++------- src/modules/module-pulse-tunnel.c | 9 ++++++--- src/modules/module-rtp/stream.c | 14 ++++++++++---- src/modules/module-snapcast-discover.c | 15 +++++++++++---- src/modules/module-vban/stream.c | 9 ++++++--- 17 files changed, 125 insertions(+), 66 deletions(-) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index a9dec8c2b..a9833d68f 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -326,9 +326,9 @@ struct stream { unsigned int have_latency:1; }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1638,7 +1638,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(props, impl->combine_props, "resample.prefill"); copy_props(props, impl->combine_props, "resample.disable"); - parse_audio_info(impl->combine_props, &impl->info); + if ((res = parse_audio_info(impl->combine_props, &impl->info)) < 0) { + pw_log_error( "can't create format: %s", spa_strerror(res)); + goto error; + } copy_props(props, impl->stream_props, PW_KEY_NODE_LOOP_NAME); copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 0d640f162..997506e6f 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -1203,9 +1203,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -1291,7 +1291,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); - parse_audio_info(props, &info); + if ((res = parse_audio_info(props, &info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->capture_info = info; impl->source_info = info; diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 6dcb155cc..60b6dda95 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -467,9 +467,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -573,8 +573,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse formats: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index b44470f68..00b880175 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -273,9 +273,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -395,7 +395,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index f04ae1afc..20f296a17 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -279,9 +279,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -401,7 +401,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index c71639b3b..de5ed0ff0 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -1429,9 +1429,9 @@ static void parse_devices(struct impl *impl, const char *val, size_t len) } } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1582,8 +1582,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_PAUSE_ON_IDLE); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index d5b15d4ab..6b759434b 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1833,9 +1833,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -1928,8 +1928,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, "resample.prefill"); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 4ca51bae8..8e8065c26 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -1053,9 +1053,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), @@ -1178,8 +1178,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "jack.connect-audio"); copy_props(impl, props, "jack.connect-midi"); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->source.n_midi = pw_properties_get_uint32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 64022b69d..976a8c7a4 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -838,9 +838,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -952,9 +952,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); - parse_audio_info(props, &impl->info); - parse_audio_info(impl->capture_props, &impl->capture_info); - parse_audio_info(impl->playback_props, &impl->playback_info); + if ((res = parse_audio_info(props, &impl->info)) < 0 || + (res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || + (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { + pw_log_error( "can't parse formats: %s", spa_strerror(res)); + goto error; + } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index b71284841..e98ac9584 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -1221,9 +1221,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, @@ -1339,8 +1339,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "midi.ports"); copy_props(impl, props, "audio.ports"); - parse_audio_info(impl->source.props, &impl->source.info); - parse_audio_info(impl->sink.props, &impl->sink.info); + if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || + (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { + pw_log_error( "can't parse format: %s", spa_strerror(res)); + goto error; + } impl->source.wanted_n_midi = pw_properties_get_int32(impl->source.props, "midi.ports", DEFAULT_MIDI_PORTS); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index e171b2785..4f3a1cf73 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -181,7 +181,7 @@ static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info); +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info); struct port { enum spa_direction direction; @@ -969,8 +969,11 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param follower->sink.direction = PW_DIRECTION_INPUT; follower->sink.props = pw_properties_copy(impl->sink_props); - parse_audio_info(follower->source.props, &follower->source.info); - parse_audio_info(follower->sink.props, &follower->sink.info); + if ((res = parse_audio_info(follower->source.props, &follower->source.info)) < 0 || + (res = parse_audio_info(follower->sink.props, &follower->sink.info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + return res; + } follower->source.n_audio = pw_properties_get_uint32(follower->source.props, "audio.ports", follower->source.info.channels ? @@ -1292,9 +1295,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index 9798336df..2f66a9ed7 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -745,9 +745,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -896,7 +896,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_TARGET_OBJECT); copy_props(impl, props, "pipe.filename"); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index 6fdef15b8..e1847577a 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -815,13 +815,14 @@ static int calc_frame_size(struct spa_audio_info_raw *info) case SPA_AUDIO_FORMAT_F64_OE: return res * 8; default: - return 0; + return -ENOTSUP; } } static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + int res; + if ((res = spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -830,7 +831,8 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, - SPA_KEY_AUDIO_POSITION, NULL); + SPA_KEY_AUDIO_POSITION, NULL)) < 0) + return res; return calc_frame_size(info); } @@ -851,6 +853,7 @@ static int parse_params(struct impl *impl) const char *str; struct spa_json it[1]; char value[512]; + int res; pw_properties_fetch_bool(impl->props, "capture", &impl->capture); pw_properties_fetch_bool(impl->props, "playback", &impl->playback); @@ -894,19 +897,20 @@ static int parse_params(struct impl *impl) copy_props(impl, PW_KEY_NODE_VIRTUAL); copy_props(impl, PW_KEY_NODE_NETWORK); - impl->capture_frame_size = parse_audio_info(impl->capture_props, &impl->capture_info); - if (impl->capture_frame_size == 0) { + if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) <= 0) { pw_log_error("unsupported capture audio format:%d channels:%d", impl->capture_info.format, impl->capture_info.channels); return -EINVAL; } + impl->capture_frame_size = res; - impl->playback_frame_size = parse_audio_info(impl->playback_props, &impl->playback_info); - if (impl->playback_frame_size == 0) { + if ((res = parse_audio_info(impl->playback_props, &impl->playback_info)) <= 0) { pw_log_error("unsupported playback audio format:%d channels:%d", impl->playback_info.format, impl->playback_info.channels); return -EINVAL; } + impl->playback_frame_size = res; + if (impl->capture_info.rate != 0 && pw_properties_get(impl->capture_props, PW_KEY_NODE_RATE) == NULL) pw_properties_setf(impl->capture_props, PW_KEY_NODE_RATE, diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 8849f7190..d613fb13b 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -1048,9 +1048,9 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -1192,7 +1192,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_NETWORK); copy_props(impl, props, PW_KEY_MEDIA_CLASS); - parse_audio_info(impl->stream_props, &impl->info); + if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto error; + } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index f5ef5188b..3834206ec 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -569,9 +569,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -675,7 +675,10 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, switch (impl->info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - parse_audio_info(props, &impl->info.info.raw); + if ((res = parse_audio_info(props, &impl->info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { @@ -704,7 +707,10 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, case SPA_MEDIA_SUBTYPE_opus: impl->stream_info.media_type = SPA_MEDIA_TYPE_audio; impl->stream_info.media_subtype = SPA_MEDIA_SUBTYPE_raw; - parse_audio_info(props, &impl->stream_info.info.raw); + if ((res = parse_audio_info(props, &impl->stream_info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } impl->stream_info.info.raw.format = SPA_AUDIO_FORMAT_F32; impl->info.info.opus.rate = impl->stream_info.info.raw.rate; impl->info.info.opus.channels = impl->stream_info.info.raw.channels; diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index c4f7c6580..ec4eb3e25 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -503,9 +503,11 @@ static int add_snapcast_stream(struct impl *impl, struct tunnel *t, return -ENOENT; } -static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + int res; + + if ((res = spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -514,12 +516,14 @@ static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, - SPA_KEY_AUDIO_POSITION, NULL); + SPA_KEY_AUDIO_POSITION, NULL)) < 0) + return res; pw_properties_set(props, PW_KEY_AUDIO_FORMAT, spa_type_audio_format_to_short_name(info->format)); pw_properties_setf(props, PW_KEY_AUDIO_RATE, "%d", info->rate); pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%d", info->channels); + return res; } static int create_stream(struct impl *impl, struct pw_properties *props, @@ -545,7 +549,10 @@ static int create_stream(struct impl *impl, struct pw_properties *props, if ((str = pw_properties_get(props, "capture.props")) == NULL) pw_properties_set(props, "capture.props", "{ media.class = Audio/Sink }"); - parse_audio_info(props, &t->audio_info); + if ((res = parse_audio_info(props, &t->audio_info)) < 0) { + pw_log_error("Can't parse format: %s", spa_strerror(res)); + goto done; + } if ((f = open_memstream(&args, &size)) == NULL) { res = -errno; diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 4ae0eacd1..22ef8bea5 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -185,9 +185,9 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) +static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - spa_audio_info_raw_init_dict_keys(info, + return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), @@ -274,7 +274,10 @@ struct vban_stream *vban_stream_new(struct pw_core *core, switch (impl->info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - parse_audio_info(props, &impl->info.info.raw); + if ((res = parse_audio_info(props, &impl->info.info.raw)) < 0) { + pw_log_error("can't parse format: %s", spa_strerror(res)); + goto out; + } if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) default_layout(impl->info.info.raw.channels, impl->info.info.raw.position, From be29ae4ef60736dd5c67558ebc92a75a7c0f0c05 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Oct 2025 18:05:22 +0200 Subject: [PATCH 0967/1014] audioadapter: add some more debug info when parsing fails --- spa/plugins/audioconvert/audioadapter.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 01e94cf58..0d0d3e15f 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -808,6 +808,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (spa_format_audio_parse(param, &info) < 0) { spa_log_error(this->log, "%p: cannot set Format param: " "parsing the POD failed", this); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { @@ -841,6 +842,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) { spa_log_error(this->log, "%p: cannot set PortConfig param: " "parsing the POD failed", this); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } @@ -848,8 +850,12 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_audio_info info; spa_zero(info); - if ((res = spa_format_audio_parse(format, &info)) < 0) + if ((res = spa_format_audio_parse(format, &info)) < 0) { + spa_log_error(this->log, "%p: cannot set PortConfig param: " + "parsing format failed: %s", this, spa_strerror(res)); + spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, format); return res; + } if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { info.info.raw.rate = 0; From 6d74eee874ac92d587050218bf7db95fefa4ef6a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 08:53:21 +0200 Subject: [PATCH 0968/1014] spa: bump channels to 128 again Remove the SPA_AUDIO_MAX_POSITION define and use the SPA_AUDIO_MAX_CHANNELS again. Make a compile time define to override the default max channels of 64. Make sure we compile the SPA library with the default 64 channels. If you use the SPA library on a spa_audio_info you will get 64 channel support, like before. If you want more channels, you will need to make a padded structure or redefine the MAX_CHANNELS before you use the spa_audio_info structures. You can use the padded structure with the new functions that take the structure size. With the extra checks in the parsing code, we avoid making a valid spa_audio_info with too many channels that don't fit in the structure. This means that code that receives a spa_audio_info can assume there is enough padding for all the channels. --- meson.build | 1 + spa/include/spa/param/audio/dsd.h | 2 +- spa/include/spa/param/audio/layout.h | 2 +- spa/include/spa/param/audio/raw-json.h | 2 +- spa/include/spa/param/audio/raw.h | 20 +++++++++----------- spa/lib/lib.c | 8 ++------ 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/meson.build b/meson.build index e0a333a8f..e81ce8214 100644 --- a/meson.build +++ b/meson.build @@ -115,6 +115,7 @@ cc_flags = common_flags + [ '-Werror=old-style-definition', '-Werror=missing-parameter-type', '-Werror=strict-prototypes', + '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') diff --git a/spa/include/spa/param/audio/dsd.h b/spa/include/spa/param/audio/dsd.h index 733823780..73f3d4e8b 100644 --- a/spa/include/spa/param/audio/dsd.h +++ b/spa/include/spa/param/audio/dsd.h @@ -45,7 +45,7 @@ struct spa_audio_info_dsd { int32_t interleave; /*< interleave bytes */ uint32_t rate; /*< sample rate (in bytes per second) */ uint32_t channels; /*< channels */ - uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; #define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index e5e59caf9..19e3a3c42 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -20,7 +20,7 @@ extern "C" { struct spa_audio_layout_info { uint32_t n_channels; - uint32_t position[SPA_AUDIO_MAX_POSITION]; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; }; #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 7c8c11ca3..c33a9310f 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -51,7 +51,7 @@ SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) { - return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_POSITION, n_channels); + return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); } SPA_API_AUDIO_RAW_JSON int diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 1688595e1..392b65daa 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -18,14 +18,10 @@ extern "C" { * \{ */ -/* This is the max number of position info, changing this will change the - * ABI */ -#define SPA_AUDIO_MAX_POSITION 64u - -/* The suggested number of max channels, can be a compile time constant and - * does not affect ABI or API */ +/* This is the max number of channels, changing this will change the + * size of some helper structures. This value should be at least 64 */ #ifndef SPA_AUDIO_MAX_CHANNELS -#define SPA_AUDIO_MAX_CHANNELS 128u +#define SPA_AUDIO_MAX_CHANNELS 64u #endif enum spa_audio_format { @@ -279,16 +275,18 @@ enum spa_audio_volume_ramp_scale { #define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */ #define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly * contains unpositioned channels. */ -/** Audio information description */ +/** Audio information description. You can assume when you receive this structure + * that there is enought padding to accomodate all channel positions in case the + * channel count is more than SPA_AUDIO_MAX_CHANNELS. */ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ - uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_POSITION + uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_CHANNELS * and you may assume there is enough padding for the extra * channel positions. */ - uint32_t position[SPA_AUDIO_MAX_POSITION]; /*< channel position from enum spa_audio_channel */ - /* more channels can be added here */ + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ + /* padding follows here when channels > SPA_AUDIO_MAX_CHANNELS */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) diff --git a/spa/lib/lib.c b/spa/lib/lib.c index 165fc81ba..30aa91194 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -1,4 +1,6 @@ +#undef SPA_AUDIO_MAX_CHANNELS + #define SPA_API_IMPL SPA_EXPORT #include #include @@ -165,9 +167,3 @@ #include #include #include - - - - - - From 78219471fffa5e147502e7cc670867a2404dbe63 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 09:35:59 +0200 Subject: [PATCH 0969/1014] spa: remove some obsolete functions The spa_audio_info array now always holds enough positions for all channels and we don't need to wrap around. --- spa/include/spa/param/audio/raw-utils.h | 29 ---------------------- spa/plugins/audioconvert/audioconvert.c | 20 +++++++-------- spa/plugins/bluez5/bluez5-dbus.c | 8 ++++-- spa/plugins/bluez5/bluez5-device.c | 5 +++- src/modules/module-combine-stream.c | 4 +-- src/modules/module-jack-tunnel.c | 3 +-- src/modules/module-loopback.c | 2 +- src/modules/module-netjack2-driver.c | 9 +++---- src/modules/module-netjack2-manager.c | 9 +++---- src/modules/module-protocol-pulse/format.c | 2 +- src/modules/module-protocol-pulse/module.c | 5 ++-- src/modules/module-pulse-tunnel.c | 2 +- 12 files changed, 34 insertions(+), 64 deletions(-) diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 551f47af1..ea7097a69 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -28,35 +28,6 @@ extern "C" { #endif #endif -SPA_API_AUDIO_RAW_UTILS uint32_t -spa_format_audio_raw_get_position(const struct spa_audio_info_raw *info, uint32_t idx) -{ - uint32_t pos, max_position = SPA_N_ELEMENTS(info->position); - if (idx < max_position) { - pos = info->position[idx]; - } else { - pos = info->position[idx % max_position]; - if (SPA_AUDIO_CHANNEL_IS_AUX(pos)) - pos += (idx / max_position) * max_position; - } - return pos; -} -SPA_API_AUDIO_RAW_UTILS void -spa_format_audio_raw_set_position(struct spa_audio_info_raw *info, uint32_t idx, uint32_t position) -{ - if (idx < SPA_N_ELEMENTS(info->position)) - info->position[idx] = position; -} - -SPA_API_AUDIO_RAW_UTILS uint32_t -spa_format_audio_raw_copy_positions(const struct spa_audio_info_raw *info, uint32_t *position, uint32_t max_position) -{ - uint32_t i, n_pos = SPA_MIN(info->channels, max_position); - for (i = 0; i < n_pos; i++) - position[i] = spa_format_audio_raw_get_position(info, i); - return n_pos; -} - SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size) { diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 1b4ae18fb..e40c4f2ab 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1897,7 +1897,7 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; for (i = 0; i < dir->n_ports; i++) { - uint32_t pos = spa_format_audio_raw_get_position(&info->info.raw, i); + uint32_t pos = info->info.raw.position[i]; init_port(this, direction, i, pos, true, false, false); if (this->monitor && direction == SPA_DIRECTION_INPUT) init_port(this, SPA_DIRECTION_OUTPUT, i+1, @@ -2067,8 +2067,8 @@ static int setup_in_convert(struct impl *this) uint32_t pi, pj; char b1[8], b2[8]; - pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); - pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + pi = src_info.info.raw.position[i]; + pj = dst_info.info.raw.position[j]; if (pi != pj) continue; in->remap[i] = j; @@ -2078,7 +2078,7 @@ static int setup_in_convert(struct impl *this) i, in->remap[i], j, spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); - spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); + dst_info.info.raw.position[j] = -1; break; } } @@ -2126,7 +2126,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) for (i = 0; i < p->n_channels; i++) { for (j = i; j < target; j++) { - uint32_t pj = spa_format_audio_raw_get_position(&info->info.raw, j); + uint32_t pj = info->info.raw.position[j]; spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, p->channel_map[i], pj); if (p->channel_map[i] != pj) @@ -2142,7 +2142,7 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) } p->n_channels = target; for (i = 0; i < p->n_channels; i++) - p->channel_map[i] = spa_format_audio_raw_get_position(&info->info.raw, i); + p->channel_map[i] = info->info.raw.position[i]; if (target == 0) return 0; @@ -2216,7 +2216,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { - p = spa_format_audio_raw_get_position(&out->format.info.raw, i); + p = out->format.info.raw.position[i]; dst_mask |= 1ULL << (p < 64 ? p : 0); } @@ -2361,8 +2361,8 @@ static int setup_out_convert(struct impl *this) uint32_t pi, pj; char b1[8], b2[8]; - pi = spa_format_audio_raw_get_position(&src_info.info.raw, i); - pj = spa_format_audio_raw_get_position(&dst_info.info.raw, j); + pi = src_info.info.raw.position[i]; + pj = dst_info.info.raw.position[j]; if (pi != pj) continue; out->remap[i] = j; @@ -2374,7 +2374,7 @@ static int setup_out_convert(struct impl *this) spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); - spa_format_audio_raw_set_position(&dst_info.info.raw, j, -1); + dst_info.info.raw.position[j] = -1; break; } } diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 2e92696bc..ac91c6cff 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -5038,9 +5038,13 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, spa_log_error(monitor->log, "invalid transport configuration"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } + if (info.info.raw.channels > MAX_CHANNELS) { + spa_log_error(monitor->log, "too many channels in transport"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } transport->n_channels = info.info.raw.channels; - spa_format_audio_raw_copy_positions(&info.info.raw, - transport->channels, SPA_N_ELEMENTS(transport->channels)); + memcpy(transport->channels, info.info.raw.position, + transport->n_channels * sizeof(uint32_t)); } else { transport->n_channels = 2; transport->channels[0] = SPA_AUDIO_CHANNEL_FL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 838bb54f3..796c73c1c 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -475,7 +475,10 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t return; } - *n_channels = spa_format_audio_raw_copy_positions(&info.info.raw, channels, max_channels); + *n_channels = info.info.raw.channels; + memcpy(channels, info.info.raw.position, + info.info.raw.channels * sizeof(uint32_t)); + } static const char *get_channel_name(uint32_t channel) diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index a9833d68f..29f57cb50 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -883,8 +883,8 @@ static int create_stream(struct stream_info *info) s->remap[i] = i; for (j = 0; j < tmp_info.channels; j++) { uint32_t pj, pi; - pj = spa_format_audio_raw_get_position(&tmp_info, j); - pi = spa_format_audio_raw_get_position(&remap_info, i); + pj = tmp_info.position[j]; + pi = remap_info.position[i]; if (pj == pi) { s->remap[i] = j; break; diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 8e8065c26..637001423 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -525,8 +525,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - pos, sizeof(pos), NULL); + s->info.position[i], pos, sizeof(pos), NULL); if (str) snprintf(name, sizeof(name), "%s_%s", prefix, str); else diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 976a8c7a4..e921cd701 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -546,7 +546,7 @@ static void param_format_changed(struct impl *impl, const struct spa_pod *param, (impl->info.channels != 0 && (impl->info.channels != info.channels || memcmp(impl->info.position, info.position, - SPA_MIN(info.channels, SPA_N_ELEMENTS(info.position)) * sizeof(uint32_t)) != 0))) { + info.channels * sizeof(uint32_t)) != 0))) { uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index e98ac9584..485570064 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -442,8 +442,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - name, sizeof(name), "UNK"); + s->info.position[i], name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str, @@ -866,8 +865,7 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p if ((uint32_t)peer->params.send_audio_channels != impl->sink.info.channels) { impl->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < impl->sink.info.channels; i++) - spa_format_audio_raw_set_position(&impl->sink.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } impl->source.n_ports = peer->params.recv_audio_channels + peer->params.recv_midi_channels; if (impl->source.n_ports > MAX_PORTS) { @@ -878,8 +876,7 @@ static int handle_follower_setup(struct impl *impl, struct nj2_session_params *p if ((uint32_t)peer->params.recv_audio_channels != impl->source.info.channels) { impl->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < impl->source.info.channels; i++) - spa_format_audio_raw_set_position(&impl->source.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } impl->samplerate = peer->params.sample_rate; impl->period_size = peer->params.period_size; diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 4f3a1cf73..615a4553b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -602,8 +602,7 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_type_audio_channel_make_short_name( - spa_format_audio_raw_get_position(&s->info, i), - name, sizeof(name), "UNK"); + s->info.position[i], name, sizeof(name), "UNK"); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str, @@ -1031,16 +1030,14 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param if ((uint32_t)peer->params.recv_audio_channels != follower->source.info.channels) { follower->source.info.channels = peer->params.recv_audio_channels; for (i = 0; i < follower->source.info.channels; i++) - spa_format_audio_raw_set_position(&follower->source.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + follower->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } follower->sink.n_ports = peer->params.send_audio_channels + peer->params.send_midi_channels; follower->sink.info.rate = peer->params.sample_rate; if ((uint32_t)peer->params.send_audio_channels != follower->sink.info.channels) { follower->sink.info.channels = peer->params.send_audio_channels; for (i = 0; i < follower->sink.info.channels; i++) - spa_format_audio_raw_set_position(&follower->sink.info, i, - SPA_AUDIO_CHANNEL_AUX0 + i); + follower->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } if (follower->source.n_ports > MAX_PORTS || follower->sink.n_ports > MAX_PORTS) { diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index b34765641..24ef024f9 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -585,7 +585,7 @@ int format_parse_param(const struct spa_pod *param, bool collect, if (info.info.raw.channels) { map->channels = SPA_MIN(info.info.raw.channels, CHANNELS_MAX); for (i = 0; i < map->channels; i++) - map->map[i] = spa_format_audio_raw_get_position(&info.info.raw, i); + map->map[i] = info.info.raw.position[i]; } } return 0; diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index fd997883e..2b47d93e9 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -244,7 +244,7 @@ int module_args_to_audioinfo_keys(struct impl *impl, struct pw_properties *props } else { /* FIXME add more mappings */ for (i = 0; i < info->channels; i++) - spa_format_audio_raw_set_position(info, i, SPA_AUDIO_CHANNEL_UNKNOWN); + info->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; } if (info->position[0] == SPA_AUDIO_CHANNEL_UNKNOWN) info->flags |= SPA_AUDIO_FLAG_UNPOSITIONED; @@ -290,8 +290,7 @@ void audioinfo_to_properties(struct spa_audio_info_raw *info, struct pw_properti p = s = alloca(info->channels * 8); for (i = 0; i < info->channels; i++) p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ", ", - channel_id2name(spa_format_audio_raw_get_position(info, i), - pos, sizeof(pos))); + channel_id2name(info->position[i], pos, sizeof(pos))); pw_properties_setf(props, SPA_KEY_AUDIO_POSITION, "[ %s ]", s); } } diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index d613fb13b..4c4ab1a26 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -755,7 +755,7 @@ static int create_pulse_stream(struct impl *impl) map.channels = impl->info.channels; for (i = 0; i < map.channels; i++) map.map[i] = (pa_channel_position_t)channel_id2pa( - spa_format_audio_raw_get_position(&impl->info, i), &aux); + impl->info.position[i], &aux); snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"), pw_get_user_name(), pw_get_host_name()); From aa0272f6f357706307ec4cab7296fd4f691db2de Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 10:28:38 +0200 Subject: [PATCH 0970/1014] treewide: remove some obsolete channel checks The spa_audio_info can not be parsed with too many channels so there is always enough space for the positions. --- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 31 ++++++++----------- spa/include/spa/param/audio/raw-utils.h | 4 ++- spa/plugins/audioconvert/audioconvert.c | 36 ++++++++++------------- spa/plugins/bluez5/a2dp-codec-opus.c | 15 +++++----- spa/plugins/bluez5/bluez5-device.c | 5 ++-- src/modules/module-ffado-driver.c | 6 ++-- src/modules/module-filter-chain.c | 3 +- src/modules/module-vban/stream.c | 14 ++++----- 8 files changed, 50 insertions(+), 64 deletions(-) diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index bf7196898..24a66eabc 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -643,11 +643,8 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(uint32_t channels, uint32_t *position, uint32_t max_position) +static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS]) { - if (max_position < 8) - return -ENOSPC; - switch (channels) { case 8: position[6] = SPA_AUDIO_CHANNEL_SL; @@ -775,8 +772,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = io->channels; pw->requested.info.raw.rate = io->rate; - set_default_channels(io->channels, pw->requested.info.raw.position, - SPA_N_ELEMENTS(pw->requested.info.raw.position)); + set_default_channels(io->channels, pw->requested.info.raw.position); fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format); pw->format = pw->requested; break; @@ -784,8 +780,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb; pw->requested.info.dsd.channels = io->channels; pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); - set_default_channels(io->channels, pw->requested.info.dsd.position, - SPA_N_ELEMENTS(pw->requested.info.dsd.position)); + set_default_channels(io->channels, pw->requested.info.dsd.position); pw->format = pw->requested; /* we need to let the server decide these values */ pw->format.info.dsd.bitorder = 0; @@ -907,30 +902,30 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; unsigned int i; - uint32_t *position, max_position; + uint32_t *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = map->channels; position = pw->requested.info.raw.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: pw->requested.info.dsd.channels = map->channels; position = pw->requested.info.dsd.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return -EINVAL; } + if (map->channels > MAX_CHANNELS) + return -ENOTSUP; + for (i = 0; i < map->channels; i++) { - uint32_t pos = chmap_to_channel(map->pos[i]); char buf[8]; - if (i < max_position) - position[i] = pos; + position[i] = chmap_to_channel(map->pos[i]); pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), - spa_type_audio_channel_make_short_name(pos, buf, sizeof(buf), "UNK")); + spa_type_audio_channel_make_short_name(position[i], + buf, sizeof(buf), "UNK")); } return 1; } @@ -939,18 +934,16 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_chmap_t *map; - uint32_t i, channels, *position, max_position; + uint32_t i, channels, *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: channels = pw->requested.info.raw.channels; position = pw->requested.info.raw.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.raw.position); break; case SPA_MEDIA_SUBTYPE_dsd: channels = pw->requested.info.dsd.channels; position = pw->requested.info.dsd.position; - max_position = SPA_N_ELEMENTS(pw->requested.info.dsd.position); break; default: return NULL; @@ -960,7 +953,7 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) channels * sizeof(unsigned int)); map->channels = channels; for (i = 0; i < channels; i++) - map->pos[i] = channel_to_chmap(position[i % max_position]); + map->pos[i] = channel_to_chmap(position[i]); return map; } diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index ea7097a69..ce5e61e67 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -43,7 +43,7 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (info->channels > max_position) - return -ENOTSUP; + return -ECHRNG; if (position == NULL || spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); @@ -78,6 +78,8 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); + /* we drop the positions here when we can't read all of them. This is + * really a malformed spa_audio_info structure. */ if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && max_position > info->channels) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e40c4f2ab..e8c95b44a 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1148,7 +1148,7 @@ struct spa_filter_graph_events graph_events = { }; static int setup_filter_graph(struct impl *this, struct filter_graph *g, - uint32_t channels, uint32_t *position, uint32_t max_position) + uint32_t channels, uint32_t *position) { int res; char rate_str[64], in_ports[64]; @@ -1163,9 +1163,8 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, snprintf(in_ports, sizeof(in_ports), "%d", channels); g->n_inputs = channels; if (position) { - uint32_t n_pos = SPA_MIN(channels, max_position); - memcpy(g->inputs_position, position, sizeof(uint32_t) * n_pos); - memcpy(g->outputs_position, position, sizeof(uint32_t) * n_pos); + memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); + memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); } } @@ -1180,7 +1179,7 @@ static int setup_filter_graph(struct impl *this, struct filter_graph *g, return res; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position); +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); static void free_tmp(struct impl *this) { @@ -1262,7 +1261,7 @@ static int ensure_tmp(struct impl *this) static int setup_filter_graphs(struct impl *impl, bool force) { int res; - uint32_t channels, *position, max_position; + uint32_t channels, *position; struct dir *in, *out; struct filter_graph *g, *t; @@ -1271,7 +1270,6 @@ static int setup_filter_graphs(struct impl *impl, bool force) channels = in->format.info.raw.channels; position = in->format.info.raw.position; - max_position = SPA_N_ELEMENTS(in->format.info.raw.position); impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); spa_list_for_each_safe(g, t, &impl->active_graphs, link) { @@ -1279,20 +1277,19 @@ static int setup_filter_graphs(struct impl *impl, bool force) continue; if (force) g->setup = false; - if ((res = setup_filter_graph(impl, g, channels, position, max_position)) < 0) { + if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { g->removing = true; spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, spa_strerror(res)); } else { channels = g->n_outputs; position = g->outputs_position; - max_position = SPA_N_ELEMENTS(g->outputs_position); impl->maxports = SPA_MAX(impl->maxports, channels); } } if ((res = ensure_tmp(impl)) < 0) return res; - if ((res = setup_channelmix(impl, channels, position, max_position)) < 0) + if ((res = setup_channelmix(impl, channels, position)) < 0) return res; return 0; @@ -2058,8 +2055,7 @@ static int setup_in_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(dst_info.info.raw.position, SPA_MIN(dst_info.info.raw.channels, - SPA_N_ELEMENTS(dst_info.info.raw.position)), + qsort(dst_info.info.raw.position, dst_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { @@ -2188,18 +2184,18 @@ static void set_volume(struct impl *this) this->params[IDX_Props].user++; } -static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position, uint32_t max_position) +static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) { uint32_t i, idx = 0; char buf[8]; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", - spa_type_audio_channel_make_short_name(position[i % max_position], + spa_type_audio_channel_make_short_name(position[i], buf, sizeof(buf), "UNK")); return str; } -static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position, uint32_t max_position) +static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; @@ -2212,7 +2208,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { - p = position[i % max_position]; + p = position[i]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { @@ -2221,10 +2217,9 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi } spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), - src_chan, position, max_position), src_mask); + src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), - dst_chan, out->format.info.raw.position, - SPA_N_ELEMENTS(out->format.info.raw.position)), dst_mask); + dst_chan, out->format.info.raw.position), dst_mask); spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), @@ -2352,8 +2347,7 @@ static int setup_out_convert(struct impl *this) dst_info.info.raw.channels, dst_info.info.raw.rate); - qsort(src_info.info.raw.position, SPA_MIN(src_info.info.raw.channels, - SPA_N_ELEMENTS(src_info.info.raw.position)), + qsort(src_info.info.raw.position, src_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 0881c0255..c5b042b20 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -491,7 +491,7 @@ static void get_default_bitrates(const struct media_codec *codec, bool bidi, int static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direction_t *conf, bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, - const uint8_t **surround_mapping, uint32_t *positions, uint32_t max_positions) + const uint8_t **surround_mapping, uint32_t *positions) { const uint32_t channels = conf->channels; const uint32_t location = OPUS_05_GET_LOCATION(*conf); @@ -539,12 +539,11 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc if (location & loc.mask) { uint32_t idx = permutation ? permutation[j] : j; - if (idx < max_positions) - positions[idx] = loc.position; + positions[idx] = loc.position; j++; } } - for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels && j < max_positions; ++i, ++j) + for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j) positions[j] = i; } @@ -779,7 +778,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags, dir = !is_duplex_codec(codec) ? &conf.main : &conf.bidi; - if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position, MAX_CHANNELS) < 0) + if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position) < 0) return -EINVAL; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); @@ -832,9 +831,9 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags info->info.raw.channels = dir1->channels; if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, - info->info.raw.position, SPA_N_ELEMENTS(info->info.raw.position)) < 0) + info->info.raw.position) < 0) return -EINVAL; - if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL, 0) < 0) + if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0) return -EINVAL; return 0; @@ -925,7 +924,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, - &enc_mapping, NULL, 0)) < 0) + &enc_mapping, NULL)) < 0) goto error; if (config_info.info.raw.channels != info->info.raw.channels) { res = -EINVAL; diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 796c73c1c..6546abf4f 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -452,8 +452,7 @@ static int node_offload_set_active(struct node *node, bool active) return res; } -static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels, - uint32_t max_channels) +static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { const struct media_codec *codec; struct spa_audio_info info = { 0 }; @@ -689,7 +688,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, this->nodes[id].active = true; this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = a2dp_duplex; - get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels, MAX_CHANNELS); + get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels); if (this->nodes[id].transport) spa_hook_remove(&this->nodes[id].transport_listener); this->nodes[id].transport = t; diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index de5ed0ff0..7094feaf4 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -761,7 +761,7 @@ static int make_stream_ports(struct stream *s) struct port *port = s->ports[i]; char channel[32]; - snprintf(channel, sizeof(channel), "AUX%u", n_channels % MAX_CHANNELS); + snprintf(channel, sizeof(channel), "AUX%u", n_channels); switch (port->stream_type) { case ffado_stream_type_audio: @@ -1229,7 +1229,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->source.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->source.info.position)); - impl->source.info.channels = n_channels; + impl->source.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -1256,7 +1256,7 @@ static int probe_ffado_device(struct impl *impl) } if (impl->sink.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->sink.info.position)); - impl->sink.info.channels = n_channels; + impl->sink.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 6b759434b..a2d1f5361 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -1691,8 +1691,7 @@ static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio { if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && !SPA_FLAG_IS_SET(src->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { - uint32_t i, n_pos = SPA_MIN(src->channels, SPA_N_ELEMENTS(dst->position)); - for (i = 0; i < n_pos; i++) + for (uint32_t i = 0; i < src->channels; i++) dst->position[i] = src->position[i]; SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 22ef8bea5..941fa1acd 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -215,16 +215,16 @@ static const struct spa_audio_layout_info layouts[] = { { SPA_AUDIO_LAYOUT_7_1 }, }; -static void default_layout(uint32_t channels, uint32_t *position, uint32_t max_position) +static void default_layout(uint32_t channels, uint32_t *position) { SPA_FOR_EACH_ELEMENT_VAR(layouts, l) { if (l->n_channels == channels) { - for (uint32_t i = 0; i < l->n_channels && i < max_position; i++) + for (uint32_t i = 0; i < l->n_channels; i++) position[i] = l->position[i]; return; } } - for (uint32_t i = 0; i < channels && i < max_position; i++) + for (uint32_t i = 0; i < channels; i++) position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } @@ -278,10 +278,10 @@ struct vban_stream *vban_stream_new(struct pw_core *core, pw_log_error("can't parse format: %s", spa_strerror(res)); goto out; } - if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) - default_layout(impl->info.info.raw.channels, - impl->info.info.raw.position, - SPA_N_ELEMENTS(impl->info.info.raw.position)); + if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { + default_layout(impl->info.info.raw.channels, impl->info.info.raw.position); + SPA_FLAG_CLEAR(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { From d18670d7bbfe88aaee7bc3c8926eb1c4fedd5173 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 10:42:05 +0200 Subject: [PATCH 0971/1014] pw-cat: improve channel checks Make sure we don't use too many channels. --- src/tools/pw-cat.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 8210addb6..632a513bf 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -2348,10 +2348,22 @@ int main(int argc, char *argv[]) .rate = data.rate, .channels = data.channels); + if (data.channels > MAX_CHANNELS) { + fprintf(stderr, "error: too many channels %d > %d\n", + data.channels, MAX_CHANNELS); + goto error_bad_file; + } if (data.channelmap.n_channels) { - uint32_t i, n_pos = SPA_MIN(data.channels, SPA_N_ELEMENTS(info.position)); - for (i = 0; i < n_pos; i++) + if (data.channels > MAX_CHANNELS) { + fprintf(stderr, "error: too many channels in channelmap %d > %d\n", + data.channelmap.n_channels, MAX_CHANNELS); + goto error_bad_file; + } + uint32_t i; + for (i = 0; i < data.channelmap.n_channels; i++) info.position[i] = data.channelmap.channels[i]; + for (; i < data.channels; i++) + info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info); break; From f7c3d379698e0c8b0eefec6cbab10a0b04f0bf8a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 12:46:38 +0200 Subject: [PATCH 0972/1014] fmt-ops: allocate shaper memory dynamically It is based on the number of channels so allocate it dynamically. --- spa/plugins/audioconvert/fmt-ops.c | 6 ++++-- spa/plugins/audioconvert/fmt-ops.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 3296c220b..3fc2c5f0a 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -551,7 +551,7 @@ int convert_init(struct convert *conv) const struct dither_info *dinfo; const struct noise_info *ninfo; const struct clear_info *cinfo; - uint32_t i, conv_flags, data_size[3]; + uint32_t i, conv_flags, data_size[4]; /* we generate int32 bits of random values. With this scale * factor, we bring this in the [-1.0, 1.0] range */ @@ -615,15 +615,17 @@ int convert_init(struct convert *conv) data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); + data_size[3] = SPA_ROUND_UP(conv->n_channels * sizeof(struct shaper), FMT_OPS_MAX_ALIGN); conv->data = calloc(FMT_OPS_MAX_ALIGN + - data_size[0] + data_size[1] + data_size[2], 1); + data_size[0] + data_size[1] + data_size[2] + data_size[3], 1); if (conv->data == NULL) return -errno; conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); + conv->shaper = SPA_PTROFF(conv->prev, data_size[2], struct shaper); for (i = 0; i < RANDOM_SIZE; i++) conv->random[i] = random(); diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index 9f4655c22..f738e3858 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -236,7 +236,7 @@ struct convert { uint32_t noise_size; const float *ns; uint32_t n_ns; - struct shaper shaper[64]; + struct shaper *shaper; void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], From c4244a6cf3e042af5c2c344ba0a52836ec45aac8 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:00:11 +0200 Subject: [PATCH 0973/1014] string: use spa_strbuf instead of snprintf magic --- spa/plugins/alsa/acp/acp.c | 19 ++++++++------- spa/plugins/alsa/acp/channelmap.h | 5 +--- spa/plugins/alsa/alsa-acp-device.c | 9 +++++--- spa/plugins/alsa/alsa-pcm.c | 37 ++++++++++++++---------------- src/pipewire/settings.c | 19 +++++++-------- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 364d71eb0..f6f03a5ff 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -245,7 +245,7 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist); } if (m->split) { - char pos[2048]; + char pos[PA_CHANNELS_MAX*8]; struct spa_strbuf b; int i; @@ -1142,8 +1142,9 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); } else { uint32_t positions[eld.lpcm_channels]; - char position[64]; - int i = 0, pos = 0; + char position[eld.lpcm_channels * 8]; + struct spa_strbuf b; + int i = 0; if (eld.speakers & 0x01) { positions[i++] = ACP_CHANNEL_FL; @@ -1172,16 +1173,14 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) if (eld.speakers & 0x10) { positions[i++] = ACP_CHANNEL_RC; } - while (i < eld.lpcm_channels) positions[i++] = ACP_CHANNEL_UNKNOWN; - for (i = 0, pos = 0; i < eld.lpcm_channels; i++) { - pos += snprintf(&position[pos], sizeof(position) - pos, "%s,", channel_names[positions[i]]); - } - - /* Overwrite trailing , */ - position[pos - 1] = 0; + spa_strbuf_init(&b, position, sizeof(position)); + spa_strbuf_append(&b, "["); + for (i = 0; i < eld.lpcm_channels; i++) + spa_strbuf_append(&b, "%s%s", i ? "," : "", channel_names[positions[i]]); + spa_strbuf_append(&b, "]"); changed |= (old_position == NULL) || (!spa_streq(old_position, position)); pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position); diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index 2d4b54444..bb8f3f5f3 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -451,7 +451,6 @@ static inline int pa_channel_map_equal(const pa_channel_map *a, const pa_channel static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { unsigned channel; - bool first = true; char *e; if (!pa_channel_map_valid(map)) { pa_snprintf(s, l, "%s", _("(invalid)")); @@ -460,12 +459,10 @@ static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_m *(e = s) = 0; for (channel = 0; channel < map->channels && l > 1; channel++) { l -= pa_snprintf(e, l, "%s%s", - first ? "" : ",", + channel == 0 ? "" : ",", pa_channel_position_to_string(map->map[channel])); e = strchr(e, 0); - first = false; } - return s; } diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index 6ab41c8d2..44342a7a3 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -156,12 +156,13 @@ static int emit_node(struct impl *this, struct acp_device *dev) const struct acp_dict_item *it; uint32_t n_items, i; char device_name[128], path[210], channels[16], ch[12], routes[16]; - char card_index[16], card_name[64], *p; + char card_index[16], card_name[64]; char positions[MAX_CHANNELS * 12]; char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; const char *stream, *card_id, *bus; + struct spa_strbuf b; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; @@ -200,11 +201,13 @@ static int emit_node(struct impl *this, struct acp_device *dev) snprintf(channels, sizeof(channels), "%d", dev->format.channels); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels); - p = positions; + spa_strbuf_init(&b, positions, sizeof(positions)); + spa_strbuf_append(&b, "["); for (i = 0; i < dev->format.channels; i++) { - p += snprintf(p, 12, "%s%s", i == 0 ? "" : ",", + spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", acp_channel_str(ch, sizeof(ch), dev->format.map[i])); } + spa_strbuf_append(&b, " ]"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions); if (dev->n_codecs > 0) { diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 676214685..309b2d91d 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -240,36 +240,33 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) static int position_to_string(struct channel_map *map, char *val, size_t len) { - uint32_t i, o = 0; + uint32_t i; char pos[8]; - int r; - o += snprintf(val, len, "[ "); + struct spa_strbuf b; + + spa_strbuf_init(&b, val, len); + spa_strbuf_append(&b, "["); for (i = 0; i < map->n_pos; i++) { - r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", + spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", spa_type_audio_channel_make_short_name(map->pos[i], pos, sizeof(pos), "UNK")); - if (r < 0 || o + r >= len) - return -ENOSPC; - o += r; } - if (len > o) - o += snprintf(val+o, len-o, " ]"); + if (spa_strbuf_append(&b, " ]") < 2) + return -ENOSPC; return 0; } static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) { - uint32_t i, o = 0; - int r; - o += snprintf(val, len, "[ "); - for (i = 0; i < n_vals; i++) { - r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]); - if (r < 0 || o + r >= len) - return -ENOSPC; - o += r; - } - if (len > o) - o += snprintf(val+o, len-o, " ]"); + uint32_t i; + struct spa_strbuf b; + + spa_strbuf_init(&b, val, len); + spa_strbuf_append(&b, "["); + for (i = 0; i < n_vals; i++) + spa_strbuf_append(&b, "%s%d", i == 0 ? " " : ", ", vals[i]); + if (spa_strbuf_append(&b, " ]") < 2) + return -ENOSPC; return 0; } diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c index 597e686a9..74d4cfcb2 100644 --- a/src/pipewire/settings.c +++ b/src/pipewire/settings.c @@ -245,24 +245,21 @@ void pw_settings_init(struct pw_context *this) static void expose_settings(struct pw_context *context, struct pw_impl_metadata *metadata) { struct settings *s = &context->settings; - uint32_t i, o; - char rates[MAX_RATES*16] = ""; + uint32_t i; + char rates[MAX_RATES*16]; + struct spa_strbuf b; pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "log.level", "", "%d", s->log_level); pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "clock.rate", "", "%d", s->clock_rate); - for (i = 0, o = 0; i < s->n_clock_rates; i++) { - int r = snprintf(rates+o, sizeof(rates)-o, "%s%d", i == 0 ? "" : ", ", + + spa_strbuf_init(&b, rates, sizeof(rates)); + for (i = 0; i < s->n_clock_rates; i++) + spa_strbuf_append(&b, "%s%d", i == 0 ? "" : ", ", s->clock_rates[i]); - if (r < 0 || o + r >= (int)sizeof(rates)) { - snprintf(rates, sizeof(rates), "%d", s->clock_rate); - break; - } - o += r; - } if (s->n_clock_rates == 0) - snprintf(rates, sizeof(rates), "%d", s->clock_rate); + spa_strbuf_append(&b, "%d", s->clock_rate); pw_impl_metadata_set_propertyf(metadata, PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates); From c8d4de5e77a016d94824e4240c851b5d032c9946 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:00:42 +0200 Subject: [PATCH 0974/1014] acp: bump max channels to 128 --- spa/plugins/alsa/acp/channelmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index bb8f3f5f3..72e59fdc6 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -27,7 +27,7 @@ extern "C" { #endif -#define PA_CHANNELS_MAX 64 +#define PA_CHANNELS_MAX 128 #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) From 88c65932d8d8a002ae9e2d42ca2a25b06a804a19 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 24 Oct 2025 17:16:03 +0200 Subject: [PATCH 0975/1014] acp: use global max channels if defined --- spa/plugins/alsa/acp/channelmap.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spa/plugins/alsa/acp/channelmap.h b/spa/plugins/alsa/acp/channelmap.h index 72e59fdc6..2b13d2528 100644 --- a/spa/plugins/alsa/acp/channelmap.h +++ b/spa/plugins/alsa/acp/channelmap.h @@ -27,7 +27,11 @@ extern "C" { #endif -#define PA_CHANNELS_MAX 128 +#ifdef SPA_AUDIO_MAX_CHANNELS +#define PA_CHANNELS_MAX ((int)SPA_AUDIO_MAX_CHANNELS) +#else +#define PA_CHANNELS_MAX 64 +#endif #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) From 9f1a149876c56ec07ae74b10e9c4ef8a97084bf7 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 22 Oct 2025 19:32:22 +0300 Subject: [PATCH 0976/1014] ci: add file package, for coverity Try to fix coverity by adding missing 'file' package to container --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9cfde68d0..448bfa06e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2025-10-15.0' + FDO_DISTRIBUTION_TAG: '2025-10-22.0' FDO_DISTRIBUTION_VERSION: '42' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -48,6 +48,7 @@ include: dbus-devel doxygen fdk-aac-free-devel + file findutils gcc gcc-c++ From 93495d3a754bc7492cfb27fd551180f0be9b6932 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 15:38:17 +0200 Subject: [PATCH 0977/1014] spa: param: infer raw audio channels from position if unset The behavior before b8eeb2db45adfb was that spa_audio_info_raw_update() always sets audio.channels when audio.position is updated. The new behavior does not set audio.channels when parsing audio.position. This breaks things e.g. when combine-sink and loopback nodes are created with only audio.position specified. Restore the previous behavior. --- spa/include/spa/param/audio/raw-json.h | 1 + 1 file changed, 1 insertion(+) diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index c33a9310f..e83c4495b 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -78,6 +78,7 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, max_position, &v) > 0) { if (v > max_position) return -ECHRNG; + info->channels = v; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } } From 3febf09b85c23a6cf69fbba1d250b85820abd76a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:22:32 +0300 Subject: [PATCH 0978/1014] alsa: fix typoed braces in condition + assign --- spa/plugins/alsa/alsa-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index 309b2d91d..4f2fde8b6 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -773,7 +773,7 @@ static void bind_ctl_event(struct spa_source *source) snd_ctl_elem_id_alloca(&bound_id); snd_ctl_elem_value_alloca(&old_value); - while ((err = snd_ctl_read(state->ctl, ev) > 0)) { + while ((err = snd_ctl_read(state->ctl, ev)) > 0) { bool changed = false; if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM) From fe2c62b9b163ff824647cf7c0ec9d8369f836a0e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:59:47 +0300 Subject: [PATCH 0979/1014] meson.build: set project cc flags also for native builds Use the build flags also for all native build targets. Avoids spurious warnings in spa-json-dump --- meson.build | 4 ++-- spa/plugins/audioconvert/meson.build | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index e81ce8214..e82ba03b3 100644 --- a/meson.build +++ b/meson.build @@ -118,8 +118,8 @@ cc_flags = common_flags + [ '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') - -cc_flags_native = cc_native.get_supported_arguments(cc_flags) +add_project_arguments(cc_native.get_supported_arguments(cc_flags), + language: 'c', native: true) have_cpp = add_languages('cpp', native: false, required : false) diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index 394bc11eb..64379c845 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -125,7 +125,7 @@ sparesampledumpcoeffs_sources = [ sparesampledumpcoeffs = executable( 'spa-resample-dump-coeffs', sparesampledumpcoeffs_sources, - c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ], + c_args : [ '-DRESAMPLE_DISABLE_PRECOMP' ], dependencies : [ spa_dep, mathlib_native ], install : false, native : true, From b0e308e0dce9c78a3ee71eab0cd9a2a8f959d47b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 14:27:10 +0300 Subject: [PATCH 0980/1014] spa: examples: fix getopt usage + typos in adapter-control --- spa/examples/adapter-control.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spa/examples/adapter-control.c b/spa/examples/adapter-control.c index 6aa36dfaa..0a48f2fd4 100644 --- a/spa/examples/adapter-control.c +++ b/spa/examples/adapter-control.c @@ -578,7 +578,7 @@ static int make_nodes(struct data *data) SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param) < 0)) { + if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup source node %d\n", res); return res; } @@ -647,7 +647,7 @@ static int make_nodes(struct data *data) SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); - if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param) < 0)) { + if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup sink node %d\n", res); return res; } @@ -987,7 +987,7 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, ""); - while ((c = getopt_long(argc, argv, "hdmstiac:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hd:m:s:t:i:a:c:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); From 68dc45cc62a6c79caca5e711faac2727e3b49154 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 13:49:46 +0300 Subject: [PATCH 0981/1014] audioconvert: simplify volume ramp generation Don't use floating point accumulators, interpolate from sample position. --- spa/plugins/audioconvert/audioconvert.c | 82 ++++++++----------------- 1 file changed, 27 insertions(+), 55 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index e8c95b44a..f201522f8 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -1538,8 +1538,6 @@ static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp) samples = (vrp->volume_ramp_time * vrp->rate) / 1000; spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); } - if (!samples) - samples = -1; return samples; } @@ -1550,12 +1548,10 @@ static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *v if (vrp->volume_ramp_step_samples) samples = vrp->volume_ramp_step_samples; else if (vrp->volume_ramp_step_time) { - /* convert the step time which is in nano seconds to seconds */ - samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); + /* convert the step time which is in nano seconds to seconds, round up */ + samples = SPA_MAX(1u, vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); } - if (!samples) - samples = -1; return samples; } @@ -1568,76 +1564,52 @@ static float get_volume_at_scale(struct volume_ramp_params *vrp, float value) return 0.0; } -static struct spa_pod *generate_ramp_up_seq(struct impl *this, struct volume_ramp_params *vrp, +static struct spa_pod *generate_ramp_seq(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f[1]; - float start = vrp->start, end = vrp->end, volume_accum = start; - int ramp_samples = get_ramp_samples(this, vrp); - int ramp_step_samples = get_ramp_step_samples(this, vrp); - float volume_step = ((end - start) / (ramp_samples / ramp_step_samples)); - uint32_t volume_offs = 0; + float start = vrp->start, end = vrp->end; + int samples = get_ramp_samples(this, vrp); + int step = get_ramp_step_samples(this, vrp); + int offs = 0; + + if (samples < 0 || step < 0 || (samples > 0 && step == 0)) + return NULL; spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_builder_push_sequence(&b.b, &f[0], 0); - spa_log_info(this->log, "generating ramp up sequence from %f to %f with a" - " step value %f at scale %d", start, end, volume_step, vrp->scale); - do { - float vas = get_volume_at_scale(vrp, volume_accum); - spa_log_trace(this->log, "volume accum %f", vas); - spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); - spa_pod_builder_add_object(&b.b, - SPA_TYPE_OBJECT_Props, 0, - SPA_PROP_volume, SPA_POD_Float(vas)); - volume_accum += volume_step; - volume_offs += ramp_step_samples; - } while (volume_accum < end); - return spa_pod_builder_pop(&b.b, &f[0]); -} + spa_log_info(this->log, "generating ramp sequence from %f to %f with " + "step %d/%d at scale %d", start, end, step, samples, vrp->scale); -static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp, - void *buffer, size_t size) -{ - struct spa_pod_dynamic_builder b; - struct spa_pod_frame f[1]; - int ramp_samples = get_ramp_samples(this, vrp); - int ramp_step_samples = get_ramp_step_samples(this, vrp); - float start = vrp->start, end = vrp->end, volume_accum = start; - float volume_step = ((start - end) / (ramp_samples / ramp_step_samples)); - uint32_t volume_offs = 0; + while (1) { + float pos = (samples == 0) ? end : + SPA_CLAMP(start + (end - start) * offs / samples, + SPA_MIN(start, end), SPA_MAX(start, end)); + float vas = get_volume_at_scale(vrp, pos); - spa_pod_dynamic_builder_init(&b, buffer, size, 4096); - - spa_pod_builder_push_sequence(&b.b, &f[0], 0); - spa_log_info(this->log, "generating ramp down sequence from %f to %f with a" - " step value %f at scale %d", start, end, volume_step, vrp->scale); - do { - float vas = get_volume_at_scale(vrp, volume_accum); - spa_log_trace(this->log, "volume accum %f", vas); - spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties); + spa_log_trace(this->log, "volume %d accum %f", offs, vas); + spa_pod_builder_control(&b.b, offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(vas)); - volume_accum -= volume_step; - volume_offs += ramp_step_samples; - } while (volume_accum > end); + if (offs >= samples) + break; + + offs = SPA_MIN(samples, offs + step); + } + return spa_pod_builder_pop(&b.b, &f[0]); } static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { - void *sequence = NULL; - if (vrp->start == vrp->end) - spa_log_error(this->log, "no change in volume, cannot ramp volume"); - else if (vrp->end > vrp->start) - sequence = generate_ramp_up_seq(this, vrp, buffer, size); - else - sequence = generate_ramp_down_seq(this, vrp, buffer, size); + void *sequence; + sequence = generate_ramp_seq(this, vrp, buffer, size); if (!sequence) spa_log_error(this->log, "unable to generate sequence"); From 3d08c0557f99d21c7fba9dd532ee14d683e440e2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 25 Oct 2025 15:14:42 +0300 Subject: [PATCH 0982/1014] properties: fix assign + conditional expression --- src/pipewire/properties.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c index de81088b2..ac0aac0d0 100644 --- a/src/pipewire/properties.c +++ b/src/pipewire/properties.c @@ -252,7 +252,8 @@ static int update_string(struct pw_properties *props, const char *str, size_t si continue; } /* item changed or added, apply changes later */ - if ((errno = -add_item(&changes, key, false, val, true) < 0)) { + if ((res = add_item(&changes, key, false, val, true)) < 0) { + errno = -res; it[0].state = SPA_JSON_ERROR_FLAG; break; } From 8a23b13798e52ea39dc2eb92dd134bae95d6c6a4 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 17:44:03 +0200 Subject: [PATCH 0983/1014] spa: param: pass correct struct size to spa_format_audio_raw_ext_parse/build --- spa/include/spa/param/audio/format-utils.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 07f5d94e3..86ea84f75 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -59,7 +59,8 @@ spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info * switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_ext_parse(format, &info->info.raw, size); + return spa_format_audio_raw_ext_parse(format, &info->info.raw, + size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: @@ -110,7 +111,8 @@ spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, { switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: - return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, size); + return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, + size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: From c6d0b364ab0657a66687a761b602100f823b8957 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 26 Oct 2025 17:51:01 +0200 Subject: [PATCH 0984/1014] spa: param: add size checks for spa_audio_info* structs In the API that take struct size for spa_audio_info*, also check the struct size. --- spa/include/spa/param/audio/format-utils.h | 51 +++++++- spa/include/spa/param/audio/format.h | 2 + spa/include/spa/param/audio/raw-json.h | 7 ++ spa/include/spa/param/audio/raw-utils.h | 8 ++ spa/include/spa/param/audio/raw.h | 2 + test/meson.build | 1 + test/test-spa-format.c | 129 +++++++++++++++++++++ 7 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 test/test-spa-format.c diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 86ea84f75..24d06dd1e 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -46,18 +46,58 @@ extern "C" { #endif #endif +SPA_API_AUDIO_FORMAT_UTILS bool +spa_format_audio_ext_valid_size(uint32_t media_subtype, size_t size) +{ + switch (media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + return size >= offsetof(struct spa_audio_info, info.raw) && + SPA_AUDIO_INFO_RAW_VALID_SIZE(size - offsetof(struct spa_audio_info, info.raw)); + +#define _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(format) \ + case SPA_MEDIA_SUBTYPE_ ## format: \ + return size >= offsetof(struct spa_audio_info, info.format) + sizeof(struct spa_audio_info_ ## format); + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsp) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(iec958) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mp3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(aac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(vorbis) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(wma) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ra) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(amr) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(alac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(flac) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ape) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(eac3) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(truehd) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dts) + _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mpegh) +#undef _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE + } + return false; +} + SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; + uint32_t media_type, media_subtype; - if ((res = spa_format_parse(format, &info->media_type, &info->media_subtype)) < 0) + if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; - if (info->media_type != SPA_MEDIA_TYPE_audio) + if (media_type != SPA_MEDIA_TYPE_audio) return -EINVAL; - switch (info->media_subtype) { + if (!spa_format_audio_ext_valid_size(media_subtype, size)) + return -EINVAL; + + info->media_type = media_type; + info->media_subtype = media_subtype; + + switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_parse(format, &info->info.raw, size - offsetof(struct spa_audio_info, info.raw)); @@ -109,6 +149,11 @@ SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info, size_t size) { + if (!spa_format_audio_ext_valid_size(info->media_subtype, size)) { + errno = EINVAL; + return NULL; + } + switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, diff --git a/spa/include/spa/param/audio/format.h b/spa/include/spa/param/audio/format.h index ac9b10dda..6e7a71f6c 100644 --- a/spa/include/spa/param/audio/format.h +++ b/spa/include/spa/param/audio/format.h @@ -59,6 +59,8 @@ struct spa_audio_info { struct spa_audio_info_dts dts; struct spa_audio_info_mpegh mpegh; } info; + + /* padding follows here when info has flexible size */ }; /** diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index e83c4495b..38fcb449c 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -60,6 +60,10 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, { uint32_t v; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); @@ -100,6 +104,9 @@ spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t { int res; + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + memset(info, 0, size); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); if (dict) { diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index ce5e61e67..3bb94eaa3 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -35,6 +35,9 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in int res; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) + return -EINVAL; + info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, @@ -64,6 +67,11 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, struct spa_pod_frame f; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); + if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) { + errno = EINVAL; + return NULL; + } + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index 392b65daa..bcc0a122d 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -293,6 +293,8 @@ struct spa_audio_info_raw { #define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) +#define SPA_AUDIO_INFO_RAW_VALID_SIZE(size) ((size) >= offsetof(struct spa_audio_info_raw, position)) + #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ diff --git a/test/meson.build b/test/meson.build index ad658bcc6..5e38db383 100644 --- a/test/meson.build +++ b/test/meson.build @@ -112,6 +112,7 @@ test('test-spa', executable('test-spa', 'test-spa-buffer.c', 'test-spa-control.c', + 'test-spa-format.c', 'test-spa-json.c', 'test-spa-utils.c', 'test-spa-log.c', diff --git a/test/test-spa-format.c b/test/test-spa-format.c new file mode 100644 index 000000000..7cbec7690 --- /dev/null +++ b/test/test-spa-format.c @@ -0,0 +1,129 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include + +#include "pwtest.h" + +PWTEST(audio_format_sizes) +{ + union { + uint8_t buf[1024]; + struct spa_audio_info align; + } data; + struct spa_audio_info info; + size_t i; + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_raw; + info.info.raw.channels = 5; + info.info.raw.format = SPA_AUDIO_FORMAT_F32P; + info.info.raw.rate = 12345; + info.info.raw.flags = 0; + info.info.raw.position[0] = 1; + info.info.raw.position[1] = 2; + info.info.raw.position[2] = 3; + info.info.raw.position[3] = 4; + info.info.raw.position[4] = 5; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + offsetof(struct spa_audio_info_raw, position) + + info.info.raw.channels*sizeof(uint32_t)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + memset(&info, 0xf3, sizeof(info)); + info.media_type = SPA_MEDIA_TYPE_audio; + info.media_subtype = SPA_MEDIA_SUBTYPE_aac; + info.info.aac.rate = 12345; + info.info.aac.channels = 6; + info.info.aac.bitrate = 54321; + info.info.aac.stream_format = SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM; + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + memcpy(data.buf, &info, sizeof(info)); + + pod = spa_format_audio_ext_build(&b, 123, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) + pwtest_bool_true(!pod); + else + pwtest_bool_true(pod); + } + + for (i = 0; i < sizeof(data.buf); ++i) { + struct spa_pod *pod; + uint8_t buf[4096]; + struct spa_pod_builder b; + int ret; + + spa_pod_builder_init(&b, buf, sizeof(buf)); + pod = spa_format_audio_ext_build(&b, 123, &info, sizeof(info)); + pwtest_bool_true(pod); + + memset(data.buf, 0xf3, sizeof(data.buf)); + + ret = spa_format_audio_ext_parse(pod, (void *)data.buf, i); + if (i < offsetof(struct spa_audio_info, info.raw) + + sizeof(struct spa_audio_info_aac)) { + for (size_t j = i; j < sizeof(data.buf); ++j) + pwtest_int_eq(data.buf[j], 0xf3); + pwtest_int_lt(ret, 0); + } else { + pwtest_int_ge(ret, 0); + pwtest_bool_true(memcmp(data.buf, &info, SPA_MIN(i, sizeof(info))) == 0); + } + } + + return PWTEST_PASS; +} + +PWTEST_SUITE(spa_format) +{ + pwtest_add(audio_format_sizes, PWTEST_NOARG); + + return PWTEST_PASS; +} From 614186a59076e8c857eaf7d94702b20e5f5030e9 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Wed, 22 Oct 2025 13:50:24 +0200 Subject: [PATCH 0985/1014] module-echo-cancel: Sync capture and sink buffers Call process() when capture and sink ringbuffers contain data from the same graph cycle and only process the latest block from them to avoid adding latency that can accumulate if one of the streams gets more than one buffer before the other gets its first buffer when starting up. --- src/modules/module-echo-cancel.c | 92 +++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 997506e6f..037509c35 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -229,8 +229,10 @@ struct impl { struct spa_audio_aec *aec; uint32_t aec_blocksize; - unsigned int capture_ready:1; - unsigned int sink_ready:1; + struct spa_io_position *capture_position; + struct spa_io_position *sink_position; + uint32_t capture_cycle; + uint32_t sink_cycle; unsigned int do_disconnect:1; @@ -309,11 +311,17 @@ static void process(struct impl *impl) struct spa_data *dd; uint32_t i, size; uint32_t rindex, pindex, oindex, pdindex, avail; + int32_t pavail, pdavail; size = impl->aec_blocksize; - /* First read a block from the playback and capture ring buffers */ - spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + /* First read a block from the capture ring buffer */ + avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + while (avail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); + avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + } for (i = 0; i < impl->rec_info.channels; i++) { /* captured samples, with echo from sink */ @@ -331,19 +339,30 @@ static void process(struct impl *impl) out[i] = &out_buf[i][0]; } - spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); - spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { pw_log_debug("out of playback buffers: %m"); /* playback stream may not yet be in streaming state, drop play * data to avoid introducing additional playback latency */ - spa_ringbuffer_read_update(&impl->play_ring, pindex + size); - spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail); + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail); goto done; } + while (pavail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->play_ring, pindex + size); + pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + } + while (pdavail > size) { + /* drop samples from previous graph cycles */ + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + } + for (i = 0; i < impl->play_info.channels; i++) { /* echo from sink */ play[i] = &play_buf[i][0]; @@ -454,8 +473,8 @@ static void process(struct impl *impl) } done: - impl->sink_ready = false; - impl->capture_ready = false; + impl->capture_cycle = 0; + impl->sink_cycle = 0; } static void reset_buffers(struct impl *impl) @@ -479,8 +498,8 @@ static void reset_buffers(struct impl *impl) spa_ringbuffer_get_read_index(&impl->play_ring, &index); spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); - impl->sink_ready = false; - impl->capture_ready = false; + impl->capture_cycle = 0; + impl->sink_cycle = 0; } static void capture_destroy(void *d) @@ -546,8 +565,11 @@ static void capture_process(void *data) spa_ringbuffer_write_update(&impl->rec_ring, index + size); if (avail + size >= impl->aec_blocksize) { - impl->capture_ready = true; - if (impl->sink_ready) + if (impl->capture_position) + impl->capture_cycle = impl->capture_position->clock.cycle; + else + pw_log_warn("no capture position"); + if (impl->capture_cycle == impl->sink_cycle) process(impl); } @@ -740,12 +762,26 @@ static void input_param_changed(void *data, uint32_t id, const struct spa_pod* p } } +static void capture_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + + switch (id) { + case SPA_IO_Position: + impl->capture_position = area; + break; + default: + break; + } +} + static const struct pw_stream_events capture_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, .state_changed = capture_state_changed, .process = capture_process, - .param_changed = input_param_changed + .param_changed = input_param_changed, + .io_changed = capture_io_changed }; static void source_destroy(void *d) @@ -930,10 +966,15 @@ static void sink_process(void *data) SPA_PTROFF(d->data, offs, void), size); } spa_ringbuffer_write_update(&impl->play_ring, index + size); + spa_ringbuffer_get_write_index(&impl->play_delayed_ring, &index); + spa_ringbuffer_write_update(&impl->play_delayed_ring, index + size); if (avail + size >= impl->aec_blocksize) { - impl->sink_ready = true; - if (impl->capture_ready) + if (impl->sink_position) + impl->sink_cycle = impl->sink_position->clock.cycle; + else + pw_log_warn("no sink position"); + if (impl->capture_cycle == impl->sink_cycle) process(impl); } @@ -955,12 +996,27 @@ static const struct pw_stream_events playback_events = { .state_changed = playback_state_changed, .param_changed = output_param_changed }; + +static void sink_io_changed(void *data, uint32_t id, void *area, uint32_t size) +{ + struct impl *impl = data; + + switch (id) { + case SPA_IO_Position: + impl->sink_position = area; + break; + default: + break; + } +} + static const struct pw_stream_events sink_events = { PW_VERSION_STREAM_EVENTS, .destroy = sink_destroy, .process = sink_process, .state_changed = sink_state_changed, - .param_changed = output_param_changed + .param_changed = output_param_changed, + .io_changed = sink_io_changed }; #define MAX_PARAMS 512u From 0276bb5b063d8b34a55b1292c4a01c1f7b5c9cc4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 11:43:04 +0100 Subject: [PATCH 0986/1014] modules: ringbuffer avail is signed --- src/modules/module-echo-cancel.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 037509c35..a4cdd2435 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -309,15 +309,15 @@ static void process(struct impl *impl) const float *play_delayed[impl->play_info.channels]; float *out[impl->out_info.channels]; struct spa_data *dd; - uint32_t i, size; - uint32_t rindex, pindex, oindex, pdindex, avail; - int32_t pavail, pdavail; + uint32_t i; + uint32_t rindex, pindex, oindex, pdindex, size; + int32_t avail, pavail, pdavail; size = impl->aec_blocksize; /* First read a block from the capture ring buffer */ avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); - while (avail > size) { + while (avail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); @@ -352,12 +352,12 @@ static void process(struct impl *impl) goto done; } - while (pavail > size) { + while (pavail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->play_ring, pindex + size); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); } - while (pdavail > size) { + while (pdavail > (int32_t)size) { /* drop samples from previous graph cycles */ spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); @@ -450,7 +450,7 @@ static void process(struct impl *impl) * available on the source */ avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex); - while (avail >= size) { + while (avail >= (int32_t)size) { if ((cout = pw_stream_dequeue_buffer(impl->source)) != NULL) { for (i = 0; i < impl->out_info.channels; i++) { dd = &cout->buffer->datas[i]; From 94d0d8bc095b001f36f4e27c23d22c7ce4aca8ca Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 13:32:03 +0100 Subject: [PATCH 0987/1014] spa: add spa_json_init_relax spa_json_init assumes that we start in an object and always requires a key/value pair. If the last part is a key, it returns and error and does not want to return the key value. This causes problems when parsing AUX0,AUX1,AUX2 or any relaxed array withand odd number of elements. Make a new spa_json_init_relax that takes the type of the container we're assuming we're in and set the state of the parser to array when we are parsing a relaxed array. Fixes #4944 --- spa/include/spa/utils/json-core.h | 9 +++++++++ spa/include/spa/utils/json.h | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index 800763571..5616bffe1 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -54,6 +54,15 @@ SPA_API_JSON void spa_json_init(struct spa_json * iter, const char *data, size_t { *iter = SPA_JSON_INIT(data, size); } + +#define SPA_JSON_INIT_RELAX(type,data,size) \ + ((struct spa_json) { (data), (data)+(size), NULL, (uint32_t)((type) == '[' ? 0x10 : 0x0), 0 }) + +SPA_API_JSON void spa_json_init_relax(struct spa_json * iter, char type, const char *data, size_t size) +{ + *iter = SPA_JSON_INIT_RELAX(type, data, size); +} + #define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 }) SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub) diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index c8030345e..212637dab 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -105,7 +105,7 @@ SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter, spa_json_init(iter, data, size); res = spa_json_enter_container(iter, iter, type); if (res == -EPROTO && relax) - spa_json_init(iter, data, size); + spa_json_init_relax(iter, type, data, size); else if (res <= 0) return res; return 1; From 23c449af5d6afc8dde0685d61e6ed11d89b09000 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Mon, 27 Oct 2025 14:20:25 +0100 Subject: [PATCH 0988/1014] test: add test for an array with odd number of items We have to use the relax version to get the expected container type correct. --- test/test-spa-json.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test-spa-json.c b/test/test-spa-json.c index 66ef2eeac..0c3c46f59 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -609,7 +609,7 @@ static void test_array(const char *str, const char * const vals[]) spa_json_init(&it[0], str, strlen(str)); if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); + spa_json_init_relax(&it[1], '[', str, strlen(str)); for (i = 0; vals[i]; i++) { pwtest_int_gt(spa_json_get_string(&it[1], val, sizeof(val)), 0); pwtest_str_eq(val, vals[i]); @@ -624,6 +624,7 @@ PWTEST(json_array) test_array("[FL FR]", (const char *[]){ "FL", "FR", NULL }); test_array("FL FR", (const char *[]){ "FL", "FR", NULL }); test_array("[ FL FR ]", (const char *[]){ "FL", "FR", NULL }); + test_array("FL FR FC", (const char *[]){ "FL", "FR", "FC", NULL }); return PWTEST_PASS; } From 76a31a47c2cf063abc70c29f42e60fb0fa03dee5 Mon Sep 17 00:00:00 2001 From: Jonas Holmberg Date: Mon, 27 Oct 2025 14:37:03 +0100 Subject: [PATCH 0989/1014] module-echo-cancel: Avoid discontinuity Keep the samples in the ringbuffer that are needed the next cycle to avoid discontinuity when the aec blocksize is not equal to or divisible by quantum. --- src/modules/module-echo-cancel.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index a4cdd2435..98efa35c5 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -317,10 +317,15 @@ static void process(struct impl *impl) /* First read a block from the capture ring buffer */ avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); - while (avail > (int32_t)size) { - /* drop samples from previous graph cycles */ + while (avail >= (int32_t)size * 2) { + /* drop samples that are not needed this or next cycle. Note + * that samples are kept in the ringbuffer until next cycle if + * size is not equal to or divisible by quantum, to avoid + * discontinuity */ + pw_log_debug("avail %d", avail); spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); + pw_log_debug("new avail %d, size %u", avail, size); } for (i = 0; i < impl->rec_info.channels; i++) { @@ -352,15 +357,19 @@ static void process(struct impl *impl) goto done; } - while (pavail > (int32_t)size) { - /* drop samples from previous graph cycles */ - spa_ringbuffer_read_update(&impl->play_ring, pindex + size); + if (pavail > avail) { + /* drop too old samples from previous graph cycles */ + pw_log_debug("pavail %d, dropping %d", pavail, pavail - avail); + spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail - avail); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); + pw_log_debug("new pavail %d, avail %d", pavail, avail); } - while (pdavail > (int32_t)size) { - /* drop samples from previous graph cycles */ - spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); + if (pdavail > avail) { + /* drop too old samples from previous graph cycles */ + pw_log_debug("pdavail %d, dropping %d", pdavail, pdavail - avail); + spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail - avail); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); + pw_log_debug("new pdavail %d, avail %d", pdavail, avail); } for (i = 0; i < impl->play_info.channels; i++) { From c1e737bbe45bab94c69dbd4fe206c2acd40e7bc0 Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Tue, 26 Aug 2025 10:40:13 +0200 Subject: [PATCH 0990/1014] module-rtp: Attempt to reconnect the ptp management socket This should gracefully recover the cases where the other end of the socket isn't ready yet when we start or terminates and gets restarted. --- src/modules/module-rtp-sap.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 22fe89b68..6ab0c8477 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -548,8 +548,13 @@ error: static bool update_ts_refclk(struct impl *impl) { - if (!impl->ptp_mgmt_socket || impl->ptp_fd < 0) + if (!impl->ptp_mgmt_socket) return false; + if (impl->ptp_fd < 0) { + impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket); + if (impl->ptp_fd < 0) + return false; + } // Read if something is left in the socket int avail; @@ -581,6 +586,12 @@ static bool update_ts_refclk(struct impl *impl) if (write(impl->ptp_fd, &req, sizeof(req)) == -1) { pw_log_warn("Failed to send PTP management request: %m"); + if (errno != ENOTCONN) + return false; + close(impl->ptp_fd); + impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket); + if (impl->ptp_fd > -1) + pw_log_info("Reopened PTP management socket"); return false; } From b57bd00be01a66dfb08673491c4412370549a169 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 23 Oct 2025 16:06:56 +0200 Subject: [PATCH 0991/1014] module-rtp-sap: Improve names for clearer code --- src/modules/module-rtp-sap.c | 50 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 6ab0c8477..1818df13e 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -265,7 +265,7 @@ struct impl { struct pw_registry *registry; struct spa_hook registry_listener; - struct pw_timer timer; + struct pw_timer sap_send_timer; char *ifname; uint32_t ttl; @@ -288,7 +288,7 @@ struct impl { char *extra_attrs_preamble; char *extra_attrs_end; - char *ptp_mgmt_socket; + char *ptp_mgmt_socket_path; int ptp_fd; uint32_t ptp_seq; uint8_t clock_id[8]; @@ -383,7 +383,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen) return false; } -static int make_unix_socket(const char *path) { +static int make_unix_ptp_mgmt_socket(const char *path) { struct sockaddr_un addr; spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); @@ -419,7 +419,7 @@ static int make_send_socket( af = src->ss_family; if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { - pw_log_error("socket failed: %m"); + pw_log_error("socket() failed: %m"); return -errno; } if (bind(fd, (struct sockaddr*)src, src_len) < 0) { @@ -451,6 +451,9 @@ static int make_send_socket( pw_log_warn("setsockopt(IPV6_MULTICAST_HOPS) failed: %m"); } } + + pw_log_info("sender socket up and running"); + return fd; error: close(fd); @@ -468,13 +471,13 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, af = sa->ss_family; if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { - pw_log_error("socket failed: %m"); + pw_log_error("socket() failed: %m"); return -errno; } val = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { res = -errno; - pw_log_error("setsockopt failed: %m"); + pw_log_error("setsockopt() failed: %m"); goto error; } spa_zero(req); @@ -540,6 +543,9 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, goto error; } } + + pw_log_info("receiver socket up and running"); + return fd; error: close(fd); @@ -548,10 +554,10 @@ error: static bool update_ts_refclk(struct impl *impl) { - if (!impl->ptp_mgmt_socket) + if (!impl->ptp_mgmt_socket_path) return false; if (impl->ptp_fd < 0) { - impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket); + impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path); if (impl->ptp_fd < 0) return false; } @@ -589,7 +595,7 @@ static bool update_ts_refclk(struct impl *impl) if (errno != ENOTCONN) return false; close(impl->ptp_fd); - impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket); + impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path); if (impl->ptp_fd > -1) pw_log_info("Reopened PTP management socket"); return false; @@ -933,7 +939,7 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) return res; } -static void on_timer_event(void *data) +static void on_sap_send_timer_event(void *data) { struct impl *impl = data; struct session *sess, *tmp; @@ -967,9 +973,9 @@ static void on_timer_event(void *data) } } - pw_timer_queue_add(impl->timer_queue, &impl->timer, - &impl->timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, - on_timer_event, impl); + pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer, + &impl->sap_send_timer.timeout, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, + on_sap_send_timer_event, impl); } static struct session *session_find(struct impl *impl, const struct sdp_info *info) @@ -1665,11 +1671,11 @@ static int start_sap(struct impl *impl) int fd = -1, res; char addr[128] = "invalid"; - pw_log_info("starting SAP timer"); - if ((res = pw_timer_queue_add(impl->timer_queue, &impl->timer, + pw_log_info("starting SAP send timer"); + if ((res = pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer, NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, - on_timer_event, impl)) < 0) { - pw_log_error("can't add timer: %s", spa_strerror(res)); + on_sap_send_timer_event, impl)) < 0) { + pw_log_error("can't add SAP send timer: %s", spa_strerror(res)); goto error; } if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) @@ -1818,7 +1824,7 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); - pw_timer_queue_cancel(&impl->timer); + pw_timer_queue_cancel(&impl->sap_send_timer); if (impl->sap_source) pw_loop_destroy_source(impl->loop, impl->sap_source); @@ -1832,7 +1838,7 @@ static void impl_destroy(struct impl *impl) free(impl->extra_attrs_preamble); free(impl->extra_attrs_end); - free(impl->ptp_mgmt_socket); + free(impl->ptp_mgmt_socket_path); free(impl->ifname); free(impl); } @@ -1904,11 +1910,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->ifname = str ? strdup(str) : NULL; str = pw_properties_get(props, "ptp.management-socket"); - impl->ptp_mgmt_socket = str ? strdup(str) : NULL; + impl->ptp_mgmt_socket_path = str ? strdup(str) : NULL; // TODO: support UDP management access as well - if (impl->ptp_mgmt_socket) - impl->ptp_fd = make_unix_socket(impl->ptp_mgmt_socket); + if (impl->ptp_mgmt_socket_path) + impl->ptp_fd = make_unix_ptp_mgmt_socket(impl->ptp_mgmt_socket_path); if ((str = pw_properties_get(props, "sap.ip")) == NULL) str = DEFAULT_SAP_IP; From 80e7302a05cc7d4da3c9a431d64c5eba538483ae Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 23 Oct 2025 16:39:51 +0200 Subject: [PATCH 0992/1014] module-rtp-sap: Add retry code for when start_sap() fails due to ENODEV --- src/modules/module-rtp-sap.c | 69 +++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 1818df13e..b9404b667 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -267,6 +267,10 @@ struct impl { struct pw_timer sap_send_timer; + /* This timer is used when the first start_sap() call fails because + * of an ENODEV error (see the start_sap() code for details) */ + struct pw_timer start_sap_retry_timer; + char *ifname; uint32_t ttl; bool mcast_loop; @@ -322,6 +326,7 @@ static const struct format_info *find_audio_format_info(const char *mime) return NULL; } +static int start_sap(struct impl *impl); static int send_sap(struct impl *impl, struct session *sess, bool bye); @@ -978,6 +983,13 @@ static void on_sap_send_timer_event(void *data) on_sap_send_timer_event, impl); } +static void on_start_sap_retry_timer_event(void *data) +{ + struct impl *impl = data; + pw_log_debug("trying again to start SAP send after previous attempt failed with ENODEV"); + start_sap(impl); +} + static struct session *session_find(struct impl *impl, const struct sdp_info *info) { struct session *sess; @@ -1668,18 +1680,62 @@ on_sap_io(void *data, int fd, uint32_t mask) static int start_sap(struct impl *impl) { - int fd = -1, res; + int fd = -1, res = 0; char addr[128] = "invalid"; pw_log_info("starting SAP send timer"); + /* start_sap() might be called more than once. See the make_recv_socket() + * call below for why that can happen. In such a case, the timer was + * started already. The easiest way of handling it is to just cancel it. + * Such cases are not expected to occur often, so canceling and then + * adding the timer again is acceptable. */ + pw_timer_queue_cancel(&impl->sap_send_timer); if ((res = pw_timer_queue_add(impl->timer_queue, &impl->sap_send_timer, NULL, SAP_INTERVAL_SEC * SPA_NSEC_PER_SEC, on_sap_send_timer_event, impl)) < 0) { pw_log_error("can't add SAP send timer: %s", spa_strerror(res)); goto error; } - if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) - return fd; + if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) { + /* If make_recv_socket() tries to create a socket and join to a multicast + * group while the network interfaces are not ready yet to do so + * (usually because a network manager component is still setting up + * those network interfaces), ENODEV will be returned. This is essentially + * a race condition. There is no discernible way to be notified when the + * network interfaces are ready for that operation, so the next best + * approach is to essentially do a form of polling by retrying the + * start_sap() call after some time. The start_sap_retry_timer exists + * precisely for that purpose. This means that ENODEV is not treated as + * an error, but instead, it triggers the creation of that timer. */ + if (fd == -ENODEV) { + pw_log_warn("failed to create receiver socket because network device " + "is not ready and present yet; will try again"); + + pw_timer_queue_cancel(&impl->start_sap_retry_timer); + /* Use a 1-second retry interval. The network interfaces + * are likely to be up and running then. */ + pw_timer_queue_add(impl->timer_queue, &impl->start_sap_retry_timer, + NULL, 1 * SPA_NSEC_PER_SEC, + on_start_sap_retry_timer_event, impl); + + /* It is important to return 0 in this case. Otherwise, the nonzero return + * value will later be propagated through the core as an error. */ + res = 0; + goto finish; + } else { + pw_log_error("failed to create socket: %s", spa_strerror(-fd)); + /* If ENODEV was returned earlier, and the start_sap_retry_timer + * was consequently created, but then a non-ENODEV error occurred, + * the timer must be stopped and removed. */ + pw_timer_queue_cancel(&impl->start_sap_retry_timer); + res = fd; + goto error; + } + } + + /* Cleanup the timer in case ENODEV occurred earlier, and this time, + * the socket creation succeeded. */ + pw_timer_queue_cancel(&impl->start_sap_retry_timer); pw_net_get_ip(&impl->sap_addr, addr, sizeof(addr), NULL, NULL); pw_log_info("starting SAP listener on %s", addr); @@ -1690,11 +1746,13 @@ static int start_sap(struct impl *impl) goto error; } - return 0; +finish: + return res; + error: if (fd > 0) close(fd); - return res; + goto finish; } static void node_event_info(void *data, const struct pw_node_info *info) @@ -1825,6 +1883,7 @@ static void impl_destroy(struct impl *impl) pw_core_disconnect(impl->core); pw_timer_queue_cancel(&impl->sap_send_timer); + pw_timer_queue_cancel(&impl->start_sap_retry_timer); if (impl->sap_source) pw_loop_destroy_source(impl->loop, impl->sap_source); From f1ffd5e5e83c91cf3176c9940bef6642b4245407 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 19 Oct 2025 16:19:16 +0200 Subject: [PATCH 0993/1014] module-rtp-source: Read cleanup.sec property from stream properties This allows for setting the cleanup.sec value in the create-stream block in the module-rtp-sap configuration. --- src/modules/module-rtp-source.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 63f1f399d..26da7c45e 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -797,7 +797,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) * till we make it (or get timed out) */ pw_properties_set(stream_props, "rtp.receiving", "true"); - impl->cleanup_interval = pw_properties_get_uint32(props, + impl->cleanup_interval = pw_properties_get_uint32(stream_props, "cleanup.sec", DEFAULT_CLEANUP_SEC); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); From 5d21e12658f0a67a87f3d9037264cf80919f89f2 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Sun, 19 Oct 2025 19:31:44 +0200 Subject: [PATCH 0994/1014] module-rtp-source: Use make_socket() error value instead of errno make_socket() already returns the negative errno. --- src/modules/module-rtp-source.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 26da7c45e..3954d3137 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -433,7 +433,7 @@ static void stream_open_connection(void *data, int *result) * stream_start() call after some time. The stream_start_retry_timer exists * precisely for that purpose. This means that ENODEV is not treated as * an error, but instead, it triggers the creation of that timer. */ - if (errno == ENODEV) { + if (fd == -ENODEV) { pw_log_warn("failed to create socket because network device is not ready " "and present yet; will try again"); @@ -449,12 +449,12 @@ static void stream_open_connection(void *data, int *result) res = 0; goto finish; } else { - pw_log_error("failed to create socket: %m"); + pw_log_error("failed to create socket: %s", spa_strerror(fd)); /* If ENODEV was returned earlier, and the stream_start_retry_timer * was consequently created, but then a non-ENODEV error occurred, * the timer must be stopped and removed. */ pw_timer_queue_cancel(&impl->stream_start_retry_timer); - res = -errno; + res = fd; goto finish; } } From 3e0f4daf600de2a04b69c0ba5ca0c26e9e5a04f6 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 23 Oct 2025 17:53:54 +0200 Subject: [PATCH 0995/1014] module-rtp-sap: implement IGMP recovery for multicast subscription loss Add IGMP recovery mechanism that monitors SAP packet reception and triggers multicast group refresh when no packets are received if a deadline is reached. The deadline is set to half of the cleanup interval, with a minimum of 1 second. When the deadline is reached, the mechanism performs IGMP leave/rejoin operations to refresh multicast group membership. This ensures SAP announcements continue to be received when network conditions cause IGMP membership to expire or become stale due to router timeouts or network issues. --- src/modules/module-rtp-sap.c | 133 ++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index b9404b667..27df24816 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -248,6 +248,16 @@ struct node { struct session *session; }; +struct igmp_recovery { + struct pw_timer timer; + int socket_fd; + struct sockaddr_storage mcast_addr; + socklen_t mcast_len; + uint32_t if_index; + bool is_ipv6; + uint32_t deadline; +}; + struct impl { struct pw_properties *props; @@ -285,6 +295,10 @@ struct impl { struct spa_source *sap_source; uint32_t cleanup_interval; + /* IGMP recovery (triggers when no SAP packets are + * received after the recovery deadline is reached) */ + struct igmp_recovery igmp_recovery; + uint32_t max_sessions; uint32_t n_sessions; struct spa_list sessions; @@ -466,7 +480,7 @@ error: } static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, - char *ifname) + char *ifname, struct igmp_recovery *igmp_recovery) { int af, fd, val, res; struct ifreq req; @@ -536,6 +550,16 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, goto error; } + /* Store multicast info for recovery */ + igmp_recovery->socket_fd = fd; + igmp_recovery->mcast_addr = ba; + igmp_recovery->mcast_len = salen; + igmp_recovery->if_index = req.ifr_ifindex; + igmp_recovery->is_ipv6 = (af == AF_INET6); + pw_log_debug("stored %s multicast info: socket_fd=%d, " + "if_index=%d", igmp_recovery->is_ipv6 ? + "IPv6" : "IPv4", fd, req.ifr_ifindex); + if (bind(fd, (struct sockaddr*)&ba, salen) < 0) { res = -errno; pw_log_error("bind() failed: %m"); @@ -944,6 +968,97 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) return res; } +static void on_igmp_recovery_timer_event(void *data) +{ + struct impl *impl = data; + char addr[128]; + int res = 0; + + /* Only attempt recovery if we have a valid socket and multicast address */ + if (impl->igmp_recovery.socket_fd < 0) { + pw_log_debug("no socket, skipping IGMP recovery"); + goto finish; + } + + pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL); + pw_log_info("IGMP recovery triggered for %s", addr); + + /* Force IGMP membership refresh by leaving the group first, then rejoin */ + if (impl->igmp_recovery.is_ipv6) { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr; + mr6.ipv6mr_interface = impl->igmp_recovery.if_index; + + /* Leave the group first */ + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + &mr6, sizeof(mr6)); + if (SPA_LIKELY(res == 0)) { + pw_log_info("left IPv6 multicast group"); + } else { + if (errno == EADDRNOTAVAIL) { + pw_log_info("attempted to leave IPv6 multicast group, but " + "membership was already silently dropped"); + } else { + pw_log_warn("failed to leave IPv6 multicast group: %m"); + } + } + + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mr6, sizeof(mr6)); + if (res < 0) { + pw_log_warn("failed to re-join IPv6 multicast group: %m"); + } else { + pw_log_info("re-joined IPv6 multicast group successfully"); + } + } else { + struct ip_mreqn mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr; + mr4.imr_ifindex = impl->igmp_recovery.if_index; + + /* Leave the group first */ + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mr4, sizeof(mr4)); + if (SPA_LIKELY(res == 0)) { + pw_log_info("left IPv4 multicast group"); + } else { + if (errno == EADDRNOTAVAIL) { + pw_log_info("attempted to leave IPv4 multicast group, but " + "membership was already silently dropped"); + } else { + pw_log_warn("failed to leave IPv4 multicast group: %m"); + } + } + + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mr4, sizeof(mr4)); + if (res < 0) { + pw_log_warn("failed to re-join IPv4 multicast group: %m"); + } else { + pw_log_info("re-joined IPv4 multicast group successfully"); + } + } + +finish: + /* If rejoining failed, try again in 1 second. This can happen + * for example when the network interface went down, and is not + * yet up and running again, and ENODEV is returned as a result. + * Otherwise, continue with the usual deadline. */ + pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer, + &impl->igmp_recovery.timer.timeout, + ((res == 0) ? impl->igmp_recovery.deadline : 1) * SPA_NSEC_PER_SEC, + on_igmp_recovery_timer_event, impl); +} + +static void rearm_igmp_recovery_timer(struct impl *impl) +{ + pw_timer_queue_cancel(&impl->igmp_recovery.timer); + pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer, + NULL, impl->igmp_recovery.deadline * SPA_NSEC_PER_SEC, + on_igmp_recovery_timer_event, impl); +} + static void on_sap_send_timer_event(void *data) { struct impl *impl = data; @@ -1675,6 +1790,8 @@ on_sap_io(void *data, int fd, uint32_t mask) buffer[len] = 0; if ((res = parse_sap(impl, buffer, len)) < 0) pw_log_warn("error parsing SAP: %s", spa_strerror(res)); + + rearm_igmp_recovery_timer(impl); } } @@ -1696,7 +1813,8 @@ static int start_sap(struct impl *impl) pw_log_error("can't add SAP send timer: %s", spa_strerror(res)); goto error; } - if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname)) < 0) { + if ((fd = make_recv_socket(&impl->sap_addr, impl->sap_len, impl->ifname, + &(impl->igmp_recovery))) < 0) { /* If make_recv_socket() tries to create a socket and join to a multicast * group while the network interfaces are not ready yet to do so * (usually because a network manager component is still setting up @@ -1746,6 +1864,8 @@ static int start_sap(struct impl *impl) goto error; } + rearm_igmp_recovery_timer(impl); + finish: return res; @@ -1884,6 +2004,7 @@ static void impl_destroy(struct impl *impl) pw_timer_queue_cancel(&impl->sap_send_timer); pw_timer_queue_cancel(&impl->start_sap_retry_timer); + pw_timer_queue_cancel(&impl->igmp_recovery.timer); if (impl->sap_source) pw_loop_destroy_source(impl->loop, impl->sap_source); @@ -1950,6 +2071,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->ptp_fd = -1; spa_list_init(&impl->sessions); + impl->igmp_recovery.socket_fd = -1; + impl->igmp_recovery.if_index = -1; + if (args == NULL) args = ""; @@ -1985,6 +2109,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->cleanup_interval = pw_properties_get_uint32(impl->props, "sap.cleanup.sec", DEFAULT_CLEANUP_SEC); + /* We will use half of the cleanup interval for IGMP deadline, minimum 1 second */ + impl->igmp_recovery.deadline = SPA_MAX(impl->cleanup_interval / 2, 1u); + pw_log_info("using IGMP deadline of %" PRIu32 " second(s)", + impl->igmp_recovery.deadline); + impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL); impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP); impl->max_sessions = pw_properties_get_uint32(props, "sap.max-sessions", DEFAULT_MAX_SESSIONS); From 955c9ae837dfdeceb4dd3b2261a32622e12807b4 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 23 Oct 2025 20:08:22 +0200 Subject: [PATCH 0996/1014] module-rtp: Get the current stream time in a reusable manner That way, redundant pw_stream_get_nsec() and clock_gettime() calls can be avoided. --- src/modules/module-rtp-session.c | 7 +++++-- src/modules/module-rtp-source.c | 23 +++++++++++++---------- src/modules/module-rtp/audio.c | 5 +++-- src/modules/module-rtp/midi.c | 3 ++- src/modules/module-rtp/opus.c | 3 ++- src/modules/module-rtp/stream.c | 14 +++++++++++--- src/modules/module-rtp/stream.h | 5 ++++- 7 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index 2b69a0be7..cd7ca7f4e 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -1043,8 +1043,11 @@ on_data_io(void *data, int fd, uint32_t mask) if (sess == NULL) goto unknown_ssrc; - if (sess->data_ready && sess->receiving) - rtp_stream_receive_packet(sess->recv, buffer, len); + if (sess->data_ready && sess->receiving) { + uint64_t current_time = rtp_stream_get_nsec(sess->recv); + rtp_stream_receive_packet(sess->recv, buffer, len, + current_time); + } } } return; diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 3954d3137..377cc4109 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -227,13 +227,6 @@ struct impl { bool waiting; }; -static inline uint64_t get_time_ns(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return SPA_TIMESPEC_TO_NSEC(&ts); -} - static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { @@ -261,6 +254,9 @@ on_rtp_io(void *data, int fd, uint32_t mask) struct impl *impl = data; ssize_t len; int suppressed; + uint64_t current_time; + + current_time = rtp_stream_get_nsec(impl->stream); if (mask & SPA_IO_IN) { if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0) @@ -270,10 +266,17 @@ on_rtp_io(void *data, int fd, uint32_t mask) goto short_packet; if (SPA_LIKELY(impl->stream)) { - if (rtp_stream_receive_packet(impl->stream, impl->buffer, len) < 0) + if (rtp_stream_receive_packet(impl->stream, impl->buffer, len, + current_time) < 0) goto receive_error; } + /* Update last packet timestamp for IGMP recovery. + * The recovery timer will check this to see if recovery + * is necessary. Do this _before_ invoking do_start() + * in case the stream is waking up from standby. */ + SPA_ATOMIC_STORE(impl->last_packet_time, current_time); + if (SPA_ATOMIC_LOAD(impl->state) != STATE_RECEIVING) { if (!SPA_ATOMIC_CAS(impl->state, STATE_PROBE, STATE_RECEIVING)) { if (SPA_ATOMIC_CAS(impl->state, STATE_IDLE, STATE_RECEIVING)) @@ -284,11 +287,11 @@ on_rtp_io(void *data, int fd, uint32_t mask) return; receive_error: - if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0) + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0) pw_log_warn("(%d suppressed) recv() error: %m", suppressed); return; short_packet: - if ((suppressed = spa_ratelimit_test(&impl->rate_limit, get_time_ns())) >= 0) + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, current_time)) >= 0) pw_log_warn("(%d suppressed) short packet of len %zd received", suppressed, len); return; diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 1563e1917..0af38d649 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -233,7 +233,8 @@ static void rtp_audio_process_playback(void *data) pw_stream_queue_buffer(impl->stream, buf); } -static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len, + uint64_t current_time) { struct rtp_header *hdr; ssize_t hlen, plen; @@ -273,7 +274,7 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) timestamp = ntohl(hdr->timestamp) - impl->ts_offset; impl->receiving = true; - impl->last_recv_timestamp = pw_stream_get_nsec(impl->stream); + impl->last_recv_timestamp = current_time; plen = len - hlen; samples = plen / stride; diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index 498c6e6a9..5fbdf3b63 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -318,7 +318,8 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti return 0; } -static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +static int rtp_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len, + uint64_t current_time) { struct rtp_header *hdr; ssize_t hlen; diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 7eeda7f43..d13a4efaf 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -99,7 +99,8 @@ static void rtp_opus_process_playback(void *data) pw_stream_queue_buffer(impl->stream, buf); } -static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len) +static int rtp_opus_receive(struct impl *impl, uint8_t *buffer, ssize_t len, + uint64_t current_time) { struct rtp_header *hdr; ssize_t hlen, plen; diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 3834206ec..fa055ce0c 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -151,7 +151,8 @@ struct impl { * access below for the reason why. */ uint8_t timer_running; - int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); + int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len, + uint64_t current_time); /* Used for resetting the ring buffer before the stream starts, to prevent * reading from uninitialized memory. This can otherwise happen in direct * timestamp mode when the read index is set to an uninitialized location. @@ -1036,10 +1037,17 @@ int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *di return pw_stream_update_properties(impl->stream, dict); } -int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len) +int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len, + uint64_t current_time) { struct impl *impl = (struct impl*)s; - return impl->receive_rtp(impl, buffer, len); + return impl->receive_rtp(impl, buffer, len, current_time); +} + +uint64_t rtp_stream_get_nsec(struct rtp_stream *s) +{ + struct impl *impl = (struct impl*)s; + return pw_stream_get_nsec(impl->stream); } uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate) diff --git a/src/modules/module-rtp/stream.h b/src/modules/module-rtp/stream.h index ea358f350..095b8395c 100644 --- a/src/modules/module-rtp/stream.h +++ b/src/modules/module-rtp/stream.h @@ -62,7 +62,10 @@ void rtp_stream_destroy(struct rtp_stream *s); int rtp_stream_update_properties(struct rtp_stream *s, const struct spa_dict *dict); -int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len); +int rtp_stream_receive_packet(struct rtp_stream *s, uint8_t *buffer, size_t len, + uint64_t current_time); + +uint64_t rtp_stream_get_nsec(struct rtp_stream *s); uint64_t rtp_stream_get_time(struct rtp_stream *s, uint32_t *rate); From 1096d634682bb2c9988c394bfc3e9d8cb5a13160 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Thu, 23 Oct 2025 20:16:14 +0200 Subject: [PATCH 0997/1014] module-rtp-source: implement IGMP recovery for multicast subscription loss Add IGMP recovery mechanism that monitors RTP packet reception and triggers multicast group refresh when no packets are received if a deadline is reached. The deadline is configurable via a new stream property "igmp.deadline.sec" (in seconds), with the default value being 30 seconds (and a minimum of 5 seconds). A timer checks regularly if the deadline was reached. That timer's interval is set by the igmp.check.interval.sec property (in seconds), with the default value being 5 seconds (and a minimum of 1 second). When the deadline is reached, the mechanism performs IGMP leave/rejoin operations to refresh multicast group membership. This ensures RTP data continues to be received when network conditions cause IGMP membership to expire or become stale due to router timeouts or network issues. --- src/modules/module-rtp-source.c | 196 +++++++++++++++++++++++++++++++- 1 file changed, 194 insertions(+), 2 deletions(-) diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 377cc4109..5c3d1f05f 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -156,6 +157,9 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define DEFAULT_IGMP_CHECK_INTERVAL_SEC 5 +#define DEFAULT_IGMP_DEADLINE_SEC 30 + #define DEFAULT_CLEANUP_SEC 60 #define DEFAULT_SOURCE_IP "224.0.0.56" @@ -180,6 +184,23 @@ static const struct spa_dict_item module_info[] = { { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; +struct igmp_recovery { + struct pw_timer timer; + int socket_fd; + struct sockaddr_storage mcast_addr; + socklen_t mcast_len; + uint32_t if_index; + bool is_ipv6; + /* This is the interval the recovery timer runs at. The timer + * checks at each interval if recovery is required. This value + * is defined by the igmp.check.interval.sec property. */ + uint32_t check_interval; + /* This is the deadline for packets to arrive. If the deadline + * is exceeded, an IGMP recovery is attempted. This value is + * defined by the igmp.deadline.sec property. */ + uint32_t deadline; +}; + struct impl { struct pw_impl_module *module; struct spa_hook module_listener; @@ -201,6 +222,15 @@ struct impl { bool always_process; uint32_t cleanup_interval; + /* IGMP recovery (triggers when no RTP packets are + * received after the recovery deadline is reached) */ + struct igmp_recovery igmp_recovery; + + /* Monotonic timestamp of the last time a packet was + * received. This is accessed with atomic accessors + * to avoid race conditions. */ + uint64_t last_packet_time; + struct pw_timer standby_timer; /* This timer is used when the first stream_start() call fails because * of an ENODEV error (see the stream_start() code for details) */ @@ -297,7 +327,138 @@ short_packet: return; } -static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname) +static int rejoin_igmp_group(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + /* IMPORTANT: This must be run from within the data loop. */ + + int res; + struct impl *impl = user_data; + uint64_t current_time; + + /* Force IGMP membership refresh by leaving the group first, then rejoin */ + if (impl->igmp_recovery.is_ipv6) { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = ((struct sockaddr_in6*)&impl->igmp_recovery.mcast_addr)->sin6_addr; + mr6.ipv6mr_interface = impl->igmp_recovery.if_index; + + /* Leave the group first */ + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, + &mr6, sizeof(mr6)); + if (SPA_LIKELY(res == 0)) { + pw_log_info("left IPv6 multicast group"); + } else { + if (errno == EADDRNOTAVAIL) { + pw_log_info("attempted to leave IPv6 multicast group, but " + "membership was already silently dropped"); + } else { + pw_log_warn("failed to leave IPv6 multicast group: %m"); + } + } + + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mr6, sizeof(mr6)); + if (res < 0) { + pw_log_warn("failed to re-join IPv6 multicast group: %m"); + } else { + pw_log_info("re-joined IPv6 multicast group successfully"); + } + } else { + struct ip_mreqn mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = ((struct sockaddr_in*)&impl->igmp_recovery.mcast_addr)->sin_addr; + mr4.imr_ifindex = impl->igmp_recovery.if_index; + + /* Leave the group first */ + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, + &mr4, sizeof(mr4)); + if (SPA_LIKELY(res == 0)) { + pw_log_info("left IPv4 multicast group"); + } else { + if (errno == EADDRNOTAVAIL) { + pw_log_info("attempted to leave IPv4 multicast group, but " + "membership was already silently dropped"); + } else { + pw_log_warn("failed to leave IPv4 multicast group: %m"); + } + } + + res = setsockopt(impl->igmp_recovery.socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mr4, sizeof(mr4)); + if (res < 0) { + pw_log_warn("failed to re-join IPv4 multicast group: %m"); + } else { + pw_log_info("re-joined IPv4 multicast group successfully"); + } + } + + current_time = rtp_stream_get_nsec(impl->stream); + SPA_ATOMIC_STORE(impl->last_packet_time, current_time); + + return res; +} + +static void on_igmp_recovery_timer_event(void *data) +{ + int res; + struct impl *impl = data; + char addr[128]; + uint64_t current_time, elapsed_seconds, last_packet_time; + + /* Only attempt recovery if we have a valid socket and multicast address */ + if (SPA_UNLIKELY(impl->igmp_recovery.socket_fd < 0)) { + pw_log_trace("no socket, skipping IGMP recovery"); + goto finish; + } + + /* This check if performed even if standby = false or + * receiving != STATE_RECEIVING , because the very reason + * for these states may be that the receiver socket was + * silently kicked out of the IGMP group (which causes data + * to no longer arrive, thus leading to these states). */ + + current_time = rtp_stream_get_nsec(impl->stream); + last_packet_time = SPA_ATOMIC_LOAD(impl->last_packet_time); + elapsed_seconds = (current_time - last_packet_time) / SPA_NSEC_PER_SEC; + + /* Only trigger recovery if enough time has elapsed since last packet */ + if (elapsed_seconds < impl->igmp_recovery.deadline) { + pw_log_trace("IGMP recovery check: %" PRIu64 " seconds elapsed, " + "need %" PRIu32 " seconds", elapsed_seconds, + impl->igmp_recovery.deadline); + goto finish; + } + + pw_net_get_ip(&impl->igmp_recovery.mcast_addr, addr, sizeof(addr), NULL, NULL); + pw_log_info("starting IGMP recovery for %s", addr); + + /* Run the actual recovery in the data loop, since recovery involves + * rejoining the socket to the IGMP group. By running this in the + * data loop, race conditions due to stray packets causing an on_rtp_io() + * invocation at the same time when the IGMP group rejoining takes place + * is avoided, since on_rtp_io() too runs in the data loop. + * This is a blocking call to make sure the rejoin attempt was fully + * done by the time this callback ends. (rejoin_igmp_group() does not + * do work that takes a long time to finish. )*/ + res = pw_loop_locked(impl->data_loop, rejoin_igmp_group, 1, NULL, 0, impl); + + if (SPA_LIKELY(res == 0)) { + pw_log_info("IGMP recovery for %s finished", addr); + } else { + pw_log_error("error while finishing IGMP recovery for %s: %s", + addr, spa_strerror(res)); + } + +finish: + pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer, + &impl->igmp_recovery.timer.timeout, + impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC, + on_igmp_recovery_timer_event, impl); +} + +static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname, + struct igmp_recovery *igmp_recovery) { int af, fd, val, res; struct ifreq req; @@ -377,6 +538,16 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname) goto error; } + /* Store multicast info for recovery */ + igmp_recovery->socket_fd = fd; + igmp_recovery->mcast_addr = ba; + igmp_recovery->mcast_len = salen; + igmp_recovery->if_index = req.ifr_ifindex; + igmp_recovery->is_ipv6 = (af == AF_INET6); + pw_log_debug("stored %s multicast info: socket_fd=%d, " + "if_index=%d", igmp_recovery->is_ipv6 ? + "IPv6" : "IPv4", fd, req.ifr_ifindex); + if (bind(fd, (struct sockaddr*)&ba, salen) < 0) { res = -errno; pw_log_error("bind() failed: %m"); @@ -425,7 +596,8 @@ static void stream_open_connection(void *data, int *result) pw_log_info("starting RTP listener"); if ((fd = make_socket((const struct sockaddr *)&impl->src_addr, - impl->src_len, impl->ifname)) < 0) { + impl->src_len, impl->ifname, + &(impl->igmp_recovery))) < 0) { /* If make_socket() tries to create a socket and join to a multicast * group while the network interfaces are not ready yet to do so * (usually because a network manager component is still setting up @@ -475,6 +647,13 @@ static void stream_open_connection(void *data, int *result) goto finish; } + if ((res = pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer, + NULL, impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC, + on_igmp_recovery_timer_event, impl)) < 0) { + pw_log_error("can't add timer: %s", spa_strerror(res)); + goto finish; + } + finish: if (res != 0) { pw_log_error("failed to start RTP stream: %s", spa_strerror(res)); @@ -498,6 +677,7 @@ static void stream_close_connection(void *data, int *result) pw_log_info("stopping RTP listener"); pw_timer_queue_cancel(&impl->stream_start_retry_timer); + pw_timer_queue_cancel(&impl->igmp_recovery.timer); pw_loop_destroy_source(impl->data_loop, impl->source); impl->source = NULL; @@ -636,6 +816,7 @@ static void impl_destroy(struct impl *impl) pw_timer_queue_cancel(&impl->standby_timer); pw_timer_queue_cancel(&impl->stream_start_retry_timer); + pw_timer_queue_cancel(&impl->igmp_recovery.timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -803,6 +984,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->cleanup_interval = pw_properties_get_uint32(stream_props, "cleanup.sec", DEFAULT_CLEANUP_SEC); + impl->igmp_recovery.check_interval = SPA_MAX(pw_properties_get_uint32(stream_props, + "igmp.check.interval.sec", + DEFAULT_IGMP_CHECK_INTERVAL_SEC), 1u); + pw_log_info("using IGMP check interval of %" PRIu32 " second(s)", + impl->igmp_recovery.check_interval); + + impl->igmp_recovery.deadline = SPA_MAX(pw_properties_get_uint32(stream_props, + "igmp.deadline.sec", DEFAULT_IGMP_DEADLINE_SEC), 5u); + pw_log_info("using IGMP deadline of %" PRIu32 " second(s)", + impl->igmp_recovery.deadline); + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); From ec11859a48571a08c8803cdf6651d829b3468f81 Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Thu, 18 Sep 2025 13:19:49 +0200 Subject: [PATCH 0998/1014] spa: Add predefined properties for clock identifiers --- spa/include/spa/param/props-types.h | 3 +++ spa/include/spa/param/props.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/spa/include/spa/param/props-types.h b/spa/include/spa/param/props-types.h index 54d17339f..0a7c916b8 100644 --- a/spa/include/spa/param/props-types.h +++ b/spa/include/spa/param/props-types.h @@ -40,6 +40,9 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL }, { SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec }, { SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL }, + { SPA_PROP_clockId, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockId", NULL }, + { SPA_PROP_clockDevice, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockDevice", NULL }, + { SPA_PROP_clockInterface, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockInterface", NULL }, { SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL }, { SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL }, diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index acc314ee1..48338b870 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -55,6 +55,9 @@ enum spa_prop { SPA_PROP_quality, SPA_PROP_bluetoothAudioCodec, SPA_PROP_bluetoothOffloadActive, + SPA_PROP_clockId, + SPA_PROP_clockDevice, + SPA_PROP_clockInterface, SPA_PROP_START_Audio = 0x10000, /**< audio related properties */ SPA_PROP_waveType, From 752de866aedfb955e61ded767f1263e2d4e6181a Mon Sep 17 00:00:00 2001 From: Rui Matos Date: Wed, 27 Aug 2025 15:50:07 +0200 Subject: [PATCH 0999/1014] spa: node-driver: Expose the clock id as param properties --- spa/plugins/support/node-driver.c | 359 +++++++++++++++++++++++++----- 1 file changed, 301 insertions(+), 58 deletions(-) diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index e7b2c702b..fa9cf3426 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver"); @@ -48,12 +50,16 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver"); #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) #define MAX_ERROR_MS 1 +#define CLOCK_NAME_MAX 64 + struct props { bool freewheel; - char clock_name[64]; + char clock_name[CLOCK_NAME_MAX]; clockid_t clock_id; uint32_t freewheel_wait; float resync_ms; + char clock_device[CLOCK_NAME_MAX]; + char clock_interface[CLOCK_NAME_MAX]; }; struct clock_offset { @@ -73,7 +79,10 @@ struct impl { uint64_t info_all; struct spa_node_info info; - struct spa_param_info params[1]; +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define N_NODE_PARAMS 2 + struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; @@ -99,13 +108,20 @@ struct impl { struct clock_offset nsec_offset; }; +static void reset_props_strings(struct props *props) +{ + spa_zero(props->clock_name); + spa_zero(props->clock_device); + spa_zero(props->clock_interface); +} + static void reset_props(struct props *props) { props->freewheel = DEFAULT_FREEWHEEL; - spa_zero(props->clock_name); props->clock_id = CLOCK_MONOTONIC; props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT; props->resync_ms = DEFAULT_RESYNC_MS; + reset_props_strings(props); } static const struct clock_info { @@ -598,10 +614,280 @@ static int impl_node_process(void *object) return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[4096]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; +next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockId), + SPA_PROP_INFO_description, SPA_POD_String("The clock id (monotonic, realtime, etc.)"), + SPA_PROP_INFO_type, SPA_POD_String(clock_id_to_name(p->clock_id))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockDevice), + SPA_PROP_INFO_description, SPA_POD_String("The clock device (eg. /dev/ptp0)"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device))); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockInterface), + SPA_PROP_INFO_description, SPA_POD_String("The clock network interface (eg. eth0)"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface))); + break; + default: + return 0; + } + + break; + } + + case SPA_PARAM_Props: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockId, SPA_POD_String(clock_id_to_name(p->clock_id)) + ); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockDevice, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device)) + ); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockInterface, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface)) + ); + break; + default: + return 0; + } + + break; + } + + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int get_phc_index(struct spa_system *s, const char *name) { +#ifdef ETHTOOL_GET_TS_INFO + struct ethtool_ts_info info = {0}; + struct ifreq ifr = {0}; + int fd, err; + + info.cmd = ETHTOOL_GET_TS_INFO; + strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); + ifr.ifr_data = (char *) &info; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -errno; + + err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr); + close(fd); + if (err < 0) + return -errno; + + return info.phc_index; +#else + return -ENOTSUP; +#endif +} + +static bool parse_clock_id(struct impl *this, const char *s) +{ + int id = clock_name_to_id(s); + if (id == -1) { + spa_log_info(this->log, "unknown clock id '%s'", s); + return false; + } + this->props.clock_id = id; + if (this->clock_fd >= 0) { + close(this->clock_fd); + this->clock_fd = -1; + } + return true; +} + +static bool parse_clock_device(struct impl *this, const char *s) +{ + int fd = open(s, O_RDONLY); + if (fd == -1) { + spa_log_info(this->log, "failed to open clock device '%s': %m", s); + return false; + } + if (this->clock_fd >= 0) { + close(this->clock_fd); + } + this->clock_fd = fd; + this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); + return true; +} + +static bool parse_clock_interface(struct impl *this, const char *s) +{ + int phc_index = get_phc_index(this->data_system, s); + if (phc_index < 0) { + spa_log_info(this->log, "failed to get phc device index for interface '%s': %s", + s, spa_strerror(phc_index)); + return false; + } else { + char dev[19]; + spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index); + if (!parse_clock_device(this, dev)) { + spa_log_info(this->log, "failed to open clock device '%s' " + "for interface '%s': %m", dev, s); + return false; + } + } + return true; +} + +static void ensure_clock_name(struct impl *this) +{ + struct props *p = &this->props; + if (p->clock_name[0] == '\0') { + const char *name = clock_id_to_name(p->clock_id); + if (p->clock_device[0]) + name = p->clock_device; + if (p->clock_interface[0]) + name = p->clock_interface; + spa_scnprintf(p->clock_name, sizeof(p->clock_name), + "%s.%s", DEFAULT_CLOCK_PREFIX, name); + } +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + bool notify = false; + char buffer[CLOCK_NAME_MAX]; + int count; + + if (param == NULL) { + return 0; + } + + /* Note that the length passed to SPA_POD_OPT_Stringn() also + * includes room for the null terminator, so the content of the + * buffer variable is always guaranteed to be null terminated. */ + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockId, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_id(this, buffer)) + { + reset_props_strings(p); + notify = true; + } + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockDevice, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_device(this, buffer)) + { + reset_props_strings(p); + strncpy(p->clock_device, buffer, sizeof(p->clock_device)); + notify = true; + } + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockInterface, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_interface(this, buffer)) + { + reset_props_strings(p); + strncpy(p->clock_interface, buffer, sizeof(p->clock_interface)); + notify = true; + } + + if (notify) + { + ensure_clock_name(this); + spa_log_info(this->log, "%p: setting clock to '%s'", this, p->clock_name); + if (this->started) { + do_stop(this); + do_start(this); + } + emit_node_info(this, true); + } + + break; + } + + default: + return -ENOENT; + break; + } + + return 0; +} + static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .process = impl_node_process, @@ -655,31 +941,6 @@ impl_get_size(const struct spa_handle_factory *factory, return sizeof(struct impl); } -static int get_phc_index(struct spa_system *s, const char *name) { -#ifdef ETHTOOL_GET_TS_INFO - struct ethtool_ts_info info = {0}; - struct ifreq ifr = {0}; - int fd, err; - - info.cmd = ETHTOOL_GET_TS_INFO; - strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); - ifr.ifr_data = (char *) &info; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) - return -errno; - - err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr); - close(fd); - if (err < 0) - return -errno; - - return info.phc_index; -#else - return -ENOTSUP; -#endif -} - static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -727,9 +988,10 @@ impl_init(const struct spa_handle_factory *factory, this->info.max_input_ports = 0; this->info.max_output_ports = 0; this->info.flags = SPA_NODE_FLAG_RT; - this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; - this->info.n_params = 0; + this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); @@ -742,37 +1004,17 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); } else if (spa_streq(k, "clock.id") && this->clock_fd < 0) { - this->props.clock_id = clock_name_to_id(s); - if (this->props.clock_id == -1) { - spa_log_warn(this->log, "unknown clock id '%s'", s); - this->props.clock_id = DEFAULT_CLOCK_ID; - } + if (parse_clock_id(this, s)) + reset_props_strings(&this->props); } else if (spa_streq(k, "clock.device")) { - if (this->clock_fd >= 0) { - close(this->clock_fd); - } - this->clock_fd = open(s, O_RDONLY); - - if (this->clock_fd == -1) { - spa_log_warn(this->log, "failed to open clock device '%s': %m", s); - } else { - this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); + if (parse_clock_device(this, s)) { + reset_props_strings(&this->props); + strncpy(this->props.clock_device, s, sizeof(this->props.clock_device)-1); } } else if (spa_streq(k, "clock.interface") && this->clock_fd < 0) { - int phc_index = get_phc_index(this->data_system, s); - if (phc_index < 0) { - spa_log_warn(this->log, "failed to get phc device index for interface '%s': %s", - s, spa_strerror(phc_index)); - } else { - char dev[19]; - spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index); - this->clock_fd = open(dev, O_RDONLY); - if (this->clock_fd == -1) { - spa_log_warn(this->log, "failed to open clock device '%s' " - "for interface '%s': %m", dev, s); - } else { - this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); - } + if (parse_clock_interface(this, s)) { + reset_props_strings(&this->props); + strncpy(this->props.clock_interface, s, sizeof(this->props.clock_interface)-1); } } else if (spa_streq(k, "freewheel.wait")) { this->props.freewheel_wait = atoi(s); @@ -785,6 +1027,7 @@ impl_init(const struct spa_handle_factory *factory, "%s.%s", DEFAULT_CLOCK_PREFIX, clock_id_to_name(this->props.clock_id)); } + ensure_clock_name(this); this->tracking = !clock_for_timerfd(this->props.clock_id); this->timer_clockid = this->tracking ? CLOCK_MONOTONIC : this->props.clock_id; From a837dcd40bbfe1a0b5231af6d52fabad43bb3846 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 28 Oct 2025 08:19:17 +0100 Subject: [PATCH 1000/1014] audioadapter: renegotiate when driver changes The renegotiated format can depend on the clock rate of the new driver. See #4933 --- spa/plugins/audioconvert/audioadapter.c | 1 + spa/plugins/videoconvert/videoadapter.c | 1 + 2 files changed, 2 insertions(+) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 0d0d3e15f..7c4605c8a 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -931,6 +931,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Position: this->io_position = data; + this->recheck_format = true; break; default: break; diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 23b55d768..f4346fab6 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -900,6 +900,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Position: this->io_position = data; + this->recheck_format = true; break; default: break; From a8138300244e0e44652e8e9bc634ee916d12563a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 28 Oct 2025 08:48:18 +0100 Subject: [PATCH 1001/1014] po: update Turkish translation --- po/tr.po | 341 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 183 insertions(+), 158 deletions(-) diff --git a/po/tr.po b/po/tr.po index 49eb6eaba..345963c70 100644 --- a/po/tr.po +++ b/po/tr.po @@ -1,46 +1,51 @@ # Turkish translation for PipeWire. -# Copyright (C) 2014-2024 PipeWire's COPYRIGHT HOLDER +# Copyright (C) 2014-2025 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # # Necdet Yücel , 2014. # Kaan Özdinçer , 2014. # Muhammet Kara , 2015, 2016, 2017. # Oğuz Ersen , 2021-2022. -# Sabri Ünal , 2024. +# Sabri Ünal , 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-25 03:43+0300\n" -"PO-Revision-Date: 2024-02-25 03:49+0300\n" -"Last-Translator: Sabri Ünal \n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2025-10-24 15:37+0000\n" +"PO-Revision-Date: 2025-10-24 20:15+0300\n" +"Last-Translator: Sabri Ünal \n" "Language-Team: Türkçe \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Poedit 3.4.2\n" +"X-Generator: Poedit 3.8\n" -#: src/daemon/pipewire.c:26 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [seçenekler]\n" " -h, --help Bu yardımı göster\n" +" -v, --verbose Ayrıntı düzeyini bir düzey artır\n" " --version Sürümü göster\n" " -c, --config Yapılandırmayı yükle (Öntanımlı %s)\n" +" -P --properties Bağlam özelliklerini ayarla\n" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "PipeWire Ortam Sistemi" -#: src/daemon/pipewire.desktop.in:5 +#: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "PipeWire Ortam Sistemini Başlat" @@ -54,26 +59,26 @@ msgstr "%s%s%s tüneli" msgid "Dummy Output" msgstr "Temsili Çıkış" -#: src/modules/module-pulse-tunnel.c:774 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "%s@%s için tünel" -#: src/modules/module-zeroconf-discover.c:315 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Bilinmeyen aygıt" -#: src/modules/module-zeroconf-discover.c:327 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s, %s@%s" -#: src/modules/module-zeroconf-discover.c:331 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s, %s" -#: src/tools/pw-cat.c:991 +#: src/tools/pw-cat.c:1096 #, c-format msgid "" "%s [options] [|-]\n" @@ -88,7 +93,7 @@ msgstr "" " -v, --verbose Ayrıntılı işlemleri etkinleştir\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:1103 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -122,7 +127,7 @@ msgstr "" " -P --properties Düğüm özelliklerini ayarla\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:1121 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -139,6 +144,10 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" +" -M, --force-midi Force midi format, one of \"midi\" " +"or \"ump\", (default ump)\n" +" -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Örnekleme oranı (kayıt için gerekli) " @@ -156,15 +165,21 @@ msgstr "" "%.3f)\n" " -q --quality Yeniden örnekleyici kalitesi (0 - " "15) (öntanımlı %d)\n" +" -a, --raw HAM kipi\n" +" -M, --force-midi Midi biçimini zorla, ikisinden " +"birisi \"midi\" ya da\"ump\", (öntanımlı ump)\n" +" -n, --sample-count COUNT COUNT örnekleme sonrası dur\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:1141 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" +" -s, --sysex SysEx mode\n" +" -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Çalma kipi\n" @@ -172,9 +187,11 @@ msgstr "" " -m, --midi Midi kipi\n" " -d, --dsd DSD kipi\n" " -o, --encoded Kodlanmış kip\n" +" -s, --sysex SysEx kipi\n" +" -c, --midi-clip MIDI klip kipi\n" "\n" -#: src/tools/pw-cli.c:2252 +#: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" @@ -193,195 +210,203 @@ msgstr "" " -r, --remote Uzak arka plan programı adı\n" " -m, --monitor Etkinliği izle\n" -#: spa/plugins/alsa/acp/acp.c:327 +#: spa/plugins/alsa/acp/acp.c:361 msgid "Pro Audio" msgstr "Profesyonel Ses" -#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 -#: spa/plugins/bluez5/bluez5-device.c:1701 +#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/bluez5/bluez5-device.c:1976 msgid "Off" msgstr "Kapalı" -#: spa/plugins/alsa/acp/alsa-mixer.c:2652 +#: spa/plugins/alsa/acp/acp.c:620 +#, c-format +msgid "%s [ALSA UCM error]" +msgstr "%s [ALSA UCM hatası]" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Input" msgstr "Giriş" -#: spa/plugins/alsa/acp/alsa-mixer.c:2653 +#: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "Docking Station Input" msgstr "Yerleştirme İstasyonu Girişi" -#: spa/plugins/alsa/acp/alsa-mixer.c:2654 +#: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Docking Station Microphone" msgstr "Yerleştirme İstasyonu Mikrofonu" -#: spa/plugins/alsa/acp/alsa-mixer.c:2655 +#: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "Docking Station Line In" msgstr "Yerleştirme İstasyonu Hat Girişi" -#: spa/plugins/alsa/acp/alsa-mixer.c:2656 -#: spa/plugins/alsa/acp/alsa-mixer.c:2747 +#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Line In" msgstr "Hat Girişi" -#: spa/plugins/alsa/acp/alsa-mixer.c:2657 -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:1989 +#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2658 -#: spa/plugins/alsa/acp/alsa-mixer.c:2742 +#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Front Microphone" msgstr "Ön Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2659 -#: spa/plugins/alsa/acp/alsa-mixer.c:2743 +#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Rear Microphone" msgstr "Arka Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2660 +#: spa/plugins/alsa/acp/alsa-mixer.c:2729 msgid "External Microphone" msgstr "Harici Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2661 -#: spa/plugins/alsa/acp/alsa-mixer.c:2745 +#: spa/plugins/alsa/acp/alsa-mixer.c:2730 +#: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "Internal Microphone" msgstr "Dahili Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2662 -#: spa/plugins/alsa/acp/alsa-mixer.c:2748 +#: spa/plugins/alsa/acp/alsa-mixer.c:2731 +#: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Radio" msgstr "Radyo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2663 -#: spa/plugins/alsa/acp/alsa-mixer.c:2749 +#: spa/plugins/alsa/acp/alsa-mixer.c:2732 +#: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Video" msgstr "Video" -#: spa/plugins/alsa/acp/alsa-mixer.c:2664 +#: spa/plugins/alsa/acp/alsa-mixer.c:2733 msgid "Automatic Gain Control" msgstr "Otomatik Kazanç Denetimi" -#: spa/plugins/alsa/acp/alsa-mixer.c:2665 +#: spa/plugins/alsa/acp/alsa-mixer.c:2734 msgid "No Automatic Gain Control" msgstr "Otomatik Kazanç Denetimi Yok" -#: spa/plugins/alsa/acp/alsa-mixer.c:2666 +#: spa/plugins/alsa/acp/alsa-mixer.c:2735 msgid "Boost" msgstr "Artır" -#: spa/plugins/alsa/acp/alsa-mixer.c:2667 +#: spa/plugins/alsa/acp/alsa-mixer.c:2736 msgid "No Boost" msgstr "Artırma Yok" -#: spa/plugins/alsa/acp/alsa-mixer.c:2668 +#: spa/plugins/alsa/acp/alsa-mixer.c:2737 msgid "Amplifier" msgstr "Yükseltici" -#: spa/plugins/alsa/acp/alsa-mixer.c:2669 +#: spa/plugins/alsa/acp/alsa-mixer.c:2738 msgid "No Amplifier" msgstr "Yükseltici Yok" -#: spa/plugins/alsa/acp/alsa-mixer.c:2670 +#: spa/plugins/alsa/acp/alsa-mixer.c:2739 msgid "Bass Boost" msgstr "Bas Artır" -#: spa/plugins/alsa/acp/alsa-mixer.c:2671 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "No Bass Boost" msgstr "Bas Artırma Yok" -#: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1995 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:2380 msgid "Speaker" msgstr "Hoparlör" -#: spa/plugins/alsa/acp/alsa-mixer.c:2673 -#: spa/plugins/alsa/acp/alsa-mixer.c:2751 +#. Don't call it "headset", the HF one has the mic +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 +#: spa/plugins/alsa/acp/alsa-mixer.c:2820 +#: spa/plugins/bluez5/bluez5-device.c:2386 +#: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "Kulaklık" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Analog Input" msgstr "Analog Giriş" -#: spa/plugins/alsa/acp/alsa-mixer.c:2744 +#: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Dock Microphone" msgstr "Yapışık Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2746 +#: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Headset Microphone" msgstr "Mikrofonlu Kulaklık" -#: spa/plugins/alsa/acp/alsa-mixer.c:2750 +#: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Analog Output" msgstr "Analog Çıkış" -#: spa/plugins/alsa/acp/alsa-mixer.c:2752 +#: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Headphones 2" msgstr "Kulaklık 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2753 +#: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Headphones Mono Output" msgstr "Kulaklık Tek Kanallı Çıkış" -#: spa/plugins/alsa/acp/alsa-mixer.c:2754 +#: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Line Out" msgstr "Hat Çıkışı" -#: spa/plugins/alsa/acp/alsa-mixer.c:2755 +#: spa/plugins/alsa/acp/alsa-mixer.c:2824 msgid "Analog Mono Output" msgstr "Analog Tek Kanallı Çıkış" -#: spa/plugins/alsa/acp/alsa-mixer.c:2756 +#: spa/plugins/alsa/acp/alsa-mixer.c:2825 msgid "Speakers" msgstr "Hoparlörler" -#: spa/plugins/alsa/acp/alsa-mixer.c:2757 +#: spa/plugins/alsa/acp/alsa-mixer.c:2826 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2758 +#: spa/plugins/alsa/acp/alsa-mixer.c:2827 msgid "Digital Output (S/PDIF)" msgstr "Sayısal Çıkış (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2759 +#: spa/plugins/alsa/acp/alsa-mixer.c:2828 msgid "Digital Input (S/PDIF)" msgstr "Sayısal Giriş (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2760 +#: spa/plugins/alsa/acp/alsa-mixer.c:2829 msgid "Multichannel Input" msgstr "Çok Kanallı Giriş" -#: spa/plugins/alsa/acp/alsa-mixer.c:2761 +#: spa/plugins/alsa/acp/alsa-mixer.c:2830 msgid "Multichannel Output" msgstr "Çok Kanallı Çıkış" -#: spa/plugins/alsa/acp/alsa-mixer.c:2762 +#: spa/plugins/alsa/acp/alsa-mixer.c:2831 msgid "Game Output" msgstr "Oyun Çıkışı" -#: spa/plugins/alsa/acp/alsa-mixer.c:2763 -#: spa/plugins/alsa/acp/alsa-mixer.c:2764 +#: spa/plugins/alsa/acp/alsa-mixer.c:2832 +#: spa/plugins/alsa/acp/alsa-mixer.c:2833 msgid "Chat Output" msgstr "Sohbet Çıkışı" -#: spa/plugins/alsa/acp/alsa-mixer.c:2765 +#: spa/plugins/alsa/acp/alsa-mixer.c:2834 msgid "Chat Input" msgstr "Sohbet Girişi" -#: spa/plugins/alsa/acp/alsa-mixer.c:2766 +#: spa/plugins/alsa/acp/alsa-mixer.c:2835 msgid "Virtual Surround 7.1" msgstr "Sanal Çevresel Ses 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4456 +#: spa/plugins/alsa/acp/alsa-mixer.c:4522 msgid "Analog Mono" msgstr "Analog Tek Kanallı" -#: spa/plugins/alsa/acp/alsa-mixer.c:4457 +#: spa/plugins/alsa/acp/alsa-mixer.c:4523 msgid "Analog Mono (Left)" msgstr "Analog Tek Kanallı (Sol)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4458 +#: spa/plugins/alsa/acp/alsa-mixer.c:4524 msgid "Analog Mono (Right)" msgstr "Analog Tek Kanallı (Sağ)" @@ -390,147 +415,147 @@ msgstr "Analog Tek Kanallı (Sağ)" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. -#: spa/plugins/alsa/acp/alsa-mixer.c:4459 -#: spa/plugins/alsa/acp/alsa-mixer.c:4467 -#: spa/plugins/alsa/acp/alsa-mixer.c:4468 +#: spa/plugins/alsa/acp/alsa-mixer.c:4525 +#: spa/plugins/alsa/acp/alsa-mixer.c:4533 +#: spa/plugins/alsa/acp/alsa-mixer.c:4534 msgid "Analog Stereo" msgstr "Analog Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4460 +#: spa/plugins/alsa/acp/alsa-mixer.c:4526 msgid "Mono" msgstr "Tek Kanallı" -#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4469 -#: spa/plugins/alsa/acp/alsa-mixer.c:4627 -#: spa/plugins/bluez5/bluez5-device.c:1977 +#: spa/plugins/alsa/acp/alsa-mixer.c:4535 +#: spa/plugins/alsa/acp/alsa-mixer.c:4693 +#: spa/plugins/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "Kulaklık" -#: spa/plugins/alsa/acp/alsa-mixer.c:4470 -#: spa/plugins/alsa/acp/alsa-mixer.c:4628 +#: spa/plugins/alsa/acp/alsa-mixer.c:4536 +#: spa/plugins/alsa/acp/alsa-mixer.c:4694 msgid "Speakerphone" msgstr "Hoparlör" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4537 +#: spa/plugins/alsa/acp/alsa-mixer.c:4538 msgid "Multichannel" msgstr "Çok kanallı" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Surround 2.1" msgstr "Analog Çevresel Ses 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +#: spa/plugins/alsa/acp/alsa-mixer.c:4540 msgid "Analog Surround 3.0" msgstr "Analog Çevresel Ses 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4541 msgid "Analog Surround 3.1" msgstr "Analog Çevresel Ses 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4542 msgid "Analog Surround 4.0" msgstr "Analog Çevresel Ses 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4477 +#: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Analog Surround 4.1" msgstr "Analog Çevresel Ses 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4478 +#: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 5.0" msgstr "Analog Çevresel Ses 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4479 +#: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 5.1" msgstr "Analog Çevresel Ses 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4480 +#: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 6.0" msgstr "Analog Çevresel Ses 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4481 +#: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 6.1" msgstr "Analog Çevresel Ses 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +#: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 7.0" msgstr "Analog Çevresel Ses 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +#: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 7.1" msgstr "Analog Çevresel Ses 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +#: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Digital Stereo (IEC958)" msgstr "Sayısal Stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +#: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Sayısal Çevresel Ses 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +#: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Sayısal Çevresel Ses 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Sayısal Çevresel Ses 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Digital Stereo (HDMI)" msgstr "Sayısal Stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Surround 5.1 (HDMI)" msgstr "Sayısal Çevresel Ses 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Chat" msgstr "Sohbet" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Game" msgstr "Oyun" -#: spa/plugins/alsa/acp/alsa-mixer.c:4625 +#: spa/plugins/alsa/acp/alsa-mixer.c:4691 msgid "Analog Mono Duplex" msgstr "Analog Tek Kanallı İkili" -#: spa/plugins/alsa/acp/alsa-mixer.c:4626 +#: spa/plugins/alsa/acp/alsa-mixer.c:4692 msgid "Analog Stereo Duplex" msgstr "Analog İkili Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/alsa/acp/alsa-mixer.c:4695 msgid "Digital Stereo Duplex (IEC958)" msgstr "Sayısal İkili Stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4630 +#: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Multichannel Duplex" msgstr "Çok Kanallı İkili" -#: spa/plugins/alsa/acp/alsa-mixer.c:4631 +#: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Stereo Duplex" msgstr "İkili Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4632 +#: spa/plugins/alsa/acp/alsa-mixer.c:4698 msgid "Mono Chat + 7.1 Surround" msgstr "Tek Kanallı Sohbet + 7.1 Çevresel Ses" -#: spa/plugins/alsa/acp/alsa-mixer.c:4733 +#: spa/plugins/alsa/acp/alsa-mixer.c:4799 #, c-format msgid "%s Output" msgstr "%s Çıkışı" -#: spa/plugins/alsa/acp/alsa-mixer.c:4741 +#: spa/plugins/alsa/acp/alsa-mixer.c:4807 #, c-format msgid "%s Input" msgstr "%s Girişi" -#: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -547,16 +572,16 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1286 +#: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" -"%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " +"(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" @@ -564,7 +589,7 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1333 +#: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -577,7 +602,7 @@ msgstr "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." -#: spa/plugins/alsa/acp/alsa-util.c:1376 +#: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -595,112 +620,112 @@ msgstr[0] "" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." -#: spa/plugins/alsa/acp/channelmap.h:457 +#: spa/plugins/alsa/acp/channelmap.h:460 msgid "(invalid)" msgstr "(geçersiz)" -#: spa/plugins/alsa/acp/compat.c:193 +#: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Dahili Ses" -#: spa/plugins/alsa/acp/compat.c:198 +#: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1712 +#: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Ses Geçidi (A2DP Kaynak & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1760 +#: spa/plugins/bluez5/bluez5-device.c:2016 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "İşitme Aygıtları İçin Ses Akışı (ASHA Alıcı)" + +#: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1763 +#: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1771 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1773 +#: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1823 +#: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Yüksek Kaliteli Çalma (BAP Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1828 +#: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Yüksek Kaliteli Giriş (BAP Kaynak, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1832 +#: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1841 +#: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "Yüksek Kaliteli Çalma (BAP Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1845 +#: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "Yüksek Kaliteli Giriş (BAP Kaynak)" -#: spa/plugins/bluez5/bluez5-device.c:1848 +#: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı)" -#: spa/plugins/bluez5/bluez5-device.c:1897 +#: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Kulaklık Ana Birimi (HSP/HFP, çözücü %s)" -#: spa/plugins/bluez5/bluez5-device.c:1978 -#: spa/plugins/bluez5/bluez5-device.c:1983 -#: spa/plugins/bluez5/bluez5-device.c:1990 -#: spa/plugins/bluez5/bluez5-device.c:1996 -#: spa/plugins/bluez5/bluez5-device.c:2002 -#: spa/plugins/bluez5/bluez5-device.c:2008 -#: spa/plugins/bluez5/bluez5-device.c:2014 -#: spa/plugins/bluez5/bluez5-device.c:2020 -#: spa/plugins/bluez5/bluez5-device.c:2026 +#: spa/plugins/bluez5/bluez5-device.c:2363 +#: spa/plugins/bluez5/bluez5-device.c:2368 +#: spa/plugins/bluez5/bluez5-device.c:2375 +#: spa/plugins/bluez5/bluez5-device.c:2381 +#: spa/plugins/bluez5/bluez5-device.c:2387 +#: spa/plugins/bluez5/bluez5-device.c:2393 +#: spa/plugins/bluez5/bluez5-device.c:2399 +#: spa/plugins/bluez5/bluez5-device.c:2405 +#: spa/plugins/bluez5/bluez5-device.c:2411 msgid "Handsfree" msgstr "Ahizesiz" -#: spa/plugins/bluez5/bluez5-device.c:1984 +#: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "Ahizesiz (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2001 -msgid "Headphone" -msgstr "Kulaklık" - -#: spa/plugins/bluez5/bluez5-device.c:2007 +#: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "Taşınabilir" -#: spa/plugins/bluez5/bluez5-device.c:2013 +#: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "Araba" -#: spa/plugins/bluez5/bluez5-device.c:2019 +#: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "Yüksek Kalite" -#: spa/plugins/bluez5/bluez5-device.c:2025 +#: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2032 +#: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2033 +#: spa/plugins/bluez5/bluez5-device.c:2418 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" From 9f3c5532982ed7aa3d4a206a535bb7c075ec7475 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 28 Oct 2025 10:26:42 +0100 Subject: [PATCH 1002/1014] tools: add -t option to the help --- src/tools/pw-link.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/pw-link.c b/src/tools/pw-link.c index 4104dd742..1853783ca 100644 --- a/src/tools/pw-link.c +++ b/src/tools/pw-link.c @@ -871,6 +871,7 @@ static void show_help(struct data *data, const char *name, bool error) " -o, --output List output ports\n" " -i, --input List input ports\n" " -l, --links List links\n" + " -t, --latency List port latencies\n" " -m, --monitor Monitor links and ports\n" " -I, --id List IDs\n" " -v, --verbose Verbose port properties\n" From 7706ca6361c13a2d430c44f931f8affdf9975830 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 10:10:10 +0100 Subject: [PATCH 1003/1014] spa: fix Cube layout define --- spa/include/spa/param/audio/layout.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 19e3a3c42..6d6ec4898 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -37,7 +37,7 @@ struct spa_audio_layout_info { SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } -#define SPA_AUDIO_LAYOUT_Cube 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }, \ +#define SPA_AUDIO_LAYOUT_Cube 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, \ SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR, } From 056f25705867319fa64c5e6c2949d1c6271af5dc Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 11:26:32 +0100 Subject: [PATCH 1004/1014] spa: fix max_position check --- spa/include/spa/param/audio/raw-types.h | 2 +- spa/include/spa/param/audio/raw-utils.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index fbaa80abf..d01ad4fd5 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -314,4 +314,4 @@ static const struct spa_type_info spa_type_audio_volume_ramp_scale[] = { } /* extern "C" */ #endif -#endif /* SPA_AUDIO_RAW_RAW_TYPES_H */ +#endif /* SPA_AUDIO_RAW_TYPES_H */ diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 3bb94eaa3..da1ec7317 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -89,7 +89,7 @@ spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, /* 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) { + info->channels <= max_position) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, info->channels, info->position), 0); From 8ba08f3029dab9bd947f11af6f37212ebaf5ce88 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 11:35:03 +0100 Subject: [PATCH 1005/1014] spa: add audio.layout property Makes it possible to define audio channels and position with a predefined layout string. It is easier and less error prone to say "5.1" than to spell out [ FL FR FC LFE RL RR ]. AUX channels have a special syntax. AUX will make AUX channels. Easier to say AUX128 than to write an array with the 128 AUX channels. --- doc/dox/config/pipewire-props.7.md | 3 + spa/include/spa/param/audio/layout-types.h | 118 +++++++++++++++++++++ spa/include/spa/param/audio/layout.h | 3 + spa/include/spa/param/audio/raw-json.h | 24 +++++ spa/include/spa/param/audio/raw.h | 1 + spa/include/spa/param/audio/type-info.h | 1 + spa/lib/lib.c | 1 + 7 files changed, 151 insertions(+) create mode 100644 spa/include/spa/param/audio/layout-types.h diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 97b758a2a..964ebec0d 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -843,6 +843,9 @@ The audio format to open the device in. By default this is "UNKNOWN", which will @PAR@ node-prop audio.position # JSON array of strings The audio position of the channels in the device. This is auto detected based on the profile. You can configure an array of channel positions, like "[ FL, FR ]". +@PAR@ node-prop audio.layout # string +The audio layout of the channels in the device. You can use any of the predefined layouts, like "Stereo", "5.1" etc. + @PAR@ node-prop audio.allowed-rates # JSON array of integers \parblock The allowed audio rates to open the device with. Default is "[ ]", which means the device can be opened in any supported rate. diff --git a/spa/include/spa/param/audio/layout-types.h b/spa/include/spa/param/audio/layout-types.h new file mode 100644 index 000000000..c8c78af39 --- /dev/null +++ b/spa/include/spa/param/audio/layout-types.h @@ -0,0 +1,118 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_LAYOUT_TYPES_H +#define SPA_AUDIO_LAYOUT_TYPES_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#ifndef SPA_API_AUDIO_LAYOUT_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_LAYOUT_TYPES SPA_API_IMPL + #else + #define SPA_API_AUDIO_LAYOUT_TYPES static inline + #endif +#endif + +static const struct spa_type_audio_layout_info { + const char *name; + struct spa_audio_layout_info layout; +} spa_type_audio_layout_info[] = { + { "Mono", { SPA_AUDIO_LAYOUT_Mono } }, + { "Stereo", { SPA_AUDIO_LAYOUT_Stereo } }, + { "Quad", { SPA_AUDIO_LAYOUT_Quad } }, + { "Pentagonal", { SPA_AUDIO_LAYOUT_Pentagonal } }, + { "Hexagonal", { SPA_AUDIO_LAYOUT_Hexagonal } }, + { "Octagonal", { SPA_AUDIO_LAYOUT_Octagonal } }, + { "Cube", { SPA_AUDIO_LAYOUT_Cube } }, + { "MPEG-1.0", { SPA_AUDIO_LAYOUT_MPEG_1_0 } }, + { "MPEG-2.0", { SPA_AUDIO_LAYOUT_MPEG_2_0 } }, + { "MPEG-3.0A", { SPA_AUDIO_LAYOUT_MPEG_3_0A } }, + { "MPEG-3.0B", { SPA_AUDIO_LAYOUT_MPEG_3_0B } }, + { "MPEG-4.0A", { SPA_AUDIO_LAYOUT_MPEG_4_0A } }, + { "MPEG-4.0B", { SPA_AUDIO_LAYOUT_MPEG_4_0B } }, + { "MPEG-5.0A", { SPA_AUDIO_LAYOUT_MPEG_5_0A } }, + { "MPEG-5.0B", { SPA_AUDIO_LAYOUT_MPEG_5_0B } }, + { "MPEG-5.0C", { SPA_AUDIO_LAYOUT_MPEG_5_0C } }, + { "MPEG-5.0D", { SPA_AUDIO_LAYOUT_MPEG_5_0D } }, + { "MPEG-5.1A", { SPA_AUDIO_LAYOUT_MPEG_5_1A } }, + { "MPEG-5.1B", { SPA_AUDIO_LAYOUT_MPEG_5_1B } }, + { "MPEG-5.1C", { SPA_AUDIO_LAYOUT_MPEG_5_1C } }, + { "MPEG-5.1D", { SPA_AUDIO_LAYOUT_MPEG_5_1D } }, + { "MPEG-6.1A", { SPA_AUDIO_LAYOUT_MPEG_6_1A } }, + { "MPEG-7.1A", { SPA_AUDIO_LAYOUT_MPEG_7_1A } }, + { "MPEG-7.1B", { SPA_AUDIO_LAYOUT_MPEG_7_1B } }, + { "MPEG-7.1C", { SPA_AUDIO_LAYOUT_MPEG_7_1C } }, + { "2.1", { SPA_AUDIO_LAYOUT_2_1 } }, + { "2RC", { SPA_AUDIO_LAYOUT_2RC } }, + { "2FC", { SPA_AUDIO_LAYOUT_2FC } }, + { "3.1", { SPA_AUDIO_LAYOUT_3_1 } }, + { "4.0", { SPA_AUDIO_LAYOUT_4_0 } }, + { "2.2", { SPA_AUDIO_LAYOUT_2_2 } }, + { "4.1", { SPA_AUDIO_LAYOUT_4_1 } }, + { "5.0", { SPA_AUDIO_LAYOUT_5_0 } }, + { "5.0R", { SPA_AUDIO_LAYOUT_5_0R } }, + { "5.1", { SPA_AUDIO_LAYOUT_5_1 } }, + { "5.1R", { SPA_AUDIO_LAYOUT_5_1R } }, + { "6.0", { SPA_AUDIO_LAYOUT_6_0 } }, + { "6.0F", { SPA_AUDIO_LAYOUT_6_0F } }, + { "6.1", { SPA_AUDIO_LAYOUT_6_1 } }, + { "6.1F", { SPA_AUDIO_LAYOUT_6_1F } }, + { "7.0", { SPA_AUDIO_LAYOUT_7_0 } }, + { "7.0F", { SPA_AUDIO_LAYOUT_7_0F } }, + { "7.1", { SPA_AUDIO_LAYOUT_7_1 } }, + { "7.1W", { SPA_AUDIO_LAYOUT_7_1W } }, + { "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } }, + { NULL, }, +}; + +SPA_API_AUDIO_LAYOUT_TYPES int +spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t size, + const char *name) +{ + uint32_t max_position = SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size); + if (spa_strstartswith(name, "AUX")) { + uint32_t i, n_pos; + if (spa_atou32(name+3, &n_pos, 10)) { + if (n_pos > max_position) + return -ECHRNG; + for (i = 0; i < 0x1000 && i < n_pos; i++) + layout->position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; + for (; i < n_pos; i++) + layout->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + layout->n_channels = n_pos; + return n_pos; + } + } + SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) { + if (spa_streq(name, i->name)) { + if (i->layout.n_channels > max_position) + return -ECHRNG; + *layout = i->layout; + return i->layout.n_channels; + } + } + return -ENOTSUP; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_LAYOUT_TYPES_H */ diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 6d6ec4898..2293d41f1 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -21,8 +21,11 @@ extern "C" { struct spa_audio_layout_info { uint32_t n_channels; uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + /* padding may follow to allow more channels */ }; +#define SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_layout_info,position))/sizeof(uint32_t)) + #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } #define SPA_AUDIO_LAYOUT_Stereo 2, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } #define SPA_AUDIO_LAYOUT_Quad 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h index 38fcb449c..6b1b25164 100644 --- a/spa/include/spa/param/audio/raw-json.h +++ b/spa/include/spa/param/audio/raw-json.h @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -54,6 +55,20 @@ spa_audio_parse_position(const char *str, size_t len, return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); } +SPA_API_AUDIO_RAW_JSON int +spa_audio_parse_layout(const char *str, uint32_t *position, uint32_t max_position, + uint32_t *n_channels) +{ + struct spa_audio_layout_info l; + uint32_t i; + if (spa_audio_layout_info_parse_name(&l, sizeof(l), str) <= 0) + return 0; + for (i = 0; i < l.n_channels && i < max_position; i++) + position[i] = l.position[i]; + *n_channels = l.n_channels; + return l.n_channels; +} + SPA_API_AUDIO_RAW_JSON int spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, const char *key, const char *val, bool force) @@ -76,6 +91,15 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, return -ECHRNG; info->channels = v; } + } else if (spa_streq(key, SPA_KEY_AUDIO_LAYOUT)) { + if (force || info->channels == 0) { + if (spa_audio_parse_layout(val, info->position, max_position, &v) > 0) { + if (v > max_position) + return -ECHRNG; + info->channels = v; + SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } + } } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index bcc0a122d..8500c4f17 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -302,6 +302,7 @@ struct spa_audio_info_raw { * Ex. "FL" */ #define SPA_KEY_AUDIO_CHANNELS "audio.channels" /**< an audio channel count as int */ #define SPA_KEY_AUDIO_RATE "audio.rate" /**< an audio sample rate as int */ +#define SPA_KEY_AUDIO_LAYOUT "audio.layout" /**< channel positions as predefined layout */ #define SPA_KEY_AUDIO_POSITION "audio.position" /**< channel positions as comma separated list * of channels ex. "FL,FR" */ #define SPA_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates diff --git a/spa/include/spa/param/audio/type-info.h b/spa/include/spa/param/audio/type-info.h index 1a15ad3dd..3c5d6f4c7 100644 --- a/spa/include/spa/param/audio/type-info.h +++ b/spa/include/spa/param/audio/type-info.h @@ -6,6 +6,7 @@ #define SPA_AUDIO_TYPES_H #include +#include #include #include #include diff --git a/spa/lib/lib.c b/spa/lib/lib.c index 30aa91194..0aa35fae3 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include From ff0bc22cb1acf4d5c6341b698100a41e4ebe2088 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 12:29:31 +0100 Subject: [PATCH 1006/1014] modules: support audio.layout where we can --- spa/plugins/audioconvert/audioconvert.c | 16 +++++++++++---- spa/plugins/avb/avb-pcm.c | 5 +++++ spa/plugins/support/null-audio-sink.c | 3 +++ src/modules/module-combine-stream.c | 12 +++++++++++- src/modules/module-echo-cancel.c | 24 +++++++++++++++++++++-- src/modules/module-example-filter.c | 3 +++ src/modules/module-example-sink.c | 3 +++ src/modules/module-example-source.c | 3 +++ src/modules/module-ffado-driver.c | 2 ++ src/modules/module-filter-chain.c | 3 +++ src/modules/module-jack-tunnel.c | 3 +++ src/modules/module-loopback.c | 3 +++ src/modules/module-netjack2-driver.c | 3 +++ src/modules/module-netjack2-manager.c | 3 +++ src/modules/module-parametric-equalizer.c | 1 + src/modules/module-pipe-tunnel.c | 3 +++ src/modules/module-protocol-simple.c | 3 +++ src/modules/module-pulse-tunnel.c | 3 +++ src/modules/module-raop-sink.c | 2 ++ src/modules/module-rtp-session.c | 11 +++++++++++ src/modules/module-rtp-sink.c | 3 +++ src/modules/module-rtp-source.c | 3 +++ src/modules/module-rtp/stream.c | 1 + src/modules/module-rtp/stream.h | 1 + src/modules/module-snapcast-discover.c | 1 + src/modules/module-vban-recv.c | 2 ++ src/modules/module-vban-send.c | 2 ++ src/modules/module-vban/stream.c | 1 + 28 files changed, 116 insertions(+), 7 deletions(-) diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index f201522f8..72ad5d792 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -4274,10 +4274,18 @@ impl_init(const struct spa_handle_factory *factory, this->direction = SPA_DIRECTION_INPUT; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { - if (s != NULL) - spa_audio_parse_position_n(s, strlen(s), - this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), - &this->props.n_channels); + if (s == NULL) + continue; + spa_audio_parse_position_n(s, strlen(s), + this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), + &this->props.n_channels); + } + else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { + if (s == NULL) + continue; + spa_audio_parse_layout(s, + this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), + &this->props.n_channels); } else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY)) this->port_ignore_latency = spa_atob(s); diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index 0402b8c75..5585cb141 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -44,6 +44,11 @@ static int avb_set_param(struct state *state, const char *k, const char *s) SPA_N_ELEMENTS(state->default_pos.pos), &state->default_pos.channels); fmt_change++; + } else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { + spa_audio_parse_layout(s, state->default_pos.pos, + SPA_N_ELEMENTS(state->default_pos.pos), + &state->default_pos.channels); + fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates, MAX_RATES, s, strlen(s)); diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index acaec6f7b..b804023b3 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -952,6 +952,9 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { spa_audio_parse_position_n(s, strlen(s), this->props.pos, SPA_N_ELEMENTS(this->props.pos), &this->props.channels); + } else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { + spa_audio_parse_layout(s, this->props.pos, + SPA_N_ELEMENTS(this->props.pos), &this->props.channels); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 29f57cb50..453203ebb 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -64,6 +64,7 @@ * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -76,9 +77,10 @@ * ## Stream options * * - `audio.position`: Set the stream channel map. By default this is the same channel - * map as the combine stream. + * map as the combine stream. You can also use audio.layout * - `combine.audio.position`: map the combine audio positions to the stream positions. * combine input channels are mapped one-by-one to stream output channels. + * You can also use combine.audio.layout. * * ## Example configuration * @@ -334,6 +336,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -868,6 +871,9 @@ static int create_stream(struct stream_info *info) if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) spa_audio_parse_position_n(str, strlen(str), s->info.position, SPA_N_ELEMENTS(s->info.position), &s->info.channels); + if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) + spa_audio_parse_layout(str, s->info.position, + SPA_N_ELEMENTS(s->info.position), &s->info.channels); if (s->info.channels == 0) s->info = impl->info; @@ -875,6 +881,9 @@ static int create_stream(struct stream_info *info) if ((str = pw_properties_get(info->stream_props, "combine.audio.position")) != NULL) spa_audio_parse_position_n(str, strlen(str), remap_info.position, SPA_N_ELEMENTS(remap_info.position), &remap_info.channels); + if ((str = pw_properties_get(info->stream_props, "combine.audio.layout")) != NULL) + spa_audio_parse_layout(str, remap_info.position, + SPA_N_ELEMENTS(remap_info.position), &remap_info.channels); if (remap_info.channels == 0) remap_info = s->info; @@ -1627,6 +1636,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(props, impl->combine_props, PW_KEY_NODE_LOOP_NAME); copy_props(props, impl->combine_props, PW_KEY_AUDIO_CHANNELS); + copy_props(props, impl->combine_props, SPA_KEY_AUDIO_LAYOUT); copy_props(props, impl->combine_props, SPA_KEY_AUDIO_POSITION); copy_props(props, impl->combine_props, PW_KEY_NODE_NAME); copy_props(props, impl->combine_props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index 98efa35c5..390563db2 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -105,6 +105,7 @@ * * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_NODE_LATENCY @@ -1278,6 +1279,7 @@ static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_r &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1416,6 +1418,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, SPA_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, "resample.prefill"); @@ -1442,21 +1445,38 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) spa_audio_parse_position_n(str, strlen(str), impl->capture_info.position, SPA_N_ELEMENTS(impl->capture_info.position), &impl->capture_info.channels); } + if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { + spa_audio_parse_layout(str, impl->capture_info.position, + SPA_N_ELEMENTS(impl->capture_info.position), &impl->capture_info.channels); + } if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->source_info.position, SPA_N_ELEMENTS(impl->source_info.position), &impl->source_info.channels); } + if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { + spa_audio_parse_layout(str, impl->source_info.position, + SPA_N_ELEMENTS(impl->source_info.position), &impl->source_info.channels); + } if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->sink_info.position, SPA_N_ELEMENTS(impl->sink_info.position), &impl->sink_info.channels); impl->playback_info = impl->sink_info; } + if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { + spa_audio_parse_layout(str, impl->sink_info.position, + SPA_N_ELEMENTS(impl->sink_info.position), &impl->sink_info.channels); + impl->playback_info = impl->sink_info; + } if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->playback_info.position, SPA_N_ELEMENTS(impl->playback_info.position), &impl->playback_info.channels); - if (impl->playback_info.channels != impl->sink_info.channels) - impl->playback_info = impl->sink_info; } + if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { + spa_audio_parse_layout(str, impl->playback_info.position, + SPA_N_ELEMENTS(impl->playback_info.position), &impl->playback_info.channels); + } + if (impl->playback_info.channels != impl->sink_info.channels) + impl->playback_info = impl->sink_info; if ((str = pw_properties_get(props, "aec.method")) != NULL) pw_log_warn("aec.method is not supported anymore use library.name"); diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 60b6dda95..209a23eac 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -46,6 +46,7 @@ * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -476,6 +477,7 @@ static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_r &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -548,6 +550,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index 00b880175..fecee854d 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -52,6 +52,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -284,6 +285,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -387,6 +389,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index 20f296a17..21d3e34cc 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -52,6 +52,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -290,6 +291,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -393,6 +395,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 7094feaf4..f6a76bed0 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -66,6 +66,7 @@ * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -1437,6 +1438,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index a2d1f5361..2cf053ff8 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -995,6 +995,7 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -1840,6 +1841,7 @@ static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_r &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1918,6 +1920,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 637001423..760da1669 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -72,6 +72,7 @@ * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -1060,6 +1061,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1169,6 +1171,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_update_string(impl->source.props, str, strlen(str)); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); copy_props(impl, props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index e921cd701..5b34834ac 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -51,6 +51,7 @@ * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY @@ -846,6 +847,7 @@ static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_r &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -927,6 +929,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 485570064..07a756266 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -99,6 +99,7 @@ * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -1225,6 +1226,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1329,6 +1331,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_ALWAYS_PROCESS); copy_props(impl, props, PW_KEY_NODE_GROUP); diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 615a4553b..0ff62d93b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -94,6 +94,7 @@ * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -1299,6 +1300,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), &props->dict, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1417,6 +1419,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOCK_QUANTUM); copy_props(impl, props, PW_KEY_NODE_LOCK_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, "audio.ports"); copy_props(impl, props, "midi.ports"); diff --git a/src/modules/module-parametric-equalizer.c b/src/modules/module-parametric-equalizer.c index a11398166..7eb89f7f5 100644 --- a/src/modules/module-parametric-equalizer.c +++ b/src/modules/module-parametric-equalizer.c @@ -70,6 +70,7 @@ * Options with well-known behaviour: * * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_REMOTE_NAME * diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index 2f66a9ed7..62de8717c 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -79,6 +79,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME @@ -756,6 +757,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -886,6 +888,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index e1847577a..ffc225b98 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -72,6 +72,7 @@ * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_RATE @@ -831,6 +832,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL)) < 0) return res; @@ -888,6 +890,7 @@ static int parse_params(struct impl *impl) copy_props(impl, PW_KEY_AUDIO_FORMAT); copy_props(impl, PW_KEY_AUDIO_RATE); copy_props(impl, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, SPA_KEY_AUDIO_POSITION); copy_props(impl, PW_KEY_NODE_RATE); copy_props(impl, PW_KEY_NODE_NAME); diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index 4c4ab1a26..fe496fa33 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -72,6 +72,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME @@ -1059,6 +1060,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } @@ -1183,6 +1185,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index accc2a9da..e217ff5b2 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -82,6 +82,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -1901,6 +1902,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_DEVICE_ICON_NAME); copy_props(impl, props, PW_KEY_NODE_NAME); diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c index cd7ca7f4e..63470c539 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -82,6 +82,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -148,6 +149,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); "( audio.rate= ) " \ "( audio.channels= ) "\ "( audio.position= ) " \ + "( audio.layout= ) " \ "( stream.props= { key=value ... } ) " static const struct spa_dict_item module_info[] = { @@ -1329,6 +1331,12 @@ static struct service *make_service(struct impl *impl, const struct service_info } else if (spa_streq(key, "channels")) { k = PW_KEY_AUDIO_CHANNELS; mask |= 1<<3; + } else if (spa_streq(key, "position")) { + pw_properties_set(props, + SPA_KEY_AUDIO_POSITION, value); + } else if (spa_streq(key, "layout")) { + pw_properties_set(props, + SPA_KEY_AUDIO_LAYOUT, value); } else if (spa_streq(key, "channelnames")) { pw_properties_set(props, PW_KEY_NODE_CHANNELNAMES, value); @@ -1587,6 +1595,8 @@ static int make_announce(struct impl *impl) txt = avahi_string_list_add_pair(txt, "channels", str); if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) txt = avahi_string_list_add_pair(txt, "position", str); + if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) + txt = avahi_string_list_add_pair(txt, "layout", str); if ((str = pw_properties_get(impl->stream_props, PW_KEY_NODE_CHANNELNAMES)) != NULL) txt = avahi_string_list_add_pair(txt, "channelnames", str); if (impl->ts_refclk != NULL) { @@ -1702,6 +1712,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index cb21cb79a..c77fc067a 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -77,6 +77,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -150,6 +151,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); "( audio.rate= ) " \ "( audio.channels= ) " \ "( audio.position= ) " \ + "( audio.layout= ) " \ "( aes67.driver-group= ) " \ "( stream.props= { key=value ... } ) " @@ -578,6 +580,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 5c3d1f05f..8e227b53b 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -72,6 +72,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_MEDIA_CLASS @@ -175,6 +176,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); "( audio.rate= ) " \ "( audio.channels= ) " \ "( audio.position= ) " \ + "( audio.layout= ) " \ "( stream.props= { key=value ... } ) " static const struct spa_dict_item module_info[] = { @@ -923,6 +925,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index fa055ce0c..e19d88a1f 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -581,6 +581,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } diff --git a/src/modules/module-rtp/stream.h b/src/modules/module-rtp/stream.h index 095b8395c..2c6e6dea5 100644 --- a/src/modules/module-rtp/stream.h +++ b/src/modules/module-rtp/stream.h @@ -15,6 +15,7 @@ struct rtp_stream; #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" +#define DEFAULT_LAYOUT "Stereo" #define ERROR_MSEC 2.0f #define DEFAULT_SESS_LATENCY 100.0f diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index ec4eb3e25..596d5677b 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -516,6 +516,7 @@ static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_r SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL)) < 0) return res; diff --git a/src/modules/module-vban-recv.c b/src/modules/module-vban-recv.c index 26dc7b507..2ee724305 100644 --- a/src/modules/module-vban-recv.c +++ b/src/modules/module-vban-recv.c @@ -75,6 +75,7 @@ * Options with well-known behavior: * * - \ref PW_KEY_REMOTE_NAME + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_MEDIA_CLASS @@ -694,6 +695,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_update_string(stream_props, str, strlen(str)); copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-vban-send.c b/src/modules/module-vban-send.c index 5fc6793a1..a3ea7c760 100644 --- a/src/modules/module-vban-send.c +++ b/src/modules/module-vban-send.c @@ -68,6 +68,7 @@ * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS + * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION @@ -417,6 +418,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_AUDIO_FORMAT); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); + copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 941fa1acd..da3469583 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -196,6 +196,7 @@ static int parse_audio_info(const struct pw_properties *props, struct spa_audio_ SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } From ece9545695b2a3af5ca30cbed9841c6f1ad97f96 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Wed, 29 Oct 2025 21:38:00 +0200 Subject: [PATCH 1007/1014] NEWS: update Bluetooth items Update changelog for 1.5.81 Bluetooth items. --- NEWS | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 016ebfa89..b6eba76cb 100644 --- a/NEWS +++ b/NEWS @@ -166,8 +166,11 @@ also contains some new features: ## Bluetooth - Telephony improvements. - ASHA support was added. - - Add packet loss correction with spandsp for some codecs. - - Synchronisation between ISO streams in the same group. + - Packet loss concealment was added. + - Improved synchronisation between LE Audio streams in the same group. + - Improved LE Audio device compatibility. + - LC3-24kHz voice codec was added (used by Airpods) + - LDAC decoding support added (requires separate decoder library) ## Pulse-server - The SUSPEND event is now correctly generated. fail-on-suspend is From e9a89822f87a184c9f4a745449305eb5b1944acd Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 30 Oct 2025 17:32:09 +0100 Subject: [PATCH 1008/1014] adapter: only recheck formats when convert EnumFormat changed Instead of always renegotiating a new format and buffers when the driver changes, only renegotiate when the converter notifies us of an EnumFormat change. It's possible that changing the driver does not actually change the result of EnumFormat and then we would simply renegotiate to the same format and buffers for nothing. In the audioconvert, check if the target_rate of the new driver has changed. We keep the last value in our own clock so that we don't renegotiate when we are removed from a driver (which sets our own clock as the position). The target rate influences the result of the EnumFormat params when we are resampling and so we need to emit a EnumFormat change. --- spa/plugins/audioconvert/audioadapter.c | 11 ++++++-- spa/plugins/audioconvert/audioconvert.c | 36 +++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 7c4605c8a..ccc9f48df 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -931,7 +931,6 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Position: this->io_position = data; - this->recheck_format = true; break; default: break; @@ -1220,6 +1219,9 @@ static void follower_convert_port_info(void *data, case SPA_PARAM_Tag: idx = IDX_Tag; break; + case SPA_PARAM_EnumFormat: + idx = IDX_EnumFormat; + break; default: continue; } @@ -1247,6 +1249,11 @@ static void follower_convert_port_info(void *data, spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); } + if (idx == IDX_EnumFormat) { + spa_log_info(this->log, "new EnumFormat from converter"); + /* we will renegotiate when restarting */ + this->recheck_format = true; + } spa_log_debug(this->log, "param %d changed", info->params[i].id); } } @@ -1445,7 +1452,7 @@ static void follower_port_info(void *data, spa_strerror(res)); } if (idx == IDX_EnumFormat) { - spa_log_debug(this->log, "new formats"); + spa_log_debug(this->log, "new EnumFormat from follower"); /* we will renegotiate when restarting */ this->recheck_format = true; } diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 72ad5d792..ebb057218 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -282,6 +282,7 @@ struct impl { struct props props; + struct spa_io_clock *io_clock; struct spa_io_position *io_position; struct spa_io_rate_match *io_rate_match; @@ -1005,12 +1006,43 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); switch (id) { - case SPA_IO_Position: - this->io_position = data; + case SPA_IO_Clock: + this->io_clock = data; break; + case SPA_IO_Position: + { + struct port *p; + uint32_t i; + + this->io_position = data; + + if (this->io_position && this->io_clock && + this->io_position->clock.target_rate.denom != this->io_clock->target_rate.denom && + !this->props.resample_disabled) { + spa_log_warn(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id, + this->io_clock->target_rate.denom, + this->io_position->clock.target_rate.denom); + + this->io_clock->target_rate = this->io_position->clock.target_rate; + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + if ((p = GET_IN_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) { + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + p->params[IDX_EnumFormat].user++; + } + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + if ((p = GET_OUT_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) { + p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + p->params[IDX_EnumFormat].user++; + } + } + } + break; + } default: return -ENOENT; } + emit_info(this, false); return 0; } From 77a51002804ed2281f9d116979d5858f7f3eee37 Mon Sep 17 00:00:00 2001 From: Robert Mader Date: Thu, 30 Oct 2025 16:22:52 +0100 Subject: [PATCH 1009/1014] gst: Use gst_util_uint64_scale instead of scale_int GST_SECOND * t.rate.num can turn into a negative gint, resulting in assertions like: _gst_util_uint64_scale_int: assertion 'num >= 0' failed Just use the 64bit version instead. --- src/gst/gstpipewireclock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gst/gstpipewireclock.c b/src/gst/gstpipewireclock.c index 701bb6ff0..10607c3e7 100644 --- a/src/gst/gstpipewireclock.c +++ b/src/gst/gstpipewireclock.c @@ -45,7 +45,7 @@ gst_pipewire_clock_get_internal_time (GstClock * clock) t.rate.denom == 0) return pclock->last_time; - result = gst_util_uint64_scale_int (t.ticks, GST_SECOND * t.rate.num, t.rate.denom); + result = gst_util_uint64_scale (t.ticks, GST_SECOND * t.rate.num, t.rate.denom); result += now - t.now; result += pclock->time_offset; From 95fb03c8e34171d603add43a1b33fdc50bcffe0d Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 31 Oct 2025 11:39:41 +0100 Subject: [PATCH 1010/1014] spa: add some more POD tests Check if the pod size is at least big enough to hold 1 item when getting array or choice items. Check that the number of choice values is at least enough to handle the given choice type. Remove some redundant checks. --- spa/include/spa/debug/format.h | 3 +-- spa/include/spa/pod/body.h | 28 ++++++++++++++++++++++++++-- spa/include/spa/pod/filter.h | 6 ++---- spa/include/spa/pod/iter.h | 2 +- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index 874dd094a..1448ae223 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -164,8 +164,7 @@ SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int in type = val->type; size = val->size; - if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1 || - size < spa_pod_type_size(type)) + if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1) continue; vals = SPA_POD_BODY(val); diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h index 07758c7e6..51f8e8f74 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -78,6 +78,27 @@ SPA_API_POD_BODY uint32_t spa_pod_type_size(uint32_t type) return 0; } +SPA_API_POD_BODY uint32_t spa_pod_choice_min_values(uint32_t choice_type) +{ + switch (choice_type) { + case SPA_CHOICE_Enum: + return 2; + case SPA_CHOICE_Range: + return 3; + case SPA_CHOICE_Step: + return 4; + case SPA_CHOICE_None: + case SPA_CHOICE_Flags: + default: + /* + * This must always return at least 1, because callers + * assume that n_vals >= spa_pod_choice_min_values() + * mean that n_vals is at least 1. + */ + return 1; + } +} + SPA_API_POD_BODY int spa_pod_body_from_data(void *data, size_t maxsize, off_t offset, size_t size, struct spa_pod *pod, const void **body) { @@ -333,6 +354,8 @@ SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_ *n_values = child_size ? (arr->pod.size - sizeof(arr->body)) / child_size : 0; *val_size = child_size; *val_type = arr->body.child.type; + if (*val_size < spa_pod_type_size(*val_type)) + *n_values = 0; return body; } @@ -371,8 +394,9 @@ SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod *val_type = pod->body.child.type; *n_values = child_size ? (pod->pod.size - sizeof(pod->body)) / child_size : 0; *choice = pod->body.type; - if (*choice == SPA_CHOICE_None) - *n_values = SPA_MIN(1u, *n_values); + if (*n_values < spa_pod_choice_min_values(*choice) || + *val_size < spa_pod_type_size(*val_type)) + *n_values = 0; return body; } diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 6e238c2e6..93d9a51ea 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -82,7 +82,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c); v2 = spa_pod_get_values(&p2->value, &nalt2, &p2c); - /* empty choices */ + /* empty/invalid choices */ if (nalt1 < 1 || nalt2 < 1) return -EINVAL; @@ -95,8 +95,6 @@ spa_pod_filter_prop(struct spa_pod_builder *b, /* incompatible property types */ if (type != v2->type || size != v2->size || p1->key != p2->key) return -EINVAL; - if (size < spa_pod_type_size(type)) - return -EINVAL; /* start with copying the property */ spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); @@ -406,7 +404,7 @@ SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); const void *vals = SPA_POD_BODY(v); - if (v->size < spa_pod_type_size(v->type)) + if (nvals < 1) continue; if (spa_pod_compare_is_valid_choice(v->type, v->size, diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index a0da580ea..779286e0c 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -228,7 +228,7 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, spa_pod_choice_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, choice, &size, &type); return (struct spa_pod*)&p->body.child; } else { - *n_vals = 1; + *n_vals = pod->size < spa_pod_type_size(pod->type) ? 0 : 1; *choice = SPA_CHOICE_None; return (struct spa_pod*)pod; } From a60eb4fe648d09c02929ce9bbc85ce1d8917a1f4 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 31 Oct 2025 11:48:22 +0100 Subject: [PATCH 1011/1014] stream: add some redundant checks Remove some redundant checks and handle calloc failure. --- src/pipewire/stream.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 4085e0885..9387ee58a 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -1270,6 +1270,9 @@ static int node_event_param(void *object, int seq, return 0; c = calloc(1, sizeof(*c) + SPA_POD_SIZE(param)); + if (c == NULL) + return -errno; + c->info = SPA_PTROFF(c, sizeof(*c), struct spa_pod); memcpy(c->info, param, SPA_POD_SIZE(param)); c->control.n_values = 0; @@ -1286,7 +1289,7 @@ static int node_event_param(void *object, int seq, } pod = spa_pod_get_values(type, &n_vals, &choice); - if (n_vals == 0) { + if (n_vals < 1) { free(c); return -EINVAL; } @@ -1318,19 +1321,12 @@ static int node_event_param(void *object, int seq, switch (choice) { case SPA_CHOICE_None: - if (n_vals < 1) { - free(c); - return -EINVAL; - } c->control.n_values = 1; c->control.max_values = 1; c->control.values[0] = c->control.def = c->control.min = c->control.max = vals[0]; break; case SPA_CHOICE_Range: - if (n_vals < 3) { - free(c); - return -EINVAL; - } + case SPA_CHOICE_Step: c->control.n_values = 1; c->control.max_values = 1; c->control.values[0] = vals[0]; From d89c85d3747ae88c1d43aefd2a150eb1784a7913 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 31 Oct 2025 11:51:32 +0100 Subject: [PATCH 1012/1014] doc: improve spa_pod_get_values() docs --- doc/dox/api/spa-pod.dox | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/dox/api/spa-pod.dox b/doc/dox/api/spa-pod.dox index 67ee20750..7d4e244cf 100644 --- a/doc/dox/api/spa-pod.dox +++ b/doc/dox/api/spa-pod.dox @@ -435,10 +435,11 @@ spa_pod_parser_get_object(&p, \endcode `spa_pod_get_values()` is a useful function. It returns a -`struct spa_pod*` with and array of values. For normal POD's -and choice none values, it simply returns the POD and one value. -For other choice values it returns the choice type and an array -of values: +`struct spa_pod*` with and array of values. For invalid PODs +it returns the POD and no values. For normal PODs it returns +the POD and one value. For choice values it returns the choice +type and an array of values. If the choice doesn't fit even a +single value, the array will have no values. \code{.c} struct spa_pod *value; From c63c100c4b85dec37481557168a80eaf83f4b53a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 31 Oct 2025 12:04:00 +0100 Subject: [PATCH 1013/1014] pulse: use more specific type when parsing params We expect a Struct for the classes and a Object for the props so we can use the right POD type when persing. --- src/modules/module-protocol-pulse/collect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c index 32e30d120..300eee262 100644 --- a/src/modules/module-protocol-pulse/collect.c +++ b/src/modules/module-protocol-pulse/collect.c @@ -169,7 +169,7 @@ uint32_t collect_profile_info(struct pw_manager_object *card, struct card_info * SPA_PARAM_PROFILE_description, SPA_POD_OPT_String(&pi->description), SPA_PARAM_PROFILE_priority, SPA_POD_OPT_Int(&pi->priority), SPA_PARAM_PROFILE_available, SPA_POD_OPT_Id(&pi->available), - SPA_PARAM_PROFILE_classes, SPA_POD_OPT_Pod(&classes)) < 0) { + SPA_PARAM_PROFILE_classes, SPA_POD_OPT_PodStruct(&classes)) < 0) { continue; } if (pi->description == NULL) @@ -246,7 +246,7 @@ static void collect_device_info(struct pw_manager_object *device, struct pw_mana SPA_TYPE_OBJECT_ParamRoute, NULL, SPA_PARAM_ROUTE_index, SPA_POD_Int(&index), SPA_PARAM_ROUTE_device, SPA_POD_Int(&dev), - SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props)) < 0) + SPA_PARAM_ROUTE_props, SPA_POD_OPT_PodObject(&props)) < 0) continue; if (dev != dev_info->device) continue; From 62022ce623d0eab7b312125da0bdeb000b5e3865 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Thu, 10 Jul 2025 13:10:02 -0400 Subject: [PATCH 1014/1014] pod: doc: Deprecate unused types I found no uses of SPA_TYPE_Bitmap outside of the SPA code itself and the now-removed v0 protocol support. Wim Taymans confirmed that it has no uses and is not usable. Deprecate it so it can hopefully be removed. --- doc/dox/api/spa-pod.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/dox/api/spa-pod.dox b/doc/dox/api/spa-pod.dox index 7d4e244cf..2ce8d6297 100644 --- a/doc/dox/api/spa-pod.dox +++ b/doc/dox/api/spa-pod.dox @@ -33,7 +33,7 @@ POD's can contain a number of basic SPA types: - `SPA_TYPE_Bytes`: A byte array. - `SPA_TYPE_Rectangle`: A rectangle with width and height. - `SPA_TYPE_Fraction`: A fraction with numerator and denominator. -- `SPA_TYPE_Bitmap`: An array of bits. +- `SPA_TYPE_Bitmap`: An array of bits. Deprecated and unused. POD's can be grouped together in these container types: