From f215062678a9af7bec6f9d7c56e86059ca636256 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 18 Jan 2026 09:30:49 +1000 Subject: [PATCH] PipeWire: Refactoring in an attempt to work around an PipeWire issue. It turns out this didn't actually fix the problem, but I actually prefer this version so I'm going to keep this. --- extras/backends/pipewire/miniaudio_pipewire.c | 175 ++++++++++-------- 1 file changed, 96 insertions(+), 79 deletions(-) diff --git a/extras/backends/pipewire/miniaudio_pipewire.c b/extras/backends/pipewire/miniaudio_pipewire.c index 8df7ea55..915f4242 100644 --- a/extras/backends/pipewire/miniaudio_pipewire.c +++ b/extras/backends/pipewire/miniaudio_pipewire.c @@ -1973,7 +1973,9 @@ static void ma_stream_event_param_changed_enumeration__pipewire(void* pUserData, { ma_stream_event_param_changed_enumeration_data__pipewire* pData = (ma_stream_event_param_changed_enumeration_data__pipewire*)pUserData; - + if (pParam == NULL) { + return; + } if (id == MA_SPA_PARAM_Format) { ma_pipewire_audio_info audioInfo; @@ -2002,6 +2004,91 @@ static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Enumeration = NULL, /* trigger_done */ }; +static void ma_add_native_data_format__pipewire(ma_context_state_pipewire* pContextStatePipeWire, struct ma_pw_core* pCore, struct ma_pw_loop* pLoop, ma_device_type deviceType, ma_device_info* pDeviceInfo) +{ + struct ma_pw_properties* pProperties; + struct ma_pw_stream* pStream; + const char* pNodeName = NULL; + + MA_PIPEWIRE_ASSERT(pDeviceInfo != NULL); + + pNodeName = pDeviceInfo->id.custom.s; + + pProperties = pContextStatePipeWire->pw_properties_new( + MA_PW_KEY_MEDIA_TYPE, "Audio", + MA_PW_KEY_MEDIA_CATEGORY, (deviceType == ma_device_type_playback) ? "Playback" : "Capture", + MA_PW_KEY_MEDIA_ROLE, "Game", + MA_PW_KEY_TARGET_OBJECT, pNodeName, + NULL); + + pStream = pContextStatePipeWire->pw_stream_new(pCore, "miniaudio-enumeration", pProperties); + if (pStream != NULL) { + ma_stream_event_param_changed_enumeration_data__pipewire eventsData; + struct ma_spa_hook eventListener; + enum ma_spa_audio_format formatPA; + struct ma_spa_pod_audio_info_raw podAudioInfo; + const struct ma_spa_pod* pConnectionParameters[1]; + enum ma_pw_stream_flags streamFlags; + int connectResult; + + /* The way to determine the "native" channel count and sample rate is by handling the SPA_PARAM_Format event in the params_changed callback. */ + MA_PIPEWIRE_ZERO_OBJECT(&eventsData); + eventsData.pLog = pContextStatePipeWire->pLog; + + pContextStatePipeWire->pw_stream_add_listener(pStream, &eventListener, &ma_gStreamEventsPipeWire_Enumeration, &eventsData); + + + /* Now we can connect the stream. */ + if (ma_is_little_endian()) { + formatPA = MA_SPA_AUDIO_FORMAT_F32_LE; + } else { + formatPA = MA_SPA_AUDIO_FORMAT_F32_BE; + } + + podAudioInfo = ma_spa_pod_audio_info_raw_init(formatPA, 0, 0); + pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; + + streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT); + connectResult = pContextStatePipeWire->pw_stream_connect(pStream, (deviceType == ma_device_type_playback) ? MA_SPA_DIRECTION_OUTPUT : MA_SPA_DIRECTION_INPUT, MA_PW_ID_ANY, streamFlags, pConnectionParameters, ma_countof(pConnectionParameters)); + if (connectResult >= 0) { + /* Keep looping until we've processed our channel count and sample rate. */ + while (!eventsData.done) { + pContextStatePipeWire->pw_loop_iterate(pLoop, 1); + } + + if (eventsData.channels != 0 && eventsData.sampleRate != 0) { + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, eventsData.channels, eventsData.channels, eventsData.sampleRate, eventsData.sampleRate); + } + } else { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); + } + + /* We're done with the stream. */ + pContextStatePipeWire->pw_stream_destroy(pStream); + } else { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); + } + + /* + In the logic above we chose the "native" format that PipeWire would pick if no channel count or sample rate is + specified during device initialization. However, PipeWire will actually do it's own data conversion, so from the + perspective of miniaudio, it also "natively" supports any format, channel count and sample rate. I'm not sure if + it's better to include these entries of if we should just leave it set to the one format. + */ + #if 0 + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s16, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s24, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_u8, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + #endif + + /* Just in case something went wrong in the logic above, if we never extracted a native format we'll add a fallback. */ + if (pDeviceInfo->nativeDataFormatCount == 0) { + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + } +} + static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumerate_devices_data_pipewire* pEnumData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props, ma_device_type deviceType) { ma_device_info deviceInfo; @@ -2034,86 +2121,10 @@ static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumer /* Name. */ ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1); - /* Data Format. Create a stream so we can identify the native channels and sample rate. */ - { - struct ma_pw_properties* pProperties; - struct ma_pw_stream* pStream; - - pProperties = pEnumData->pContextStatePipeWire->pw_properties_new( - MA_PW_KEY_MEDIA_TYPE, "Audio", - MA_PW_KEY_MEDIA_CATEGORY, (deviceType == ma_device_type_playback) ? "Playback" : "Capture", - MA_PW_KEY_MEDIA_ROLE, "Game", - MA_PW_KEY_TARGET_OBJECT, pNodeName, - NULL); - - pStream = pEnumData->pContextStatePipeWire->pw_stream_new(pEnumData->pCore, "miniaudio-enumeration", pProperties); - if (pStream != NULL) { - ma_stream_event_param_changed_enumeration_data__pipewire eventsData; - struct ma_spa_hook eventListener; - enum ma_spa_audio_format formatPA; - struct ma_spa_pod_audio_info_raw podAudioInfo; - const struct ma_spa_pod* pConnectionParameters[1]; - enum ma_pw_stream_flags streamFlags; - int connectResult; - - /* The way to determine the "native" channel count and sample rate is by handling the SPA_PARAM_Format event in the params_changed callback. */ - MA_PIPEWIRE_ZERO_OBJECT(&eventsData); - eventsData.pLog = pEnumData->pContextStatePipeWire->pLog; - - pEnumData->pContextStatePipeWire->pw_stream_add_listener(pStream, &eventListener, &ma_gStreamEventsPipeWire_Enumeration, &eventsData); + /* The native data format is set later in a second pass. */ - /* Now we can connect the stream. */ - if (ma_is_little_endian()) { - formatPA = MA_SPA_AUDIO_FORMAT_F32_LE; - } else { - formatPA = MA_SPA_AUDIO_FORMAT_F32_BE; - } - - podAudioInfo = ma_spa_pod_audio_info_raw_init(formatPA, 0, 0); - pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; - - streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT); - connectResult = pEnumData->pContextStatePipeWire->pw_stream_connect(pStream, (deviceType == ma_device_type_playback) ? MA_SPA_DIRECTION_OUTPUT : MA_SPA_DIRECTION_INPUT, MA_PW_ID_ANY, streamFlags, pConnectionParameters, ma_countof(pConnectionParameters)); - if (connectResult >= 0) { - /* Keep looping until we've processed our channel count and sample rate. */ - while (!eventsData.done) { - pEnumData->pContextStatePipeWire->pw_loop_iterate(pEnumData->pLoop, 1); - } - - if (eventsData.channels != 0 && eventsData.sampleRate != 0) { - ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, eventsData.channels, eventsData.channels, eventsData.sampleRate, eventsData.sampleRate); - } - } else { - ma_log_postf(pEnumData->pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); - } - - /* We're done with the stream. */ - pEnumData->pContextStatePipeWire->pw_stream_destroy(pStream); - } else { - ma_log_postf(pEnumData->pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); - } - } - - /* - In the logic above we chose the "native" format that PipeWire would pick if no channel count or sample rate is - specified during device initialization. However, PipeWire will actually do it's own data conversion, so from the - perspective of miniaudio, it also "natively" supports any format, channel count and sample rate. I'm not sure if - it's better to include these entries of if we should just leave it set to the one format. - */ - #if 0 - ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - ma_device_info_add_native_data_format(&deviceInfo, ma_format_s32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - ma_device_info_add_native_data_format(&deviceInfo, ma_format_s24, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - #endif - - /* Just in case something went wrong in the logic above, if we never extracted a native format we'll add a fallback. */ - if (deviceInfo.nativeDataFormatCount == 0) { - ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - } - + /* Finally we can add the device into to our internal list. */ ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, &deviceInfo); /*printf("Registry Global Added By Type: ID=%u, Type=%s, DeviceType=%d, NiceName=%s\n", id, type, deviceType, pNiceName);*/ @@ -2269,6 +2280,9 @@ static ma_result ma_context_enumerate_devices__pipewire(ma_context* pContext, ma hasDefaultPlaybackDevice = MA_TRUE; } + /* Now we need to open the stream and get it's native data format. */ + ma_add_native_data_format__pipewire(pContextStatePipeWire, pCore, pLoop, ma_device_type_playback, &enumData.playback.pDeviceInfos[iDevice]); + cbResult = callback(ma_device_type_playback, &enumData.playback.pDeviceInfos[iDevice], pUserData); } } @@ -2288,6 +2302,9 @@ static ma_result ma_context_enumerate_devices__pipewire(ma_context* pContext, ma hasDefaultCaptureDevice = MA_TRUE; } + /* Now we need to open the stream and get it's native data format. */ + ma_add_native_data_format__pipewire(pContextStatePipeWire, pCore, pLoop, ma_device_type_capture, &enumData.capture.pDeviceInfos[iDevice]); + cbResult = callback(ma_device_type_capture, &enumData.capture.pDeviceInfos[iDevice], pUserData); } }