From f37ffed28359a23081f18de3fd3d802f1cbb40c5 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 20 Jan 2026 17:15:32 +1000 Subject: [PATCH] Merge PipeWire backend into the main library. --- CMakeLists.txt | 25 +- extras/backends/pipewire/miniaudio_pipewire.c | 3183 ----------------- extras/backends/pipewire/miniaudio_pipewire.h | 34 - miniaudio.h | 3101 +++++++++++++++- tests/deviceio/deviceio.c | 25 +- 5 files changed, 3099 insertions(+), 3269 deletions(-) delete mode 100644 extras/backends/pipewire/miniaudio_pipewire.c delete mode 100644 extras/backends/pipewire/miniaudio_pipewire.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 32de9458..5d266df9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -607,24 +607,6 @@ target_compile_definitions(miniaudio PRIVATE ${COMPILE_DEFINES}) # Extra Backends if(NOT MINIAUDIO_NO_DEVICEIO) - if(NOT MINIAUDIO_NO_PIPEWIRE) - add_library(miniaudio_pipewire STATIC - extras/backends/pipewire/miniaudio_pipewire.c - extras/backends/pipewire/miniaudio_pipewire.h - ) - - list(APPEND LIBS_TO_INSTALL miniaudio_pipewire) - install(FILES extras/backends/pipewire/miniaudio_pipewire.h DESTINATION include/miniaudio/extras/backends/pipewire) - - target_include_directories(miniaudio_pipewire PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/extras/backends/pipewire) - target_compile_options (miniaudio_pipewire PRIVATE ${COMPILE_OPTIONS}) - target_compile_definitions(miniaudio_pipewire PRIVATE ${COMPILE_DEFINES}) - - if(MINIAUDIO_NO_RUNTIME_LINKING AND TARGET PkgConfig::PipeWire AND TARGET PkgConfig::SPA) - target_link_libraries(miniaudio_pipewire PRIVATE PkgConfig::PipeWire PkgConfig::SPA) - endif() - endif() - if(NOT MINIAUDIO_NO_SDL2) add_library(miniaudio_sdl2 STATIC extras/backends/sdl2/miniaudio_sdl2.c @@ -757,8 +739,7 @@ if(MINIAUDIO_NO_RUNTIME_LINKING) if(TARGET PkgConfig::PipeWire AND TARGET PkgConfig::SPA) target_link_libraries(miniaudio PRIVATE PkgConfig::PipeWire PkgConfig::SPA) target_link_libraries(miniaudio_test INTERFACE PkgConfig::PipeWire PkgConfig::SPA) - list(APPEND LINKED_LIBS libpipewire-0.3) - list(APPEND LINKED_LIBS libspa-0.2) + list(APPEND LINKED_LIBS libpipewire-0.3 libspa-0.2) endif() endif() @@ -806,10 +787,6 @@ if(MINIAUDIO_BUILD_TESTS) function(add_miniaudio_test name source) add_executable(${name} ${TESTS_DIR}/${source}) target_link_libraries(${name} PRIVATE miniaudio_test) - if(TARGET miniaudio_pipewire) - target_link_libraries(${name} PRIVATE miniaudio_pipewire) - target_compile_definitions(${name} PRIVATE MA_TESTS_INCLUDE_PIPEWIRE) - endif() endfunction() # Disable C++ tests when forcing C89. This is needed because we'll be passing -std=c89 which will cause errors when trying to compile a C++ file. diff --git a/extras/backends/pipewire/miniaudio_pipewire.c b/extras/backends/pipewire/miniaudio_pipewire.c deleted file mode 100644 index 5fb75fa3..00000000 --- a/extras/backends/pipewire/miniaudio_pipewire.c +++ /dev/null @@ -1,3183 +0,0 @@ -#ifndef miniaudio_backend_pipewire_c -#define miniaudio_backend_pipewire_c - -#include "miniaudio_pipewire.h" - -/* This is temporary until this is merged into miniaudio proper. */ -MA_API MA_NO_INLINE int ma_pipewire_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) -{ - int sign; - unsigned int valueU; - char* dstEnd; - - if (dst == NULL || dstSizeInBytes == 0) { - return 22; - } - if (radix < 2 || radix > 36) { - dst[0] = '\0'; - return 22; - } - - sign = (value < 0 && radix == 10) ? -1 : 1; /* The negative sign is only used when the base is 10. */ - - if (value < 0) { - valueU = -value; - } else { - valueU = value; - } - - dstEnd = dst; - do - { - int remainder = valueU % radix; - if (remainder > 9) { - *dstEnd = (char)((remainder - 10) + 'a'); - } else { - *dstEnd = (char)(remainder + '0'); - } - - dstEnd += 1; - dstSizeInBytes -= 1; - valueU /= radix; - } while (dstSizeInBytes > 0 && valueU > 0); - - if (dstSizeInBytes == 0) { - dst[0] = '\0'; - return 22; /* Ran out of room in the output buffer. */ - } - - if (sign < 0) { - *dstEnd++ = '-'; - dstSizeInBytes -= 1; - } - - if (dstSizeInBytes == 0) { - dst[0] = '\0'; - return 22; /* Ran out of room in the output buffer. */ - } - - *dstEnd = '\0'; - - - /* At this point the string will be reversed. */ - dstEnd -= 1; - while (dst < dstEnd) { - char temp = *dst; - *dst = *dstEnd; - *dstEnd = temp; - - dst += 1; - dstEnd -= 1; - } - - return 0; -} - -MA_API MA_NO_INLINE int ma_pipewire_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) -{ - char* dstorig; - - if (dst == 0) { - return 22; - } - if (dstSizeInBytes == 0) { - return 34; - } - if (src == 0) { - dst[0] = '\0'; - return 22; - } - - dstorig = dst; - - while (dstSizeInBytes > 0 && dst[0] != '\0') { - dst += 1; - dstSizeInBytes -= 1; - } - - if (dstSizeInBytes == 0) { - return 22; /* Unterminated. */ - } - - - while (dstSizeInBytes > 0 && src[0] != '\0') { - *dst++ = *src++; - dstSizeInBytes -= 1; - } - - if (dstSizeInBytes > 0) { - dst[0] = '\0'; - } else { - dstorig[0] = '\0'; - return 34; - } - - return 0; -} - - -#include /* memset() */ -#include /* assert() */ -#include /* atoi() */ - -#ifndef MA_PIPEWIRE_ASSERT -#define MA_PIPEWIRE_ASSERT(x) assert(x) -#endif - -#ifndef MA_PIPEWIRE_COPY_MEMORY -#define MA_PIPEWIRE_COPY_MEMORY(dest, src, size) memcpy((dest), (src), (size)) -#endif - -#ifndef MA_PIPEWIRE_ZERO_MEMORY -#define MA_PIPEWIRE_ZERO_MEMORY(x, size) memset((x), 0, (size)) -#endif - -#ifndef MA_PIPEWIRE_ZERO_OBJECT -#define MA_PIPEWIRE_ZERO_OBJECT(x) MA_PIPEWIRE_ZERO_MEMORY((x), sizeof(*(x))) -#endif - - -#if defined(MA_LINUX) - #if !defined(MA_NO_RUNTIME_LINKING) || (defined(__STDC_VERSION__) || defined(__cplusplus)) /* <-- PipeWire cannot be used with C89 mode (__STDC_VERSION__ is only defined starting with C90). */ - #define MA_SUPPORT_PIPEWIRE - #endif -#endif - -#if defined(MA_SUPPORT_PIPEWIRE) && !defined(MA_NO_PIPEWIRE) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PIPEWIRE)) - #define MA_HAS_PIPEWIRE -#endif - -#if defined(MA_HAS_PIPEWIRE) -#if defined(MA_NO_RUNTIME_LINKING) -#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc99-extensions" - #pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" - #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" - #endif -#endif - -#include -#include - -#include -#include -#include -#include -#include - -#define ma_spa_source_event_func_t spa_source_event_func_t - -#define ma_spa_direction spa_direction -#define ma_spa_audio_format spa_audio_format -#define ma_spa_audio_channel spa_audio_channel -#define ma_spa_callbacks spa_callbacks -#define ma_spa_interface spa_interface -#define ma_spa_dict_item spa_dict_item -#define MA_SPA_DICT_ITEM_INIT SPA_DICT_ITEM_INIT -#define ma_spa_dict spa_dict -#define MA_SPA_DICT_INIT SPA_DICT_INIT -#define ma_spa_dict_lookup spa_dict_lookup -#define ma_spa_chunk spa_chunk -#define ma_spa_data spa_data -#define ma_spa_meta spa_meta -#define ma_spa_buffer spa_buffer -#define ma_spa_list spa_list -#define ma_spa_hook spa_hook -#define ma_spa_pod spa_pod -#define ma_spa_pod_id spa_pod_id -#define ma_spa_pod_int spa_pod_int -#define ma_spa_choice_type spa_choice_type -#define ma_spa_pod_choice spa_pod_choice -#define ma_spa_pod_choice_body spa_pod_choice_body -#define ma_spa_pod_array_body spa_pod_array_body -#define ma_spa_pod_array spa_pod_array -#define ma_spa_pod_object_body spa_pod_object_body -#define ma_spa_pod_object spa_pod_object -#define ma_spa_pod_prop spa_pod_prop -#define ma_spa_command spa_command -#define ma_spa_fraction spa_fraction - -#define MA_SPA_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT -#define MA_SPA_DIRECTION_INPUT SPA_DIRECTION_INPUT - -#define MA_SPA_TYPE_Id SPA_TYPE_Id -#define MA_SPA_TYPE_Int SPA_TYPE_Int -#define MA_SPA_TYPE_Array SPA_TYPE_Array -#define MA_SPA_TYPE_Object SPA_TYPE_Object -#define MA_SPA_TYPE_Choice SPA_TYPE_Choice - -#define MA_SPA_TYPE_OBJECT_Format SPA_TYPE_OBJECT_Format -#define MA_SPA_TYPE_OBJECT_ParamBuffers SPA_TYPE_OBJECT_ParamBuffers - -#define MA_SPA_PARAM_EnumFormat SPA_PARAM_EnumFormat -#define MA_SPA_PARAM_Format SPA_PARAM_Format -#define MA_SPA_PARAM_Buffers SPA_PARAM_Buffers - -#define MA_SPA_PARAM_BUFFERS_buffers SPA_PARAM_BUFFERS_buffers -#define MA_SPA_PARAM_BUFFERS_blocks SPA_PARAM_BUFFERS_blocks -#define MA_SPA_PARAM_BUFFERS_size SPA_PARAM_BUFFERS_size -#define MA_SPA_PARAM_BUFFERS_stride SPA_PARAM_BUFFERS_stride - -#define MA_SPA_FORMAT_mediaType SPA_FORMAT_mediaType -#define MA_SPA_FORMAT_mediaSubtype SPA_FORMAT_mediaSubtype - -#define MA_SPA_MEDIA_TYPE_audio SPA_MEDIA_TYPE_audio -#define MA_SPA_MEDIA_SUBTYPE_raw SPA_MEDIA_SUBTYPE_raw - -#define MA_SPA_FORMAT_AUDIO_format SPA_FORMAT_AUDIO_format -#define MA_SPA_FORMAT_AUDIO_rate SPA_FORMAT_AUDIO_rate -#define MA_SPA_FORMAT_AUDIO_channels SPA_FORMAT_AUDIO_channels -#define MA_SPA_FORMAT_AUDIO_position SPA_FORMAT_AUDIO_position - -#define MA_SPA_AUDIO_FORMAT_UNKNOWN SPA_AUDIO_FORMAT_UNKNOWN -#define MA_SPA_AUDIO_FORMAT_U8 SPA_AUDIO_FORMAT_U8 -#define MA_SPA_AUDIO_FORMAT_S16_LE SPA_AUDIO_FORMAT_S16_LE -#define MA_SPA_AUDIO_FORMAT_S16_BE SPA_AUDIO_FORMAT_S16_BE -#define MA_SPA_AUDIO_FORMAT_S24_LE SPA_AUDIO_FORMAT_S24_LE -#define MA_SPA_AUDIO_FORMAT_S24_BE SPA_AUDIO_FORMAT_S24_BE -#define MA_SPA_AUDIO_FORMAT_S32_LE SPA_AUDIO_FORMAT_S32_LE -#define MA_SPA_AUDIO_FORMAT_S32_BE SPA_AUDIO_FORMAT_S32_BE -#define MA_SPA_AUDIO_FORMAT_F32_LE SPA_AUDIO_FORMAT_F32_LE -#define MA_SPA_AUDIO_FORMAT_F32_BE SPA_AUDIO_FORMAT_F32_BE - -#define MA_SPA_AUDIO_CHANNEL_MONO SPA_AUDIO_CHANNEL_MONO -#define MA_SPA_AUDIO_CHANNEL_FL SPA_AUDIO_CHANNEL_FL -#define MA_SPA_AUDIO_CHANNEL_FR SPA_AUDIO_CHANNEL_FR -#define MA_SPA_AUDIO_CHANNEL_FC SPA_AUDIO_CHANNEL_FC -#define MA_SPA_AUDIO_CHANNEL_LFE SPA_AUDIO_CHANNEL_LFE -#define MA_SPA_AUDIO_CHANNEL_SL SPA_AUDIO_CHANNEL_SL -#define MA_SPA_AUDIO_CHANNEL_SR SPA_AUDIO_CHANNEL_SR -#define MA_SPA_AUDIO_CHANNEL_FLC SPA_AUDIO_CHANNEL_FLC -#define MA_SPA_AUDIO_CHANNEL_FRC SPA_AUDIO_CHANNEL_FRC -#define MA_SPA_AUDIO_CHANNEL_RC SPA_AUDIO_CHANNEL_RC -#define MA_SPA_AUDIO_CHANNEL_RL SPA_AUDIO_CHANNEL_RL -#define MA_SPA_AUDIO_CHANNEL_RR SPA_AUDIO_CHANNEL_RR -#define MA_SPA_AUDIO_CHANNEL_TC SPA_AUDIO_CHANNEL_TC -#define MA_SPA_AUDIO_CHANNEL_TFL SPA_AUDIO_CHANNEL_TFL -#define MA_SPA_AUDIO_CHANNEL_TFC SPA_AUDIO_CHANNEL_TFC -#define MA_SPA_AUDIO_CHANNEL_TFR SPA_AUDIO_CHANNEL_TFR -#define MA_SPA_AUDIO_CHANNEL_TRL SPA_AUDIO_CHANNEL_TRL -#define MA_SPA_AUDIO_CHANNEL_TRC SPA_AUDIO_CHANNEL_TRC -#define MA_SPA_AUDIO_CHANNEL_TRR SPA_AUDIO_CHANNEL_TRR -#define MA_SPA_AUDIO_CHANNEL_RLC SPA_AUDIO_CHANNEL_RLC -#define MA_SPA_AUDIO_CHANNEL_RRC SPA_AUDIO_CHANNEL_RRC -#define MA_SPA_AUDIO_CHANNEL_FLW SPA_AUDIO_CHANNEL_FLW -#define MA_SPA_AUDIO_CHANNEL_FRW SPA_AUDIO_CHANNEL_FRW -#define MA_SPA_AUDIO_CHANNEL_LFE2 SPA_AUDIO_CHANNEL_LFE2 -#define MA_SPA_AUDIO_CHANNEL_FLH SPA_AUDIO_CHANNEL_FLH -#define MA_SPA_AUDIO_CHANNEL_FCH SPA_AUDIO_CHANNEL_FCH -#define MA_SPA_AUDIO_CHANNEL_FRH SPA_AUDIO_CHANNEL_FRH -#define MA_SPA_AUDIO_CHANNEL_TFLC SPA_AUDIO_CHANNEL_TFLC -#define MA_SPA_AUDIO_CHANNEL_TFRC SPA_AUDIO_CHANNEL_TFRC -#define MA_SPA_AUDIO_CHANNEL_TSL SPA_AUDIO_CHANNEL_TSL -#define MA_SPA_AUDIO_CHANNEL_TSR SPA_AUDIO_CHANNEL_TSR -#define MA_SPA_AUDIO_CHANNEL_LLFE SPA_AUDIO_CHANNEL_LLFE -#define MA_SPA_AUDIO_CHANNEL_RLFE SPA_AUDIO_CHANNEL_RLFE -#define MA_SPA_AUDIO_CHANNEL_BC SPA_AUDIO_CHANNEL_BC -#define MA_SPA_AUDIO_CHANNEL_BLC SPA_AUDIO_CHANNEL_BLC -#define MA_SPA_AUDIO_CHANNEL_BRC SPA_AUDIO_CHANNEL_BRC -#define MA_SPA_AUDIO_CHANNEL_AUX0 SPA_AUDIO_CHANNEL_AUX0 -#define MA_SPA_AUDIO_CHANNEL_AUX1 SPA_AUDIO_CHANNEL_AUX1 -#define MA_SPA_AUDIO_CHANNEL_AUX2 SPA_AUDIO_CHANNEL_AUX2 -#define MA_SPA_AUDIO_CHANNEL_AUX3 SPA_AUDIO_CHANNEL_AUX3 -#define MA_SPA_AUDIO_CHANNEL_AUX4 SPA_AUDIO_CHANNEL_AUX4 -#define MA_SPA_AUDIO_CHANNEL_AUX5 SPA_AUDIO_CHANNEL_AUX5 -#define MA_SPA_AUDIO_CHANNEL_AUX6 SPA_AUDIO_CHANNEL_AUX6 -#define MA_SPA_AUDIO_CHANNEL_AUX7 SPA_AUDIO_CHANNEL_AUX7 -#define MA_SPA_AUDIO_CHANNEL_AUX8 SPA_AUDIO_CHANNEL_AUX8 -#define MA_SPA_AUDIO_CHANNEL_AUX9 SPA_AUDIO_CHANNEL_AUX9 -#define MA_SPA_AUDIO_CHANNEL_AUX10 SPA_AUDIO_CHANNEL_AUX10 -#define MA_SPA_AUDIO_CHANNEL_AUX11 SPA_AUDIO_CHANNEL_AUX11 -#define MA_SPA_AUDIO_CHANNEL_AUX12 SPA_AUDIO_CHANNEL_AUX12 -#define MA_SPA_AUDIO_CHANNEL_AUX13 SPA_AUDIO_CHANNEL_AUX13 -#define MA_SPA_AUDIO_CHANNEL_AUX14 SPA_AUDIO_CHANNEL_AUX14 -#define MA_SPA_AUDIO_CHANNEL_AUX15 SPA_AUDIO_CHANNEL_AUX15 -#define MA_SPA_AUDIO_CHANNEL_AUX16 SPA_AUDIO_CHANNEL_AUX16 -#define MA_SPA_AUDIO_CHANNEL_AUX17 SPA_AUDIO_CHANNEL_AUX17 -#define MA_SPA_AUDIO_CHANNEL_AUX18 SPA_AUDIO_CHANNEL_AUX18 -#define MA_SPA_AUDIO_CHANNEL_AUX19 SPA_AUDIO_CHANNEL_AUX19 -#define MA_SPA_AUDIO_CHANNEL_AUX20 SPA_AUDIO_CHANNEL_AUX20 -#define MA_SPA_AUDIO_CHANNEL_AUX21 SPA_AUDIO_CHANNEL_AUX21 -#define MA_SPA_AUDIO_CHANNEL_AUX22 SPA_AUDIO_CHANNEL_AUX22 -#define MA_SPA_AUDIO_CHANNEL_AUX23 SPA_AUDIO_CHANNEL_AUX23 -#define MA_SPA_AUDIO_CHANNEL_AUX24 SPA_AUDIO_CHANNEL_AUX24 -#define MA_SPA_AUDIO_CHANNEL_AUX25 SPA_AUDIO_CHANNEL_AUX25 -#define MA_SPA_AUDIO_CHANNEL_AUX26 SPA_AUDIO_CHANNEL_AUX26 -#define MA_SPA_AUDIO_CHANNEL_AUX27 SPA_AUDIO_CHANNEL_AUX27 -#define MA_SPA_AUDIO_CHANNEL_AUX28 SPA_AUDIO_CHANNEL_AUX28 -#define MA_SPA_AUDIO_CHANNEL_AUX29 SPA_AUDIO_CHANNEL_AUX29 -#define MA_SPA_AUDIO_CHANNEL_AUX30 SPA_AUDIO_CHANNEL_AUX30 -#define MA_SPA_AUDIO_CHANNEL_AUX31 SPA_AUDIO_CHANNEL_AUX31 -#define MA_SPA_AUDIO_CHANNEL_AUX32 SPA_AUDIO_CHANNEL_AUX32 -#define MA_SPA_AUDIO_CHANNEL_AUX33 SPA_AUDIO_CHANNEL_AUX33 -#define MA_SPA_AUDIO_CHANNEL_AUX34 SPA_AUDIO_CHANNEL_AUX34 -#define MA_SPA_AUDIO_CHANNEL_AUX35 SPA_AUDIO_CHANNEL_AUX35 -#define MA_SPA_AUDIO_CHANNEL_AUX36 SPA_AUDIO_CHANNEL_AUX36 -#define MA_SPA_AUDIO_CHANNEL_AUX37 SPA_AUDIO_CHANNEL_AUX37 -#define MA_SPA_AUDIO_CHANNEL_AUX38 SPA_AUDIO_CHANNEL_AUX38 -#define MA_SPA_AUDIO_CHANNEL_AUX39 SPA_AUDIO_CHANNEL_AUX39 -#define MA_SPA_AUDIO_CHANNEL_AUX40 SPA_AUDIO_CHANNEL_AUX40 -#define MA_SPA_AUDIO_CHANNEL_AUX41 SPA_AUDIO_CHANNEL_AUX41 -#define MA_SPA_AUDIO_CHANNEL_AUX42 SPA_AUDIO_CHANNEL_AUX42 -#define MA_SPA_AUDIO_CHANNEL_AUX43 SPA_AUDIO_CHANNEL_AUX43 -#define MA_SPA_AUDIO_CHANNEL_AUX44 SPA_AUDIO_CHANNEL_AUX44 -#define MA_SPA_AUDIO_CHANNEL_AUX45 SPA_AUDIO_CHANNEL_AUX45 -#define MA_SPA_AUDIO_CHANNEL_AUX46 SPA_AUDIO_CHANNEL_AUX46 -#define MA_SPA_AUDIO_CHANNEL_AUX47 SPA_AUDIO_CHANNEL_AUX47 -#define MA_SPA_AUDIO_CHANNEL_AUX48 SPA_AUDIO_CHANNEL_AUX48 -#define MA_SPA_AUDIO_CHANNEL_AUX49 SPA_AUDIO_CHANNEL_AUX49 -#define MA_SPA_AUDIO_CHANNEL_AUX50 SPA_AUDIO_CHANNEL_AUX50 -#define MA_SPA_AUDIO_CHANNEL_AUX51 SPA_AUDIO_CHANNEL_AUX51 -#define MA_SPA_AUDIO_CHANNEL_AUX52 SPA_AUDIO_CHANNEL_AUX52 -#define MA_SPA_AUDIO_CHANNEL_AUX53 SPA_AUDIO_CHANNEL_AUX53 -#define MA_SPA_AUDIO_CHANNEL_AUX54 SPA_AUDIO_CHANNEL_AUX54 -#define MA_SPA_AUDIO_CHANNEL_AUX55 SPA_AUDIO_CHANNEL_AUX55 -#define MA_SPA_AUDIO_CHANNEL_AUX56 SPA_AUDIO_CHANNEL_AUX56 -#define MA_SPA_AUDIO_CHANNEL_AUX57 SPA_AUDIO_CHANNEL_AUX57 -#define MA_SPA_AUDIO_CHANNEL_AUX58 SPA_AUDIO_CHANNEL_AUX58 -#define MA_SPA_AUDIO_CHANNEL_AUX59 SPA_AUDIO_CHANNEL_AUX59 -#define MA_SPA_AUDIO_CHANNEL_AUX60 SPA_AUDIO_CHANNEL_AUX60 -#define MA_SPA_AUDIO_CHANNEL_AUX61 SPA_AUDIO_CHANNEL_AUX61 -#define MA_SPA_AUDIO_CHANNEL_AUX62 SPA_AUDIO_CHANNEL_AUX62 -#define MA_SPA_AUDIO_CHANNEL_AUX63 SPA_AUDIO_CHANNEL_AUX63 - - -#define MA_PW_KEY_MEDIA_TYPE PW_KEY_MEDIA_TYPE -#define MA_PW_KEY_MEDIA_CATEGORY PW_KEY_MEDIA_CATEGORY -#define MA_PW_KEY_MEDIA_ROLE PW_KEY_MEDIA_ROLE -#define MA_PW_KEY_MEDIA_CLASS PW_KEY_MEDIA_CLASS -#define MA_PW_KEY_NODE_LATENCY PW_KEY_NODE_LATENCY -#define MA_PW_KEY_TARGET_OBJECT PW_KEY_TARGET_OBJECT -#define MA_PW_KEY_METADATA_NAME PW_KEY_METADATA_NAME - -#define MA_PW_TYPE_INTERFACE_Node PW_TYPE_INTERFACE_Node -#define MA_PW_TYPE_INTERFACE_Metadata PW_TYPE_INTERFACE_Metadata - -#define MA_PW_ID_CORE PW_ID_CORE -#define MA_PW_ID_ANY PW_ID_ANY - -#define ma_pw_thread_loop pw_thread_loop -#define ma_pw_loop pw_loop -#define ma_pw_context pw_context -#define ma_pw_core pw_core -#define ma_pw_registry pw_registry -#define ma_pw_metadata pw_metadata -#define ma_pw_metadata_events pw_metadata_events -#define ma_pw_proxy pw_proxy -#define ma_pw_properties pw_properties -#define ma_pw_stream pw_stream -#define ma_pw_stream_control pw_stream_control -#define ma_pw_core_info pw_core_info - -#define ma_pw_stream_state pw_stream_state -#define MA_PW_STREAM_STATE_ERROR PW_STREAM_STATE_ERROR -#define MA_PW_STREAM_STATE_UNCONNECTED PW_STREAM_STATE_UNCONNECTED -#define MA_PW_STREAM_STATE_CONNECTING PW_STREAM_STATE_CONNECTING -#define MA_PW_STREAM_STATE_PAUSED PW_STREAM_STATE_PAUSED -#define MA_PW_STREAM_STATE_STREAMING PW_STREAM_STATE_STREAMING - -#define ma_pw_stream_flags pw_stream_flags -#define MA_PW_STREAM_FLAG_NONE PW_STREAM_FLAG_NONE -#define MA_PW_STREAM_FLAG_AUTOCONNECT PW_STREAM_FLAG_AUTOCONNECT -#define MA_PW_STREAM_FLAG_INACTIVE PW_STREAM_FLAG_INACTIVE -#define MA_PW_STREAM_FLAG_MAP_BUFFERS PW_STREAM_FLAG_MAP_BUFFERS -#define MA_PW_STREAM_FLAG_DRIVER PW_STREAM_FLAG_DRIVER -#define MA_PW_STREAM_FLAG_RT_PROCESS PW_STREAM_FLAG_RT_PROCESS -#define MA_PW_STREAM_FLAG_NO_CONVERT PW_STREAM_FLAG_NO_CONVERT -#define MA_PW_STREAM_FLAG_EXCLUSIVE PW_STREAM_FLAG_EXCLUSIVE -#define MA_PW_STREAM_FLAG_DONT_RECONNECT PW_STREAM_FLAG_DONT_RECONNECT -#define MA_PW_STREAM_FLAG_ALLOC_BUFFERS PW_STREAM_FLAG_ALLOC_BUFFERS -#define MA_PW_STREAM_FLAG_TRIGGER PW_STREAM_FLAG_TRIGGER -#define MA_PW_STREAM_FLAG_ASYNC PW_STREAM_FLAG_ASYNC - -#define ma_pw_buffer pw_buffer -#define ma_pw_time pw_time - -#define MA_PW_VERSION_CORE_EVENTS PW_VERSION_CORE_EVENTS -#define ma_pw_core_events pw_core_events - -#define MA_PW_VERSION_REGISTRY PW_VERSION_REGISTRY -#define MA_PW_VERSION_REGISTRY_EVENTS PW_VERSION_REGISTRY_EVENTS -#define ma_pw_registry_events pw_registry_events - -#define MA_PW_VERSION_METADATA_METHODS PW_VERSION_METADATA_METHODS -#define ma_pw_metadata_methods pw_metadata_methods - -#define MA_PW_VERSION_METADATA PW_VERSION_METADATA -#define MA_PW_VERSION_METADATA_EVENTS PW_VERSION_METADATA_EVENTS -#define ma_pw_metadata_events pw_metadata_events - -#define MA_PW_VERSION_STREAM_EVENTS PW_VERSION_STREAM_EVENTS -#define ma_pw_stream_events pw_stream_events -#else -typedef void (* ma_spa_source_event_func_t)(void* data, ma_uint64 count); - -/* SPA enums. */ -enum ma_spa_direction -{ - MA_SPA_DIRECTION_INPUT = 0, - MA_SPA_DIRECTION_OUTPUT = 1 -}; - -enum ma_spa_audio_format -{ - MA_SPA_AUDIO_FORMAT_UNKNOWN = 0, - MA_SPA_AUDIO_FORMAT_ENCODED, - - MA_SPA_AUDIO_FORMAT_START_Interleaved = 0x100, - MA_SPA_AUDIO_FORMAT_S8, - MA_SPA_AUDIO_FORMAT_U8, - MA_SPA_AUDIO_FORMAT_S16_LE, - MA_SPA_AUDIO_FORMAT_S16_BE, - MA_SPA_AUDIO_FORMAT_U16_LE, - MA_SPA_AUDIO_FORMAT_U16_BE, - MA_SPA_AUDIO_FORMAT_S24_32_LE, - MA_SPA_AUDIO_FORMAT_S24_32_BE, - MA_SPA_AUDIO_FORMAT_U24_32_LE, - MA_SPA_AUDIO_FORMAT_U24_32_BE, - MA_SPA_AUDIO_FORMAT_S32_LE, - MA_SPA_AUDIO_FORMAT_S32_BE, - MA_SPA_AUDIO_FORMAT_U32_LE, - MA_SPA_AUDIO_FORMAT_U32_BE, - MA_SPA_AUDIO_FORMAT_S24_LE, - MA_SPA_AUDIO_FORMAT_S24_BE, - MA_SPA_AUDIO_FORMAT_U24_LE, - MA_SPA_AUDIO_FORMAT_U24_BE, - MA_SPA_AUDIO_FORMAT_S20_LE, - MA_SPA_AUDIO_FORMAT_S20_BE, - MA_SPA_AUDIO_FORMAT_U20_LE, - MA_SPA_AUDIO_FORMAT_U20_BE, - MA_SPA_AUDIO_FORMAT_S18_LE, - MA_SPA_AUDIO_FORMAT_S18_BE, - MA_SPA_AUDIO_FORMAT_U18_LE, - MA_SPA_AUDIO_FORMAT_U18_BE, - MA_SPA_AUDIO_FORMAT_F32_LE, - MA_SPA_AUDIO_FORMAT_F32_BE, - MA_SPA_AUDIO_FORMAT_F64_LE, - MA_SPA_AUDIO_FORMAT_F64_BE, - MA_SPA_AUDIO_FORMAT_ULAW, - MA_SPA_AUDIO_FORMAT_ALAW, -}; - -enum ma_spa_audio_channel -{ - MA_SPA_AUDIO_CHANNEL_UNKNOWN, - MA_SPA_AUDIO_CHANNEL_NA, - - MA_SPA_AUDIO_CHANNEL_MONO, - MA_SPA_AUDIO_CHANNEL_FL, - MA_SPA_AUDIO_CHANNEL_FR, - MA_SPA_AUDIO_CHANNEL_FC, - MA_SPA_AUDIO_CHANNEL_LFE, - MA_SPA_AUDIO_CHANNEL_SL, - MA_SPA_AUDIO_CHANNEL_SR, - MA_SPA_AUDIO_CHANNEL_FLC, - MA_SPA_AUDIO_CHANNEL_FRC, - MA_SPA_AUDIO_CHANNEL_RC, - MA_SPA_AUDIO_CHANNEL_RL, - MA_SPA_AUDIO_CHANNEL_RR, - MA_SPA_AUDIO_CHANNEL_TC, - MA_SPA_AUDIO_CHANNEL_TFL, - MA_SPA_AUDIO_CHANNEL_TFC, - MA_SPA_AUDIO_CHANNEL_TFR, - MA_SPA_AUDIO_CHANNEL_TRL, - MA_SPA_AUDIO_CHANNEL_TRC, - MA_SPA_AUDIO_CHANNEL_TRR, - MA_SPA_AUDIO_CHANNEL_RLC, - MA_SPA_AUDIO_CHANNEL_RRC, - MA_SPA_AUDIO_CHANNEL_FLW, - MA_SPA_AUDIO_CHANNEL_FRW, - MA_SPA_AUDIO_CHANNEL_LFE2, - MA_SPA_AUDIO_CHANNEL_FLH, - MA_SPA_AUDIO_CHANNEL_FCH, - MA_SPA_AUDIO_CHANNEL_FRH, - MA_SPA_AUDIO_CHANNEL_TFLC, - MA_SPA_AUDIO_CHANNEL_TFRC, - MA_SPA_AUDIO_CHANNEL_TSL, - MA_SPA_AUDIO_CHANNEL_TSR, - MA_SPA_AUDIO_CHANNEL_LLFE, - MA_SPA_AUDIO_CHANNEL_RLFE, - MA_SPA_AUDIO_CHANNEL_BC, - MA_SPA_AUDIO_CHANNEL_BLC, - MA_SPA_AUDIO_CHANNEL_BRC, - - MA_SPA_AUDIO_CHANNEL_START_Aux = 0x1000, - MA_SPA_AUDIO_CHANNEL_AUX0 = MA_SPA_AUDIO_CHANNEL_START_Aux, - MA_SPA_AUDIO_CHANNEL_AUX1, - MA_SPA_AUDIO_CHANNEL_AUX2, - MA_SPA_AUDIO_CHANNEL_AUX3, - MA_SPA_AUDIO_CHANNEL_AUX4, - MA_SPA_AUDIO_CHANNEL_AUX5, - MA_SPA_AUDIO_CHANNEL_AUX6, - MA_SPA_AUDIO_CHANNEL_AUX7, - MA_SPA_AUDIO_CHANNEL_AUX8, - MA_SPA_AUDIO_CHANNEL_AUX9, - MA_SPA_AUDIO_CHANNEL_AUX10, - MA_SPA_AUDIO_CHANNEL_AUX11, - MA_SPA_AUDIO_CHANNEL_AUX12, - MA_SPA_AUDIO_CHANNEL_AUX13, - MA_SPA_AUDIO_CHANNEL_AUX14, - MA_SPA_AUDIO_CHANNEL_AUX15, - MA_SPA_AUDIO_CHANNEL_AUX16, - MA_SPA_AUDIO_CHANNEL_AUX17, - MA_SPA_AUDIO_CHANNEL_AUX18, - MA_SPA_AUDIO_CHANNEL_AUX19, - MA_SPA_AUDIO_CHANNEL_AUX20, - MA_SPA_AUDIO_CHANNEL_AUX21, - MA_SPA_AUDIO_CHANNEL_AUX22, - MA_SPA_AUDIO_CHANNEL_AUX23, - MA_SPA_AUDIO_CHANNEL_AUX24, - MA_SPA_AUDIO_CHANNEL_AUX25, - MA_SPA_AUDIO_CHANNEL_AUX26, - MA_SPA_AUDIO_CHANNEL_AUX27, - MA_SPA_AUDIO_CHANNEL_AUX28, - MA_SPA_AUDIO_CHANNEL_AUX29, - MA_SPA_AUDIO_CHANNEL_AUX30, - MA_SPA_AUDIO_CHANNEL_AUX31, - MA_SPA_AUDIO_CHANNEL_AUX32, - MA_SPA_AUDIO_CHANNEL_AUX33, - MA_SPA_AUDIO_CHANNEL_AUX34, - MA_SPA_AUDIO_CHANNEL_AUX35, - MA_SPA_AUDIO_CHANNEL_AUX36, - MA_SPA_AUDIO_CHANNEL_AUX37, - MA_SPA_AUDIO_CHANNEL_AUX38, - MA_SPA_AUDIO_CHANNEL_AUX39, - MA_SPA_AUDIO_CHANNEL_AUX40, - MA_SPA_AUDIO_CHANNEL_AUX41, - MA_SPA_AUDIO_CHANNEL_AUX42, - MA_SPA_AUDIO_CHANNEL_AUX43, - MA_SPA_AUDIO_CHANNEL_AUX44, - MA_SPA_AUDIO_CHANNEL_AUX45, - MA_SPA_AUDIO_CHANNEL_AUX46, - MA_SPA_AUDIO_CHANNEL_AUX47, - MA_SPA_AUDIO_CHANNEL_AUX48, - MA_SPA_AUDIO_CHANNEL_AUX49, - MA_SPA_AUDIO_CHANNEL_AUX50, - MA_SPA_AUDIO_CHANNEL_AUX51, - MA_SPA_AUDIO_CHANNEL_AUX52, - MA_SPA_AUDIO_CHANNEL_AUX53, - MA_SPA_AUDIO_CHANNEL_AUX54, - MA_SPA_AUDIO_CHANNEL_AUX55, - MA_SPA_AUDIO_CHANNEL_AUX56, - MA_SPA_AUDIO_CHANNEL_AUX57, - MA_SPA_AUDIO_CHANNEL_AUX58, - MA_SPA_AUDIO_CHANNEL_AUX59, - MA_SPA_AUDIO_CHANNEL_AUX60, - MA_SPA_AUDIO_CHANNEL_AUX61, - MA_SPA_AUDIO_CHANNEL_AUX62, - MA_SPA_AUDIO_CHANNEL_AUX63 -}; - - - -/* The spa_interface thing is only used by one thing here, and only because the function we want is not exported by the SO. */ -struct ma_spa_callbacks -{ - const void* funcs; - void* data; -}; - -struct ma_spa_interface -{ - const char* type; - ma_uint32 version; - struct ma_spa_callbacks cb; -}; - - -/* -spa_dict is just a list of key/value pairs as a string. We can easily write our own version of this to remove -the dependency on the SPA headers. - -We'll keep the naming consistent here with the actual SPA library, with just the "ma_" prefix added. -*/ -struct ma_spa_dict_item -{ - const char* key; - const char* value; -}; - -static MA_INLINE struct ma_spa_dict_item MA_SPA_DICT_ITEM_INIT(const char* key, const char* value) -{ - struct ma_spa_dict_item item; - - item.key = key; - item.value = value; - - return item; -} - - -struct ma_spa_dict -{ - ma_uint32 flags; - ma_uint32 n_items; - const struct ma_spa_dict_item* items; -}; - -static MA_INLINE struct ma_spa_dict MA_SPA_DICT_INIT(const struct ma_spa_dict_item* items, ma_uint32 n_items) -{ - struct ma_spa_dict dict; - - dict.flags = 0; - dict.n_items = n_items; - dict.items = items; - - return dict; -} - -static MA_INLINE const char* ma_spa_dict_lookup(const struct ma_spa_dict* dict, const char* key) -{ - ma_uint32 i; - - for (i = 0; i < dict->n_items; i += 1) { - if (strcmp(dict->items[i].key, key) == 0) { - return dict->items[i].value; - } - } - - return NULL; -} - - -/* -spa_buffer is just a few trivial structs. -*/ -struct ma_spa_chunk -{ - ma_uint32 offset; - ma_uint32 size; - ma_int32 stride; - ma_int32 flags; -}; - -struct ma_spa_data -{ - ma_uint32 type; - ma_uint32 flags; - ma_int64 fd; - ma_uint32 mapoffset; - ma_uint32 maxsize; - void* data; - struct ma_spa_chunk* chunk; -}; - -struct ma_spa_meta -{ - ma_uint32 type; - ma_uint32 size; - void* data; -}; - -struct ma_spa_buffer -{ - ma_uint32 n_metas; - ma_uint32 n_datas; - struct ma_spa_meta* metas; - struct ma_spa_data* datas; -}; - - -/* spa_hook */ -struct ma_spa_list -{ - struct ma_spa_list* next; - struct ma_spa_list* prev; -}; - -struct ma_spa_hook -{ - struct ma_spa_list link; - struct ma_spa_callbacks cb; - void (* removed)(struct ma_spa_hook* hook); - void* priv; -}; - - -/* spa_pod is the most complicated part of SPA that we use. We're only implementing specifically what we need. */ -#define MA_SPA_TYPE_Id 3 -#define MA_SPA_TYPE_Int 4 -#define MA_SPA_TYPE_Array 13 -#define MA_SPA_TYPE_Object 15 -#define MA_SPA_TYPE_Choice 19 - -#define MA_SPA_TYPE_OBJECT_Format 262147 -#define MA_SPA_TYPE_OBJECT_ParamBuffers 262148 - -#define MA_SPA_PARAM_EnumFormat 3 -#define MA_SPA_PARAM_Format 4 -#define MA_SPA_PARAM_Buffers 5 - -#define MA_SPA_PARAM_BUFFERS_buffers 1 -#define MA_SPA_PARAM_BUFFERS_blocks 2 -#define MA_SPA_PARAM_BUFFERS_size 3 -#define MA_SPA_PARAM_BUFFERS_stride 4 - -#define MA_SPA_FORMAT_mediaType 1 -#define MA_SPA_FORMAT_mediaSubtype 2 - -#define MA_SPA_MEDIA_TYPE_audio 1 -#define MA_SPA_MEDIA_SUBTYPE_raw 1 - -#define MA_SPA_FORMAT_AUDIO_format 65537 -#define MA_SPA_FORMAT_AUDIO_rate 65539 -#define MA_SPA_FORMAT_AUDIO_channels 65540 -#define MA_SPA_FORMAT_AUDIO_position 65541 - - -struct ma_spa_pod -{ - ma_uint32 size; - ma_uint32 type; -}; - - -struct ma_spa_pod_id -{ - struct ma_spa_pod pod; - ma_uint32 value; - ma_int32 _padding; -}; - -struct ma_spa_pod_int -{ - struct ma_spa_pod pod; - ma_int32 value; - ma_int32 _padding; -}; - - -enum ma_spa_choice_type -{ - MA_SPA_CHOICE_None = 0, - MA_SPA_CHOICE_Range, - MA_SPA_CHOICE_Step, - MA_SPA_CHOICE_Enum, - MA_SPA_CHOICE_Flags -}; - -struct ma_spa_pod_choice_body -{ - ma_uint32 type; - ma_uint32 flags; - struct ma_spa_pod child; -}; - -struct ma_spa_pod_choice -{ - struct ma_spa_pod pod; - struct ma_spa_pod_choice_body body; -}; - - -struct ma_spa_pod_array_body -{ - struct ma_spa_pod child; -}; - -struct ma_spa_pod_array -{ - struct ma_spa_pod pod; - struct ma_spa_pod_array_body body; -}; - - -struct ma_spa_pod_object_body -{ - ma_uint32 type; - ma_uint32 id; -}; - -struct ma_spa_pod_object -{ - struct ma_spa_pod pod; - struct ma_spa_pod_object_body body; -}; - -struct ma_spa_pod_prop -{ - ma_uint32 key; - ma_uint32 flags; - struct ma_spa_pod value; -}; - - -/* Miscellaneous SPA references that we don't actively use but are required by structs or function parameters. */ -typedef struct ma_spa_command ma_spa_command; - -struct ma_spa_fraction -{ - ma_uint32 num; - ma_uint32 denom; -}; - - - -#define MA_PW_KEY_MEDIA_TYPE "media.type" -#define MA_PW_KEY_MEDIA_CATEGORY "media.category" -#define MA_PW_KEY_MEDIA_ROLE "media.role" -#define MA_PW_KEY_MEDIA_CLASS "media.class" -#define MA_PW_KEY_NODE_LATENCY "node.latency" -#define MA_PW_KEY_TARGET_OBJECT "target.object" -#define MA_PW_KEY_METADATA_NAME "metadata.name" - -#define MA_PW_TYPE_INTERFACE_Node "PipeWire:Interface:Node" -#define MA_PW_TYPE_INTERFACE_Metadata "PipeWire:Interface:Metadata" - -#define MA_PW_ID_CORE 0 -#define MA_PW_ID_ANY 0xFFFFFFFF - -struct ma_pw_thread_loop; -struct ma_pw_loop; -struct ma_pw_context; -struct ma_pw_core; -struct ma_pw_registry; -struct ma_pw_metadata; -struct ma_pw_metadata_events; -struct ma_pw_proxy; -struct ma_pw_properties; -struct ma_pw_stream; -struct ma_pw_stream_control; -struct ma_pw_core_info; - -enum ma_pw_stream_state -{ - MA_PW_STREAM_STATE_ERROR = -1, - MA_PW_STREAM_STATE_UNCONNECTED = 0, - MA_PW_STREAM_STATE_CONNECTING = 1, - MA_PW_STREAM_STATE_PAUSED = 2, - MA_PW_STREAM_STATE_STREAMING = 3 -}; - -enum ma_pw_stream_flags -{ - MA_PW_STREAM_FLAG_NONE = 0, - MA_PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), - MA_PW_STREAM_FLAG_INACTIVE = (1 << 1), - MA_PW_STREAM_FLAG_MAP_BUFFERS = (1 << 2), - MA_PW_STREAM_FLAG_DRIVER = (1 << 3), - MA_PW_STREAM_FLAG_RT_PROCESS = (1 << 4), - MA_PW_STREAM_FLAG_NO_CONVERT = (1 << 5), - MA_PW_STREAM_FLAG_EXCLUSIVE = (1 << 6), - MA_PW_STREAM_FLAG_DONT_RECONNECT = (1 << 7), - MA_PW_STREAM_FLAG_ALLOC_BUFFERS = (1 << 8), - MA_PW_STREAM_FLAG_TRIGGER = (1 << 9), - MA_PW_STREAM_FLAG_ASYNC = (1 << 10) -}; - -struct ma_pw_buffer -{ - struct ma_spa_buffer* buffer; - void* user_data; - ma_uint64 size; - ma_uint64 requested; -}; - -struct ma_pw_time -{ - ma_int64 now; - struct ma_spa_fraction rate; - ma_uint64 ticks; - ma_int64 delay; - ma_uint64 queued; - ma_uint64 buffered; - ma_uint32 queued_buffers; - ma_uint32 avail_buffers; - ma_uint64 size; -}; - - -#define MA_PW_VERSION_CORE_EVENTS 1 -struct ma_pw_core_events -{ - ma_uint32 version; - void (* info )(void* data, const struct ma_pw_core_info* info); - void (* done )(void* data, ma_uint32 id, int seq); - void (* ping )(void* data, ma_uint32 id, int seq); - void (* error )(void* data, ma_uint32 id, int seq, int res, const char* message); - void (* remove_id )(void* data, ma_uint32 id); - void (* bound_id )(void* data, ma_uint32 id, ma_uint32 global_id); - void (* add_mem )(void* data, ma_uint32 id, ma_uint32 type, int fd, ma_uint32 flags); - void (* remove_mem )(void* data, ma_uint32 id); - void (* bound_props)(void* data, ma_uint32 id, ma_uint32 global_id, const struct ma_spa_dict* props); -}; - - -#define MA_PW_VERSION_REGISTRY 3 -#define MA_PW_VERSION_REGISTRY_EVENTS 0 -struct ma_pw_registry_events -{ - ma_uint32 version; - void (* global_add )(void* data, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props); - void (* global_remove)(void* data, ma_uint32 id); -}; - - - -#define PW_VERSION_METADATA_METHODS 0 -struct ma_pw_metadata_methods -{ - ma_uint32 version; - int (* add_listener)(void* object, struct ma_spa_hook* listener, const struct ma_pw_metadata_events* events, void* data); - int (* set_property)(void* object, ma_uint32 subject, const char* key, const char* type, const char* value); - int (* clear )(void* object); -}; - -#define MA_PW_VERSION_METADATA 3 -#define MA_PW_VERSION_METADATA_EVENTS 0 -struct ma_pw_metadata_events -{ - ma_uint32 version; - int (* property)(void* data, ma_uint32 subject, const char* key, const char* type, const char* value); -}; - - -#define MA_PW_VERSION_STREAM_EVENTS 2 -struct ma_pw_stream_events -{ - ma_uint32 version; - void (* destroy )(void* data); - void (* state_changed)(void* data, enum ma_pw_stream_state oldState, enum ma_pw_stream_state newState, const char* error); - void (* control_info )(void* data, ma_uint32 id, const struct ma_pw_stream_control* control); - void (* io_changed )(void* data, ma_uint32 id, void* area, ma_uint32 size); - void (* param_changed)(void* data, ma_uint32 id, const struct ma_spa_pod* param); - void (* add_buffer )(void* data, struct ma_pw_buffer* buffer); - void (* remove_buffer)(void* data, struct ma_pw_buffer* buffer); - void (* process )(void* data); - void (* drained )(void* data); - void (* command )(void* data, const struct ma_spa_command* command); - void (* trigger_done )(void* data); -}; -#endif - -static MA_INLINE struct ma_spa_pod_id ma_spa_pod_id_init(ma_uint32 value) -{ - struct ma_spa_pod_id pod; - - MA_PIPEWIRE_ZERO_OBJECT(&pod); - pod.pod.size = 4; /* Does not include the header or padding. */ - pod.pod.type = MA_SPA_TYPE_Id; - pod.value = value; - - return pod; -} - -static MA_INLINE struct ma_spa_pod_int ma_spa_pod_int_init(ma_int32 value) -{ - struct ma_spa_pod_int pod; - - MA_PIPEWIRE_ZERO_OBJECT(&pod); - pod.pod.size = 4; /* Does not include the header or padding. */ - pod.pod.type = MA_SPA_TYPE_Int; - pod.value = value; - - return pod; -} - -static MA_INLINE void* ma_spa_pod_choice_get_values(const struct ma_spa_pod_choice* choice) -{ - /* Values are stored as a flat array after the main struct. */ - return (void*)ma_offset_ptr(choice, sizeof(struct ma_spa_pod_choice)); -} - -#if 0 -static MA_INLINE ma_uint32 ma_spa_pod_array_get_length(const struct ma_spa_pod_array* array) -{ - return (array->pod.size - sizeof(struct ma_spa_pod_array_body)) / array->body.child.size; -} -#endif - -static MA_INLINE void* ma_spa_pod_array_get_values(const struct ma_spa_pod_array* array) -{ - /* Values are stored as a flat array after the main struct. */ - return (void*)ma_offset_ptr(array, sizeof(struct ma_spa_pod_array)); -} - - -struct ma_spa_pod_prop_id -{ - ma_uint32 key; - ma_uint32 flags; - struct ma_spa_pod_id value; -}; - -static MA_INLINE struct ma_spa_pod_prop_id ma_spa_pod_prop_id_init(ma_uint32 key, ma_uint32 value) -{ - struct ma_spa_pod_prop_id prop; - - prop.key = key; - prop.flags = 0; - prop.value = ma_spa_pod_id_init(value); - - return prop; -} - - -struct ma_spa_pod_prop_int -{ - ma_uint32 key; - ma_uint32 flags; - struct ma_spa_pod_int value; -}; - -static MA_INLINE struct ma_spa_pod_prop_int ma_spa_pod_prop_int_init(ma_uint32 key, ma_int32 value) -{ - struct ma_spa_pod_prop_int prop; - - prop.key = key; - prop.flags = 0; - prop.value = ma_spa_pod_int_init(value); - - return prop; -} - - -/* This is a custom pod for our buffer parameters. */ -struct ma_spa_pod_buffer_params -{ - struct ma_spa_pod_object object; - struct ma_spa_pod_prop_int buffers; - struct ma_spa_pod_prop_int blocks; - struct ma_spa_pod_prop_int stride; - struct ma_spa_pod_prop_int size; -}; - -static MA_INLINE struct ma_spa_pod_buffer_params ma_spa_pod_buffer_params_init(ma_uint32 strideInBytes, ma_uint32 bufferSizeInFrames) -{ - struct ma_spa_pod_buffer_params pod; - - /* - spa_pod_builder_add_object(&podBuilder, - SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, - SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(2), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), - SPA_PARAM_BUFFERS_stride, SPA_POD_Int(bytesPerFrame), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(bytesPerFrame * pStreamState->bufferSizeInFrames)); - */ - - MA_PIPEWIRE_ZERO_OBJECT(&pod); - - pod.object.pod.size = sizeof(struct ma_spa_pod_buffer_params) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ - pod.object.pod.type = MA_SPA_TYPE_Object; - pod.object.body.type = MA_SPA_TYPE_OBJECT_ParamBuffers; - pod.object.body.id = MA_SPA_PARAM_Buffers; - - pod.buffers = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_buffers, 2); - pod.blocks = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_blocks, 1); - pod.stride = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_stride, (ma_int32)strideInBytes); - pod.size = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_size, (ma_int32)(strideInBytes * bufferSizeInFrames)); - - return pod; -} - - -/* A custom pod for audio info. */ -struct ma_spa_pod_audio_info_raw -{ - struct ma_spa_pod_object object; - struct ma_spa_pod_prop_id mediaType; - struct ma_spa_pod_prop_id mediaSubtype; - struct ma_spa_pod_prop_id format; - struct ma_spa_pod_prop_int extra[2]; /* Channels and sample rate. Both are optional. */ -}; - -static MA_INLINE struct ma_spa_pod_audio_info_raw ma_spa_pod_audio_info_raw_init(enum ma_spa_audio_format format, ma_uint32 channels, ma_uint32 sampleRate) -{ - struct ma_spa_pod_audio_info_raw pod; - int extraIndex = 0; - - /* - struct spa_audio_info_raw audioInfo; - char podBuilderBuffer[1024]; - struct spa_pod_builder podBuilder; - - podBuilder = SPA_POD_BUILDER_INIT(podBuilderBuffer, sizeof(podBuilderBuffer)); - - memset(&audioInfo, 0, sizeof(audioInfo)); - audioInfo.flags = SPA_AUDIO_FLAG_UNPOSITIONED; - audioInfo.format = (enum spa_audio_format)formatPA; - audioInfo.channels = pDescriptor->channels; - audioInfo.rate = pDescriptor->sampleRate; - - pConnectionParameters[0] = spa_format_audio_raw_build(&podBuilder, SPA_PARAM_EnumFormat, &audioInfo); - */ - - MA_PIPEWIRE_ZERO_OBJECT(&pod); - - pod.object.pod.size = sizeof(struct ma_spa_pod_object) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ - pod.object.pod.type = MA_SPA_TYPE_Object; - pod.object.body.type = MA_SPA_TYPE_OBJECT_Format; - pod.object.body.id = MA_SPA_PARAM_EnumFormat; - - pod.mediaType = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaType, MA_SPA_MEDIA_TYPE_audio); - pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); - - pod.mediaSubtype = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaSubtype, MA_SPA_MEDIA_SUBTYPE_raw); - pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); - - pod.format = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_AUDIO_format, (ma_uint32)format); - pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); - - if (channels > 0) { - pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_channels, (ma_int32)channels); - pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); - extraIndex += 1; - } - - if (sampleRate > 0) { - pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_rate, (ma_int32)sampleRate); - pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); - /*extraIndex += 1;*/ - } - - /* We're going to leave the channel map alone and just do a conversion ourselves if it differs from the native map. */ - - return pod; -} - - -/* Extracts the value of an ID from a pod, depending on its type. */ -static MA_INLINE ma_uint32 ma_spa_pod_get_id_value(const struct ma_spa_pod* pod) -{ - MA_PIPEWIRE_ASSERT(pod != NULL); - - switch (pod->type) - { - case MA_SPA_TYPE_Id: - { - const struct ma_spa_pod_id* pPodID = (const struct ma_spa_pod_id*)pod; - return pPodID->value; - } - - case MA_SPA_TYPE_Choice: - { - const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; - const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); - - /* We're just going to return the first value. */ - return ((ma_uint32*)pValues)[0]; - } - - default: - { - MA_PIPEWIRE_ASSERT(MA_FALSE); /* Unsupported type. */ - return 0; - } - } -} - -static MA_INLINE ma_int32 ma_spa_pod_get_int_value(const struct ma_spa_pod* pod) -{ - MA_PIPEWIRE_ASSERT(pod != NULL); - - switch (pod->type) - { - case MA_SPA_TYPE_Id: - { - const struct ma_spa_pod_int* pPodInt = (const struct ma_spa_pod_int*)pod; - return pPodInt->value; - } - - case MA_SPA_TYPE_Choice: - { - const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; - const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); - - /* We're just going to return the first value. */ - return ((ma_int32*)pValues)[0]; - } - - default: - { - MA_PIPEWIRE_ASSERT(MA_FALSE); /* Unsupported type. */ - return 0; - } - } -} - - - -typedef void (* ma_pw_init_proc )(int* argc, char*** argv); -typedef void (* ma_pw_deinit_proc )(void); -typedef struct ma_pw_loop* (* ma_pw_loop_new_proc )(const struct ma_spa_dict* props); -typedef void (* ma_pw_loop_destroy_proc )(struct ma_pw_loop* loop); -typedef int (* ma_pw_loop_set_name_proc )(struct ma_pw_loop* loop, const char* name); -typedef void (* ma_pw_loop_enter_proc )(struct ma_pw_loop* loop); -typedef void (* ma_pw_loop_leave_proc )(struct ma_pw_loop* loop); -typedef int (* ma_pw_loop_iterate_proc )(struct ma_pw_loop* loop, int timeout); -typedef struct spa_source* (* ma_pw_loop_add_event_proc )(struct ma_pw_loop* loop, ma_spa_source_event_func_t func, void* data); -typedef int (* ma_pw_loop_signal_event_proc )(struct ma_pw_loop* loop, struct spa_source* source); -typedef struct ma_pw_thread_loop* (* ma_pw_thread_loop_new_proc )(const char* name, const struct ma_spa_dict* props); -typedef void (* ma_pw_thread_loop_destroy_proc )(struct ma_pw_thread_loop* loop); -typedef struct ma_pw_loop* (* ma_pw_thread_loop_get_loop_proc )(struct ma_pw_thread_loop* loop); -typedef int (* ma_pw_thread_loop_start_proc )(struct ma_pw_thread_loop* loop); -typedef void (* ma_pw_thread_loop_lock_proc )(struct ma_pw_thread_loop* loop); -typedef void (* ma_pw_thread_loop_unlock_proc )(struct ma_pw_thread_loop* loop); -typedef struct ma_pw_context* (* ma_pw_context_new_proc )(struct ma_pw_loop* loop, struct ma_pw_properties *props, size_t user_data_size); -typedef void (* ma_pw_context_destroy_proc )(struct ma_pw_context* context); -typedef struct ma_pw_core* (* ma_pw_context_connect_proc )(struct ma_pw_context* context, struct ma_pw_properties* properties, size_t user_data_size); -typedef int (* ma_pw_core_disconnect_proc )(struct ma_pw_core* core); -typedef int (* ma_pw_core_add_listener_proc )(struct ma_pw_core* core, struct ma_spa_hook* listener, const struct ma_pw_core_events* events, void* data); -typedef struct ma_pw_registry* (* ma_pw_core_get_registry_proc )(struct ma_pw_core* core, ma_uint32 version, size_t user_data_size); -typedef int (* ma_pw_core_sync_proc )(struct ma_pw_core* core, ma_uint32 id, int seq); -typedef int (* ma_pw_registry_add_listener_proc )(struct ma_pw_registry* registry, struct ma_spa_hook* listener, const struct ma_pw_registry_events* events, void* data); -typedef void* (* ma_pw_registry_bind_proc )(struct ma_pw_registry* registry, ma_uint32 id, const char* type, ma_uint32 version, size_t user_data_size); -typedef void (* ma_pw_proxy_destroy_proc )(struct ma_pw_proxy* proxy); -typedef struct ma_pw_properties* (* ma_pw_properties_new_proc )(const char* key, ...); -typedef void (* ma_pw_properties_free_proc )(struct ma_pw_properties* properties); -typedef int (* ma_pw_properties_set_proc )(struct ma_pw_properties* properties, const char* key, const char* value); -typedef struct ma_pw_stream* (* ma_pw_stream_new_proc )(struct ma_pw_core* core, const char* name, struct ma_pw_properties* props); -typedef void (* ma_pw_stream_destroy_proc )(struct ma_pw_stream* stream); -typedef void (* ma_pw_stream_add_listener_proc )(struct ma_pw_stream* stream, struct ma_spa_hook* listener, const struct ma_pw_stream_events* events, void* data); -typedef int (* ma_pw_stream_connect_proc )(struct ma_pw_stream* stream, enum ma_spa_direction direction, ma_uint32 target_id, enum ma_pw_stream_flags flags, const struct ma_spa_pod** params, ma_uint32 paramCount); -typedef int (* ma_pw_stream_set_active_proc )(struct ma_pw_stream* stream, ma_bool8 active); -typedef struct ma_pw_buffer* (* ma_pw_stream_dequeue_buffer_proc )(struct ma_pw_stream* stream); -typedef int (* ma_pw_stream_queue_buffer_proc )(struct ma_pw_stream* stream, struct ma_pw_buffer* buffer); -typedef int (* ma_pw_stream_update_params_proc )(struct ma_pw_stream* stream, const struct ma_spa_pod** params, ma_uint32 paramCount); -typedef int (* ma_pw_stream_update_properties_proc)(struct ma_pw_stream* stream, const struct ma_spa_dict* dict); -typedef int (* ma_pw_stream_get_time_n_proc )(struct ma_pw_stream* stream, struct ma_pw_time* time, size_t size); - -typedef struct -{ - ma_log* pLog; - ma_handle hPipeWire; - ma_pw_init_proc pw_init; - ma_pw_deinit_proc pw_deinit; - ma_pw_loop_new_proc pw_loop_new; - ma_pw_loop_destroy_proc pw_loop_destroy; - ma_pw_loop_set_name_proc pw_loop_set_name; - ma_pw_loop_enter_proc pw_loop_enter; - ma_pw_loop_leave_proc pw_loop_leave; - ma_pw_loop_iterate_proc pw_loop_iterate; - ma_pw_loop_add_event_proc pw_loop_add_event; - ma_pw_loop_signal_event_proc pw_loop_signal_event; - ma_pw_thread_loop_new_proc pw_thread_loop_new; - ma_pw_thread_loop_destroy_proc pw_thread_loop_destroy; - ma_pw_thread_loop_get_loop_proc pw_thread_loop_get_loop; - ma_pw_thread_loop_start_proc pw_thread_loop_start; - ma_pw_thread_loop_lock_proc pw_thread_loop_lock; - ma_pw_thread_loop_unlock_proc pw_thread_loop_unlock; - ma_pw_context_new_proc pw_context_new; - ma_pw_context_destroy_proc pw_context_destroy; - ma_pw_context_connect_proc pw_context_connect; - ma_pw_core_disconnect_proc pw_core_disconnect; - ma_pw_core_add_listener_proc pw_core_add_listener; - ma_pw_core_get_registry_proc pw_core_get_registry; - ma_pw_core_sync_proc pw_core_sync; - ma_pw_registry_add_listener_proc pw_registry_add_listener; - ma_pw_registry_bind_proc pw_registry_bind; - ma_pw_proxy_destroy_proc pw_proxy_destroy; - ma_pw_properties_new_proc pw_properties_new; - ma_pw_properties_free_proc pw_properties_free; - ma_pw_properties_set_proc pw_properties_set; - ma_pw_stream_new_proc pw_stream_new; - ma_pw_stream_destroy_proc pw_stream_destroy; - ma_pw_stream_add_listener_proc pw_stream_add_listener; - ma_pw_stream_connect_proc pw_stream_connect; - ma_pw_stream_set_active_proc pw_stream_set_active; - ma_pw_stream_dequeue_buffer_proc pw_stream_dequeue_buffer; - ma_pw_stream_queue_buffer_proc pw_stream_queue_buffer; - ma_pw_stream_update_params_proc pw_stream_update_params; - ma_pw_stream_update_properties_proc pw_stream_update_properties; - ma_pw_stream_get_time_n_proc pw_stream_get_time_n; -} ma_context_state_pipewire; - - -#define MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT 0x01 -#define MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY 0x02 -#define MA_PIPEWIRE_INIT_STATUS_INITIALIZED 0x04 - -typedef struct -{ - struct ma_pw_stream* pStream; - struct ma_spa_hook eventListener; - ma_uint32 initStatus; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_channel channelMap[MA_MAX_CHANNELS]; - ma_uint32 bufferSizeInFrames; - ma_uint32 bufferCount; - ma_uint32 rbSizeInFrames; - ma_pcm_rb rb; /* Only used in single-threaded mode. Acts as an intermediary buffer for step(). For playback, PipeWire will read from this ring buffer. For capture, it'll write to it. */ - ma_device_descriptor* pDescriptor; /* This is only used for setting up internal format. It's needed here because it looks like the only way to get the internal format is via a stupid callback. Will be set to NULL after initialization of the PipeWire stream. */ -} ma_pipewire_stream_state; - -typedef struct -{ - ma_context_state_pipewire* pContextStatePipeWire; - ma_device_type deviceType; - ma_device* pDevice; /* Only needed for ma_stream_event_process__pipewire(). We may change this later in which case we can delete this. */ - struct ma_pw_loop* pLoop; - struct ma_pw_context* pContext; - struct ma_pw_core* pCore; - struct spa_source* pWakeup; /* This is for waking up the loop which we need to do after each data processing callback and the miniaudio wakeup callback. */ - ma_pipewire_stream_state playback; - ma_pipewire_stream_state capture; - struct - { - ma_timer timer; - double lastTimeInSeconds; - } debugging; -} ma_device_state_pipewire; - - -static enum ma_spa_audio_format ma_format_to_pipewire(ma_format format) -{ - if (format == ma_format_u8) { - return MA_SPA_AUDIO_FORMAT_U8; - } - - if (ma_is_little_endian()) { - switch (format) { - case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_LE; - case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_LE; - case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_LE; - case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_LE; - default: break; - } - } else { - switch (format) { - case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_BE; - case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_BE; - case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_BE; - case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_BE; - default: break; - } - } - - return MA_SPA_AUDIO_FORMAT_UNKNOWN; -} - -static ma_format ma_format_from_pipewire(enum ma_spa_audio_format format) -{ - if (format == MA_SPA_AUDIO_FORMAT_U8) { - return ma_format_u8; - } - - if (ma_is_little_endian()) { - switch (format) { - case MA_SPA_AUDIO_FORMAT_S16_LE: return ma_format_s16; - case MA_SPA_AUDIO_FORMAT_S24_LE: return ma_format_s24; - case MA_SPA_AUDIO_FORMAT_S32_LE: return ma_format_s32; - case MA_SPA_AUDIO_FORMAT_F32_LE: return ma_format_f32; - default: break; - } - } else { - switch (format) { - case MA_SPA_AUDIO_FORMAT_S16_BE: return ma_format_s16; - case MA_SPA_AUDIO_FORMAT_S24_BE: return ma_format_s24; - case MA_SPA_AUDIO_FORMAT_S32_BE: return ma_format_s32; - case MA_SPA_AUDIO_FORMAT_F32_BE: return ma_format_f32; - default: break; - } - } - - return ma_format_unknown; -} - -static ma_channel ma_channel_from_pipewire(ma_uint32 channel) -{ - switch (channel) - { - case MA_SPA_AUDIO_CHANNEL_MONO: return MA_CHANNEL_MONO; - case MA_SPA_AUDIO_CHANNEL_FL: return MA_CHANNEL_FRONT_LEFT; - case MA_SPA_AUDIO_CHANNEL_FR: return MA_CHANNEL_FRONT_RIGHT; - case MA_SPA_AUDIO_CHANNEL_FC: return MA_CHANNEL_FRONT_CENTER; - case MA_SPA_AUDIO_CHANNEL_LFE: return MA_CHANNEL_LFE; - case MA_SPA_AUDIO_CHANNEL_SL: return MA_CHANNEL_SIDE_LEFT; - case MA_SPA_AUDIO_CHANNEL_SR: return MA_CHANNEL_SIDE_RIGHT; - case MA_SPA_AUDIO_CHANNEL_FLC: return MA_CHANNEL_FRONT_LEFT_CENTER; - case MA_SPA_AUDIO_CHANNEL_FRC: return MA_CHANNEL_FRONT_RIGHT_CENTER; - case MA_SPA_AUDIO_CHANNEL_RC: return MA_CHANNEL_BACK_CENTER; - case MA_SPA_AUDIO_CHANNEL_RL: return MA_CHANNEL_BACK_LEFT; - case MA_SPA_AUDIO_CHANNEL_RR: return MA_CHANNEL_BACK_RIGHT; - case MA_SPA_AUDIO_CHANNEL_TC: return MA_CHANNEL_TOP_CENTER; - case MA_SPA_AUDIO_CHANNEL_TFL: return MA_CHANNEL_TOP_FRONT_LEFT; - case MA_SPA_AUDIO_CHANNEL_TFC: return MA_CHANNEL_TOP_FRONT_CENTER; - case MA_SPA_AUDIO_CHANNEL_TFR: return MA_CHANNEL_TOP_FRONT_RIGHT; - case MA_SPA_AUDIO_CHANNEL_TRL: return MA_CHANNEL_TOP_BACK_LEFT; - case MA_SPA_AUDIO_CHANNEL_TRC: return MA_CHANNEL_TOP_BACK_CENTER; - case MA_SPA_AUDIO_CHANNEL_TRR: return MA_CHANNEL_TOP_BACK_RIGHT; - - /* These need to be added to miniaudio. */ - #if 0 - case MA_SPA_AUDIO_CHANNEL_RLC: return MA_CHANNEL_BACK_LEFT_CENTER; - case MA_SPA_AUDIO_CHANNEL_RRC: return MA_CHANNEL_BACK_RIGHT_CENTER; - case MA_SPA_AUDIO_CHANNEL_FLW: return MA_CHANNEL_FRONT_LEFT_WIDE; - case MA_SPA_AUDIO_CHANNEL_FRW: return MA_CHANNEL_FRONT_RIGHT_WIDE; - case MA_SPA_AUDIO_CHANNEL_LFE2: return MA_CHANNEL_LFE2; - case MA_SPA_AUDIO_CHANNEL_FLH: return MA_CHANNEL_FRONT_LEFT_HIGH; - case MA_SPA_AUDIO_CHANNEL_FCH: return MA_CHANNEL_FRONT_CENTER_HIGH; - case MA_SPA_AUDIO_CHANNEL_FRH: return MA_CHANNEL_FRONT_RIGHT_HIGH; - case MA_SPA_AUDIO_CHANNEL_TFLC: return MA_CHANNEL_TOP_FRONT_LEFT_CENTER; - case MA_SPA_AUDIO_CHANNEL_TFRC: return MA_CHANNEL_TOP_FRONT_RIGHT_CENTER; - case MA_SPA_AUDIO_CHANNEL_TSL: return MA_CHANNEL_TOP_SIDE_LEFT; - case MA_SPA_AUDIO_CHANNEL_TSR: return MA_CHANNEL_TOP_SIDE_RIGHT; - case MA_SPA_AUDIO_CHANNEL_LLFE: return MA_CHANNEL_LEFT_LFE; - case MA_SPA_AUDIO_CHANNEL_RLFE: return MA_CHANNEL_RIGHT_LFE; - case MA_SPA_AUDIO_CHANNEL_BC: return MA_CHANNEL_BOTTOM_CENTER; - case MA_SPA_AUDIO_CHANNEL_BLC: return MA_CHANNEL_BOTTOM_LEFT_CENTER; - case MA_SPA_AUDIO_CHANNEL_BRC: return MA_CHANNEL_BOTTOM_RIGHT_CENTER; - #endif - - case MA_SPA_AUDIO_CHANNEL_AUX0: return MA_CHANNEL_AUX_0; - case MA_SPA_AUDIO_CHANNEL_AUX1: return MA_CHANNEL_AUX_1; - case MA_SPA_AUDIO_CHANNEL_AUX2: return MA_CHANNEL_AUX_2; - case MA_SPA_AUDIO_CHANNEL_AUX3: return MA_CHANNEL_AUX_3; - case MA_SPA_AUDIO_CHANNEL_AUX4: return MA_CHANNEL_AUX_4; - case MA_SPA_AUDIO_CHANNEL_AUX5: return MA_CHANNEL_AUX_5; - case MA_SPA_AUDIO_CHANNEL_AUX6: return MA_CHANNEL_AUX_6; - case MA_SPA_AUDIO_CHANNEL_AUX7: return MA_CHANNEL_AUX_7; - case MA_SPA_AUDIO_CHANNEL_AUX8: return MA_CHANNEL_AUX_8; - case MA_SPA_AUDIO_CHANNEL_AUX9: return MA_CHANNEL_AUX_9; - case MA_SPA_AUDIO_CHANNEL_AUX10: return MA_CHANNEL_AUX_10; - case MA_SPA_AUDIO_CHANNEL_AUX11: return MA_CHANNEL_AUX_11; - case MA_SPA_AUDIO_CHANNEL_AUX12: return MA_CHANNEL_AUX_12; - case MA_SPA_AUDIO_CHANNEL_AUX13: return MA_CHANNEL_AUX_13; - case MA_SPA_AUDIO_CHANNEL_AUX14: return MA_CHANNEL_AUX_14; - case MA_SPA_AUDIO_CHANNEL_AUX15: return MA_CHANNEL_AUX_15; - case MA_SPA_AUDIO_CHANNEL_AUX16: return MA_CHANNEL_AUX_16; - case MA_SPA_AUDIO_CHANNEL_AUX17: return MA_CHANNEL_AUX_17; - case MA_SPA_AUDIO_CHANNEL_AUX18: return MA_CHANNEL_AUX_18; - case MA_SPA_AUDIO_CHANNEL_AUX19: return MA_CHANNEL_AUX_19; - case MA_SPA_AUDIO_CHANNEL_AUX20: return MA_CHANNEL_AUX_20; - case MA_SPA_AUDIO_CHANNEL_AUX21: return MA_CHANNEL_AUX_21; - case MA_SPA_AUDIO_CHANNEL_AUX22: return MA_CHANNEL_AUX_22; - case MA_SPA_AUDIO_CHANNEL_AUX23: return MA_CHANNEL_AUX_23; - case MA_SPA_AUDIO_CHANNEL_AUX24: return MA_CHANNEL_AUX_24; - case MA_SPA_AUDIO_CHANNEL_AUX25: return MA_CHANNEL_AUX_25; - case MA_SPA_AUDIO_CHANNEL_AUX26: return MA_CHANNEL_AUX_26; - case MA_SPA_AUDIO_CHANNEL_AUX27: return MA_CHANNEL_AUX_27; - case MA_SPA_AUDIO_CHANNEL_AUX28: return MA_CHANNEL_AUX_28; - case MA_SPA_AUDIO_CHANNEL_AUX29: return MA_CHANNEL_AUX_29; - case MA_SPA_AUDIO_CHANNEL_AUX30: return MA_CHANNEL_AUX_30; - case MA_SPA_AUDIO_CHANNEL_AUX31: return MA_CHANNEL_AUX_31; - - /* These need to be added to miniaudio. */ - #if 0 - case MA_SPA_AUDIO_CHANNEL_AUX32: return MA_CHANNEL_AUX_32; - case MA_SPA_AUDIO_CHANNEL_AUX33: return MA_CHANNEL_AUX_33; - case MA_SPA_AUDIO_CHANNEL_AUX34: return MA_CHANNEL_AUX_34; - case MA_SPA_AUDIO_CHANNEL_AUX35: return MA_CHANNEL_AUX_35; - case MA_SPA_AUDIO_CHANNEL_AUX36: return MA_CHANNEL_AUX_36; - case MA_SPA_AUDIO_CHANNEL_AUX37: return MA_CHANNEL_AUX_37; - case MA_SPA_AUDIO_CHANNEL_AUX38: return MA_CHANNEL_AUX_38; - case MA_SPA_AUDIO_CHANNEL_AUX39: return MA_CHANNEL_AUX_39; - case MA_SPA_AUDIO_CHANNEL_AUX40: return MA_CHANNEL_AUX_40; - case MA_SPA_AUDIO_CHANNEL_AUX41: return MA_CHANNEL_AUX_41; - case MA_SPA_AUDIO_CHANNEL_AUX42: return MA_CHANNEL_AUX_42; - case MA_SPA_AUDIO_CHANNEL_AUX43: return MA_CHANNEL_AUX_43; - case MA_SPA_AUDIO_CHANNEL_AUX44: return MA_CHANNEL_AUX_44; - case MA_SPA_AUDIO_CHANNEL_AUX45: return MA_CHANNEL_AUX_45; - case MA_SPA_AUDIO_CHANNEL_AUX46: return MA_CHANNEL_AUX_46; - case MA_SPA_AUDIO_CHANNEL_AUX47: return MA_CHANNEL_AUX_47; - case MA_SPA_AUDIO_CHANNEL_AUX48: return MA_CHANNEL_AUX_48; - case MA_SPA_AUDIO_CHANNEL_AUX49: return MA_CHANNEL_AUX_49; - case MA_SPA_AUDIO_CHANNEL_AUX50: return MA_CHANNEL_AUX_50; - case MA_SPA_AUDIO_CHANNEL_AUX51: return MA_CHANNEL_AUX_51; - case MA_SPA_AUDIO_CHANNEL_AUX52: return MA_CHANNEL_AUX_52; - case MA_SPA_AUDIO_CHANNEL_AUX53: return MA_CHANNEL_AUX_53; - case MA_SPA_AUDIO_CHANNEL_AUX54: return MA_CHANNEL_AUX_54; - case MA_SPA_AUDIO_CHANNEL_AUX55: return MA_CHANNEL_AUX_55; - case MA_SPA_AUDIO_CHANNEL_AUX56: return MA_CHANNEL_AUX_56; - case MA_SPA_AUDIO_CHANNEL_AUX57: return MA_CHANNEL_AUX_57; - case MA_SPA_AUDIO_CHANNEL_AUX58: return MA_CHANNEL_AUX_58; - case MA_SPA_AUDIO_CHANNEL_AUX59: return MA_CHANNEL_AUX_59; - case MA_SPA_AUDIO_CHANNEL_AUX60: return MA_CHANNEL_AUX_60; - case MA_SPA_AUDIO_CHANNEL_AUX61: return MA_CHANNEL_AUX_61; - case MA_SPA_AUDIO_CHANNEL_AUX62: return MA_CHANNEL_AUX_62; - case MA_SPA_AUDIO_CHANNEL_AUX63: return MA_CHANNEL_AUX_63; - #endif - - default: return MA_CHANNEL_NONE; - } -} - - -static ma_context_state_pipewire* ma_context_get_backend_state__pipewire(ma_context* pContext) -{ - return (ma_context_state_pipewire*)ma_context_get_backend_state(pContext); -} - -static ma_device_state_pipewire* ma_device_get_backend_state__pipewire(ma_device* pDevice) -{ - return (ma_device_state_pipewire*)ma_device_get_backend_state(pDevice); -} - - -static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode); - - -static void ma_backend_info__pipewire(ma_device_backend_info* pBackendInfo) -{ - MA_PIPEWIRE_ASSERT(pBackendInfo != NULL); - pBackendInfo->pName = "PipeWire"; -} - -static ma_result ma_context_init__pipewire(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) -{ - /* We'll use a list of possible shared object names for easier extensibility. */ - ma_context_state_pipewire* pContextStatePipeWire; - ma_context_config_pipewire* pContextConfigPipeWire = (ma_context_config_pipewire*)pContextBackendConfig; - ma_log* pLog = ma_context_get_log(pContext); - - (void)pContextConfigPipeWire; - - pContextStatePipeWire = (ma_context_state_pipewire*)ma_calloc(sizeof(*pContextStatePipeWire), ma_context_get_allocation_callbacks(pContext)); - if (pContextStatePipeWire == NULL) { - return MA_OUT_OF_MEMORY; - } - - pContextStatePipeWire->pLog = pLog; - - - /* Check if we have a PipeWire SO. If we can't find this we need to abort. */ - #ifndef MA_NO_RUNTIME_LINKING - { - ma_handle hPipeWire; - size_t iName; - const char* pSONames[] = { - "libpipewire-0.3.so.0", - "libpipewire.so" - }; - - for (iName = 0; iName < ma_countof(pSONames); iName += 1) { - hPipeWire = ma_dlopen(pLog, pSONames[iName]); - if (hPipeWire != NULL) { - break; - } - } - - if (hPipeWire == NULL) { - ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); - return MA_NO_BACKEND; /* PipeWire could not be loaded. */ - } - - pContextStatePipeWire->hPipeWire = hPipeWire; - pContextStatePipeWire->pw_init = (ma_pw_init_proc )ma_dlsym(pLog, hPipeWire, "pw_init"); - pContextStatePipeWire->pw_deinit = (ma_pw_deinit_proc )ma_dlsym(pLog, hPipeWire, "pw_deinit"); - pContextStatePipeWire->pw_loop_new = (ma_pw_loop_new_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_new"); - pContextStatePipeWire->pw_loop_destroy = (ma_pw_loop_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_destroy"); - pContextStatePipeWire->pw_loop_set_name = (ma_pw_loop_set_name_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_set_name"); - pContextStatePipeWire->pw_loop_enter = (ma_pw_loop_enter_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_enter"); - pContextStatePipeWire->pw_loop_leave = (ma_pw_loop_leave_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_leave"); - pContextStatePipeWire->pw_loop_iterate = (ma_pw_loop_iterate_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_iterate"); - pContextStatePipeWire->pw_loop_add_event = (ma_pw_loop_add_event_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_add_event"); - pContextStatePipeWire->pw_loop_signal_event = (ma_pw_loop_signal_event_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_signal_event"); - pContextStatePipeWire->pw_thread_loop_new = (ma_pw_thread_loop_new_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_new"); - pContextStatePipeWire->pw_thread_loop_destroy = (ma_pw_thread_loop_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_destroy"); - pContextStatePipeWire->pw_thread_loop_get_loop = (ma_pw_thread_loop_get_loop_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_get_loop"); - pContextStatePipeWire->pw_thread_loop_start = (ma_pw_thread_loop_start_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_start"); - pContextStatePipeWire->pw_thread_loop_lock = (ma_pw_thread_loop_lock_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_lock"); - pContextStatePipeWire->pw_thread_loop_unlock = (ma_pw_thread_loop_unlock_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_unlock"); - pContextStatePipeWire->pw_context_new = (ma_pw_context_new_proc )ma_dlsym(pLog, hPipeWire, "pw_context_new"); - pContextStatePipeWire->pw_context_destroy = (ma_pw_context_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_context_destroy"); - pContextStatePipeWire->pw_context_connect = (ma_pw_context_connect_proc )ma_dlsym(pLog, hPipeWire, "pw_context_connect"); - pContextStatePipeWire->pw_core_disconnect = (ma_pw_core_disconnect_proc )ma_dlsym(pLog, hPipeWire, "pw_core_disconnect"); - pContextStatePipeWire->pw_core_add_listener = (ma_pw_core_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_core_add_listener"); - pContextStatePipeWire->pw_core_get_registry = (ma_pw_core_get_registry_proc )ma_dlsym(pLog, hPipeWire, "pw_core_get_registry"); - pContextStatePipeWire->pw_core_sync = (ma_pw_core_sync_proc )ma_dlsym(pLog, hPipeWire, "pw_core_sync"); - pContextStatePipeWire->pw_registry_add_listener = (ma_pw_registry_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_registry_add_listener"); - pContextStatePipeWire->pw_registry_bind = (ma_pw_registry_bind_proc )ma_dlsym(pLog, hPipeWire, "pw_registry_bind"); - pContextStatePipeWire->pw_proxy_destroy = (ma_pw_proxy_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_proxy_destroy"); - pContextStatePipeWire->pw_properties_new = (ma_pw_properties_new_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_new"); - pContextStatePipeWire->pw_properties_free = (ma_pw_properties_free_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_free"); - pContextStatePipeWire->pw_properties_set = (ma_pw_properties_set_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_set"); - pContextStatePipeWire->pw_stream_new = (ma_pw_stream_new_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_new"); - pContextStatePipeWire->pw_stream_destroy = (ma_pw_stream_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_destroy"); - pContextStatePipeWire->pw_stream_add_listener = (ma_pw_stream_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_add_listener"); - pContextStatePipeWire->pw_stream_connect = (ma_pw_stream_connect_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_connect"); - pContextStatePipeWire->pw_stream_set_active = (ma_pw_stream_set_active_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_set_active"); - pContextStatePipeWire->pw_stream_dequeue_buffer = (ma_pw_stream_dequeue_buffer_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_dequeue_buffer"); - pContextStatePipeWire->pw_stream_queue_buffer = (ma_pw_stream_queue_buffer_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_queue_buffer"); - pContextStatePipeWire->pw_stream_update_params = (ma_pw_stream_update_params_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_update_params"); - pContextStatePipeWire->pw_stream_update_properties = (ma_pw_stream_update_properties_proc)ma_dlsym(pLog, hPipeWire, "pw_stream_update_properties"); - pContextStatePipeWire->pw_stream_get_time_n = (ma_pw_stream_get_time_n_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_get_time_n"); - } - #else - { - pContextStatePipeWire->pw_init = pw_init; - pContextStatePipeWire->pw_deinit = pw_deinit; - pContextStatePipeWire->pw_loop_new = pw_loop_new; - pContextStatePipeWire->pw_loop_destroy = pw_loop_destroy; - pContextStatePipeWire->pw_loop_set_name = pw_loop_set_name; - pContextStatePipeWire->pw_loop_enter = pw_loop_enter; - pContextStatePipeWire->pw_loop_leave = pw_loop_leave; - pContextStatePipeWire->pw_loop_iterate = pw_loop_iterate; - pContextStatePipeWire->pw_loop_add_event = pw_loop_add_event; - pContextStatePipeWire->pw_loop_signal_event = pw_loop_signal_event; - pContextStatePipeWire->pw_thread_loop_new = pw_thread_loop_new; - pContextStatePipeWire->pw_thread_loop_destroy = pw_thread_loop_destroy; - pContextStatePipeWire->pw_thread_loop_get_loop = pw_thread_loop_get_loop; - pContextStatePipeWire->pw_thread_loop_start = pw_thread_loop_start; - pContextStatePipeWire->pw_thread_loop_lock = pw_thread_loop_lock; - pContextStatePipeWire->pw_thread_loop_unlock = pw_thread_loop_unlock; - pContextStatePipeWire->pw_context_new = pw_context_new; - pContextStatePipeWire->pw_context_destroy = pw_context_destroy; - pContextStatePipeWire->pw_context_connect = pw_context_connect; - pContextStatePipeWire->pw_core_disconnect = pw_core_disconnect; - pContextStatePipeWire->pw_core_add_listener = pw_core_add_listener; - pContextStatePipeWire->pw_core_get_registry = pw_core_get_registry; - pContextStatePipeWire->pw_core_sync = pw_core_sync; - pContextStatePipeWire->pw_registry_add_listener = pw_registry_add_listener; - pContextStatePipeWire->pw_registry_bind = pw_registry_bind; - pContextStatePipeWire->pw_proxy_destroy = pw_proxy_destroy; - pContextStatePipeWire->pw_properties_new = pw_properties_new; - pContextStatePipeWire->pw_properties_free = pw_properties_free; - pContextStatePipeWire->pw_properties_set = pw_properties_set; - pContextStatePipeWire->pw_stream_new = pw_stream_new; - pContextStatePipeWire->pw_stream_destroy = pw_stream_destroy; - pContextStatePipeWire->pw_stream_add_listener = pw_stream_add_listener; - pContextStatePipeWire->pw_stream_connect = pw_stream_connect; - pContextStatePipeWire->pw_stream_set_active = (ma_pw_stream_set_active_proc)(void*)pw_stream_set_active; /* This cast is because we use `unsigned char` instead of `bool`. */ - pContextStatePipeWire->pw_stream_dequeue_buffer = pw_stream_dequeue_buffer; - pContextStatePipeWire->pw_stream_queue_buffer = pw_stream_queue_buffer; - pContextStatePipeWire->pw_stream_update_params = pw_stream_update_params; - pContextStatePipeWire->pw_stream_update_properties = pw_stream_update_properties; - pContextStatePipeWire->pw_stream_get_time_n = pw_stream_get_time_n; - } - #endif - - if (pContextStatePipeWire->pw_init != NULL) { - pContextStatePipeWire->pw_init(NULL, NULL); - } - - *ppContextState = pContextStatePipeWire; - - return MA_SUCCESS; -} - -static void ma_context_uninit__pipewire(ma_context* pContext) -{ - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); - - MA_PIPEWIRE_ASSERT(pContextStatePipeWire != NULL); - - if (pContextStatePipeWire->pw_deinit != NULL) { - pContextStatePipeWire->pw_deinit(); - } - - /* Close the handle to the PipeWire shared object last. */ - #ifndef MA_NO_RUNTIME_LINKING - { - ma_dlclose(ma_context_get_log(pContext), pContextStatePipeWire->hPipeWire); - pContextStatePipeWire->hPipeWire = NULL; - } - #endif - - ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); -} - - -static ma_device_enumeration_result ma_context_enumerate_default_device_by_type__pipewire(ma_context* pContext, ma_device_type deviceType, ma_uint32 sampleRate, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_device_info deviceInfo; - ma_uint32 minSampleRate; - ma_uint32 maxSampleRate; - - (void)pContext; - - if (sampleRate != 0) { - minSampleRate = sampleRate; - maxSampleRate = sampleRate; - } else { - minSampleRate = ma_standard_sample_rate_min; - maxSampleRate = ma_standard_sample_rate_max; - } - - MA_PIPEWIRE_ZERO_OBJECT(&deviceInfo); - - /* Default. */ - deviceInfo.isDefault = MA_TRUE; - - /* ID. */ - deviceInfo.id.custom.i = 0; - - /* Name. */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); - } else { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Capture Device", (size_t)-1); - } - - /* Data Format. */ - ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, minSampleRate, maxSampleRate); - - return callback(deviceType, &deviceInfo, pUserData); -} - - - -#define MA_PW_CORE_SYNC_FLAG_ENUM_DONE (1 << 0) -#define MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE (1 << 1) -#define MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE (1 << 2) - -typedef struct -{ - ma_device_info info; - ma_uint32 pwID; -} ma_enumerate_devices_entry_pipewire; - -typedef struct -{ - ma_context_state_pipewire* pContextStatePipeWire; - struct ma_pw_loop* pLoop; - struct ma_pw_core* pCore; - struct ma_pw_registry* pRegistry; - struct ma_pw_metadata* pMetadataDefault; /* "default" metadata. */ - struct ma_pw_metadata* pMetadataSettings; /* "settings" metadata. */ - struct ma_spa_hook metadataListenerDefault; /* "default" metadata */ - struct ma_spa_hook metadataListenerSettings; /* "settings" metadata. */ - int seqDefaults; - int seqSettings; - int seqEnumeration; - ma_uint32 syncFlags; - ma_uint32 clockRate; /* "clock.rate" from the "settings" metadata. */ - const ma_allocation_callbacks* pAllocationCallbacks; - struct - { - ma_device_id defaultDeviceID; - ma_enumerate_devices_entry_pipewire* pDevices; - size_t deviceInfoCount; - size_t deviceInfoCap; - } playback, capture; - ma_bool32 isAborted; /* We can't seem to be able to abort enumeration with PipeWire, so we'll just set a flag to indicate it and then ignore anything that comes after. */ -} ma_enumerate_devices_data_pipewire; - - -static void ma_on_core_done__pipewire(void* pUserData, ma_uint32 id, int seq) -{ - ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; - - (void)id; - (void)seq; - - /*printf("Core Done: ID=%u, Seq=%d\n", id, seq);*/ - if (pEnumData->seqEnumeration == seq) { - pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_ENUM_DONE; - } else if (pEnumData->seqDefaults == seq) { - pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE; - } -} - -static const struct ma_pw_core_events ma_gCoreEventsPipeWire = -{ - MA_PW_VERSION_CORE_EVENTS, - NULL, - ma_on_core_done__pipewire, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL -}; - - -static void ma_enumerate_devices_data_pipewire_init(ma_enumerate_devices_data_pipewire* pEnumData, ma_context_state_pipewire* pContextStatePipeWire, struct ma_pw_loop* pLoop, struct ma_pw_core* pCore, struct ma_pw_registry* pRegistry, const ma_allocation_callbacks* pAllocationCallbacks) -{ - MA_PIPEWIRE_ZERO_OBJECT(pEnumData); - pEnumData->pContextStatePipeWire = pContextStatePipeWire; - pEnumData->pLoop = pLoop; - pEnumData->pCore = pCore; - pEnumData->pRegistry = pRegistry; - pEnumData->pAllocationCallbacks = pAllocationCallbacks; -} - -static void ma_enumerate_devices_data_pipewire_uninit(ma_enumerate_devices_data_pipewire* pEnumData) -{ - if (pEnumData->pMetadataDefault != NULL) { - pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataDefault); - } - if (pEnumData->pMetadataSettings != NULL) { - pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataSettings); - } - - ma_free(pEnumData->playback.pDevices, pEnumData->pAllocationCallbacks); - ma_free(pEnumData->capture.pDevices, pEnumData->pAllocationCallbacks); -} - -static ma_result ma_enumerate_devices_data_pipewire_add(ma_enumerate_devices_data_pipewire* pEnumData, ma_device_type deviceType, ma_uint32 pwID, const ma_device_info* pDeviceInfo) -{ - size_t* pDeviceCount; - size_t* pDeviceCap; - ma_enumerate_devices_entry_pipewire** ppDevices; - - if (deviceType == ma_device_type_playback) { - pDeviceCount = &pEnumData->playback.deviceInfoCount; - pDeviceCap = &pEnumData->playback.deviceInfoCap; - ppDevices = &pEnumData->playback.pDevices; - } else { - pDeviceCount = &pEnumData->capture.deviceInfoCount; - pDeviceCap = &pEnumData->capture.deviceInfoCap; - ppDevices = &pEnumData->capture.pDevices; - } - - if (*pDeviceCount + 1 > *pDeviceCap) { - size_t newCap = *pDeviceCap * 2; - ma_enumerate_devices_entry_pipewire* pNewDevices; - - if (newCap == 0) { - newCap = 8; - } - - pNewDevices = (ma_enumerate_devices_entry_pipewire*)ma_realloc(*ppDevices, newCap * sizeof(*pNewDevices), pEnumData->pAllocationCallbacks); - if (pNewDevices == NULL) { - return MA_OUT_OF_MEMORY; - } - - *ppDevices = pNewDevices; - *pDeviceCap = newCap; - } - - ((*ppDevices)[*pDeviceCount]).info = *pDeviceInfo; - ((*ppDevices)[*pDeviceCount]).pwID = pwID; - *pDeviceCount += 1; - - return MA_SUCCESS; -} - - - -static int ma_on_metadata_property_default__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) -{ - ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; - - (void)subject; - (void)type; - - if (key == NULL) { - return 0; - } - - /* - Well this is fun. To get the default device we need to get the value of the "default.audio.sink" and "default.audio.source" keys. Sounds - simple enough, except that the value is actually JSON... Why is the default device stored as a JSON string? Who does this? We're just - going to use a simplified parser that finds the first ":\"" and takes everything until the next "\"". - */ - if (strcmp(key, "default.audio.sink") == 0 || strcmp(key, "default.audio.source") == 0) { - const char* pValue = value; - const char* pStart; - const char* pEnd; - ma_device_id* pDefaultDeviceID; - - if (strcmp(key, "default.audio.sink") == 0) { - pDefaultDeviceID = &pEnumData->playback.defaultDeviceID; - } else { - pDefaultDeviceID = &pEnumData->capture.defaultDeviceID; - } - - pStart = strstr(pValue, ":\""); - if (pStart != NULL) { - pStart += 2; /* Move past the :". */ - pEnd = strchr(pStart, '\"'); - if (pEnd != NULL) { - size_t len = (size_t)(pEnd - pStart); - if (len >= sizeof(pDefaultDeviceID->custom.s)) { - len = sizeof(pDefaultDeviceID->custom.s) - 1; - } - - ma_strncpy_s(pDefaultDeviceID->custom.s, sizeof(pDefaultDeviceID->custom.s), pStart, len); - } - } - } - - /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ - return 0; -} - -static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Default = -{ - MA_PW_VERSION_METADATA_EVENTS, - ma_on_metadata_property_default__pipewire -}; - - -static int ma_on_metadata_property_settings__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) -{ - ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; - - (void)pEnumData; - (void)subject; - (void)type; - - if (key == NULL) { - return 0; - } - - if (strcmp(key, "clock.rate") == 0) { - pEnumData->clockRate = (ma_uint32)atoi(value); - } - - /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ - return 0; -} - -static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Settings = -{ - MA_PW_VERSION_METADATA_EVENTS, - ma_on_metadata_property_settings__pipewire -}; - - -typedef struct -{ - enum ma_spa_audio_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - const ma_uint32* pChannelPositions; -} ma_pipewire_audio_info; - -static void ma_pipewire_audio_info_parse(const struct ma_spa_pod* pParam, ma_log* pLog, ma_pipewire_audio_info* pAudioInfo) -{ - struct ma_spa_pod_object* pObject; - struct ma_spa_pod_prop* pNextProp; - ma_uint8* ptr = (ma_uint8*)pParam; - ma_uint32 cursor = 0; - ma_uint32 size; - - MA_PIPEWIRE_ZERO_OBJECT(pAudioInfo); - - if (pParam == NULL) { - return; - } - - if (pParam->type != MA_SPA_TYPE_Object || pParam->size < sizeof(struct ma_spa_pod_object_body)) { - ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid pod type)."); - return; - } - - pObject = (struct ma_spa_pod_object*)pParam; - - /* The size of the pod does not include the header which makes parsing kind of annoying for us. We'll add it here. */ - size = pParam->size + sizeof(struct ma_spa_pod); - cursor += sizeof(struct ma_spa_pod); - - if (pObject->body.type != MA_SPA_TYPE_OBJECT_Format) { - ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid body type)."); - return; - } - - cursor += sizeof(struct ma_spa_pod_object_body); - - /* At this point we have parsed the header part of the object, and what follows now should be a list of properties. */ - while (cursor < size) { - struct ma_spa_pod* pPropValue; - - pNextProp = (struct ma_spa_pod_prop*)(ptr + cursor); - if (cursor + sizeof(struct ma_spa_pod_prop) > size) { - ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid size for property)."); - return; - } - - /* Place the cursor on the prop value, which is a spa_pod. */ - cursor += 8; /* Size of the key and flags. */ - pPropValue = (struct ma_spa_pod*)(ptr + cursor); - - switch (pNextProp->key) - { - case MA_SPA_FORMAT_AUDIO_format: - { - pAudioInfo->format = (enum ma_spa_audio_format)ma_spa_pod_get_id_value(pPropValue); - } break; - - case MA_SPA_FORMAT_AUDIO_channels: - { - pAudioInfo->channels = ma_spa_pod_get_int_value(pPropValue); - } break; - - case MA_SPA_FORMAT_AUDIO_rate: - { - pAudioInfo->sampleRate = ma_spa_pod_get_int_value(pPropValue); - } break; - - case MA_SPA_FORMAT_AUDIO_position: - { - /* I'm assuming we can only get an array back for this. */ - if (pPropValue->type != MA_SPA_TYPE_Array) { - ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid type for position property)."); - return; - } - - pAudioInfo->pChannelPositions = (const ma_uint32*)ma_spa_pod_array_get_values((const struct ma_spa_pod_array*)pPropValue); - } break; - } - - cursor += ma_align_64(8 + pPropValue->size); /* Size of the value pod header + the size of the pod itself. */ - } -} - - -/* -NOTE - -The commented out code below was my first attempt to extract the "native" channel count and sample rate of -a device. Essentially what it's doing is probing each device by creating stream, connecting it, and then -querying the chosen channels and rate. This was a complete fail because there's what I consider to be a bug -somewhere in the PipeWire/ALSA pipeline. I have S/PDIF device with no device actually physically connected -to it. When I probe that device, the system volume for the entire operating system will suddenly go full -blast. This is clearly not something that can be shipped so I've had to comment out this code. I'm leaving -it here more as a reference and a reminder. - -This can be replicated from the OS itself without needing to run a program. In my case, if I switch the -default device to the S/PDIF device, and then back to my headphones, everything goes full volume and your -ears will get blown out. -*/ -#if 0 -typedef struct -{ - ma_log* pLog; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_bool32 done; -} ma_stream_event_param_changed_enumeration_data__pipewire; - -static void ma_stream_event_param_changed_enumeration__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) -{ - 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; - ma_pipewire_audio_info_parse(pParam, pData->pLog, &audioInfo); - - pData->channels = audioInfo.channels; - pData->sampleRate = audioInfo.sampleRate; - - pData->done = MA_TRUE; - } -} - -static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Enumeration = -{ - MA_PW_VERSION_STREAM_EVENTS, - NULL, /* destroy */ - NULL, /* state_changed */ - NULL, /* control_info */ - NULL, /* io_changed */ - ma_stream_event_param_changed_enumeration__pipewire, - NULL, /* add_buffer */ - NULL, /* remove_buffer */ - NULL, /* process */ - NULL, /* drained */ - NULL, /* command */ - 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); - } -} -#endif - -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; - const char* pNodeName; /* <-- This is the ID. */ - const char* pNiceName; - - (void)permissions; - (void)version; - (void)type; - - /* The node name is the ID. */ - pNodeName = ma_spa_dict_lookup(props, "node.name"); - - /* Name. */ - pNiceName = ma_spa_dict_lookup(props, "node.description"); - if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.description"); } - if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.nick"); } - if (pNiceName == NULL) { pNiceName = pNodeName; } - if (pNiceName == NULL) { pNiceName = "Unknown"; } - - /* Fill out the device info structure. */ - MA_PIPEWIRE_ZERO_OBJECT(&deviceInfo); - - /* The default flag is set later in a second pass. */ - - /* ID. */ - ma_strncpy_s(deviceInfo.id.custom.s, sizeof(deviceInfo.id.custom.s), pNodeName, (size_t)-1); - - /* Name. */ - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1); - - /* The native data format is set later in a second pass. */ - - - /* Finally we can add the device into to our internal list. */ - ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, id, &deviceInfo); - - /*printf("Registry Global Added By Type: ID=%u, Type=%s, DeviceType=%d, NiceName=%s\n", id, type, deviceType, pNiceName);*/ -} - -static void ma_registry_event_global_add_enumeration__pipewire(void* pUserData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props) -{ - ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; - const char* pMediaClass; - - /* Ignore all future iterations if we have aborted. */ - if (pEnumData->isAborted) { - return; - } - - /* Some device info needs to be retrieved from metadata. */ - if (strcmp(type, MA_PW_TYPE_INTERFACE_Metadata) == 0) { - const char* pName; - - pName = ma_spa_dict_lookup(props, MA_PW_KEY_METADATA_NAME); - if (pName != NULL) { - /* Default devices. */ - if (strcmp(pName, "default") == 0) { - pEnumData->pMetadataDefault = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); - if (pEnumData->pMetadataDefault != NULL) { - MA_PIPEWIRE_ZERO_OBJECT(&pEnumData->metadataListenerDefault); - - /* Not using pw_metadata_add_listener() because it appears to be an inline function and thus not exported by libpipewire. */ - ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataDefault)->cb.funcs)->add_listener(pEnumData->pMetadataDefault, &pEnumData->metadataListenerDefault, &ma_gMetadataEventsPipeWire_Default, pEnumData); - - /*spa_api_method_r(int, -ENOTSUP, ma_pw_metadata, (struct spa_interface*)pEnumData->pMetadata, add_listener, 0, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, pEnumData);*/ - /*pEnumData->pContextStatePipeWire->pw_metadata_add_listener(pMetadata, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, NULL);*/ - - pEnumData->seqDefaults = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); - } - } - - /* Sample rate. */ - if (strcmp(pName, "settings") == 0) { - pEnumData->pMetadataSettings = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); - if (pEnumData->pMetadataSettings != NULL) { - MA_PIPEWIRE_ZERO_OBJECT(&pEnumData->metadataListenerSettings); - ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataSettings)->cb.funcs)->add_listener(pEnumData->pMetadataSettings, &pEnumData->metadataListenerSettings, &ma_gMetadataEventsPipeWire_Settings, pEnumData); - - pEnumData->seqSettings = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); - } - } - } - - - return; - } - - /* From here on out we only care about nodes. */ - if (strcmp(type, MA_PW_TYPE_INTERFACE_Node) != 0) { - return; - } - - pMediaClass = ma_spa_dict_lookup(props, MA_PW_KEY_MEDIA_CLASS); - if (pMediaClass == NULL) { - return; - } - - /* If the string contains Audio/Sink or Audio/Source we can assume it's an enumerable node. */ - /* */ if (strcmp(pMediaClass, "Audio/Sink") == 0) { - ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_playback); - } else if (strcmp(pMediaClass, "Audio/Source") == 0) { - ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_capture); - } else { - return; /* Not an audio node we care about. */ - } -} - -static const struct ma_pw_registry_events ma_gRegistryEventsPipeWire_Enumeration = -{ - MA_PW_VERSION_REGISTRY_EVENTS, - ma_registry_event_global_add_enumeration__pipewire, - NULL -}; - - -static ma_result ma_context_enumerate_devices__pipewire(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); - ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; - struct ma_pw_loop* pLoop; - struct ma_pw_context* pPipeWireContext; - struct ma_pw_core* pCore; - struct ma_pw_registry* pRegistry; - struct ma_spa_hook coreListener; - struct ma_spa_hook registeryListener; - ma_enumerate_devices_data_pipewire enumData; - - MA_PIPEWIRE_ASSERT(pContextStatePipeWire != NULL); - MA_PIPEWIRE_ASSERT(callback != NULL); - - pLoop = pContextStatePipeWire->pw_loop_new(NULL); - if (pLoop == NULL) { - return MA_ERROR; - } - - pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); - if (pPipeWireContext == NULL) { - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_ERROR; - } - - pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); - if (pCore == NULL) { - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_ERROR; - } - - pContextStatePipeWire->pw_core_add_listener(pCore, &coreListener, &ma_gCoreEventsPipeWire, &enumData); - - pRegistry = pContextStatePipeWire->pw_core_get_registry(pCore, MA_PW_VERSION_REGISTRY, 0); - if (pRegistry == NULL) { - pContextStatePipeWire->pw_core_disconnect(pCore); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_ERROR; - } - - ma_enumerate_devices_data_pipewire_init(&enumData, pContextStatePipeWire, pLoop, pCore, pRegistry, ma_context_get_allocation_callbacks(pContext)); - - MA_PIPEWIRE_ZERO_OBJECT(®isteryListener); - pContextStatePipeWire->pw_registry_add_listener(pRegistry, ®isteryListener, &ma_gRegistryEventsPipeWire_Enumeration, &enumData); - - /* - The pw_core_sync() function is extremely confusing. The documentation says this: - - Ask the server to emit the 'done' event with seq. - - The last parameter of pw_core_sync() is `seq`, and in the `done` callback, there is a parameter called `seq`. The documentation - makes it sound like the `seq` argument of the `done` callback will be set to what you specify in the `pw_core_sync()` call, but - this is not the case. The `seq` argument in the `done` callback will actually be the return value of `pw_core_sync()`. If there - is a PipeWire developer reading this, this documentation needs to be addressed. Feedback welcome if I'm misunderstanding or just - doing something wrong here. - */ - enumData.seqEnumeration = pContextStatePipeWire->pw_core_sync(pCore, MA_PW_ID_CORE, 0); - for (;;) { - pContextStatePipeWire->pw_loop_iterate(pLoop, -1); - - if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_ENUM_DONE) != 0) { - if (enumData.seqDefaults == 0) { - break; /* We don't have a "default" metadata. */ - } - if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE) != 0) { - break; - } - - if (enumData.seqSettings == 0) { - break; /* We don't have a "settings" metadata. */ - } - if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE) != 0) { - break; - } - } - } - - - /* Here is where we iterate over each device and fire the callback. */ - { - size_t iDevice; - ma_bool32 hasDefaultPlaybackDevice = MA_FALSE; - ma_bool32 hasDefaultCaptureDevice = MA_FALSE; - ma_uint32 minChannels = 1; - ma_uint32 maxChannels = 64; - ma_uint32 minSampleRate; - ma_uint32 maxSampleRate; - - if (enumData.clockRate != 0) { - minSampleRate = enumData.clockRate; - maxSampleRate = enumData.clockRate; - } else { - minSampleRate = ma_standard_sample_rate_min; - maxSampleRate = ma_standard_sample_rate_max; - } - - /* Playback devices. */ - for (iDevice = 0; iDevice < enumData.playback.deviceInfoCount; iDevice += 1) { - if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { - if (enumData.playback.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.playback.pDevices[iDevice].info.id.custom.s, enumData.playback.defaultDeviceID.custom.s) == 0) { - enumData.playback.pDevices[iDevice].info.isDefault = MA_TRUE; - hasDefaultPlaybackDevice = MA_TRUE; - } - - /* Now we need to open the stream and get it's native data format. */ - ma_device_info_add_native_data_format(&enumData.playback.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); - - cbResult = callback(ma_device_type_playback, &enumData.playback.pDevices[iDevice].info, pUserData); - } - } - - if (enumData.playback.deviceInfoCount > 0 && !hasDefaultPlaybackDevice) { - if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { - cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_playback, enumData.clockRate, callback, pUserData); - } - } - - - /* Capture devices. */ - for (iDevice = 0; iDevice < enumData.capture.deviceInfoCount; iDevice += 1) { - if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { - if (enumData.capture.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.capture.pDevices[iDevice].info.id.custom.s, enumData.capture.defaultDeviceID.custom.s) == 0) { - enumData.capture.pDevices[iDevice].info.isDefault = MA_TRUE; - hasDefaultCaptureDevice = MA_TRUE; - } - - /* Now we need to open the stream and get it's native data format. */ - ma_device_info_add_native_data_format(&enumData.capture.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); - - cbResult = callback(ma_device_type_capture, &enumData.capture.pDevices[iDevice].info, pUserData); - } - } - - if (enumData.capture.deviceInfoCount > 0 && !hasDefaultCaptureDevice) { - if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { - cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_capture, enumData.clockRate, callback, pUserData); - } - } - - (void)cbResult; /* Silence a static analysis warning. Want to keep this assignment in case we extend this logic later. */ - } - - - ma_enumerate_devices_data_pipewire_uninit(&enumData); - pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pRegistry); - pContextStatePipeWire->pw_core_disconnect(pCore); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - - return MA_SUCCESS; -} - - -static void ma_stream_event_param_changed__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam, ma_device_type deviceType) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; - ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; - - if (id == MA_SPA_PARAM_Format) { - ma_pipewire_stream_state* pStreamState; - struct ma_spa_pod_buffer_params bufferParams; - const struct ma_spa_pod* pBufferParameters[1]; - ma_uint32 bytesPerFrame; - ma_uint32 iChannel; - ma_pipewire_audio_info audioInfo; - - /* It's possible for PipeWire to fire this callback with pParam set to NULL. I noticed it when tearing down a stream. Why does it do this? */ - if (pParam == NULL) { - return; - } - - if (deviceType == ma_device_type_playback) { - pStreamState = &pDeviceStatePipeWire->playback; - } else { - pStreamState = &pDeviceStatePipeWire->capture; - } - - if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) != 0) { - ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "PipeWire format parameter changed after device has been initialized."); - return; - } - - /* - We can now determine the format/channels/rate which will let us configure the size of the buffer and set the - internal format of the device. To do this we need to parse the pParam pod. - */ - #if 0 - { - struct spa_audio_info_raw audioInfo; - spa_format_audio_raw_parse(pParam, &audioInfo); - } - #endif - { - ma_pipewire_audio_info_parse(pParam, pContextStatePipeWire->pLog, &audioInfo); - } - - /* Now that we definitely know the sample rate, we can reliably configure the size of the buffer. */ - if (pStreamState->bufferSizeInFrames == 0) { - pStreamState->bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pStreamState->pDescriptor, audioInfo.sampleRate); - } - - pStreamState->format = ma_format_from_pipewire(audioInfo.format); - pStreamState->channels = audioInfo.channels; - pStreamState->sampleRate = audioInfo.sampleRate; - - /* We should always get a channel map, but just to be safe we'll check for it, and if we don't get one back we'll use a default. */ - if (audioInfo.pChannelPositions != NULL) { - for (iChannel = 0; iChannel < audioInfo.channels; iChannel += 1) { - pStreamState->channelMap[iChannel] = ma_channel_from_pipewire(audioInfo.pChannelPositions[iChannel]); - } - } else { - ma_channel_map_init_standard(ma_standard_channel_map_alsa, pStreamState->channelMap, ma_countof(pStreamState->channelMap), audioInfo.channels); - } - - - /* Now that we know both the buffer size and sample rate we can update the latency on the PipeWire side. */ - { - struct ma_spa_dict_item items[1]; - struct ma_spa_dict dict; - char bufferSizeInFramesStr[32]; - char sampleRateStr[32]; - char latencyStr[32]; - - ma_pipewire_itoa_s(pStreamState->bufferSizeInFrames, bufferSizeInFramesStr, sizeof(bufferSizeInFramesStr), 10); - ma_pipewire_itoa_s(pStreamState->sampleRate, sampleRateStr, sizeof(sampleRateStr), 10); - - latencyStr[0] = '\0'; - ma_pipewire_strcat_s(latencyStr, sizeof(latencyStr), bufferSizeInFramesStr); - ma_pipewire_strcat_s(latencyStr, sizeof(latencyStr), "/"); - ma_pipewire_strcat_s(latencyStr, sizeof(latencyStr), sampleRateStr); - - items[0] = MA_SPA_DICT_ITEM_INIT(MA_PW_KEY_NODE_LATENCY, latencyStr); - - dict = MA_SPA_DICT_INIT(items, 1); - - pContextStatePipeWire->pw_stream_update_properties(pStreamState->pStream, &dict); - } - - - bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); - - /* - Now update the PipeWire buffer properties. Note that spa_pod_buffer_params_init() is not a standard SPA function. It's a - custom function to eliminate the dependency on libspa for building miniaudio. - */ - bufferParams = ma_spa_pod_buffer_params_init(bytesPerFrame, pStreamState->bufferSizeInFrames); - pBufferParameters[0] = (struct ma_spa_pod*)&bufferParams; - - pContextStatePipeWire->pw_stream_update_params(pStreamState->pStream, pBufferParameters, sizeof(pBufferParameters) / sizeof(pBufferParameters[0])); - - pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT; - } -} - -static void ma_stream_event_process__pipewire(void* pUserData, ma_device_type deviceType) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; - ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; - ma_result result; - ma_pipewire_stream_state* pStreamState; - struct ma_pw_buffer* pBuffer; - ma_uint32 bytesPerFrame; - ma_uint32 frameCount; - - if (deviceType == ma_device_type_playback) { - pStreamState = &pDeviceStatePipeWire->playback; - } else { - pStreamState = &pDeviceStatePipeWire->capture; - } - - /* Debugging stuff. */ - #if 0 - { - double currentTimeInSeconds; - double deltaTimeInSeconds; - - currentTimeInSeconds = ma_timer_get_time_in_seconds(&pDeviceStatePipeWire->debugging.timer); - deltaTimeInSeconds = currentTimeInSeconds - pDeviceStatePipeWire->debugging.lastTimeInSeconds; - pDeviceStatePipeWire->debugging.lastTimeInSeconds = currentTimeInSeconds; - - printf("TRACE: Process Callback %.2f ms\n", deltaTimeInSeconds * 1000.0); - } - #endif - - /* - PipeWire has a bizarre buffer management system. Normally with an audio API you do processing after a certain amount of - time has elapsed, based on the sample rate and buffer size. The frequency at which the processing callback is fired - directly affects latency which is an important metric for audio applications and data management. From what I can tell, - the only way to determine the rate at which this processing callback is fired is from within the callback itself. There - are two ways I'm aware of: - - 1) Dequeue the first buffer and check the `requested` member of `pw_buffer`. - 2) Get the stream time using `pw_stream_get_time_n()` and inspect the `size` member of `pw_time`. - - In capture, the first option cannot be used because `requested` is always zero. That leaves only the second option which - appears to work for both playback and capture. However, the `size` member will only work for the first invocation of the - processing callback because it can change as you enqueue buffers. - - Advice welcome on how to improve this. - */ - if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { - struct ma_pw_time time; - - pContextStatePipeWire->pw_stream_get_time_n(pStreamState->pStream, &time, sizeof(time)); - - if (pStreamState->rbSizeInFrames > 0) { - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { - ma_pcm_rb_uninit(&pStreamState->rb); - } - } - - pStreamState->rbSizeInFrames = (ma_uint32)time.size; - - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { - ma_pcm_rb_init(pStreamState->format, pStreamState->channels, pStreamState->rbSizeInFrames, NULL, ma_device_get_allocation_callbacks(pDeviceStatePipeWire->pDevice), &pStreamState->rb); - } - - pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY; - return; - } - - - bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); - - pBuffer = pContextStatePipeWire->pw_stream_dequeue_buffer(pStreamState->pStream); - if (pBuffer == NULL) { - return; - } - - if (deviceType == ma_device_type_playback) { - frameCount = (ma_uint32)ma_min(pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame); - /*frameCount = (ma_uint32)(pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ - } else { - frameCount = (ma_uint32)(pBuffer->buffer->datas[0].chunk->size / bytesPerFrame); - } - - MA_PIPEWIRE_ASSERT(pBuffer->buffer != NULL); - MA_PIPEWIRE_ASSERT(pBuffer->buffer->n_datas > 0); - MA_PIPEWIRE_ASSERT(pBuffer->buffer->datas[0].data != NULL); - - if (frameCount > 0) { - if (deviceType == ma_device_type_playback) { - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { - ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, pBuffer->buffer->datas[0].data, NULL, frameCount); - } else { - ma_uint32 framesRemaining = frameCount; - ma_uint32 framesAvailable = ma_pcm_rb_available_read(&pStreamState->rb); - - /* Copy data in. Read from the ring buffer, output to the PipeWire buffer. */ - if (framesAvailable < frameCount) { - /* Underflow. Just write silence. */ - MA_PIPEWIRE_ZERO_MEMORY((char*)pBuffer->buffer->datas[0].data + (frameCount - framesRemaining) * bytesPerFrame, framesRemaining * bytesPerFrame); - } else { - /* - Two ways of handling this. In multi-threaded mode, it doesn't really matter which thread does the data - processing so we can just do it directly from here and bypass the ring buffer entirely. - */ - - while (framesRemaining > 0) { - ma_uint32 framesToProcess = (ma_uint32)ma_min(framesRemaining, framesAvailable); - void* pMappedBuffer; - - result = ma_pcm_rb_acquire_read(&pStreamState->rb, &framesToProcess, &pMappedBuffer); - if (result != MA_SUCCESS) { - ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to acquire data from ring buffer."); - break; - } - - MA_PIPEWIRE_COPY_MEMORY((char*)pBuffer->buffer->datas[0].data + ((frameCount - framesRemaining) * bytesPerFrame), pMappedBuffer, framesToProcess * bytesPerFrame); - framesRemaining -= framesToProcess; - - result = ma_pcm_rb_commit_read(&pStreamState->rb, framesToProcess); - if (result != MA_SUCCESS) { - ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to commit read to ring buffer."); - break; - } - } - } - } - - /*printf("(Playback) Processing %d frames... %d %d\n", (int)frameCount, (int)pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ - } else { - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { - ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, NULL, pBuffer->buffer->datas[0].data, frameCount); - } else { - ma_uint32 framesRemaining = frameCount; - ma_uint32 framesAvailable = ma_pcm_rb_available_write(&pStreamState->rb); - - /* Copy data out. Write from the PipeWire buffer to the ring buffer. */ - while (framesRemaining > 0) { - ma_uint32 framesToProcess = (ma_uint32)ma_min(framesRemaining, framesAvailable); - void* pMappedBuffer; - - result = ma_pcm_rb_acquire_write(&pStreamState->rb, &framesToProcess, &pMappedBuffer); - if (result != MA_SUCCESS) { - ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to acquire space in ring buffer."); - break; - } - - MA_PIPEWIRE_COPY_MEMORY(pMappedBuffer, (char*)pBuffer->buffer->datas[0].data + ((frameCount - framesRemaining) * bytesPerFrame), framesToProcess * bytesPerFrame); - framesRemaining -= framesToProcess; - - result = ma_pcm_rb_commit_write(&pStreamState->rb, framesToProcess); - if (result != MA_SUCCESS) { - ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to commit write to ring buffer."); - break; - } - } - } - - /*printf("(Capture) Processing %d frames...\n", (int)frameCount);*/ - } - } else { - /*ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "(PipeWire) No frames to process.\n");*/ - } - - pBuffer->buffer->datas[0].chunk->offset = 0; - pBuffer->buffer->datas[0].chunk->size = frameCount * bytesPerFrame; - - pContextStatePipeWire->pw_stream_queue_buffer(pStreamState->pStream, pBuffer); - - /* We need to make sure the loop is woken up so we can refill the intermediary buffer in the step function. */ - pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); -} - - -static void ma_stream_event_param_changed_playback__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) -{ - ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_playback); -} - -static void ma_stream_event_param_changed_capture__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) -{ - ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_capture); -} - - -static void ma_stream_event_process_playback__pipewire(void* pUserData) -{ - ma_stream_event_process__pipewire(pUserData, ma_device_type_playback); -} - -static void ma_stream_event_process_capture__pipewire(void* pUserData) -{ - ma_stream_event_process__pipewire(pUserData, ma_device_type_capture); -} - - -static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Playback = -{ - MA_PW_VERSION_STREAM_EVENTS, - NULL, /* destroy */ - NULL, /* state_changed */ - NULL, /* control_info */ - NULL, /* io_changed */ - ma_stream_event_param_changed_playback__pipewire, - NULL, /* add_buffer */ - NULL, /* remove_buffer */ - ma_stream_event_process_playback__pipewire, - NULL, /* drained */ - NULL, /* command */ - NULL, /* trigger_done */ -}; - - -static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Capture = -{ - MA_PW_VERSION_STREAM_EVENTS, - NULL, /* destroy */ - NULL, /* state_changed */ - NULL, /* control_info */ - NULL, /* io_changed */ - ma_stream_event_param_changed_capture__pipewire, - NULL, /* add_buffer */ - NULL, /* remove_buffer */ - ma_stream_event_process_capture__pipewire, - NULL, /* drained */ - NULL, /* command */ - NULL, /* trigger_done */ -}; - -static ma_result ma_device_init_internal__pipewire(ma_device* pDevice, ma_context_state_pipewire* pContextStatePipeWire, ma_device_state_pipewire* pDeviceStatePipeWire, const ma_device_config_pipewire* pDeviceConfigPipeWire, ma_device_type deviceType, ma_device_descriptor* pDescriptor) -{ - ma_pipewire_stream_state* pStreamState; - struct ma_pw_properties* pProperties; - const struct ma_pw_stream_events* pStreamEvents; - 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; - - /* This function can only be called for playback or capture sides. */ - if (deviceType != ma_device_type_playback && deviceType != ma_device_type_capture) { - return MA_INVALID_ARGS; - } - - if (deviceType == ma_device_type_playback) { - pStreamState = &pDeviceStatePipeWire->playback; - pStreamEvents = &ma_gStreamEventsPipeWire_Playback; - } else { - pStreamState = &pDeviceStatePipeWire->capture; - pStreamEvents = &ma_gStreamEventsPipeWire_Capture; - } - - /* Set up the buffer size first so the parameter negotiation callback knows how to configure the buffer on the PipeWire side. */ - pStreamState->pDescriptor = pDescriptor; - pStreamState->bufferSizeInFrames = pDescriptor->periodSizeInFrames; - - 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, (pDeviceConfigPipeWire->pMediaRole != NULL) ? pDeviceConfigPipeWire->pMediaRole : "Game", - /* MA_PW_KEY_NODE_LATENCY is set during format negotiation because it depends on knowledge of the sample rate. */ - NULL); - - if (pDescriptor->pDeviceID != NULL) { - pContextStatePipeWire->pw_properties_set(pProperties, MA_PW_KEY_TARGET_OBJECT, pDescriptor->pDeviceID->custom.s); - } - - pStreamState->pStream = pContextStatePipeWire->pw_stream_new(pDeviceStatePipeWire->pCore, (pDeviceConfigPipeWire->pStreamName != NULL) ? pDeviceConfigPipeWire->pStreamName : "miniaudio", pProperties); - if (pStreamState->pStream == NULL) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream."); - return MA_ERROR; - } - - /* This installs callbacks for process and param_changed. "process" is for queuing audio data, and "param_changed" is for getting the internal format/channels/rate. */ - pContextStatePipeWire->pw_stream_add_listener(pStreamState->pStream, &pStreamState->eventListener, pStreamEvents, pDeviceStatePipeWire); - - /* If the format is SPA_AUDIO_FORMAT_UNKNOWN, PipeWire can pick a planar data layout (de-interleaved) which breaks things for us. Just force interleaved F32 in this case. */ - formatPA = ma_format_to_pipewire(pDescriptor->format); - if (formatPA == MA_SPA_AUDIO_FORMAT_UNKNOWN) { - 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, pDescriptor->channels, pDescriptor->sampleRate); - pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; - - - /* - I'm just using MAP_BUFFERS because it's what the PipeWire examples do. I don't know what this does. Also, what's the - point in the AUTOCONNECT flag? Is that not what we're already doing by calling a function called "connect"?! - - We also can't use INACTIVE because without it, the param_changed callback will not get called, but we depend on that - so we can get access to the internal format/channels/rate. - */ - streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT | /*MA_PW_STREAM_FLAG_INACTIVE |*/ MA_PW_STREAM_FLAG_MAP_BUFFERS); - - connectResult = pContextStatePipeWire->pw_stream_connect(pStreamState->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) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream."); - pContextStatePipeWire->pw_stream_destroy(pStreamState->pStream); - return MA_ERROR; - } - - /* We need to keep iterating until we have finalized our internal format. */ - while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) == 0) { - pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); - } - - /* We should have our format at this point, but we will not know the exact period size yet until we've done the first processing callback. */ - pDescriptor->format = pStreamState->format; - pDescriptor->channels = pStreamState->channels; - pDescriptor->sampleRate = pStreamState->sampleRate; - ma_channel_map_copy_or_default(pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pStreamState->channelMap, pStreamState->channels); - - /* Now we need to wait until we know our period size. */ - while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { - pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); - } - - pDescriptor->periodSizeInFrames = pStreamState->rbSizeInFrames; - pDescriptor->periodCount = 1; - - /* Devices are in a stopped state by default in miniaudio. */ - pContextStatePipeWire->pw_stream_set_active(pStreamState->pStream, MA_FALSE); - - pStreamState->pDescriptor = NULL; - pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_INITIALIZED; - return MA_SUCCESS; -} - -static void ma_device_on_wakupe__pipewire(void* pUserData, ma_uint64 count) -{ - /* Nothing to do here. This is only used for waking up the loop. */ - (void)pUserData; - (void)count; -} - -static ma_result ma_device_init__pipewire(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) -{ - ma_result result; - struct ma_pw_loop* pLoop; - struct ma_pw_context* pPipeWireContext; - struct ma_pw_core* pCore; - ma_context_state_pipewire* pContextStatePipeWire; - ma_device_state_pipewire* pDeviceStatePipeWire; - const ma_device_config_pipewire* pDeviceConfigPipeWire; - ma_device_config_pipewire defaultDeviceConfigPipeWire; - ma_device_type deviceType; - - pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - MA_PIPEWIRE_ASSERT(pContextStatePipeWire != NULL); - - /* Grab the config. This can be null in which case we'll use a default. */ - pDeviceConfigPipeWire = (const ma_device_config_pipewire*)pDeviceBackendConfig; - if (pDeviceConfigPipeWire == NULL) { - defaultDeviceConfigPipeWire = ma_device_config_pipewire_init(); - pDeviceConfigPipeWire = &defaultDeviceConfigPipeWire; - } - - deviceType = ma_device_get_type(pDevice); - - /* Not sure how to do loopback with PipeWire, but it feels like something PipeWire would support. Look into this. */ - if (deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } - - pLoop = pContextStatePipeWire->pw_loop_new(NULL); - if (pLoop == NULL) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop."); - return MA_ERROR; - } - - pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); - if (pPipeWireContext == NULL) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire context."); - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_ERROR; - } - - pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); - if (pCore == NULL) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire context."); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_ERROR; - } - - /* We can now allocate our per-device PipeWire-specific data. */ - pDeviceStatePipeWire = (ma_device_state_pipewire*)ma_calloc(sizeof(*pDeviceStatePipeWire), ma_device_get_allocation_callbacks(pDevice)); - if (pDeviceStatePipeWire == NULL) { - pContextStatePipeWire->pw_core_disconnect(pCore); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - return MA_OUT_OF_MEMORY; - } - - pDeviceStatePipeWire->pContextStatePipeWire = pContextStatePipeWire; - pDeviceStatePipeWire->deviceType = deviceType; - pDeviceStatePipeWire->pDevice = pDevice; - pDeviceStatePipeWire->pLoop = pLoop; - pDeviceStatePipeWire->pContext = pPipeWireContext; - pDeviceStatePipeWire->pCore = pCore; - - /* Enter the main loop before we start iterating. */ - pContextStatePipeWire->pw_loop_enter(pLoop); - - result = MA_DEVICE_TYPE_NOT_SUPPORTED; - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_capture, pDescriptorCapture); - } - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_playback, pDescriptorPlayback); - } - - if (result != MA_SUCCESS) { - pContextStatePipeWire->pw_core_disconnect(pCore); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); - return result; - } - - /* We need an event for waking up the loop. */ - pDeviceStatePipeWire->pWakeup = pContextStatePipeWire->pw_loop_add_event(pLoop, (ma_spa_source_event_func_t)ma_device_on_wakupe__pipewire, pDeviceStatePipeWire); /* Cast should be fine here. It's complaining about the difference between ma_uint64 and uint64_t. */ - if (pDeviceStatePipeWire->pWakeup == NULL) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop wakeup event."); - pContextStatePipeWire->pw_core_disconnect(pCore); - pContextStatePipeWire->pw_context_destroy(pPipeWireContext); - pContextStatePipeWire->pw_loop_destroy(pLoop); - ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); - return MA_ERROR; - } - - *ppDeviceState = pDeviceStatePipeWire; - - return MA_SUCCESS; -} - -static void ma_device_uninit__pipewire(ma_device* pDevice) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - ma_device_type deviceType = ma_device_get_type(pDevice); - - if (pDeviceStatePipeWire->capture.pStream != NULL) { - pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->capture.pStream); - pDeviceStatePipeWire->capture.pStream = NULL; - } - - if (pDeviceStatePipeWire->playback.pStream != NULL) { - pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->playback.pStream); - pDeviceStatePipeWire->playback.pStream = NULL; - } - - /* This will be called from the same thread that called ma_device_init__pipewire() and is therefore an appropriate place to leave the main loop. */ - pContextStatePipeWire->pw_loop_leave(pDeviceStatePipeWire->pLoop); - - pContextStatePipeWire->pw_core_disconnect(pDeviceStatePipeWire->pCore); - pContextStatePipeWire->pw_context_destroy(pDeviceStatePipeWire->pContext); - pContextStatePipeWire->pw_loop_destroy(pDeviceStatePipeWire->pLoop); - - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDeviceStatePipeWire->capture.rb); - } - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDeviceStatePipeWire->playback.rb); - } - } - - ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); -} - - -static ma_result ma_device_start__pipewire(ma_device* pDevice) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - - /* Prepare our buffers before starting the streams. To do this we just need to step. */ - ma_device_step__pipewire(pDevice, MA_BLOCKING_MODE_NON_BLOCKING); - - if (pDeviceStatePipeWire->capture.pStream != NULL) { - pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_TRUE); - } - - if (pDeviceStatePipeWire->playback.pStream != NULL) { - pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_TRUE); - } - - return MA_SUCCESS; -} - -static ma_result ma_device_stop__pipewire(ma_device* pDevice) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - - if (pDeviceStatePipeWire->capture.pStream != NULL) { - pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_FALSE); - } - - if (pDeviceStatePipeWire->playback.pStream != NULL) { - pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_FALSE); - } - - return MA_SUCCESS; -} - -static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - ma_device_type deviceType = ma_device_get_type(pDevice); - int timeout; - ma_bool32 hasProcessedData = MA_FALSE; - - if (blockingMode == MA_BLOCKING_MODE_BLOCKING) { - timeout = -1; - } else { - timeout = 0; - } - - /* We will keep looping until we've processed some data. This should keep our stepping in time with data processing. */ - for (;;) { - pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, timeout); - - if (!ma_device_is_started(pDevice)) { - return MA_DEVICE_NOT_STARTED; - } - - /* We need only process data here in single-threaded mode. */ - if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { - /* We want to handle both playback and capture in a single iteration for duplex mode. */ - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - ma_uint32 framesAvailable; - - framesAvailable = ma_pcm_rb_available_read(&pDeviceStatePipeWire->capture.rb); - if (framesAvailable > 0) { - hasProcessedData = MA_TRUE; - } - - while (framesAvailable > 0) { - void* pMappedBuffer; - ma_uint32 framesToRead = framesAvailable; - ma_result result; - - result = ma_pcm_rb_acquire_read(&pDeviceStatePipeWire->capture.rb, &framesToRead, &pMappedBuffer); - if (result == MA_SUCCESS) { - ma_device_handle_backend_data_callback(pDevice, NULL, pMappedBuffer, framesToRead); - - ma_pcm_rb_commit_read(&pDeviceStatePipeWire->capture.rb, framesToRead); - framesAvailable -= framesToRead; - } - } - } - - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - ma_uint32 framesAvailable; - - framesAvailable = ma_pcm_rb_available_write(&pDeviceStatePipeWire->playback.rb); - if (framesAvailable > 0) { - hasProcessedData = MA_TRUE; - } - - while (framesAvailable > 0) { - void* pMappedBuffer; - ma_uint32 framesToWrite = framesAvailable; - ma_result result; - - result = ma_pcm_rb_acquire_write(&pDeviceStatePipeWire->playback.rb, &framesToWrite, &pMappedBuffer); - if (result == MA_SUCCESS) { - ma_device_handle_backend_data_callback(pDevice, pMappedBuffer, NULL, framesToWrite); - - ma_pcm_rb_commit_write(&pDeviceStatePipeWire->playback.rb, framesToWrite); - framesAvailable -= framesToWrite; - } - } - } - } - - if (hasProcessedData || blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { - break; - } - } - - return MA_SUCCESS; -} - -static void ma_device_wakeup__pipewire(ma_device* pDevice) -{ - ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); - ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); - - pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); -} - - -static ma_device_backend_vtable ma_gDeviceBackendVTable_PipeWire = -{ - ma_backend_info__pipewire, - ma_context_init__pipewire, - ma_context_uninit__pipewire, - ma_context_enumerate_devices__pipewire, - ma_device_init__pipewire, - ma_device_uninit__pipewire, - ma_device_start__pipewire, - ma_device_stop__pipewire, - ma_device_step__pipewire, - ma_device_wakeup__pipewire -}; - -ma_device_backend_vtable* ma_device_backend_pipewire = &ma_gDeviceBackendVTable_PipeWire; - -#if defined(MA_NO_RUNTIME_LINKING) - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic pop - #endif -#endif - -#else -ma_device_backend_vtable* ma_device_backend_pipewire = NULL; -#endif /* MA_HAS_PIPEWIRE */ - -MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void) -{ - return ma_device_backend_pipewire; -} - - -MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void) -{ - ma_context_config_pipewire config; - - memset(&config, 0, sizeof(config)); - - return config; -} - -MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void) -{ - ma_device_config_pipewire config; - - memset(&config, 0, sizeof(config)); - - return config; -} -#endif /* miniaudio_backend_pipewire_c */ diff --git a/extras/backends/pipewire/miniaudio_pipewire.h b/extras/backends/pipewire/miniaudio_pipewire.h deleted file mode 100644 index 8320a54f..00000000 --- a/extras/backends/pipewire/miniaudio_pipewire.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef miniaudio_backend_pipewire_h -#define miniaudio_backend_pipewire_h - -#include "../../../miniaudio.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct -{ - int _unused; -} ma_context_config_pipewire; - -MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void); - - -typedef struct -{ - const char* pThreadName; /* If NULL, defaults to "miniaudio-pipewire". */ - const char* pStreamName; /* If NULL, defaults to "miniaudio" */ - const char* pMediaRole; /* If NULL, defaults to "Game". */ -} ma_device_config_pipewire; - -MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void); - - -extern ma_device_backend_vtable* ma_device_backend_pipewire; -MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void); - -#ifdef __cplusplus -} -#endif -#endif /* miniaudio_backend_pipewire_h */ diff --git a/miniaudio.h b/miniaudio.h index c5bb1bb9..e9e3a229 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -559,6 +559,8 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_COREAUDIO | Disables the Core Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_PIPWIRE | Disables the PipeWire backend. | + +----------------------------------+--------------------------------------------------------------------+ | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_JACK | Disables the JACK backend. | @@ -593,6 +595,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WINMM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the WinMM backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_PIPEWIRE | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the PipeWire backend. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_PULSEAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the PulseAudio backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -3635,6 +3640,7 @@ example, ALSA, which is specific to Linux, will not be included in the Windows b | DirectSound | ma_device_backend_dsound | Windows XP+ | | WinMM | ma_device_backend_winmm | Windows 95+ | | Core Audio | ma_device_backend_coreaudio | macOS, iOS | + | PipeWire | ma_device_backend_pipewire | Linux | | PulseAudio | ma_device_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | | JACK | ma_device_backend_jack | Cross Platform (disabled on BSD and Android) | | ALSA | ma_device_backend_alsa | Linux | @@ -6907,6 +6913,30 @@ MA_API ma_device_backend_vtable* ma_coreaudio_get_vtable(void); /* END COREAUDIO */ +/* BEG PIPEWIRE */ +typedef struct +{ + int _unused; +} ma_context_config_pipewire; + +MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void); + + +typedef struct +{ + const char* pThreadName; /* If NULL, defaults to "miniaudio-pipewire". */ + const char* pStreamName; /* If NULL, defaults to "miniaudio" */ + const char* pMediaRole; /* If NULL, defaults to "Game". */ +} ma_device_config_pipewire; + +MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_pipewire; +MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void); +/* END PIPEWIRE */ + + /* BEG PULSEAUDIO */ typedef struct ma_context_config_pulseaudio { @@ -7506,6 +7536,7 @@ typedef union ma_uint8 dsound[16]; /* DirectSound uses a GUID for identification. */ /*UINT_PTR*/ ma_uint32 winmm; /* When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. */ char alsa[256]; /* ALSA uses a name string for identification. */ + char pipewire[256]; /* PipeWire uses a string for identification. */ char pulse[256]; /* PulseAudio uses a name string for identification. */ char jack[256]; /* JACK uses a name string. */ char coreaudio[256]; /* Core Audio uses a string for identification. */ @@ -7629,6 +7660,7 @@ struct ma_device_config ma_device_config_dsound dsound; ma_device_config_winmm winmm; ma_device_config_coreaudio coreaudio; + ma_device_config_pipewire pipewire; ma_device_config_pulseaudio pulseaudio; ma_device_config_jack jack; ma_device_config_alsa alsa; @@ -7767,6 +7799,7 @@ struct ma_context_config ma_context_config_dsound dsound; ma_context_config_winmm winmm; ma_context_config_coreaudio coreaudio; + ma_context_config_pipewire pipewire; ma_context_config_pulseaudio pulseaudio; ma_context_config_jack jack; ma_context_config_alsa alsa; @@ -7965,9 +7998,10 @@ When `pBackends` is NULL, the default priority order will be used. Below is a li | DirectSound | ma_device_backend_dsound | Windows XP+ | | WinMM | ma_device_backend_winmm | Windows 95+ | | Core Audio | ma_device_backend_coreaudio | macOS, iOS | + | PipeWire | ma_device_backend_pipewire | Linux | | PulseAudio | ma_device_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | - | ALSA | ma_device_backend_alsa | Linux | | JACK | ma_device_backend_jack | Cross Platform (disabled on BSD and Android) | + | ALSA | ma_device_backend_alsa | Linux | | sndio | ma_device_backend_sndio | OpenBSD | | audio(4) | ma_device_backend_audio4 | NetBSD, OpenBSD | | OSS | ma_device_backend_oss | FreeBSD | @@ -8010,13 +8044,13 @@ can then be set directly on the structure. Below are the members of the `ma_cont Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation callbacks will be used for anything tied to the context, including devices. - pulse.pApplicationName + pulseaudio.pApplicationName PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. - pulse.pServerName + pulseaudio.pServerName PulseAudio only. The name of the server to connect to with `pa_context_connect()`. - pulse.tryAutoSpawn + pulseaudio.tryAutoSpawn PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be intrusive for the end user. @@ -8672,13 +8706,13 @@ then be set directly on the structure. Below are the members of the `ma_device_c alsa.noAutoResample ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. - pulse.pStreamNamePlayback + pulseaudio.pStreamNamePlayback PulseAudio only. Sets the stream name for playback. - pulse.pStreamNameCapture + pulseaudio.pStreamNameCapture PulseAudio only. Sets the stream name for capture. - pulse.channelMap + pulseaudio.channelMap PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF. coreaudio.allowNominalSampleRateChange @@ -19454,6 +19488,7 @@ BACKENDS #if defined(MA_UNIX) && !defined(MA_ORBIS) && !defined(MA_PROSPERO) #if defined(MA_LINUX) #if !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) /* ALSA is not supported on Android. */ + #define MA_SUPPORT_PIPEWIRE #define MA_SUPPORT_ALSA #endif #endif @@ -19503,6 +19538,9 @@ BACKENDS #if defined(MA_SUPPORT_COREAUDIO) && !defined(MA_NO_COREAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_COREAUDIO)) #define MA_HAS_COREAUDIO #endif +#if defined(MA_SUPPORT_PIPEWIRE) && !defined(MA_NO_PIPEWIRE) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PIPEWIRE)) + #define MA_HAS_PIPEWIRE +#endif #if defined(MA_SUPPORT_PULSEAUDIO) && !defined(MA_NO_PULSEAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PULSEAUDIO)) #define MA_HAS_PULSEAUDIO #endif @@ -19682,6 +19720,27 @@ MA_API ma_device_config_oss ma_device_config_oss_init(void) /* END OSS */ +/* BEG PIPEWIRE */ +MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void) +{ + ma_context_config_pipewire config; + + memset(&config, 0, sizeof(config)); + + return config; +} + +MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void) +{ + ma_device_config_pipewire config; + + memset(&config, 0, sizeof(config)); + + return config; +} +/* END PIPEWIRE */ + + /* BEG PULSEAUDIO */ MA_API ma_context_config_pulseaudio ma_context_config_pulseaudio_init(void) { @@ -29937,6 +29996,3027 @@ MA_API ma_device_backend_vtable* ma_alsa_get_vtable(void) +/****************************************************************************** + +PipeWire Backend + +******************************************************************************/ +#if defined(MA_HAS_PIPEWIRE) +#if defined(MA_NO_RUNTIME_LINKING) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc99-extensions" + #pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" + #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + #endif +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#define ma_spa_source_event_func_t spa_source_event_func_t + +#define ma_spa_direction spa_direction +#define ma_spa_audio_format spa_audio_format +#define ma_spa_audio_channel spa_audio_channel +#define ma_spa_callbacks spa_callbacks +#define ma_spa_interface spa_interface +#define ma_spa_dict_item spa_dict_item +#define MA_SPA_DICT_ITEM_INIT SPA_DICT_ITEM_INIT +#define ma_spa_dict spa_dict +#define MA_SPA_DICT_INIT SPA_DICT_INIT +#define ma_spa_dict_lookup spa_dict_lookup +#define ma_spa_chunk spa_chunk +#define ma_spa_data spa_data +#define ma_spa_meta spa_meta +#define ma_spa_buffer spa_buffer +#define ma_spa_list spa_list +#define ma_spa_hook spa_hook +#define ma_spa_pod spa_pod +#define ma_spa_pod_id spa_pod_id +#define ma_spa_pod_int spa_pod_int +#define ma_spa_choice_type spa_choice_type +#define ma_spa_pod_choice spa_pod_choice +#define ma_spa_pod_choice_body spa_pod_choice_body +#define ma_spa_pod_array_body spa_pod_array_body +#define ma_spa_pod_array spa_pod_array +#define ma_spa_pod_object_body spa_pod_object_body +#define ma_spa_pod_object spa_pod_object +#define ma_spa_pod_prop spa_pod_prop +#define ma_spa_command spa_command +#define ma_spa_fraction spa_fraction + +#define MA_SPA_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT +#define MA_SPA_DIRECTION_INPUT SPA_DIRECTION_INPUT + +#define MA_SPA_TYPE_Id SPA_TYPE_Id +#define MA_SPA_TYPE_Int SPA_TYPE_Int +#define MA_SPA_TYPE_Array SPA_TYPE_Array +#define MA_SPA_TYPE_Object SPA_TYPE_Object +#define MA_SPA_TYPE_Choice SPA_TYPE_Choice + +#define MA_SPA_TYPE_OBJECT_Format SPA_TYPE_OBJECT_Format +#define MA_SPA_TYPE_OBJECT_ParamBuffers SPA_TYPE_OBJECT_ParamBuffers + +#define MA_SPA_PARAM_EnumFormat SPA_PARAM_EnumFormat +#define MA_SPA_PARAM_Format SPA_PARAM_Format +#define MA_SPA_PARAM_Buffers SPA_PARAM_Buffers + +#define MA_SPA_PARAM_BUFFERS_buffers SPA_PARAM_BUFFERS_buffers +#define MA_SPA_PARAM_BUFFERS_blocks SPA_PARAM_BUFFERS_blocks +#define MA_SPA_PARAM_BUFFERS_size SPA_PARAM_BUFFERS_size +#define MA_SPA_PARAM_BUFFERS_stride SPA_PARAM_BUFFERS_stride + +#define MA_SPA_FORMAT_mediaType SPA_FORMAT_mediaType +#define MA_SPA_FORMAT_mediaSubtype SPA_FORMAT_mediaSubtype + +#define MA_SPA_MEDIA_TYPE_audio SPA_MEDIA_TYPE_audio +#define MA_SPA_MEDIA_SUBTYPE_raw SPA_MEDIA_SUBTYPE_raw + +#define MA_SPA_FORMAT_AUDIO_format SPA_FORMAT_AUDIO_format +#define MA_SPA_FORMAT_AUDIO_rate SPA_FORMAT_AUDIO_rate +#define MA_SPA_FORMAT_AUDIO_channels SPA_FORMAT_AUDIO_channels +#define MA_SPA_FORMAT_AUDIO_position SPA_FORMAT_AUDIO_position + +#define MA_SPA_AUDIO_FORMAT_UNKNOWN SPA_AUDIO_FORMAT_UNKNOWN +#define MA_SPA_AUDIO_FORMAT_U8 SPA_AUDIO_FORMAT_U8 +#define MA_SPA_AUDIO_FORMAT_S16_LE SPA_AUDIO_FORMAT_S16_LE +#define MA_SPA_AUDIO_FORMAT_S16_BE SPA_AUDIO_FORMAT_S16_BE +#define MA_SPA_AUDIO_FORMAT_S24_LE SPA_AUDIO_FORMAT_S24_LE +#define MA_SPA_AUDIO_FORMAT_S24_BE SPA_AUDIO_FORMAT_S24_BE +#define MA_SPA_AUDIO_FORMAT_S32_LE SPA_AUDIO_FORMAT_S32_LE +#define MA_SPA_AUDIO_FORMAT_S32_BE SPA_AUDIO_FORMAT_S32_BE +#define MA_SPA_AUDIO_FORMAT_F32_LE SPA_AUDIO_FORMAT_F32_LE +#define MA_SPA_AUDIO_FORMAT_F32_BE SPA_AUDIO_FORMAT_F32_BE + +#define MA_SPA_AUDIO_CHANNEL_MONO SPA_AUDIO_CHANNEL_MONO +#define MA_SPA_AUDIO_CHANNEL_FL SPA_AUDIO_CHANNEL_FL +#define MA_SPA_AUDIO_CHANNEL_FR SPA_AUDIO_CHANNEL_FR +#define MA_SPA_AUDIO_CHANNEL_FC SPA_AUDIO_CHANNEL_FC +#define MA_SPA_AUDIO_CHANNEL_LFE SPA_AUDIO_CHANNEL_LFE +#define MA_SPA_AUDIO_CHANNEL_SL SPA_AUDIO_CHANNEL_SL +#define MA_SPA_AUDIO_CHANNEL_SR SPA_AUDIO_CHANNEL_SR +#define MA_SPA_AUDIO_CHANNEL_FLC SPA_AUDIO_CHANNEL_FLC +#define MA_SPA_AUDIO_CHANNEL_FRC SPA_AUDIO_CHANNEL_FRC +#define MA_SPA_AUDIO_CHANNEL_RC SPA_AUDIO_CHANNEL_RC +#define MA_SPA_AUDIO_CHANNEL_RL SPA_AUDIO_CHANNEL_RL +#define MA_SPA_AUDIO_CHANNEL_RR SPA_AUDIO_CHANNEL_RR +#define MA_SPA_AUDIO_CHANNEL_TC SPA_AUDIO_CHANNEL_TC +#define MA_SPA_AUDIO_CHANNEL_TFL SPA_AUDIO_CHANNEL_TFL +#define MA_SPA_AUDIO_CHANNEL_TFC SPA_AUDIO_CHANNEL_TFC +#define MA_SPA_AUDIO_CHANNEL_TFR SPA_AUDIO_CHANNEL_TFR +#define MA_SPA_AUDIO_CHANNEL_TRL SPA_AUDIO_CHANNEL_TRL +#define MA_SPA_AUDIO_CHANNEL_TRC SPA_AUDIO_CHANNEL_TRC +#define MA_SPA_AUDIO_CHANNEL_TRR SPA_AUDIO_CHANNEL_TRR +#define MA_SPA_AUDIO_CHANNEL_RLC SPA_AUDIO_CHANNEL_RLC +#define MA_SPA_AUDIO_CHANNEL_RRC SPA_AUDIO_CHANNEL_RRC +#define MA_SPA_AUDIO_CHANNEL_FLW SPA_AUDIO_CHANNEL_FLW +#define MA_SPA_AUDIO_CHANNEL_FRW SPA_AUDIO_CHANNEL_FRW +#define MA_SPA_AUDIO_CHANNEL_LFE2 SPA_AUDIO_CHANNEL_LFE2 +#define MA_SPA_AUDIO_CHANNEL_FLH SPA_AUDIO_CHANNEL_FLH +#define MA_SPA_AUDIO_CHANNEL_FCH SPA_AUDIO_CHANNEL_FCH +#define MA_SPA_AUDIO_CHANNEL_FRH SPA_AUDIO_CHANNEL_FRH +#define MA_SPA_AUDIO_CHANNEL_TFLC SPA_AUDIO_CHANNEL_TFLC +#define MA_SPA_AUDIO_CHANNEL_TFRC SPA_AUDIO_CHANNEL_TFRC +#define MA_SPA_AUDIO_CHANNEL_TSL SPA_AUDIO_CHANNEL_TSL +#define MA_SPA_AUDIO_CHANNEL_TSR SPA_AUDIO_CHANNEL_TSR +#define MA_SPA_AUDIO_CHANNEL_LLFE SPA_AUDIO_CHANNEL_LLFE +#define MA_SPA_AUDIO_CHANNEL_RLFE SPA_AUDIO_CHANNEL_RLFE +#define MA_SPA_AUDIO_CHANNEL_BC SPA_AUDIO_CHANNEL_BC +#define MA_SPA_AUDIO_CHANNEL_BLC SPA_AUDIO_CHANNEL_BLC +#define MA_SPA_AUDIO_CHANNEL_BRC SPA_AUDIO_CHANNEL_BRC +#define MA_SPA_AUDIO_CHANNEL_AUX0 SPA_AUDIO_CHANNEL_AUX0 +#define MA_SPA_AUDIO_CHANNEL_AUX1 SPA_AUDIO_CHANNEL_AUX1 +#define MA_SPA_AUDIO_CHANNEL_AUX2 SPA_AUDIO_CHANNEL_AUX2 +#define MA_SPA_AUDIO_CHANNEL_AUX3 SPA_AUDIO_CHANNEL_AUX3 +#define MA_SPA_AUDIO_CHANNEL_AUX4 SPA_AUDIO_CHANNEL_AUX4 +#define MA_SPA_AUDIO_CHANNEL_AUX5 SPA_AUDIO_CHANNEL_AUX5 +#define MA_SPA_AUDIO_CHANNEL_AUX6 SPA_AUDIO_CHANNEL_AUX6 +#define MA_SPA_AUDIO_CHANNEL_AUX7 SPA_AUDIO_CHANNEL_AUX7 +#define MA_SPA_AUDIO_CHANNEL_AUX8 SPA_AUDIO_CHANNEL_AUX8 +#define MA_SPA_AUDIO_CHANNEL_AUX9 SPA_AUDIO_CHANNEL_AUX9 +#define MA_SPA_AUDIO_CHANNEL_AUX10 SPA_AUDIO_CHANNEL_AUX10 +#define MA_SPA_AUDIO_CHANNEL_AUX11 SPA_AUDIO_CHANNEL_AUX11 +#define MA_SPA_AUDIO_CHANNEL_AUX12 SPA_AUDIO_CHANNEL_AUX12 +#define MA_SPA_AUDIO_CHANNEL_AUX13 SPA_AUDIO_CHANNEL_AUX13 +#define MA_SPA_AUDIO_CHANNEL_AUX14 SPA_AUDIO_CHANNEL_AUX14 +#define MA_SPA_AUDIO_CHANNEL_AUX15 SPA_AUDIO_CHANNEL_AUX15 +#define MA_SPA_AUDIO_CHANNEL_AUX16 SPA_AUDIO_CHANNEL_AUX16 +#define MA_SPA_AUDIO_CHANNEL_AUX17 SPA_AUDIO_CHANNEL_AUX17 +#define MA_SPA_AUDIO_CHANNEL_AUX18 SPA_AUDIO_CHANNEL_AUX18 +#define MA_SPA_AUDIO_CHANNEL_AUX19 SPA_AUDIO_CHANNEL_AUX19 +#define MA_SPA_AUDIO_CHANNEL_AUX20 SPA_AUDIO_CHANNEL_AUX20 +#define MA_SPA_AUDIO_CHANNEL_AUX21 SPA_AUDIO_CHANNEL_AUX21 +#define MA_SPA_AUDIO_CHANNEL_AUX22 SPA_AUDIO_CHANNEL_AUX22 +#define MA_SPA_AUDIO_CHANNEL_AUX23 SPA_AUDIO_CHANNEL_AUX23 +#define MA_SPA_AUDIO_CHANNEL_AUX24 SPA_AUDIO_CHANNEL_AUX24 +#define MA_SPA_AUDIO_CHANNEL_AUX25 SPA_AUDIO_CHANNEL_AUX25 +#define MA_SPA_AUDIO_CHANNEL_AUX26 SPA_AUDIO_CHANNEL_AUX26 +#define MA_SPA_AUDIO_CHANNEL_AUX27 SPA_AUDIO_CHANNEL_AUX27 +#define MA_SPA_AUDIO_CHANNEL_AUX28 SPA_AUDIO_CHANNEL_AUX28 +#define MA_SPA_AUDIO_CHANNEL_AUX29 SPA_AUDIO_CHANNEL_AUX29 +#define MA_SPA_AUDIO_CHANNEL_AUX30 SPA_AUDIO_CHANNEL_AUX30 +#define MA_SPA_AUDIO_CHANNEL_AUX31 SPA_AUDIO_CHANNEL_AUX31 +#define MA_SPA_AUDIO_CHANNEL_AUX32 SPA_AUDIO_CHANNEL_AUX32 +#define MA_SPA_AUDIO_CHANNEL_AUX33 SPA_AUDIO_CHANNEL_AUX33 +#define MA_SPA_AUDIO_CHANNEL_AUX34 SPA_AUDIO_CHANNEL_AUX34 +#define MA_SPA_AUDIO_CHANNEL_AUX35 SPA_AUDIO_CHANNEL_AUX35 +#define MA_SPA_AUDIO_CHANNEL_AUX36 SPA_AUDIO_CHANNEL_AUX36 +#define MA_SPA_AUDIO_CHANNEL_AUX37 SPA_AUDIO_CHANNEL_AUX37 +#define MA_SPA_AUDIO_CHANNEL_AUX38 SPA_AUDIO_CHANNEL_AUX38 +#define MA_SPA_AUDIO_CHANNEL_AUX39 SPA_AUDIO_CHANNEL_AUX39 +#define MA_SPA_AUDIO_CHANNEL_AUX40 SPA_AUDIO_CHANNEL_AUX40 +#define MA_SPA_AUDIO_CHANNEL_AUX41 SPA_AUDIO_CHANNEL_AUX41 +#define MA_SPA_AUDIO_CHANNEL_AUX42 SPA_AUDIO_CHANNEL_AUX42 +#define MA_SPA_AUDIO_CHANNEL_AUX43 SPA_AUDIO_CHANNEL_AUX43 +#define MA_SPA_AUDIO_CHANNEL_AUX44 SPA_AUDIO_CHANNEL_AUX44 +#define MA_SPA_AUDIO_CHANNEL_AUX45 SPA_AUDIO_CHANNEL_AUX45 +#define MA_SPA_AUDIO_CHANNEL_AUX46 SPA_AUDIO_CHANNEL_AUX46 +#define MA_SPA_AUDIO_CHANNEL_AUX47 SPA_AUDIO_CHANNEL_AUX47 +#define MA_SPA_AUDIO_CHANNEL_AUX48 SPA_AUDIO_CHANNEL_AUX48 +#define MA_SPA_AUDIO_CHANNEL_AUX49 SPA_AUDIO_CHANNEL_AUX49 +#define MA_SPA_AUDIO_CHANNEL_AUX50 SPA_AUDIO_CHANNEL_AUX50 +#define MA_SPA_AUDIO_CHANNEL_AUX51 SPA_AUDIO_CHANNEL_AUX51 +#define MA_SPA_AUDIO_CHANNEL_AUX52 SPA_AUDIO_CHANNEL_AUX52 +#define MA_SPA_AUDIO_CHANNEL_AUX53 SPA_AUDIO_CHANNEL_AUX53 +#define MA_SPA_AUDIO_CHANNEL_AUX54 SPA_AUDIO_CHANNEL_AUX54 +#define MA_SPA_AUDIO_CHANNEL_AUX55 SPA_AUDIO_CHANNEL_AUX55 +#define MA_SPA_AUDIO_CHANNEL_AUX56 SPA_AUDIO_CHANNEL_AUX56 +#define MA_SPA_AUDIO_CHANNEL_AUX57 SPA_AUDIO_CHANNEL_AUX57 +#define MA_SPA_AUDIO_CHANNEL_AUX58 SPA_AUDIO_CHANNEL_AUX58 +#define MA_SPA_AUDIO_CHANNEL_AUX59 SPA_AUDIO_CHANNEL_AUX59 +#define MA_SPA_AUDIO_CHANNEL_AUX60 SPA_AUDIO_CHANNEL_AUX60 +#define MA_SPA_AUDIO_CHANNEL_AUX61 SPA_AUDIO_CHANNEL_AUX61 +#define MA_SPA_AUDIO_CHANNEL_AUX62 SPA_AUDIO_CHANNEL_AUX62 +#define MA_SPA_AUDIO_CHANNEL_AUX63 SPA_AUDIO_CHANNEL_AUX63 + + +#define MA_PW_KEY_MEDIA_TYPE PW_KEY_MEDIA_TYPE +#define MA_PW_KEY_MEDIA_CATEGORY PW_KEY_MEDIA_CATEGORY +#define MA_PW_KEY_MEDIA_ROLE PW_KEY_MEDIA_ROLE +#define MA_PW_KEY_MEDIA_CLASS PW_KEY_MEDIA_CLASS +#define MA_PW_KEY_NODE_LATENCY PW_KEY_NODE_LATENCY +#define MA_PW_KEY_TARGET_OBJECT PW_KEY_TARGET_OBJECT +#define MA_PW_KEY_METADATA_NAME PW_KEY_METADATA_NAME + +#define MA_PW_TYPE_INTERFACE_Node PW_TYPE_INTERFACE_Node +#define MA_PW_TYPE_INTERFACE_Metadata PW_TYPE_INTERFACE_Metadata + +#define MA_PW_ID_CORE PW_ID_CORE +#define MA_PW_ID_ANY PW_ID_ANY + +#define ma_pw_thread_loop pw_thread_loop +#define ma_pw_loop pw_loop +#define ma_pw_context pw_context +#define ma_pw_core pw_core +#define ma_pw_registry pw_registry +#define ma_pw_metadata pw_metadata +#define ma_pw_metadata_events pw_metadata_events +#define ma_pw_proxy pw_proxy +#define ma_pw_properties pw_properties +#define ma_pw_stream pw_stream +#define ma_pw_stream_control pw_stream_control +#define ma_pw_core_info pw_core_info + +#define ma_pw_stream_state pw_stream_state +#define MA_PW_STREAM_STATE_ERROR PW_STREAM_STATE_ERROR +#define MA_PW_STREAM_STATE_UNCONNECTED PW_STREAM_STATE_UNCONNECTED +#define MA_PW_STREAM_STATE_CONNECTING PW_STREAM_STATE_CONNECTING +#define MA_PW_STREAM_STATE_PAUSED PW_STREAM_STATE_PAUSED +#define MA_PW_STREAM_STATE_STREAMING PW_STREAM_STATE_STREAMING + +#define ma_pw_stream_flags pw_stream_flags +#define MA_PW_STREAM_FLAG_NONE PW_STREAM_FLAG_NONE +#define MA_PW_STREAM_FLAG_AUTOCONNECT PW_STREAM_FLAG_AUTOCONNECT +#define MA_PW_STREAM_FLAG_INACTIVE PW_STREAM_FLAG_INACTIVE +#define MA_PW_STREAM_FLAG_MAP_BUFFERS PW_STREAM_FLAG_MAP_BUFFERS +#define MA_PW_STREAM_FLAG_DRIVER PW_STREAM_FLAG_DRIVER +#define MA_PW_STREAM_FLAG_RT_PROCESS PW_STREAM_FLAG_RT_PROCESS +#define MA_PW_STREAM_FLAG_NO_CONVERT PW_STREAM_FLAG_NO_CONVERT +#define MA_PW_STREAM_FLAG_EXCLUSIVE PW_STREAM_FLAG_EXCLUSIVE +#define MA_PW_STREAM_FLAG_DONT_RECONNECT PW_STREAM_FLAG_DONT_RECONNECT +#define MA_PW_STREAM_FLAG_ALLOC_BUFFERS PW_STREAM_FLAG_ALLOC_BUFFERS +#define MA_PW_STREAM_FLAG_TRIGGER PW_STREAM_FLAG_TRIGGER +#define MA_PW_STREAM_FLAG_ASYNC PW_STREAM_FLAG_ASYNC + +#define ma_pw_buffer pw_buffer +#define ma_pw_time pw_time + +#define MA_PW_VERSION_CORE_EVENTS PW_VERSION_CORE_EVENTS +#define ma_pw_core_events pw_core_events + +#define MA_PW_VERSION_REGISTRY PW_VERSION_REGISTRY +#define MA_PW_VERSION_REGISTRY_EVENTS PW_VERSION_REGISTRY_EVENTS +#define ma_pw_registry_events pw_registry_events + +#define MA_PW_VERSION_METADATA_METHODS PW_VERSION_METADATA_METHODS +#define ma_pw_metadata_methods pw_metadata_methods + +#define MA_PW_VERSION_METADATA PW_VERSION_METADATA +#define MA_PW_VERSION_METADATA_EVENTS PW_VERSION_METADATA_EVENTS +#define ma_pw_metadata_events pw_metadata_events + +#define MA_PW_VERSION_STREAM_EVENTS PW_VERSION_STREAM_EVENTS +#define ma_pw_stream_events pw_stream_events +#else +typedef void (* ma_spa_source_event_func_t)(void* data, ma_uint64 count); + +/* SPA enums. */ +enum ma_spa_direction +{ + MA_SPA_DIRECTION_INPUT = 0, + MA_SPA_DIRECTION_OUTPUT = 1 +}; + +enum ma_spa_audio_format +{ + MA_SPA_AUDIO_FORMAT_UNKNOWN = 0, + MA_SPA_AUDIO_FORMAT_ENCODED, + + MA_SPA_AUDIO_FORMAT_START_Interleaved = 0x100, + MA_SPA_AUDIO_FORMAT_S8, + MA_SPA_AUDIO_FORMAT_U8, + MA_SPA_AUDIO_FORMAT_S16_LE, + MA_SPA_AUDIO_FORMAT_S16_BE, + MA_SPA_AUDIO_FORMAT_U16_LE, + MA_SPA_AUDIO_FORMAT_U16_BE, + MA_SPA_AUDIO_FORMAT_S24_32_LE, + MA_SPA_AUDIO_FORMAT_S24_32_BE, + MA_SPA_AUDIO_FORMAT_U24_32_LE, + MA_SPA_AUDIO_FORMAT_U24_32_BE, + MA_SPA_AUDIO_FORMAT_S32_LE, + MA_SPA_AUDIO_FORMAT_S32_BE, + MA_SPA_AUDIO_FORMAT_U32_LE, + MA_SPA_AUDIO_FORMAT_U32_BE, + MA_SPA_AUDIO_FORMAT_S24_LE, + MA_SPA_AUDIO_FORMAT_S24_BE, + MA_SPA_AUDIO_FORMAT_U24_LE, + MA_SPA_AUDIO_FORMAT_U24_BE, + MA_SPA_AUDIO_FORMAT_S20_LE, + MA_SPA_AUDIO_FORMAT_S20_BE, + MA_SPA_AUDIO_FORMAT_U20_LE, + MA_SPA_AUDIO_FORMAT_U20_BE, + MA_SPA_AUDIO_FORMAT_S18_LE, + MA_SPA_AUDIO_FORMAT_S18_BE, + MA_SPA_AUDIO_FORMAT_U18_LE, + MA_SPA_AUDIO_FORMAT_U18_BE, + MA_SPA_AUDIO_FORMAT_F32_LE, + MA_SPA_AUDIO_FORMAT_F32_BE, + MA_SPA_AUDIO_FORMAT_F64_LE, + MA_SPA_AUDIO_FORMAT_F64_BE, + MA_SPA_AUDIO_FORMAT_ULAW, + MA_SPA_AUDIO_FORMAT_ALAW, +}; + +enum ma_spa_audio_channel +{ + MA_SPA_AUDIO_CHANNEL_UNKNOWN, + MA_SPA_AUDIO_CHANNEL_NA, + + MA_SPA_AUDIO_CHANNEL_MONO, + MA_SPA_AUDIO_CHANNEL_FL, + MA_SPA_AUDIO_CHANNEL_FR, + MA_SPA_AUDIO_CHANNEL_FC, + MA_SPA_AUDIO_CHANNEL_LFE, + MA_SPA_AUDIO_CHANNEL_SL, + MA_SPA_AUDIO_CHANNEL_SR, + MA_SPA_AUDIO_CHANNEL_FLC, + MA_SPA_AUDIO_CHANNEL_FRC, + MA_SPA_AUDIO_CHANNEL_RC, + MA_SPA_AUDIO_CHANNEL_RL, + MA_SPA_AUDIO_CHANNEL_RR, + MA_SPA_AUDIO_CHANNEL_TC, + MA_SPA_AUDIO_CHANNEL_TFL, + MA_SPA_AUDIO_CHANNEL_TFC, + MA_SPA_AUDIO_CHANNEL_TFR, + MA_SPA_AUDIO_CHANNEL_TRL, + MA_SPA_AUDIO_CHANNEL_TRC, + MA_SPA_AUDIO_CHANNEL_TRR, + MA_SPA_AUDIO_CHANNEL_RLC, + MA_SPA_AUDIO_CHANNEL_RRC, + MA_SPA_AUDIO_CHANNEL_FLW, + MA_SPA_AUDIO_CHANNEL_FRW, + MA_SPA_AUDIO_CHANNEL_LFE2, + MA_SPA_AUDIO_CHANNEL_FLH, + MA_SPA_AUDIO_CHANNEL_FCH, + MA_SPA_AUDIO_CHANNEL_FRH, + MA_SPA_AUDIO_CHANNEL_TFLC, + MA_SPA_AUDIO_CHANNEL_TFRC, + MA_SPA_AUDIO_CHANNEL_TSL, + MA_SPA_AUDIO_CHANNEL_TSR, + MA_SPA_AUDIO_CHANNEL_LLFE, + MA_SPA_AUDIO_CHANNEL_RLFE, + MA_SPA_AUDIO_CHANNEL_BC, + MA_SPA_AUDIO_CHANNEL_BLC, + MA_SPA_AUDIO_CHANNEL_BRC, + + MA_SPA_AUDIO_CHANNEL_START_Aux = 0x1000, + MA_SPA_AUDIO_CHANNEL_AUX0 = MA_SPA_AUDIO_CHANNEL_START_Aux, + MA_SPA_AUDIO_CHANNEL_AUX1, + MA_SPA_AUDIO_CHANNEL_AUX2, + MA_SPA_AUDIO_CHANNEL_AUX3, + MA_SPA_AUDIO_CHANNEL_AUX4, + MA_SPA_AUDIO_CHANNEL_AUX5, + MA_SPA_AUDIO_CHANNEL_AUX6, + MA_SPA_AUDIO_CHANNEL_AUX7, + MA_SPA_AUDIO_CHANNEL_AUX8, + MA_SPA_AUDIO_CHANNEL_AUX9, + MA_SPA_AUDIO_CHANNEL_AUX10, + MA_SPA_AUDIO_CHANNEL_AUX11, + MA_SPA_AUDIO_CHANNEL_AUX12, + MA_SPA_AUDIO_CHANNEL_AUX13, + MA_SPA_AUDIO_CHANNEL_AUX14, + MA_SPA_AUDIO_CHANNEL_AUX15, + MA_SPA_AUDIO_CHANNEL_AUX16, + MA_SPA_AUDIO_CHANNEL_AUX17, + MA_SPA_AUDIO_CHANNEL_AUX18, + MA_SPA_AUDIO_CHANNEL_AUX19, + MA_SPA_AUDIO_CHANNEL_AUX20, + MA_SPA_AUDIO_CHANNEL_AUX21, + MA_SPA_AUDIO_CHANNEL_AUX22, + MA_SPA_AUDIO_CHANNEL_AUX23, + MA_SPA_AUDIO_CHANNEL_AUX24, + MA_SPA_AUDIO_CHANNEL_AUX25, + MA_SPA_AUDIO_CHANNEL_AUX26, + MA_SPA_AUDIO_CHANNEL_AUX27, + MA_SPA_AUDIO_CHANNEL_AUX28, + MA_SPA_AUDIO_CHANNEL_AUX29, + MA_SPA_AUDIO_CHANNEL_AUX30, + MA_SPA_AUDIO_CHANNEL_AUX31, + MA_SPA_AUDIO_CHANNEL_AUX32, + MA_SPA_AUDIO_CHANNEL_AUX33, + MA_SPA_AUDIO_CHANNEL_AUX34, + MA_SPA_AUDIO_CHANNEL_AUX35, + MA_SPA_AUDIO_CHANNEL_AUX36, + MA_SPA_AUDIO_CHANNEL_AUX37, + MA_SPA_AUDIO_CHANNEL_AUX38, + MA_SPA_AUDIO_CHANNEL_AUX39, + MA_SPA_AUDIO_CHANNEL_AUX40, + MA_SPA_AUDIO_CHANNEL_AUX41, + MA_SPA_AUDIO_CHANNEL_AUX42, + MA_SPA_AUDIO_CHANNEL_AUX43, + MA_SPA_AUDIO_CHANNEL_AUX44, + MA_SPA_AUDIO_CHANNEL_AUX45, + MA_SPA_AUDIO_CHANNEL_AUX46, + MA_SPA_AUDIO_CHANNEL_AUX47, + MA_SPA_AUDIO_CHANNEL_AUX48, + MA_SPA_AUDIO_CHANNEL_AUX49, + MA_SPA_AUDIO_CHANNEL_AUX50, + MA_SPA_AUDIO_CHANNEL_AUX51, + MA_SPA_AUDIO_CHANNEL_AUX52, + MA_SPA_AUDIO_CHANNEL_AUX53, + MA_SPA_AUDIO_CHANNEL_AUX54, + MA_SPA_AUDIO_CHANNEL_AUX55, + MA_SPA_AUDIO_CHANNEL_AUX56, + MA_SPA_AUDIO_CHANNEL_AUX57, + MA_SPA_AUDIO_CHANNEL_AUX58, + MA_SPA_AUDIO_CHANNEL_AUX59, + MA_SPA_AUDIO_CHANNEL_AUX60, + MA_SPA_AUDIO_CHANNEL_AUX61, + MA_SPA_AUDIO_CHANNEL_AUX62, + MA_SPA_AUDIO_CHANNEL_AUX63 +}; + + + +/* The spa_interface thing is only used by one thing here, and only because the function we want is not exported by the SO. */ +struct ma_spa_callbacks +{ + const void* funcs; + void* data; +}; + +struct ma_spa_interface +{ + const char* type; + ma_uint32 version; + struct ma_spa_callbacks cb; +}; + + +/* +spa_dict is just a list of key/value pairs as a string. We can easily write our own version of this to remove +the dependency on the SPA headers. + +We'll keep the naming consistent here with the actual SPA library, with just the "ma_" prefix added. +*/ +struct ma_spa_dict_item +{ + const char* key; + const char* value; +}; + +static MA_INLINE struct ma_spa_dict_item MA_SPA_DICT_ITEM_INIT(const char* key, const char* value) +{ + struct ma_spa_dict_item item; + + item.key = key; + item.value = value; + + return item; +} + + +struct ma_spa_dict +{ + ma_uint32 flags; + ma_uint32 n_items; + const struct ma_spa_dict_item* items; +}; + +static MA_INLINE struct ma_spa_dict MA_SPA_DICT_INIT(const struct ma_spa_dict_item* items, ma_uint32 n_items) +{ + struct ma_spa_dict dict; + + dict.flags = 0; + dict.n_items = n_items; + dict.items = items; + + return dict; +} + +static MA_INLINE const char* ma_spa_dict_lookup(const struct ma_spa_dict* dict, const char* key) +{ + ma_uint32 i; + + for (i = 0; i < dict->n_items; i += 1) { + if (strcmp(dict->items[i].key, key) == 0) { + return dict->items[i].value; + } + } + + return NULL; +} + + +/* +spa_buffer is just a few trivial structs. +*/ +struct ma_spa_chunk +{ + ma_uint32 offset; + ma_uint32 size; + ma_int32 stride; + ma_int32 flags; +}; + +struct ma_spa_data +{ + ma_uint32 type; + ma_uint32 flags; + ma_int64 fd; + ma_uint32 mapoffset; + ma_uint32 maxsize; + void* data; + struct ma_spa_chunk* chunk; +}; + +struct ma_spa_meta +{ + ma_uint32 type; + ma_uint32 size; + void* data; +}; + +struct ma_spa_buffer +{ + ma_uint32 n_metas; + ma_uint32 n_datas; + struct ma_spa_meta* metas; + struct ma_spa_data* datas; +}; + + +/* spa_hook */ +struct ma_spa_list +{ + struct ma_spa_list* next; + struct ma_spa_list* prev; +}; + +struct ma_spa_hook +{ + struct ma_spa_list link; + struct ma_spa_callbacks cb; + void (* removed)(struct ma_spa_hook* hook); + void* priv; +}; + + +/* spa_pod is the most complicated part of SPA that we use. We're only implementing specifically what we need. */ +#define MA_SPA_TYPE_Id 3 +#define MA_SPA_TYPE_Int 4 +#define MA_SPA_TYPE_Array 13 +#define MA_SPA_TYPE_Object 15 +#define MA_SPA_TYPE_Choice 19 + +#define MA_SPA_TYPE_OBJECT_Format 262147 +#define MA_SPA_TYPE_OBJECT_ParamBuffers 262148 + +#define MA_SPA_PARAM_EnumFormat 3 +#define MA_SPA_PARAM_Format 4 +#define MA_SPA_PARAM_Buffers 5 + +#define MA_SPA_PARAM_BUFFERS_buffers 1 +#define MA_SPA_PARAM_BUFFERS_blocks 2 +#define MA_SPA_PARAM_BUFFERS_size 3 +#define MA_SPA_PARAM_BUFFERS_stride 4 + +#define MA_SPA_FORMAT_mediaType 1 +#define MA_SPA_FORMAT_mediaSubtype 2 + +#define MA_SPA_MEDIA_TYPE_audio 1 +#define MA_SPA_MEDIA_SUBTYPE_raw 1 + +#define MA_SPA_FORMAT_AUDIO_format 65537 +#define MA_SPA_FORMAT_AUDIO_rate 65539 +#define MA_SPA_FORMAT_AUDIO_channels 65540 +#define MA_SPA_FORMAT_AUDIO_position 65541 + + +struct ma_spa_pod +{ + ma_uint32 size; + ma_uint32 type; +}; + + +struct ma_spa_pod_id +{ + struct ma_spa_pod pod; + ma_uint32 value; + ma_int32 _padding; +}; + +struct ma_spa_pod_int +{ + struct ma_spa_pod pod; + ma_int32 value; + ma_int32 _padding; +}; + + +enum ma_spa_choice_type +{ + MA_SPA_CHOICE_None = 0, + MA_SPA_CHOICE_Range, + MA_SPA_CHOICE_Step, + MA_SPA_CHOICE_Enum, + MA_SPA_CHOICE_Flags +}; + +struct ma_spa_pod_choice_body +{ + ma_uint32 type; + ma_uint32 flags; + struct ma_spa_pod child; +}; + +struct ma_spa_pod_choice +{ + struct ma_spa_pod pod; + struct ma_spa_pod_choice_body body; +}; + + +struct ma_spa_pod_array_body +{ + struct ma_spa_pod child; +}; + +struct ma_spa_pod_array +{ + struct ma_spa_pod pod; + struct ma_spa_pod_array_body body; +}; + + +struct ma_spa_pod_object_body +{ + ma_uint32 type; + ma_uint32 id; +}; + +struct ma_spa_pod_object +{ + struct ma_spa_pod pod; + struct ma_spa_pod_object_body body; +}; + +struct ma_spa_pod_prop +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod value; +}; + + +/* Miscellaneous SPA references that we don't actively use but are required by structs or function parameters. */ +typedef struct ma_spa_command ma_spa_command; + +struct ma_spa_fraction +{ + ma_uint32 num; + ma_uint32 denom; +}; + + + +#define MA_PW_KEY_MEDIA_TYPE "media.type" +#define MA_PW_KEY_MEDIA_CATEGORY "media.category" +#define MA_PW_KEY_MEDIA_ROLE "media.role" +#define MA_PW_KEY_MEDIA_CLASS "media.class" +#define MA_PW_KEY_NODE_LATENCY "node.latency" +#define MA_PW_KEY_TARGET_OBJECT "target.object" +#define MA_PW_KEY_METADATA_NAME "metadata.name" + +#define MA_PW_TYPE_INTERFACE_Node "PipeWire:Interface:Node" +#define MA_PW_TYPE_INTERFACE_Metadata "PipeWire:Interface:Metadata" + +#define MA_PW_ID_CORE 0 +#define MA_PW_ID_ANY 0xFFFFFFFF + +struct ma_pw_thread_loop; +struct ma_pw_loop; +struct ma_pw_context; +struct ma_pw_core; +struct ma_pw_registry; +struct ma_pw_metadata; +struct ma_pw_metadata_events; +struct ma_pw_proxy; +struct ma_pw_properties; +struct ma_pw_stream; +struct ma_pw_stream_control; +struct ma_pw_core_info; + +enum ma_pw_stream_state +{ + MA_PW_STREAM_STATE_ERROR = -1, + MA_PW_STREAM_STATE_UNCONNECTED = 0, + MA_PW_STREAM_STATE_CONNECTING = 1, + MA_PW_STREAM_STATE_PAUSED = 2, + MA_PW_STREAM_STATE_STREAMING = 3 +}; + +enum ma_pw_stream_flags +{ + MA_PW_STREAM_FLAG_NONE = 0, + MA_PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), + MA_PW_STREAM_FLAG_INACTIVE = (1 << 1), + MA_PW_STREAM_FLAG_MAP_BUFFERS = (1 << 2), + MA_PW_STREAM_FLAG_DRIVER = (1 << 3), + MA_PW_STREAM_FLAG_RT_PROCESS = (1 << 4), + MA_PW_STREAM_FLAG_NO_CONVERT = (1 << 5), + MA_PW_STREAM_FLAG_EXCLUSIVE = (1 << 6), + MA_PW_STREAM_FLAG_DONT_RECONNECT = (1 << 7), + MA_PW_STREAM_FLAG_ALLOC_BUFFERS = (1 << 8), + MA_PW_STREAM_FLAG_TRIGGER = (1 << 9), + MA_PW_STREAM_FLAG_ASYNC = (1 << 10) +}; + +struct ma_pw_buffer +{ + struct ma_spa_buffer* buffer; + void* user_data; + ma_uint64 size; + ma_uint64 requested; +}; + +struct ma_pw_time +{ + ma_int64 now; + struct ma_spa_fraction rate; + ma_uint64 ticks; + ma_int64 delay; + ma_uint64 queued; + ma_uint64 buffered; + ma_uint32 queued_buffers; + ma_uint32 avail_buffers; + ma_uint64 size; +}; + + +#define MA_PW_VERSION_CORE_EVENTS 1 +struct ma_pw_core_events +{ + ma_uint32 version; + void (* info )(void* data, const struct ma_pw_core_info* info); + void (* done )(void* data, ma_uint32 id, int seq); + void (* ping )(void* data, ma_uint32 id, int seq); + void (* error )(void* data, ma_uint32 id, int seq, int res, const char* message); + void (* remove_id )(void* data, ma_uint32 id); + void (* bound_id )(void* data, ma_uint32 id, ma_uint32 global_id); + void (* add_mem )(void* data, ma_uint32 id, ma_uint32 type, int fd, ma_uint32 flags); + void (* remove_mem )(void* data, ma_uint32 id); + void (* bound_props)(void* data, ma_uint32 id, ma_uint32 global_id, const struct ma_spa_dict* props); +}; + + +#define MA_PW_VERSION_REGISTRY 3 +#define MA_PW_VERSION_REGISTRY_EVENTS 0 +struct ma_pw_registry_events +{ + ma_uint32 version; + void (* global_add )(void* data, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props); + void (* global_remove)(void* data, ma_uint32 id); +}; + + + +#define PW_VERSION_METADATA_METHODS 0 +struct ma_pw_metadata_methods +{ + ma_uint32 version; + int (* add_listener)(void* object, struct ma_spa_hook* listener, const struct ma_pw_metadata_events* events, void* data); + int (* set_property)(void* object, ma_uint32 subject, const char* key, const char* type, const char* value); + int (* clear )(void* object); +}; + +#define MA_PW_VERSION_METADATA 3 +#define MA_PW_VERSION_METADATA_EVENTS 0 +struct ma_pw_metadata_events +{ + ma_uint32 version; + int (* property)(void* data, ma_uint32 subject, const char* key, const char* type, const char* value); +}; + + +#define MA_PW_VERSION_STREAM_EVENTS 2 +struct ma_pw_stream_events +{ + ma_uint32 version; + void (* destroy )(void* data); + void (* state_changed)(void* data, enum ma_pw_stream_state oldState, enum ma_pw_stream_state newState, const char* error); + void (* control_info )(void* data, ma_uint32 id, const struct ma_pw_stream_control* control); + void (* io_changed )(void* data, ma_uint32 id, void* area, ma_uint32 size); + void (* param_changed)(void* data, ma_uint32 id, const struct ma_spa_pod* param); + void (* add_buffer )(void* data, struct ma_pw_buffer* buffer); + void (* remove_buffer)(void* data, struct ma_pw_buffer* buffer); + void (* process )(void* data); + void (* drained )(void* data); + void (* command )(void* data, const struct ma_spa_command* command); + void (* trigger_done )(void* data); +}; +#endif + +static MA_INLINE struct ma_spa_pod_id ma_spa_pod_id_init(ma_uint32 value) +{ + struct ma_spa_pod_id pod; + + MA_ZERO_OBJECT(&pod); + pod.pod.size = 4; /* Does not include the header or padding. */ + pod.pod.type = MA_SPA_TYPE_Id; + pod.value = value; + + return pod; +} + +static MA_INLINE struct ma_spa_pod_int ma_spa_pod_int_init(ma_int32 value) +{ + struct ma_spa_pod_int pod; + + MA_ZERO_OBJECT(&pod); + pod.pod.size = 4; /* Does not include the header or padding. */ + pod.pod.type = MA_SPA_TYPE_Int; + pod.value = value; + + return pod; +} + +static MA_INLINE void* ma_spa_pod_choice_get_values(const struct ma_spa_pod_choice* choice) +{ + /* Values are stored as a flat array after the main struct. */ + return (void*)ma_offset_ptr(choice, sizeof(struct ma_spa_pod_choice)); +} + +#if 0 +static MA_INLINE ma_uint32 ma_spa_pod_array_get_length(const struct ma_spa_pod_array* array) +{ + return (array->pod.size - sizeof(struct ma_spa_pod_array_body)) / array->body.child.size; +} +#endif + +static MA_INLINE void* ma_spa_pod_array_get_values(const struct ma_spa_pod_array* array) +{ + /* Values are stored as a flat array after the main struct. */ + return (void*)ma_offset_ptr(array, sizeof(struct ma_spa_pod_array)); +} + + +struct ma_spa_pod_prop_id +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod_id value; +}; + +static MA_INLINE struct ma_spa_pod_prop_id ma_spa_pod_prop_id_init(ma_uint32 key, ma_uint32 value) +{ + struct ma_spa_pod_prop_id prop; + + prop.key = key; + prop.flags = 0; + prop.value = ma_spa_pod_id_init(value); + + return prop; +} + + +struct ma_spa_pod_prop_int +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod_int value; +}; + +static MA_INLINE struct ma_spa_pod_prop_int ma_spa_pod_prop_int_init(ma_uint32 key, ma_int32 value) +{ + struct ma_spa_pod_prop_int prop; + + prop.key = key; + prop.flags = 0; + prop.value = ma_spa_pod_int_init(value); + + return prop; +} + + +/* This is a custom pod for our buffer parameters. */ +struct ma_spa_pod_buffer_params +{ + struct ma_spa_pod_object object; + struct ma_spa_pod_prop_int buffers; + struct ma_spa_pod_prop_int blocks; + struct ma_spa_pod_prop_int stride; + struct ma_spa_pod_prop_int size; +}; + +static MA_INLINE struct ma_spa_pod_buffer_params ma_spa_pod_buffer_params_init(ma_uint32 strideInBytes, ma_uint32 bufferSizeInFrames) +{ + struct ma_spa_pod_buffer_params pod; + + /* + spa_pod_builder_add_object(&podBuilder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(2), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(bytesPerFrame), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(bytesPerFrame * pStreamState->bufferSizeInFrames)); + */ + + MA_ZERO_OBJECT(&pod); + + pod.object.pod.size = sizeof(struct ma_spa_pod_buffer_params) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ + pod.object.pod.type = MA_SPA_TYPE_Object; + pod.object.body.type = MA_SPA_TYPE_OBJECT_ParamBuffers; + pod.object.body.id = MA_SPA_PARAM_Buffers; + + pod.buffers = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_buffers, 2); + pod.blocks = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_blocks, 1); + pod.stride = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_stride, (ma_int32)strideInBytes); + pod.size = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_size, (ma_int32)(strideInBytes * bufferSizeInFrames)); + + return pod; +} + + +/* A custom pod for audio info. */ +struct ma_spa_pod_audio_info_raw +{ + struct ma_spa_pod_object object; + struct ma_spa_pod_prop_id mediaType; + struct ma_spa_pod_prop_id mediaSubtype; + struct ma_spa_pod_prop_id format; + struct ma_spa_pod_prop_int extra[2]; /* Channels and sample rate. Both are optional. */ +}; + +static MA_INLINE struct ma_spa_pod_audio_info_raw ma_spa_pod_audio_info_raw_init(enum ma_spa_audio_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + struct ma_spa_pod_audio_info_raw pod; + int extraIndex = 0; + + /* + struct spa_audio_info_raw audioInfo; + char podBuilderBuffer[1024]; + struct spa_pod_builder podBuilder; + + podBuilder = SPA_POD_BUILDER_INIT(podBuilderBuffer, sizeof(podBuilderBuffer)); + + memset(&audioInfo, 0, sizeof(audioInfo)); + audioInfo.flags = SPA_AUDIO_FLAG_UNPOSITIONED; + audioInfo.format = (enum spa_audio_format)formatPA; + audioInfo.channels = pDescriptor->channels; + audioInfo.rate = pDescriptor->sampleRate; + + pConnectionParameters[0] = spa_format_audio_raw_build(&podBuilder, SPA_PARAM_EnumFormat, &audioInfo); + */ + + MA_ZERO_OBJECT(&pod); + + pod.object.pod.size = sizeof(struct ma_spa_pod_object) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ + pod.object.pod.type = MA_SPA_TYPE_Object; + pod.object.body.type = MA_SPA_TYPE_OBJECT_Format; + pod.object.body.id = MA_SPA_PARAM_EnumFormat; + + pod.mediaType = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaType, MA_SPA_MEDIA_TYPE_audio); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); + + pod.mediaSubtype = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaSubtype, MA_SPA_MEDIA_SUBTYPE_raw); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); + + pod.format = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_AUDIO_format, (ma_uint32)format); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); + + if (channels > 0) { + pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_channels, (ma_int32)channels); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); + extraIndex += 1; + } + + if (sampleRate > 0) { + pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_rate, (ma_int32)sampleRate); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); + /*extraIndex += 1;*/ + } + + /* We're going to leave the channel map alone and just do a conversion ourselves if it differs from the native map. */ + + return pod; +} + + +/* Extracts the value of an ID from a pod, depending on its type. */ +static MA_INLINE ma_uint32 ma_spa_pod_get_id_value(const struct ma_spa_pod* pod) +{ + MA_ASSERT(pod != NULL); + + switch (pod->type) + { + case MA_SPA_TYPE_Id: + { + const struct ma_spa_pod_id* pPodID = (const struct ma_spa_pod_id*)pod; + return pPodID->value; + } + + case MA_SPA_TYPE_Choice: + { + const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; + const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); + + /* We're just going to return the first value. */ + return ((ma_uint32*)pValues)[0]; + } + + default: + { + MA_ASSERT(MA_FALSE); /* Unsupported type. */ + return 0; + } + } +} + +static MA_INLINE ma_int32 ma_spa_pod_get_int_value(const struct ma_spa_pod* pod) +{ + MA_ASSERT(pod != NULL); + + switch (pod->type) + { + case MA_SPA_TYPE_Id: + { + const struct ma_spa_pod_int* pPodInt = (const struct ma_spa_pod_int*)pod; + return pPodInt->value; + } + + case MA_SPA_TYPE_Choice: + { + const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; + const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); + + /* We're just going to return the first value. */ + return ((ma_int32*)pValues)[0]; + } + + default: + { + MA_ASSERT(MA_FALSE); /* Unsupported type. */ + return 0; + } + } +} + + + +typedef void (* ma_pw_init_proc )(int* argc, char*** argv); +typedef void (* ma_pw_deinit_proc )(void); +typedef struct ma_pw_loop* (* ma_pw_loop_new_proc )(const struct ma_spa_dict* props); +typedef void (* ma_pw_loop_destroy_proc )(struct ma_pw_loop* loop); +typedef int (* ma_pw_loop_set_name_proc )(struct ma_pw_loop* loop, const char* name); +typedef void (* ma_pw_loop_enter_proc )(struct ma_pw_loop* loop); +typedef void (* ma_pw_loop_leave_proc )(struct ma_pw_loop* loop); +typedef int (* ma_pw_loop_iterate_proc )(struct ma_pw_loop* loop, int timeout); +typedef struct spa_source* (* ma_pw_loop_add_event_proc )(struct ma_pw_loop* loop, ma_spa_source_event_func_t func, void* data); +typedef int (* ma_pw_loop_signal_event_proc )(struct ma_pw_loop* loop, struct spa_source* source); +typedef struct ma_pw_thread_loop* (* ma_pw_thread_loop_new_proc )(const char* name, const struct ma_spa_dict* props); +typedef void (* ma_pw_thread_loop_destroy_proc )(struct ma_pw_thread_loop* loop); +typedef struct ma_pw_loop* (* ma_pw_thread_loop_get_loop_proc )(struct ma_pw_thread_loop* loop); +typedef int (* ma_pw_thread_loop_start_proc )(struct ma_pw_thread_loop* loop); +typedef void (* ma_pw_thread_loop_lock_proc )(struct ma_pw_thread_loop* loop); +typedef void (* ma_pw_thread_loop_unlock_proc )(struct ma_pw_thread_loop* loop); +typedef struct ma_pw_context* (* ma_pw_context_new_proc )(struct ma_pw_loop* loop, struct ma_pw_properties *props, size_t user_data_size); +typedef void (* ma_pw_context_destroy_proc )(struct ma_pw_context* context); +typedef struct ma_pw_core* (* ma_pw_context_connect_proc )(struct ma_pw_context* context, struct ma_pw_properties* properties, size_t user_data_size); +typedef int (* ma_pw_core_disconnect_proc )(struct ma_pw_core* core); +typedef int (* ma_pw_core_add_listener_proc )(struct ma_pw_core* core, struct ma_spa_hook* listener, const struct ma_pw_core_events* events, void* data); +typedef struct ma_pw_registry* (* ma_pw_core_get_registry_proc )(struct ma_pw_core* core, ma_uint32 version, size_t user_data_size); +typedef int (* ma_pw_core_sync_proc )(struct ma_pw_core* core, ma_uint32 id, int seq); +typedef int (* ma_pw_registry_add_listener_proc )(struct ma_pw_registry* registry, struct ma_spa_hook* listener, const struct ma_pw_registry_events* events, void* data); +typedef void* (* ma_pw_registry_bind_proc )(struct ma_pw_registry* registry, ma_uint32 id, const char* type, ma_uint32 version, size_t user_data_size); +typedef void (* ma_pw_proxy_destroy_proc )(struct ma_pw_proxy* proxy); +typedef struct ma_pw_properties* (* ma_pw_properties_new_proc )(const char* key, ...); +typedef void (* ma_pw_properties_free_proc )(struct ma_pw_properties* properties); +typedef int (* ma_pw_properties_set_proc )(struct ma_pw_properties* properties, const char* key, const char* value); +typedef struct ma_pw_stream* (* ma_pw_stream_new_proc )(struct ma_pw_core* core, const char* name, struct ma_pw_properties* props); +typedef void (* ma_pw_stream_destroy_proc )(struct ma_pw_stream* stream); +typedef void (* ma_pw_stream_add_listener_proc )(struct ma_pw_stream* stream, struct ma_spa_hook* listener, const struct ma_pw_stream_events* events, void* data); +typedef int (* ma_pw_stream_connect_proc )(struct ma_pw_stream* stream, enum ma_spa_direction direction, ma_uint32 target_id, enum ma_pw_stream_flags flags, const struct ma_spa_pod** params, ma_uint32 paramCount); +typedef int (* ma_pw_stream_set_active_proc )(struct ma_pw_stream* stream, ma_bool8 active); +typedef struct ma_pw_buffer* (* ma_pw_stream_dequeue_buffer_proc )(struct ma_pw_stream* stream); +typedef int (* ma_pw_stream_queue_buffer_proc )(struct ma_pw_stream* stream, struct ma_pw_buffer* buffer); +typedef int (* ma_pw_stream_update_params_proc )(struct ma_pw_stream* stream, const struct ma_spa_pod** params, ma_uint32 paramCount); +typedef int (* ma_pw_stream_update_properties_proc)(struct ma_pw_stream* stream, const struct ma_spa_dict* dict); +typedef int (* ma_pw_stream_get_time_n_proc )(struct ma_pw_stream* stream, struct ma_pw_time* time, size_t size); + +typedef struct +{ + ma_log* pLog; + ma_handle hPipeWire; + ma_pw_init_proc pw_init; + ma_pw_deinit_proc pw_deinit; + ma_pw_loop_new_proc pw_loop_new; + ma_pw_loop_destroy_proc pw_loop_destroy; + ma_pw_loop_set_name_proc pw_loop_set_name; + ma_pw_loop_enter_proc pw_loop_enter; + ma_pw_loop_leave_proc pw_loop_leave; + ma_pw_loop_iterate_proc pw_loop_iterate; + ma_pw_loop_add_event_proc pw_loop_add_event; + ma_pw_loop_signal_event_proc pw_loop_signal_event; + ma_pw_thread_loop_new_proc pw_thread_loop_new; + ma_pw_thread_loop_destroy_proc pw_thread_loop_destroy; + ma_pw_thread_loop_get_loop_proc pw_thread_loop_get_loop; + ma_pw_thread_loop_start_proc pw_thread_loop_start; + ma_pw_thread_loop_lock_proc pw_thread_loop_lock; + ma_pw_thread_loop_unlock_proc pw_thread_loop_unlock; + ma_pw_context_new_proc pw_context_new; + ma_pw_context_destroy_proc pw_context_destroy; + ma_pw_context_connect_proc pw_context_connect; + ma_pw_core_disconnect_proc pw_core_disconnect; + ma_pw_core_add_listener_proc pw_core_add_listener; + ma_pw_core_get_registry_proc pw_core_get_registry; + ma_pw_core_sync_proc pw_core_sync; + ma_pw_registry_add_listener_proc pw_registry_add_listener; + ma_pw_registry_bind_proc pw_registry_bind; + ma_pw_proxy_destroy_proc pw_proxy_destroy; + ma_pw_properties_new_proc pw_properties_new; + ma_pw_properties_free_proc pw_properties_free; + ma_pw_properties_set_proc pw_properties_set; + ma_pw_stream_new_proc pw_stream_new; + ma_pw_stream_destroy_proc pw_stream_destroy; + ma_pw_stream_add_listener_proc pw_stream_add_listener; + ma_pw_stream_connect_proc pw_stream_connect; + ma_pw_stream_set_active_proc pw_stream_set_active; + ma_pw_stream_dequeue_buffer_proc pw_stream_dequeue_buffer; + ma_pw_stream_queue_buffer_proc pw_stream_queue_buffer; + ma_pw_stream_update_params_proc pw_stream_update_params; + ma_pw_stream_update_properties_proc pw_stream_update_properties; + ma_pw_stream_get_time_n_proc pw_stream_get_time_n; +} ma_context_state_pipewire; + + +#define MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT 0x01 +#define MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY 0x02 +#define MA_PIPEWIRE_INIT_STATUS_INITIALIZED 0x04 + +typedef struct +{ + struct ma_pw_stream* pStream; + struct ma_spa_hook eventListener; + ma_uint32 initStatus; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFrames; + ma_uint32 bufferCount; + ma_uint32 rbSizeInFrames; + ma_pcm_rb rb; /* Only used in single-threaded mode. Acts as an intermediary buffer for step(). For playback, PipeWire will read from this ring buffer. For capture, it'll write to it. */ + ma_device_descriptor* pDescriptor; /* This is only used for setting up internal format. It's needed here because it looks like the only way to get the internal format is via a stupid callback. Will be set to NULL after initialization of the PipeWire stream. */ +} ma_pipewire_stream_state; + +typedef struct +{ + ma_context_state_pipewire* pContextStatePipeWire; + ma_device_type deviceType; + ma_device* pDevice; /* Only needed for ma_stream_event_process__pipewire(). We may change this later in which case we can delete this. */ + struct ma_pw_loop* pLoop; + struct ma_pw_context* pContext; + struct ma_pw_core* pCore; + struct spa_source* pWakeup; /* This is for waking up the loop which we need to do after each data processing callback and the miniaudio wakeup callback. */ + ma_pipewire_stream_state playback; + ma_pipewire_stream_state capture; + struct + { + ma_timer timer; + double lastTimeInSeconds; + } debugging; +} ma_device_state_pipewire; + + +static enum ma_spa_audio_format ma_format_to_pipewire(ma_format format) +{ + if (format == ma_format_u8) { + return MA_SPA_AUDIO_FORMAT_U8; + } + + if (ma_is_little_endian()) { + switch (format) { + case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_LE; + case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_LE; + case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_LE; + case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_LE; + default: break; + } + } else { + switch (format) { + case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_BE; + case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_BE; + case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_BE; + case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_BE; + default: break; + } + } + + return MA_SPA_AUDIO_FORMAT_UNKNOWN; +} + +static ma_format ma_format_from_pipewire(enum ma_spa_audio_format format) +{ + if (format == MA_SPA_AUDIO_FORMAT_U8) { + return ma_format_u8; + } + + if (ma_is_little_endian()) { + switch (format) { + case MA_SPA_AUDIO_FORMAT_S16_LE: return ma_format_s16; + case MA_SPA_AUDIO_FORMAT_S24_LE: return ma_format_s24; + case MA_SPA_AUDIO_FORMAT_S32_LE: return ma_format_s32; + case MA_SPA_AUDIO_FORMAT_F32_LE: return ma_format_f32; + default: break; + } + } else { + switch (format) { + case MA_SPA_AUDIO_FORMAT_S16_BE: return ma_format_s16; + case MA_SPA_AUDIO_FORMAT_S24_BE: return ma_format_s24; + case MA_SPA_AUDIO_FORMAT_S32_BE: return ma_format_s32; + case MA_SPA_AUDIO_FORMAT_F32_BE: return ma_format_f32; + default: break; + } + } + + return ma_format_unknown; +} + +static ma_channel ma_channel_from_pipewire(ma_uint32 channel) +{ + switch (channel) + { + case MA_SPA_AUDIO_CHANNEL_MONO: return MA_CHANNEL_MONO; + case MA_SPA_AUDIO_CHANNEL_FL: return MA_CHANNEL_FRONT_LEFT; + case MA_SPA_AUDIO_CHANNEL_FR: return MA_CHANNEL_FRONT_RIGHT; + case MA_SPA_AUDIO_CHANNEL_FC: return MA_CHANNEL_FRONT_CENTER; + case MA_SPA_AUDIO_CHANNEL_LFE: return MA_CHANNEL_LFE; + case MA_SPA_AUDIO_CHANNEL_SL: return MA_CHANNEL_SIDE_LEFT; + case MA_SPA_AUDIO_CHANNEL_SR: return MA_CHANNEL_SIDE_RIGHT; + case MA_SPA_AUDIO_CHANNEL_FLC: return MA_CHANNEL_FRONT_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_FRC: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_RC: return MA_CHANNEL_BACK_CENTER; + case MA_SPA_AUDIO_CHANNEL_RL: return MA_CHANNEL_BACK_LEFT; + case MA_SPA_AUDIO_CHANNEL_RR: return MA_CHANNEL_BACK_RIGHT; + case MA_SPA_AUDIO_CHANNEL_TC: return MA_CHANNEL_TOP_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFL: return MA_CHANNEL_TOP_FRONT_LEFT; + case MA_SPA_AUDIO_CHANNEL_TFC: return MA_CHANNEL_TOP_FRONT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFR: return MA_CHANNEL_TOP_FRONT_RIGHT; + case MA_SPA_AUDIO_CHANNEL_TRL: return MA_CHANNEL_TOP_BACK_LEFT; + case MA_SPA_AUDIO_CHANNEL_TRC: return MA_CHANNEL_TOP_BACK_CENTER; + case MA_SPA_AUDIO_CHANNEL_TRR: return MA_CHANNEL_TOP_BACK_RIGHT; + + /* These need to be added to miniaudio. */ + #if 0 + case MA_SPA_AUDIO_CHANNEL_RLC: return MA_CHANNEL_BACK_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_RRC: return MA_CHANNEL_BACK_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_FLW: return MA_CHANNEL_FRONT_LEFT_WIDE; + case MA_SPA_AUDIO_CHANNEL_FRW: return MA_CHANNEL_FRONT_RIGHT_WIDE; + case MA_SPA_AUDIO_CHANNEL_LFE2: return MA_CHANNEL_LFE2; + case MA_SPA_AUDIO_CHANNEL_FLH: return MA_CHANNEL_FRONT_LEFT_HIGH; + case MA_SPA_AUDIO_CHANNEL_FCH: return MA_CHANNEL_FRONT_CENTER_HIGH; + case MA_SPA_AUDIO_CHANNEL_FRH: return MA_CHANNEL_FRONT_RIGHT_HIGH; + case MA_SPA_AUDIO_CHANNEL_TFLC: return MA_CHANNEL_TOP_FRONT_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFRC: return MA_CHANNEL_TOP_FRONT_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TSL: return MA_CHANNEL_TOP_SIDE_LEFT; + case MA_SPA_AUDIO_CHANNEL_TSR: return MA_CHANNEL_TOP_SIDE_RIGHT; + case MA_SPA_AUDIO_CHANNEL_LLFE: return MA_CHANNEL_LEFT_LFE; + case MA_SPA_AUDIO_CHANNEL_RLFE: return MA_CHANNEL_RIGHT_LFE; + case MA_SPA_AUDIO_CHANNEL_BC: return MA_CHANNEL_BOTTOM_CENTER; + case MA_SPA_AUDIO_CHANNEL_BLC: return MA_CHANNEL_BOTTOM_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_BRC: return MA_CHANNEL_BOTTOM_RIGHT_CENTER; + #endif + + case MA_SPA_AUDIO_CHANNEL_AUX0: return MA_CHANNEL_AUX_0; + case MA_SPA_AUDIO_CHANNEL_AUX1: return MA_CHANNEL_AUX_1; + case MA_SPA_AUDIO_CHANNEL_AUX2: return MA_CHANNEL_AUX_2; + case MA_SPA_AUDIO_CHANNEL_AUX3: return MA_CHANNEL_AUX_3; + case MA_SPA_AUDIO_CHANNEL_AUX4: return MA_CHANNEL_AUX_4; + case MA_SPA_AUDIO_CHANNEL_AUX5: return MA_CHANNEL_AUX_5; + case MA_SPA_AUDIO_CHANNEL_AUX6: return MA_CHANNEL_AUX_6; + case MA_SPA_AUDIO_CHANNEL_AUX7: return MA_CHANNEL_AUX_7; + case MA_SPA_AUDIO_CHANNEL_AUX8: return MA_CHANNEL_AUX_8; + case MA_SPA_AUDIO_CHANNEL_AUX9: return MA_CHANNEL_AUX_9; + case MA_SPA_AUDIO_CHANNEL_AUX10: return MA_CHANNEL_AUX_10; + case MA_SPA_AUDIO_CHANNEL_AUX11: return MA_CHANNEL_AUX_11; + case MA_SPA_AUDIO_CHANNEL_AUX12: return MA_CHANNEL_AUX_12; + case MA_SPA_AUDIO_CHANNEL_AUX13: return MA_CHANNEL_AUX_13; + case MA_SPA_AUDIO_CHANNEL_AUX14: return MA_CHANNEL_AUX_14; + case MA_SPA_AUDIO_CHANNEL_AUX15: return MA_CHANNEL_AUX_15; + case MA_SPA_AUDIO_CHANNEL_AUX16: return MA_CHANNEL_AUX_16; + case MA_SPA_AUDIO_CHANNEL_AUX17: return MA_CHANNEL_AUX_17; + case MA_SPA_AUDIO_CHANNEL_AUX18: return MA_CHANNEL_AUX_18; + case MA_SPA_AUDIO_CHANNEL_AUX19: return MA_CHANNEL_AUX_19; + case MA_SPA_AUDIO_CHANNEL_AUX20: return MA_CHANNEL_AUX_20; + case MA_SPA_AUDIO_CHANNEL_AUX21: return MA_CHANNEL_AUX_21; + case MA_SPA_AUDIO_CHANNEL_AUX22: return MA_CHANNEL_AUX_22; + case MA_SPA_AUDIO_CHANNEL_AUX23: return MA_CHANNEL_AUX_23; + case MA_SPA_AUDIO_CHANNEL_AUX24: return MA_CHANNEL_AUX_24; + case MA_SPA_AUDIO_CHANNEL_AUX25: return MA_CHANNEL_AUX_25; + case MA_SPA_AUDIO_CHANNEL_AUX26: return MA_CHANNEL_AUX_26; + case MA_SPA_AUDIO_CHANNEL_AUX27: return MA_CHANNEL_AUX_27; + case MA_SPA_AUDIO_CHANNEL_AUX28: return MA_CHANNEL_AUX_28; + case MA_SPA_AUDIO_CHANNEL_AUX29: return MA_CHANNEL_AUX_29; + case MA_SPA_AUDIO_CHANNEL_AUX30: return MA_CHANNEL_AUX_30; + case MA_SPA_AUDIO_CHANNEL_AUX31: return MA_CHANNEL_AUX_31; + + /* These need to be added to miniaudio. */ + #if 0 + case MA_SPA_AUDIO_CHANNEL_AUX32: return MA_CHANNEL_AUX_32; + case MA_SPA_AUDIO_CHANNEL_AUX33: return MA_CHANNEL_AUX_33; + case MA_SPA_AUDIO_CHANNEL_AUX34: return MA_CHANNEL_AUX_34; + case MA_SPA_AUDIO_CHANNEL_AUX35: return MA_CHANNEL_AUX_35; + case MA_SPA_AUDIO_CHANNEL_AUX36: return MA_CHANNEL_AUX_36; + case MA_SPA_AUDIO_CHANNEL_AUX37: return MA_CHANNEL_AUX_37; + case MA_SPA_AUDIO_CHANNEL_AUX38: return MA_CHANNEL_AUX_38; + case MA_SPA_AUDIO_CHANNEL_AUX39: return MA_CHANNEL_AUX_39; + case MA_SPA_AUDIO_CHANNEL_AUX40: return MA_CHANNEL_AUX_40; + case MA_SPA_AUDIO_CHANNEL_AUX41: return MA_CHANNEL_AUX_41; + case MA_SPA_AUDIO_CHANNEL_AUX42: return MA_CHANNEL_AUX_42; + case MA_SPA_AUDIO_CHANNEL_AUX43: return MA_CHANNEL_AUX_43; + case MA_SPA_AUDIO_CHANNEL_AUX44: return MA_CHANNEL_AUX_44; + case MA_SPA_AUDIO_CHANNEL_AUX45: return MA_CHANNEL_AUX_45; + case MA_SPA_AUDIO_CHANNEL_AUX46: return MA_CHANNEL_AUX_46; + case MA_SPA_AUDIO_CHANNEL_AUX47: return MA_CHANNEL_AUX_47; + case MA_SPA_AUDIO_CHANNEL_AUX48: return MA_CHANNEL_AUX_48; + case MA_SPA_AUDIO_CHANNEL_AUX49: return MA_CHANNEL_AUX_49; + case MA_SPA_AUDIO_CHANNEL_AUX50: return MA_CHANNEL_AUX_50; + case MA_SPA_AUDIO_CHANNEL_AUX51: return MA_CHANNEL_AUX_51; + case MA_SPA_AUDIO_CHANNEL_AUX52: return MA_CHANNEL_AUX_52; + case MA_SPA_AUDIO_CHANNEL_AUX53: return MA_CHANNEL_AUX_53; + case MA_SPA_AUDIO_CHANNEL_AUX54: return MA_CHANNEL_AUX_54; + case MA_SPA_AUDIO_CHANNEL_AUX55: return MA_CHANNEL_AUX_55; + case MA_SPA_AUDIO_CHANNEL_AUX56: return MA_CHANNEL_AUX_56; + case MA_SPA_AUDIO_CHANNEL_AUX57: return MA_CHANNEL_AUX_57; + case MA_SPA_AUDIO_CHANNEL_AUX58: return MA_CHANNEL_AUX_58; + case MA_SPA_AUDIO_CHANNEL_AUX59: return MA_CHANNEL_AUX_59; + case MA_SPA_AUDIO_CHANNEL_AUX60: return MA_CHANNEL_AUX_60; + case MA_SPA_AUDIO_CHANNEL_AUX61: return MA_CHANNEL_AUX_61; + case MA_SPA_AUDIO_CHANNEL_AUX62: return MA_CHANNEL_AUX_62; + case MA_SPA_AUDIO_CHANNEL_AUX63: return MA_CHANNEL_AUX_63; + #endif + + default: return MA_CHANNEL_NONE; + } +} + + +static ma_context_state_pipewire* ma_context_get_backend_state__pipewire(ma_context* pContext) +{ + return (ma_context_state_pipewire*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_pipewire* ma_device_get_backend_state__pipewire(ma_device* pDevice) +{ + return (ma_device_state_pipewire*)ma_device_get_backend_state(pDevice); +} + + +static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode); + + +static void ma_backend_info__pipewire(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "PipeWire"; +} + +static ma_result ma_context_init__pipewire(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + /* We'll use a list of possible shared object names for easier extensibility. */ + ma_context_state_pipewire* pContextStatePipeWire; + ma_context_config_pipewire* pContextConfigPipeWire = (ma_context_config_pipewire*)pContextBackendConfig; + ma_log* pLog = ma_context_get_log(pContext); + + (void)pContextConfigPipeWire; + + pContextStatePipeWire = (ma_context_state_pipewire*)ma_calloc(sizeof(*pContextStatePipeWire), ma_context_get_allocation_callbacks(pContext)); + if (pContextStatePipeWire == NULL) { + return MA_OUT_OF_MEMORY; + } + + pContextStatePipeWire->pLog = pLog; + + + /* Check if we have a PipeWire SO. If we can't find this we need to abort. */ + #ifndef MA_NO_RUNTIME_LINKING + { + ma_handle hPipeWire; + size_t iName; + const char* pSONames[] = { + "libpipewire-0.3.so.0", + "libpipewire.so" + }; + + for (iName = 0; iName < ma_countof(pSONames); iName += 1) { + hPipeWire = ma_dlopen(pLog, pSONames[iName]); + if (hPipeWire != NULL) { + break; + } + } + + if (hPipeWire == NULL) { + ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; /* PipeWire could not be loaded. */ + } + + pContextStatePipeWire->hPipeWire = hPipeWire; + pContextStatePipeWire->pw_init = (ma_pw_init_proc )ma_dlsym(pLog, hPipeWire, "pw_init"); + pContextStatePipeWire->pw_deinit = (ma_pw_deinit_proc )ma_dlsym(pLog, hPipeWire, "pw_deinit"); + pContextStatePipeWire->pw_loop_new = (ma_pw_loop_new_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_new"); + pContextStatePipeWire->pw_loop_destroy = (ma_pw_loop_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_destroy"); + pContextStatePipeWire->pw_loop_set_name = (ma_pw_loop_set_name_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_set_name"); + pContextStatePipeWire->pw_loop_enter = (ma_pw_loop_enter_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_enter"); + pContextStatePipeWire->pw_loop_leave = (ma_pw_loop_leave_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_leave"); + pContextStatePipeWire->pw_loop_iterate = (ma_pw_loop_iterate_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_iterate"); + pContextStatePipeWire->pw_loop_add_event = (ma_pw_loop_add_event_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_add_event"); + pContextStatePipeWire->pw_loop_signal_event = (ma_pw_loop_signal_event_proc )ma_dlsym(pLog, hPipeWire, "pw_loop_signal_event"); + pContextStatePipeWire->pw_thread_loop_new = (ma_pw_thread_loop_new_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_new"); + pContextStatePipeWire->pw_thread_loop_destroy = (ma_pw_thread_loop_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_destroy"); + pContextStatePipeWire->pw_thread_loop_get_loop = (ma_pw_thread_loop_get_loop_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_get_loop"); + pContextStatePipeWire->pw_thread_loop_start = (ma_pw_thread_loop_start_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_start"); + pContextStatePipeWire->pw_thread_loop_lock = (ma_pw_thread_loop_lock_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_lock"); + pContextStatePipeWire->pw_thread_loop_unlock = (ma_pw_thread_loop_unlock_proc )ma_dlsym(pLog, hPipeWire, "pw_thread_loop_unlock"); + pContextStatePipeWire->pw_context_new = (ma_pw_context_new_proc )ma_dlsym(pLog, hPipeWire, "pw_context_new"); + pContextStatePipeWire->pw_context_destroy = (ma_pw_context_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_context_destroy"); + pContextStatePipeWire->pw_context_connect = (ma_pw_context_connect_proc )ma_dlsym(pLog, hPipeWire, "pw_context_connect"); + pContextStatePipeWire->pw_core_disconnect = (ma_pw_core_disconnect_proc )ma_dlsym(pLog, hPipeWire, "pw_core_disconnect"); + pContextStatePipeWire->pw_core_add_listener = (ma_pw_core_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_core_add_listener"); + pContextStatePipeWire->pw_core_get_registry = (ma_pw_core_get_registry_proc )ma_dlsym(pLog, hPipeWire, "pw_core_get_registry"); + pContextStatePipeWire->pw_core_sync = (ma_pw_core_sync_proc )ma_dlsym(pLog, hPipeWire, "pw_core_sync"); + pContextStatePipeWire->pw_registry_add_listener = (ma_pw_registry_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_registry_add_listener"); + pContextStatePipeWire->pw_registry_bind = (ma_pw_registry_bind_proc )ma_dlsym(pLog, hPipeWire, "pw_registry_bind"); + pContextStatePipeWire->pw_proxy_destroy = (ma_pw_proxy_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_proxy_destroy"); + pContextStatePipeWire->pw_properties_new = (ma_pw_properties_new_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_new"); + pContextStatePipeWire->pw_properties_free = (ma_pw_properties_free_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_free"); + pContextStatePipeWire->pw_properties_set = (ma_pw_properties_set_proc )ma_dlsym(pLog, hPipeWire, "pw_properties_set"); + pContextStatePipeWire->pw_stream_new = (ma_pw_stream_new_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_new"); + pContextStatePipeWire->pw_stream_destroy = (ma_pw_stream_destroy_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_destroy"); + pContextStatePipeWire->pw_stream_add_listener = (ma_pw_stream_add_listener_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_add_listener"); + pContextStatePipeWire->pw_stream_connect = (ma_pw_stream_connect_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_connect"); + pContextStatePipeWire->pw_stream_set_active = (ma_pw_stream_set_active_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_set_active"); + pContextStatePipeWire->pw_stream_dequeue_buffer = (ma_pw_stream_dequeue_buffer_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_dequeue_buffer"); + pContextStatePipeWire->pw_stream_queue_buffer = (ma_pw_stream_queue_buffer_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_queue_buffer"); + pContextStatePipeWire->pw_stream_update_params = (ma_pw_stream_update_params_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_update_params"); + pContextStatePipeWire->pw_stream_update_properties = (ma_pw_stream_update_properties_proc)ma_dlsym(pLog, hPipeWire, "pw_stream_update_properties"); + pContextStatePipeWire->pw_stream_get_time_n = (ma_pw_stream_get_time_n_proc )ma_dlsym(pLog, hPipeWire, "pw_stream_get_time_n"); + } + #else + { + pContextStatePipeWire->pw_init = pw_init; + pContextStatePipeWire->pw_deinit = pw_deinit; + pContextStatePipeWire->pw_loop_new = pw_loop_new; + pContextStatePipeWire->pw_loop_destroy = pw_loop_destroy; + pContextStatePipeWire->pw_loop_set_name = pw_loop_set_name; + pContextStatePipeWire->pw_loop_enter = pw_loop_enter; + pContextStatePipeWire->pw_loop_leave = pw_loop_leave; + pContextStatePipeWire->pw_loop_iterate = pw_loop_iterate; + pContextStatePipeWire->pw_loop_add_event = pw_loop_add_event; + pContextStatePipeWire->pw_loop_signal_event = pw_loop_signal_event; + pContextStatePipeWire->pw_thread_loop_new = pw_thread_loop_new; + pContextStatePipeWire->pw_thread_loop_destroy = pw_thread_loop_destroy; + pContextStatePipeWire->pw_thread_loop_get_loop = pw_thread_loop_get_loop; + pContextStatePipeWire->pw_thread_loop_start = pw_thread_loop_start; + pContextStatePipeWire->pw_thread_loop_lock = pw_thread_loop_lock; + pContextStatePipeWire->pw_thread_loop_unlock = pw_thread_loop_unlock; + pContextStatePipeWire->pw_context_new = pw_context_new; + pContextStatePipeWire->pw_context_destroy = pw_context_destroy; + pContextStatePipeWire->pw_context_connect = pw_context_connect; + pContextStatePipeWire->pw_core_disconnect = pw_core_disconnect; + pContextStatePipeWire->pw_core_add_listener = pw_core_add_listener; + pContextStatePipeWire->pw_core_get_registry = pw_core_get_registry; + pContextStatePipeWire->pw_core_sync = pw_core_sync; + pContextStatePipeWire->pw_registry_add_listener = pw_registry_add_listener; + pContextStatePipeWire->pw_registry_bind = pw_registry_bind; + pContextStatePipeWire->pw_proxy_destroy = pw_proxy_destroy; + pContextStatePipeWire->pw_properties_new = pw_properties_new; + pContextStatePipeWire->pw_properties_free = pw_properties_free; + pContextStatePipeWire->pw_properties_set = pw_properties_set; + pContextStatePipeWire->pw_stream_new = pw_stream_new; + pContextStatePipeWire->pw_stream_destroy = pw_stream_destroy; + pContextStatePipeWire->pw_stream_add_listener = pw_stream_add_listener; + pContextStatePipeWire->pw_stream_connect = pw_stream_connect; + pContextStatePipeWire->pw_stream_set_active = (ma_pw_stream_set_active_proc)(void*)pw_stream_set_active; /* This cast is because we use `unsigned char` instead of `bool`. */ + pContextStatePipeWire->pw_stream_dequeue_buffer = pw_stream_dequeue_buffer; + pContextStatePipeWire->pw_stream_queue_buffer = pw_stream_queue_buffer; + pContextStatePipeWire->pw_stream_update_params = pw_stream_update_params; + pContextStatePipeWire->pw_stream_update_properties = pw_stream_update_properties; + pContextStatePipeWire->pw_stream_get_time_n = pw_stream_get_time_n; + } + #endif + + if (pContextStatePipeWire->pw_init != NULL) { + pContextStatePipeWire->pw_init(NULL, NULL); + } + + *ppContextState = pContextStatePipeWire; + + return MA_SUCCESS; +} + +static void ma_context_uninit__pipewire(ma_context* pContext) +{ + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); + + MA_ASSERT(pContextStatePipeWire != NULL); + + if (pContextStatePipeWire->pw_deinit != NULL) { + pContextStatePipeWire->pw_deinit(); + } + + /* Close the handle to the PipeWire shared object last. */ + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStatePipeWire->hPipeWire); + pContextStatePipeWire->hPipeWire = NULL; + } + #endif + + ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); +} + + +static ma_device_enumeration_result ma_context_enumerate_default_device_by_type__pipewire(ma_context* pContext, ma_device_type deviceType, ma_uint32 sampleRate, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_device_info deviceInfo; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + + (void)pContext; + + if (sampleRate != 0) { + minSampleRate = sampleRate; + maxSampleRate = sampleRate; + } else { + minSampleRate = ma_standard_sample_rate_min; + maxSampleRate = ma_standard_sample_rate_max; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* Default. */ + deviceInfo.isDefault = MA_TRUE; + + /* ID. */ + deviceInfo.id.custom.i = 0; + + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Capture Device", (size_t)-1); + } + + /* Data Format. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, minSampleRate, maxSampleRate); + + return callback(deviceType, &deviceInfo, pUserData); +} + + + +#define MA_PW_CORE_SYNC_FLAG_ENUM_DONE (1 << 0) +#define MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE (1 << 1) +#define MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE (1 << 2) + +typedef struct +{ + ma_device_info info; + ma_uint32 pwID; +} ma_enumerate_devices_entry_pipewire; + +typedef struct +{ + ma_context_state_pipewire* pContextStatePipeWire; + struct ma_pw_loop* pLoop; + struct ma_pw_core* pCore; + struct ma_pw_registry* pRegistry; + struct ma_pw_metadata* pMetadataDefault; /* "default" metadata. */ + struct ma_pw_metadata* pMetadataSettings; /* "settings" metadata. */ + struct ma_spa_hook metadataListenerDefault; /* "default" metadata */ + struct ma_spa_hook metadataListenerSettings; /* "settings" metadata. */ + int seqDefaults; + int seqSettings; + int seqEnumeration; + ma_uint32 syncFlags; + ma_uint32 clockRate; /* "clock.rate" from the "settings" metadata. */ + const ma_allocation_callbacks* pAllocationCallbacks; + struct + { + ma_device_id defaultDeviceID; + ma_enumerate_devices_entry_pipewire* pDevices; + size_t deviceInfoCount; + size_t deviceInfoCap; + } playback, capture; + ma_bool32 isAborted; /* We can't seem to be able to abort enumeration with PipeWire, so we'll just set a flag to indicate it and then ignore anything that comes after. */ +} ma_enumerate_devices_data_pipewire; + + +static void ma_on_core_done__pipewire(void* pUserData, ma_uint32 id, int seq) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; + + (void)id; + (void)seq; + + /*printf("Core Done: ID=%u, Seq=%d\n", id, seq);*/ + if (pEnumData->seqEnumeration == seq) { + pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_ENUM_DONE; + } else if (pEnumData->seqDefaults == seq) { + pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE; + } +} + +static const struct ma_pw_core_events ma_gCoreEventsPipeWire = +{ + MA_PW_VERSION_CORE_EVENTS, + NULL, + ma_on_core_done__pipewire, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +static void ma_enumerate_devices_data_pipewire_init(ma_enumerate_devices_data_pipewire* pEnumData, ma_context_state_pipewire* pContextStatePipeWire, struct ma_pw_loop* pLoop, struct ma_pw_core* pCore, struct ma_pw_registry* pRegistry, const ma_allocation_callbacks* pAllocationCallbacks) +{ + MA_ZERO_OBJECT(pEnumData); + pEnumData->pContextStatePipeWire = pContextStatePipeWire; + pEnumData->pLoop = pLoop; + pEnumData->pCore = pCore; + pEnumData->pRegistry = pRegistry; + pEnumData->pAllocationCallbacks = pAllocationCallbacks; +} + +static void ma_enumerate_devices_data_pipewire_uninit(ma_enumerate_devices_data_pipewire* pEnumData) +{ + if (pEnumData->pMetadataDefault != NULL) { + pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataDefault); + } + if (pEnumData->pMetadataSettings != NULL) { + pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataSettings); + } + + ma_free(pEnumData->playback.pDevices, pEnumData->pAllocationCallbacks); + ma_free(pEnumData->capture.pDevices, pEnumData->pAllocationCallbacks); +} + +static ma_result ma_enumerate_devices_data_pipewire_add(ma_enumerate_devices_data_pipewire* pEnumData, ma_device_type deviceType, ma_uint32 pwID, const ma_device_info* pDeviceInfo) +{ + size_t* pDeviceCount; + size_t* pDeviceCap; + ma_enumerate_devices_entry_pipewire** ppDevices; + + if (deviceType == ma_device_type_playback) { + pDeviceCount = &pEnumData->playback.deviceInfoCount; + pDeviceCap = &pEnumData->playback.deviceInfoCap; + ppDevices = &pEnumData->playback.pDevices; + } else { + pDeviceCount = &pEnumData->capture.deviceInfoCount; + pDeviceCap = &pEnumData->capture.deviceInfoCap; + ppDevices = &pEnumData->capture.pDevices; + } + + if (*pDeviceCount + 1 > *pDeviceCap) { + size_t newCap = *pDeviceCap * 2; + ma_enumerate_devices_entry_pipewire* pNewDevices; + + if (newCap == 0) { + newCap = 8; + } + + pNewDevices = (ma_enumerate_devices_entry_pipewire*)ma_realloc(*ppDevices, newCap * sizeof(*pNewDevices), pEnumData->pAllocationCallbacks); + if (pNewDevices == NULL) { + return MA_OUT_OF_MEMORY; + } + + *ppDevices = pNewDevices; + *pDeviceCap = newCap; + } + + ((*ppDevices)[*pDeviceCount]).info = *pDeviceInfo; + ((*ppDevices)[*pDeviceCount]).pwID = pwID; + *pDeviceCount += 1; + + return MA_SUCCESS; +} + + + +static int ma_on_metadata_property_default__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; + + (void)subject; + (void)type; + + if (key == NULL) { + return 0; + } + + /* + Well this is fun. To get the default device we need to get the value of the "default.audio.sink" and "default.audio.source" keys. Sounds + simple enough, except that the value is actually JSON... Why is the default device stored as a JSON string? Who does this? We're just + going to use a simplified parser that finds the first ":\"" and takes everything until the next "\"". + */ + if (strcmp(key, "default.audio.sink") == 0 || strcmp(key, "default.audio.source") == 0) { + const char* pValue = value; + const char* pStart; + const char* pEnd; + ma_device_id* pDefaultDeviceID; + + if (strcmp(key, "default.audio.sink") == 0) { + pDefaultDeviceID = &pEnumData->playback.defaultDeviceID; + } else { + pDefaultDeviceID = &pEnumData->capture.defaultDeviceID; + } + + pStart = strstr(pValue, ":\""); + if (pStart != NULL) { + pStart += 2; /* Move past the :". */ + pEnd = strchr(pStart, '\"'); + if (pEnd != NULL) { + size_t len = (size_t)(pEnd - pStart); + if (len >= sizeof(pDefaultDeviceID->custom.s)) { + len = sizeof(pDefaultDeviceID->custom.s) - 1; + } + + ma_strncpy_s(pDefaultDeviceID->custom.s, sizeof(pDefaultDeviceID->custom.s), pStart, len); + } + } + } + + /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ + return 0; +} + +static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Default = +{ + MA_PW_VERSION_METADATA_EVENTS, + ma_on_metadata_property_default__pipewire +}; + + +static int ma_on_metadata_property_settings__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; + + (void)pEnumData; + (void)subject; + (void)type; + + if (key == NULL) { + return 0; + } + + if (strcmp(key, "clock.rate") == 0) { + pEnumData->clockRate = (ma_uint32)atoi(value); + } + + /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ + return 0; +} + +static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Settings = +{ + MA_PW_VERSION_METADATA_EVENTS, + ma_on_metadata_property_settings__pipewire +}; + + +typedef struct +{ + enum ma_spa_audio_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + const ma_uint32* pChannelPositions; +} ma_pipewire_audio_info; + +static void ma_pipewire_audio_info_parse(const struct ma_spa_pod* pParam, ma_log* pLog, ma_pipewire_audio_info* pAudioInfo) +{ + struct ma_spa_pod_object* pObject; + struct ma_spa_pod_prop* pNextProp; + ma_uint8* ptr = (ma_uint8*)pParam; + ma_uint32 cursor = 0; + ma_uint32 size; + + MA_ZERO_OBJECT(pAudioInfo); + + if (pParam == NULL) { + return; + } + + if (pParam->type != MA_SPA_TYPE_Object || pParam->size < sizeof(struct ma_spa_pod_object_body)) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid pod type)."); + return; + } + + pObject = (struct ma_spa_pod_object*)pParam; + + /* The size of the pod does not include the header which makes parsing kind of annoying for us. We'll add it here. */ + size = pParam->size + sizeof(struct ma_spa_pod); + cursor += sizeof(struct ma_spa_pod); + + if (pObject->body.type != MA_SPA_TYPE_OBJECT_Format) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid body type)."); + return; + } + + cursor += sizeof(struct ma_spa_pod_object_body); + + /* At this point we have parsed the header part of the object, and what follows now should be a list of properties. */ + while (cursor < size) { + struct ma_spa_pod* pPropValue; + + pNextProp = (struct ma_spa_pod_prop*)(ptr + cursor); + if (cursor + sizeof(struct ma_spa_pod_prop) > size) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid size for property)."); + return; + } + + /* Place the cursor on the prop value, which is a spa_pod. */ + cursor += 8; /* Size of the key and flags. */ + pPropValue = (struct ma_spa_pod*)(ptr + cursor); + + switch (pNextProp->key) + { + case MA_SPA_FORMAT_AUDIO_format: + { + pAudioInfo->format = (enum ma_spa_audio_format)ma_spa_pod_get_id_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_channels: + { + pAudioInfo->channels = ma_spa_pod_get_int_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_rate: + { + pAudioInfo->sampleRate = ma_spa_pod_get_int_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_position: + { + /* I'm assuming we can only get an array back for this. */ + if (pPropValue->type != MA_SPA_TYPE_Array) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid type for position property)."); + return; + } + + pAudioInfo->pChannelPositions = (const ma_uint32*)ma_spa_pod_array_get_values((const struct ma_spa_pod_array*)pPropValue); + } break; + } + + cursor += ma_align_64(8 + pPropValue->size); /* Size of the value pod header + the size of the pod itself. */ + } +} + + +/* +NOTE + +The commented out code below was my first attempt to extract the "native" channel count and sample rate of +a device. Essentially what it's doing is probing each device by creating stream, connecting it, and then +querying the chosen channels and rate. This was a complete fail because there's what I consider to be a bug +somewhere in the PipeWire/ALSA pipeline. I have S/PDIF device with no device actually physically connected +to it. When I probe that device, the system volume for the entire operating system will suddenly go full +blast. This is clearly not something that can be shipped so I've had to comment out this code. I'm leaving +it here more as a reference and a reminder. + +This can be replicated from the OS itself without needing to run a program. In my case, if I switch the +default device to the S/PDIF device, and then back to my headphones, everything goes full volume and your +ears will get blown out. +*/ +#if 0 +typedef struct +{ + ma_log* pLog; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_bool32 done; +} ma_stream_event_param_changed_enumeration_data__pipewire; + +static void ma_stream_event_param_changed_enumeration__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + 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; + ma_pipewire_audio_info_parse(pParam, pData->pLog, &audioInfo); + + pData->channels = audioInfo.channels; + pData->sampleRate = audioInfo.sampleRate; + + pData->done = MA_TRUE; + } +} + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Enumeration = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + NULL, /* state_changed */ + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_enumeration__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + NULL, /* process */ + NULL, /* drained */ + NULL, /* command */ + 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_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_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); + } +} +#endif + +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; + const char* pNodeName; /* <-- This is the ID. */ + const char* pNiceName; + + (void)permissions; + (void)version; + (void)type; + + /* The node name is the ID. */ + pNodeName = ma_spa_dict_lookup(props, "node.name"); + + /* Name. */ + pNiceName = ma_spa_dict_lookup(props, "node.description"); + if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.description"); } + if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.nick"); } + if (pNiceName == NULL) { pNiceName = pNodeName; } + if (pNiceName == NULL) { pNiceName = "Unknown"; } + + /* Fill out the device info structure. */ + MA_ZERO_OBJECT(&deviceInfo); + + /* The default flag is set later in a second pass. */ + + /* ID. */ + ma_strncpy_s(deviceInfo.id.pipewire, sizeof(deviceInfo.id.pipewire), pNodeName, (size_t)-1); + + /* Name. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1); + + /* The native data format is set later in a second pass. */ + + + /* Finally we can add the device into to our internal list. */ + ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, id, &deviceInfo); + + /*printf("Registry Global Added By Type: ID=%u, Type=%s, DeviceType=%d, NiceName=%s\n", id, type, deviceType, pNiceName);*/ +} + +static void ma_registry_event_global_add_enumeration__pipewire(void* pUserData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; + const char* pMediaClass; + + /* Ignore all future iterations if we have aborted. */ + if (pEnumData->isAborted) { + return; + } + + /* Some device info needs to be retrieved from metadata. */ + if (strcmp(type, MA_PW_TYPE_INTERFACE_Metadata) == 0) { + const char* pName; + + pName = ma_spa_dict_lookup(props, MA_PW_KEY_METADATA_NAME); + if (pName != NULL) { + /* Default devices. */ + if (strcmp(pName, "default") == 0) { + pEnumData->pMetadataDefault = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); + if (pEnumData->pMetadataDefault != NULL) { + MA_ZERO_OBJECT(&pEnumData->metadataListenerDefault); + + /* Not using pw_metadata_add_listener() because it appears to be an inline function and thus not exported by libpipewire. */ + ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataDefault)->cb.funcs)->add_listener(pEnumData->pMetadataDefault, &pEnumData->metadataListenerDefault, &ma_gMetadataEventsPipeWire_Default, pEnumData); + + /*spa_api_method_r(int, -ENOTSUP, ma_pw_metadata, (struct spa_interface*)pEnumData->pMetadata, add_listener, 0, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, pEnumData);*/ + /*pEnumData->pContextStatePipeWire->pw_metadata_add_listener(pMetadata, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, NULL);*/ + + pEnumData->seqDefaults = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); + } + } + + /* Sample rate. */ + if (strcmp(pName, "settings") == 0) { + pEnumData->pMetadataSettings = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); + if (pEnumData->pMetadataSettings != NULL) { + MA_ZERO_OBJECT(&pEnumData->metadataListenerSettings); + ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataSettings)->cb.funcs)->add_listener(pEnumData->pMetadataSettings, &pEnumData->metadataListenerSettings, &ma_gMetadataEventsPipeWire_Settings, pEnumData); + + pEnumData->seqSettings = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); + } + } + } + + + return; + } + + /* From here on out we only care about nodes. */ + if (strcmp(type, MA_PW_TYPE_INTERFACE_Node) != 0) { + return; + } + + pMediaClass = ma_spa_dict_lookup(props, MA_PW_KEY_MEDIA_CLASS); + if (pMediaClass == NULL) { + return; + } + + /* If the string contains Audio/Sink or Audio/Source we can assume it's an enumerable node. */ + /* */ if (strcmp(pMediaClass, "Audio/Sink") == 0) { + ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_playback); + } else if (strcmp(pMediaClass, "Audio/Source") == 0) { + ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_capture); + } else { + return; /* Not an audio node we care about. */ + } +} + +static const struct ma_pw_registry_events ma_gRegistryEventsPipeWire_Enumeration = +{ + MA_PW_VERSION_REGISTRY_EVENTS, + ma_registry_event_global_add_enumeration__pipewire, + NULL +}; + + +static ma_result ma_context_enumerate_devices__pipewire(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; + struct ma_pw_loop* pLoop; + struct ma_pw_context* pPipeWireContext; + struct ma_pw_core* pCore; + struct ma_pw_registry* pRegistry; + struct ma_spa_hook coreListener; + struct ma_spa_hook registeryListener; + ma_enumerate_devices_data_pipewire enumData; + + MA_ASSERT(pContextStatePipeWire != NULL); + MA_ASSERT(callback != NULL); + + pLoop = pContextStatePipeWire->pw_loop_new(NULL); + if (pLoop == NULL) { + return MA_ERROR; + } + + pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); + if (pPipeWireContext == NULL) { + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); + if (pCore == NULL) { + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pContextStatePipeWire->pw_core_add_listener(pCore, &coreListener, &ma_gCoreEventsPipeWire, &enumData); + + pRegistry = pContextStatePipeWire->pw_core_get_registry(pCore, MA_PW_VERSION_REGISTRY, 0); + if (pRegistry == NULL) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + ma_enumerate_devices_data_pipewire_init(&enumData, pContextStatePipeWire, pLoop, pCore, pRegistry, ma_context_get_allocation_callbacks(pContext)); + + MA_ZERO_OBJECT(®isteryListener); + pContextStatePipeWire->pw_registry_add_listener(pRegistry, ®isteryListener, &ma_gRegistryEventsPipeWire_Enumeration, &enumData); + + /* + The pw_core_sync() function is extremely confusing. The documentation says this: + + Ask the server to emit the 'done' event with seq. + + The last parameter of pw_core_sync() is `seq`, and in the `done` callback, there is a parameter called `seq`. The documentation + makes it sound like the `seq` argument of the `done` callback will be set to what you specify in the `pw_core_sync()` call, but + this is not the case. The `seq` argument in the `done` callback will actually be the return value of `pw_core_sync()`. If there + is a PipeWire developer reading this, this documentation needs to be addressed. Feedback welcome if I'm misunderstanding or just + doing something wrong here. + */ + enumData.seqEnumeration = pContextStatePipeWire->pw_core_sync(pCore, MA_PW_ID_CORE, 0); + for (;;) { + pContextStatePipeWire->pw_loop_iterate(pLoop, -1); + + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_ENUM_DONE) != 0) { + if (enumData.seqDefaults == 0) { + break; /* We don't have a "default" metadata. */ + } + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE) != 0) { + break; + } + + if (enumData.seqSettings == 0) { + break; /* We don't have a "settings" metadata. */ + } + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE) != 0) { + break; + } + } + } + + + /* Here is where we iterate over each device and fire the callback. */ + { + size_t iDevice; + ma_bool32 hasDefaultPlaybackDevice = MA_FALSE; + ma_bool32 hasDefaultCaptureDevice = MA_FALSE; + ma_uint32 minChannels = 1; + ma_uint32 maxChannels = 64; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + + if (enumData.clockRate != 0) { + minSampleRate = enumData.clockRate; + maxSampleRate = enumData.clockRate; + } else { + minSampleRate = ma_standard_sample_rate_min; + maxSampleRate = ma_standard_sample_rate_max; + } + + /* Playback devices. */ + for (iDevice = 0; iDevice < enumData.playback.deviceInfoCount; iDevice += 1) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + if (enumData.playback.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.playback.pDevices[iDevice].info.id.custom.s, enumData.playback.defaultDeviceID.custom.s) == 0) { + enumData.playback.pDevices[iDevice].info.isDefault = MA_TRUE; + hasDefaultPlaybackDevice = MA_TRUE; + } + + /* Now we need to open the stream and get it's native data format. */ + ma_device_info_add_native_data_format(&enumData.playback.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); + + cbResult = callback(ma_device_type_playback, &enumData.playback.pDevices[iDevice].info, pUserData); + } + } + + if (enumData.playback.deviceInfoCount > 0 && !hasDefaultPlaybackDevice) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_playback, enumData.clockRate, callback, pUserData); + } + } + + + /* Capture devices. */ + for (iDevice = 0; iDevice < enumData.capture.deviceInfoCount; iDevice += 1) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + if (enumData.capture.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.capture.pDevices[iDevice].info.id.custom.s, enumData.capture.defaultDeviceID.custom.s) == 0) { + enumData.capture.pDevices[iDevice].info.isDefault = MA_TRUE; + hasDefaultCaptureDevice = MA_TRUE; + } + + /* Now we need to open the stream and get it's native data format. */ + ma_device_info_add_native_data_format(&enumData.capture.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); + + cbResult = callback(ma_device_type_capture, &enumData.capture.pDevices[iDevice].info, pUserData); + } + } + + if (enumData.capture.deviceInfoCount > 0 && !hasDefaultCaptureDevice) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_capture, enumData.clockRate, callback, pUserData); + } + } + + (void)cbResult; /* Silence a static analysis warning. Want to keep this assignment in case we extend this logic later. */ + } + + + ma_enumerate_devices_data_pipewire_uninit(&enumData); + pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pRegistry); + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + + return MA_SUCCESS; +} + + +static void ma_stream_event_param_changed__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam, ma_device_type deviceType) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; + ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; + + if (id == MA_SPA_PARAM_Format) { + ma_pipewire_stream_state* pStreamState; + struct ma_spa_pod_buffer_params bufferParams; + const struct ma_spa_pod* pBufferParameters[1]; + ma_uint32 bytesPerFrame; + ma_uint32 iChannel; + ma_pipewire_audio_info audioInfo; + + /* It's possible for PipeWire to fire this callback with pParam set to NULL. I noticed it when tearing down a stream. Why does it do this? */ + if (pParam == NULL) { + return; + } + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + } + + if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) != 0) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "PipeWire format parameter changed after device has been initialized."); + return; + } + + /* + We can now determine the format/channels/rate which will let us configure the size of the buffer and set the + internal format of the device. To do this we need to parse the pParam pod. + */ + #if 0 + { + struct spa_audio_info_raw audioInfo; + spa_format_audio_raw_parse(pParam, &audioInfo); + } + #endif + { + ma_pipewire_audio_info_parse(pParam, pContextStatePipeWire->pLog, &audioInfo); + } + + /* Now that we definitely know the sample rate, we can reliably configure the size of the buffer. */ + if (pStreamState->bufferSizeInFrames == 0) { + pStreamState->bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pStreamState->pDescriptor, audioInfo.sampleRate); + } + + pStreamState->format = ma_format_from_pipewire(audioInfo.format); + pStreamState->channels = audioInfo.channels; + pStreamState->sampleRate = audioInfo.sampleRate; + + /* We should always get a channel map, but just to be safe we'll check for it, and if we don't get one back we'll use a default. */ + if (audioInfo.pChannelPositions != NULL) { + for (iChannel = 0; iChannel < audioInfo.channels; iChannel += 1) { + pStreamState->channelMap[iChannel] = ma_channel_from_pipewire(audioInfo.pChannelPositions[iChannel]); + } + } else { + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pStreamState->channelMap, ma_countof(pStreamState->channelMap), audioInfo.channels); + } + + + /* Now that we know both the buffer size and sample rate we can update the latency on the PipeWire side. */ + { + struct ma_spa_dict_item items[1]; + struct ma_spa_dict dict; + char bufferSizeInFramesStr[32]; + char sampleRateStr[32]; + char latencyStr[32]; + + ma_itoa_s(pStreamState->bufferSizeInFrames, bufferSizeInFramesStr, sizeof(bufferSizeInFramesStr), 10); + ma_itoa_s(pStreamState->sampleRate, sampleRateStr, sizeof(sampleRateStr), 10); + + latencyStr[0] = '\0'; + ma_strcat_s(latencyStr, sizeof(latencyStr), bufferSizeInFramesStr); + ma_strcat_s(latencyStr, sizeof(latencyStr), "/"); + ma_strcat_s(latencyStr, sizeof(latencyStr), sampleRateStr); + + items[0] = MA_SPA_DICT_ITEM_INIT(MA_PW_KEY_NODE_LATENCY, latencyStr); + + dict = MA_SPA_DICT_INIT(items, 1); + + pContextStatePipeWire->pw_stream_update_properties(pStreamState->pStream, &dict); + } + + + bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); + + /* + Now update the PipeWire buffer properties. Note that spa_pod_buffer_params_init() is not a standard SPA function. It's a + custom function to eliminate the dependency on libspa for building miniaudio. + */ + bufferParams = ma_spa_pod_buffer_params_init(bytesPerFrame, pStreamState->bufferSizeInFrames); + pBufferParameters[0] = (struct ma_spa_pod*)&bufferParams; + + pContextStatePipeWire->pw_stream_update_params(pStreamState->pStream, pBufferParameters, sizeof(pBufferParameters) / sizeof(pBufferParameters[0])); + + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT; + } +} + +static void ma_stream_event_process__pipewire(void* pUserData, ma_device_type deviceType) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; + ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; + ma_result result; + ma_pipewire_stream_state* pStreamState; + struct ma_pw_buffer* pBuffer; + ma_uint32 bytesPerFrame; + ma_uint32 frameCount; + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + } + + /* Debugging stuff. */ + #if 0 + { + double currentTimeInSeconds; + double deltaTimeInSeconds; + + currentTimeInSeconds = ma_timer_get_time_in_seconds(&pDeviceStatePipeWire->debugging.timer); + deltaTimeInSeconds = currentTimeInSeconds - pDeviceStatePipeWire->debugging.lastTimeInSeconds; + pDeviceStatePipeWire->debugging.lastTimeInSeconds = currentTimeInSeconds; + + printf("TRACE: Process Callback %.2f ms\n", deltaTimeInSeconds * 1000.0); + } + #endif + + /* + PipeWire has a bizarre buffer management system. Normally with an audio API you do processing after a certain amount of + time has elapsed, based on the sample rate and buffer size. The frequency at which the processing callback is fired + directly affects latency which is an important metric for audio applications and data management. From what I can tell, + the only way to determine the rate at which this processing callback is fired is from within the callback itself. There + are two ways I'm aware of: + + 1) Dequeue the first buffer and check the `requested` member of `pw_buffer`. + 2) Get the stream time using `pw_stream_get_time_n()` and inspect the `size` member of `pw_time`. + + In capture, the first option cannot be used because `requested` is always zero. That leaves only the second option which + appears to work for both playback and capture. However, the `size` member will only work for the first invocation of the + processing callback because it can change as you enqueue buffers. + + Advice welcome on how to improve this. + */ + if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { + struct ma_pw_time time; + + pContextStatePipeWire->pw_stream_get_time_n(pStreamState->pStream, &time, sizeof(time)); + + if (pStreamState->rbSizeInFrames > 0) { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_pcm_rb_uninit(&pStreamState->rb); + } + } + + pStreamState->rbSizeInFrames = (ma_uint32)time.size; + + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_pcm_rb_init(pStreamState->format, pStreamState->channels, pStreamState->rbSizeInFrames, NULL, ma_device_get_allocation_callbacks(pDeviceStatePipeWire->pDevice), &pStreamState->rb); + } + + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY; + return; + } + + + bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); + + pBuffer = pContextStatePipeWire->pw_stream_dequeue_buffer(pStreamState->pStream); + if (pBuffer == NULL) { + return; + } + + if (deviceType == ma_device_type_playback) { + frameCount = (ma_uint32)ma_min(pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame); + /*frameCount = (ma_uint32)(pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ + } else { + frameCount = (ma_uint32)(pBuffer->buffer->datas[0].chunk->size / bytesPerFrame); + } + + MA_ASSERT(pBuffer->buffer != NULL); + MA_ASSERT(pBuffer->buffer->n_datas > 0); + MA_ASSERT(pBuffer->buffer->datas[0].data != NULL); + + if (frameCount > 0) { + if (deviceType == ma_device_type_playback) { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { + ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, pBuffer->buffer->datas[0].data, NULL, frameCount); + } else { + ma_uint32 framesRemaining = frameCount; + ma_uint32 framesAvailable = ma_pcm_rb_available_read(&pStreamState->rb); + + /* Copy data in. Read from the ring buffer, output to the PipeWire buffer. */ + if (framesAvailable < frameCount) { + /* Underflow. Just write silence. */ + MA_ZERO_MEMORY((char*)pBuffer->buffer->datas[0].data + (frameCount - framesRemaining) * bytesPerFrame, framesRemaining * bytesPerFrame); + } else { + /* + Two ways of handling this. In multi-threaded mode, it doesn't really matter which thread does the data + processing so we can just do it directly from here and bypass the ring buffer entirely. + */ + + while (framesRemaining > 0) { + ma_uint32 framesToProcess = (ma_uint32)ma_min(framesRemaining, framesAvailable); + void* pMappedBuffer; + + result = ma_pcm_rb_acquire_read(&pStreamState->rb, &framesToProcess, &pMappedBuffer); + if (result != MA_SUCCESS) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to acquire data from ring buffer."); + break; + } + + MA_COPY_MEMORY((char*)pBuffer->buffer->datas[0].data + ((frameCount - framesRemaining) * bytesPerFrame), pMappedBuffer, framesToProcess * bytesPerFrame); + framesRemaining -= framesToProcess; + + result = ma_pcm_rb_commit_read(&pStreamState->rb, framesToProcess); + if (result != MA_SUCCESS) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to commit read to ring buffer."); + break; + } + } + } + } + + /*printf("(Playback) Processing %d frames... %d %d\n", (int)frameCount, (int)pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ + } else { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { + ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, NULL, pBuffer->buffer->datas[0].data, frameCount); + } else { + ma_uint32 framesRemaining = frameCount; + ma_uint32 framesAvailable = ma_pcm_rb_available_write(&pStreamState->rb); + + /* Copy data out. Write from the PipeWire buffer to the ring buffer. */ + while (framesRemaining > 0) { + ma_uint32 framesToProcess = (ma_uint32)ma_min(framesRemaining, framesAvailable); + void* pMappedBuffer; + + result = ma_pcm_rb_acquire_write(&pStreamState->rb, &framesToProcess, &pMappedBuffer); + if (result != MA_SUCCESS) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to acquire space in ring buffer."); + break; + } + + MA_COPY_MEMORY(pMappedBuffer, (char*)pBuffer->buffer->datas[0].data + ((frameCount - framesRemaining) * bytesPerFrame), framesToProcess * bytesPerFrame); + framesRemaining -= framesToProcess; + + result = ma_pcm_rb_commit_write(&pStreamState->rb, framesToProcess); + if (result != MA_SUCCESS) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "(PipeWire) Failed to commit write to ring buffer."); + break; + } + } + } + + /*printf("(Capture) Processing %d frames...\n", (int)frameCount);*/ + } + } else { + /*ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "(PipeWire) No frames to process.\n");*/ + } + + pBuffer->buffer->datas[0].chunk->offset = 0; + pBuffer->buffer->datas[0].chunk->size = frameCount * bytesPerFrame; + + pContextStatePipeWire->pw_stream_queue_buffer(pStreamState->pStream, pBuffer); + + /* We need to make sure the loop is woken up so we can refill the intermediary buffer in the step function. */ + pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); +} + + +static void ma_stream_event_param_changed_playback__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_playback); +} + +static void ma_stream_event_param_changed_capture__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_capture); +} + + +static void ma_stream_event_process_playback__pipewire(void* pUserData) +{ + ma_stream_event_process__pipewire(pUserData, ma_device_type_playback); +} + +static void ma_stream_event_process_capture__pipewire(void* pUserData) +{ + ma_stream_event_process__pipewire(pUserData, ma_device_type_capture); +} + + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Playback = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + NULL, /* state_changed */ + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_playback__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + ma_stream_event_process_playback__pipewire, + NULL, /* drained */ + NULL, /* command */ + NULL, /* trigger_done */ +}; + + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Capture = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + NULL, /* state_changed */ + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_capture__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + ma_stream_event_process_capture__pipewire, + NULL, /* drained */ + NULL, /* command */ + NULL, /* trigger_done */ +}; + +static ma_result ma_device_init_internal__pipewire(ma_device* pDevice, ma_context_state_pipewire* pContextStatePipeWire, ma_device_state_pipewire* pDeviceStatePipeWire, const ma_device_config_pipewire* pDeviceConfigPipeWire, ma_device_type deviceType, ma_device_descriptor* pDescriptor) +{ + ma_pipewire_stream_state* pStreamState; + struct ma_pw_properties* pProperties; + const struct ma_pw_stream_events* pStreamEvents; + 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; + + /* This function can only be called for playback or capture sides. */ + if (deviceType != ma_device_type_playback && deviceType != ma_device_type_capture) { + return MA_INVALID_ARGS; + } + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + pStreamEvents = &ma_gStreamEventsPipeWire_Playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + pStreamEvents = &ma_gStreamEventsPipeWire_Capture; + } + + /* Set up the buffer size first so the parameter negotiation callback knows how to configure the buffer on the PipeWire side. */ + pStreamState->pDescriptor = pDescriptor; + pStreamState->bufferSizeInFrames = pDescriptor->periodSizeInFrames; + + 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, (pDeviceConfigPipeWire->pMediaRole != NULL) ? pDeviceConfigPipeWire->pMediaRole : "Game", + /* MA_PW_KEY_NODE_LATENCY is set during format negotiation because it depends on knowledge of the sample rate. */ + NULL); + + if (pDescriptor->pDeviceID != NULL) { + pContextStatePipeWire->pw_properties_set(pProperties, MA_PW_KEY_TARGET_OBJECT, pDescriptor->pDeviceID->custom.s); + } + + pStreamState->pStream = pContextStatePipeWire->pw_stream_new(pDeviceStatePipeWire->pCore, (pDeviceConfigPipeWire->pStreamName != NULL) ? pDeviceConfigPipeWire->pStreamName : "miniaudio", pProperties); + if (pStreamState->pStream == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream."); + return MA_ERROR; + } + + /* This installs callbacks for process and param_changed. "process" is for queuing audio data, and "param_changed" is for getting the internal format/channels/rate. */ + pContextStatePipeWire->pw_stream_add_listener(pStreamState->pStream, &pStreamState->eventListener, pStreamEvents, pDeviceStatePipeWire); + + /* If the format is SPA_AUDIO_FORMAT_UNKNOWN, PipeWire can pick a planar data layout (de-interleaved) which breaks things for us. Just force interleaved F32 in this case. */ + formatPA = ma_format_to_pipewire(pDescriptor->format); + if (formatPA == MA_SPA_AUDIO_FORMAT_UNKNOWN) { + 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, pDescriptor->channels, pDescriptor->sampleRate); + pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; + + + /* + I'm just using MAP_BUFFERS because it's what the PipeWire examples do. I don't know what this does. Also, what's the + point in the AUTOCONNECT flag? Is that not what we're already doing by calling a function called "connect"?! + + We also can't use INACTIVE because without it, the param_changed callback will not get called, but we depend on that + so we can get access to the internal format/channels/rate. + */ + streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT | /*MA_PW_STREAM_FLAG_INACTIVE |*/ MA_PW_STREAM_FLAG_MAP_BUFFERS); + + connectResult = pContextStatePipeWire->pw_stream_connect(pStreamState->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) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream."); + pContextStatePipeWire->pw_stream_destroy(pStreamState->pStream); + return MA_ERROR; + } + + /* We need to keep iterating until we have finalized our internal format. */ + while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) == 0) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); + } + + /* We should have our format at this point, but we will not know the exact period size yet until we've done the first processing callback. */ + pDescriptor->format = pStreamState->format; + pDescriptor->channels = pStreamState->channels; + pDescriptor->sampleRate = pStreamState->sampleRate; + ma_channel_map_copy_or_default(pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pStreamState->channelMap, pStreamState->channels); + + /* Now we need to wait until we know our period size. */ + while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); + } + + pDescriptor->periodSizeInFrames = pStreamState->rbSizeInFrames; + pDescriptor->periodCount = 1; + + /* Devices are in a stopped state by default in miniaudio. */ + pContextStatePipeWire->pw_stream_set_active(pStreamState->pStream, MA_FALSE); + + pStreamState->pDescriptor = NULL; + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_INITIALIZED; + return MA_SUCCESS; +} + +static void ma_device_on_wakupe__pipewire(void* pUserData, ma_uint64 count) +{ + /* Nothing to do here. This is only used for waking up the loop. */ + (void)pUserData; + (void)count; +} + +static ma_result ma_device_init__pipewire(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_result result; + struct ma_pw_loop* pLoop; + struct ma_pw_context* pPipeWireContext; + struct ma_pw_core* pCore; + ma_context_state_pipewire* pContextStatePipeWire; + ma_device_state_pipewire* pDeviceStatePipeWire; + const ma_device_config_pipewire* pDeviceConfigPipeWire; + ma_device_config_pipewire defaultDeviceConfigPipeWire; + ma_device_type deviceType; + + pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + MA_ASSERT(pContextStatePipeWire != NULL); + + /* Grab the config. This can be null in which case we'll use a default. */ + pDeviceConfigPipeWire = (const ma_device_config_pipewire*)pDeviceBackendConfig; + if (pDeviceConfigPipeWire == NULL) { + defaultDeviceConfigPipeWire = ma_device_config_pipewire_init(); + pDeviceConfigPipeWire = &defaultDeviceConfigPipeWire; + } + + deviceType = ma_device_get_type(pDevice); + + /* Not sure how to do loopback with PipeWire, but it feels like something PipeWire would support. Look into this. */ + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + pLoop = pContextStatePipeWire->pw_loop_new(NULL); + if (pLoop == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop."); + return MA_ERROR; + } + + pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); + if (pPipeWireContext == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire context."); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); + if (pCore == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire context."); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + /* We can now allocate our per-device PipeWire-specific data. */ + pDeviceStatePipeWire = (ma_device_state_pipewire*)ma_calloc(sizeof(*pDeviceStatePipeWire), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStatePipeWire == NULL) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_OUT_OF_MEMORY; + } + + pDeviceStatePipeWire->pContextStatePipeWire = pContextStatePipeWire; + pDeviceStatePipeWire->deviceType = deviceType; + pDeviceStatePipeWire->pDevice = pDevice; + pDeviceStatePipeWire->pLoop = pLoop; + pDeviceStatePipeWire->pContext = pPipeWireContext; + pDeviceStatePipeWire->pCore = pCore; + + /* Enter the main loop before we start iterating. */ + pContextStatePipeWire->pw_loop_enter(pLoop); + + result = MA_DEVICE_TYPE_NOT_SUPPORTED; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_capture, pDescriptorCapture); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_playback, pDescriptorPlayback); + } + + if (result != MA_SUCCESS) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); + return result; + } + + /* We need an event for waking up the loop. */ + pDeviceStatePipeWire->pWakeup = pContextStatePipeWire->pw_loop_add_event(pLoop, (ma_spa_source_event_func_t)ma_device_on_wakupe__pipewire, pDeviceStatePipeWire); /* Cast should be fine here. It's complaining about the difference between ma_uint64 and uint64_t. */ + if (pDeviceStatePipeWire->pWakeup == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop wakeup event."); + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); + return MA_ERROR; + } + + *ppDeviceState = pDeviceStatePipeWire; + + return MA_SUCCESS; +} + +static void ma_device_uninit__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->capture.pStream); + pDeviceStatePipeWire->capture.pStream = NULL; + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->playback.pStream); + pDeviceStatePipeWire->playback.pStream = NULL; + } + + /* This will be called from the same thread that called ma_device_init__pipewire() and is therefore an appropriate place to leave the main loop. */ + pContextStatePipeWire->pw_loop_leave(pDeviceStatePipeWire->pLoop); + + pContextStatePipeWire->pw_core_disconnect(pDeviceStatePipeWire->pCore); + pContextStatePipeWire->pw_context_destroy(pDeviceStatePipeWire->pContext); + pContextStatePipeWire->pw_loop_destroy(pDeviceStatePipeWire->pLoop); + + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDeviceStatePipeWire->capture.rb); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_pcm_rb_uninit(&pDeviceStatePipeWire->playback.rb); + } + } + + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); +} + + +static ma_result ma_device_start__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + /* Prepare our buffers before starting the streams. To do this we just need to step. */ + ma_device_step__pipewire(pDevice, MA_BLOCKING_MODE_NON_BLOCKING); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_TRUE); + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_TRUE); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_FALSE); + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_FALSE); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + int timeout; + ma_bool32 hasProcessedData = MA_FALSE; + + if (blockingMode == MA_BLOCKING_MODE_BLOCKING) { + timeout = -1; + } else { + timeout = 0; + } + + /* We will keep looping until we've processed some data. This should keep our stepping in time with data processing. */ + for (;;) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, timeout); + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + /* We need only process data here in single-threaded mode. */ + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + /* We want to handle both playback and capture in a single iteration for duplex mode. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_uint32 framesAvailable; + + framesAvailable = ma_pcm_rb_available_read(&pDeviceStatePipeWire->capture.rb); + if (framesAvailable > 0) { + hasProcessedData = MA_TRUE; + } + + while (framesAvailable > 0) { + void* pMappedBuffer; + ma_uint32 framesToRead = framesAvailable; + ma_result result; + + result = ma_pcm_rb_acquire_read(&pDeviceStatePipeWire->capture.rb, &framesToRead, &pMappedBuffer); + if (result == MA_SUCCESS) { + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedBuffer, framesToRead); + + ma_pcm_rb_commit_read(&pDeviceStatePipeWire->capture.rb, framesToRead); + framesAvailable -= framesToRead; + } + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_uint32 framesAvailable; + + framesAvailable = ma_pcm_rb_available_write(&pDeviceStatePipeWire->playback.rb); + if (framesAvailable > 0) { + hasProcessedData = MA_TRUE; + } + + while (framesAvailable > 0) { + void* pMappedBuffer; + ma_uint32 framesToWrite = framesAvailable; + ma_result result; + + result = ma_pcm_rb_acquire_write(&pDeviceStatePipeWire->playback.rb, &framesToWrite, &pMappedBuffer); + if (result == MA_SUCCESS) { + ma_device_handle_backend_data_callback(pDevice, pMappedBuffer, NULL, framesToWrite); + + ma_pcm_rb_commit_write(&pDeviceStatePipeWire->playback.rb, framesToWrite); + framesAvailable -= framesToWrite; + } + } + } + } + + if (hasProcessedData || blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } + } + + return MA_SUCCESS; +} + +static void ma_device_wakeup__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); +} + + +static ma_device_backend_vtable ma_gDeviceBackendVTable_PipeWire = +{ + ma_backend_info__pipewire, + ma_context_init__pipewire, + ma_context_uninit__pipewire, + ma_context_enumerate_devices__pipewire, + ma_device_init__pipewire, + ma_device_uninit__pipewire, + ma_device_start__pipewire, + ma_device_stop__pipewire, + ma_device_step__pipewire, + ma_device_wakeup__pipewire +}; + +ma_device_backend_vtable* ma_device_backend_pipewire = &ma_gDeviceBackendVTable_PipeWire; + +#if defined(MA_NO_RUNTIME_LINKING) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif + +#else +ma_device_backend_vtable* ma_device_backend_pipewire = NULL; +#endif /* MA_HAS_PIPEWIRE */ + +MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void) +{ + return ma_device_backend_pipewire; +} + + /****************************************************************************** PulseAudio Backend @@ -44791,6 +47871,9 @@ static const void* ma_context_config_find_backend_config(const ma_context_config if (pVTable == ma_device_backend_coreaudio) { return &pConfig->coreaudio; } + if (pVTable == ma_device_backend_pipewire) { + return &pConfig->pipewire; + } if (pVTable == ma_device_backend_pulseaudio) { return &pConfig->pulseaudio; } @@ -44837,6 +47920,7 @@ MA_API ma_uint32 ma_get_stock_device_backends(ma_device_backend_config* pBackend if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_dsound, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_winmm, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_coreaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_pipewire, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_pulseaudio, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_jack, NULL); } if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_alsa, NULL); } @@ -45281,6 +48365,9 @@ static const void* ma_device_config_find_backend_config(const ma_device_config* if (pVTable == ma_device_backend_coreaudio) { return &pConfig->coreaudio; } + if (pVTable == ma_device_backend_pipewire) { + return &pConfig->pipewire; + } if (pVTable == ma_device_backend_pulseaudio) { return &pConfig->pulseaudio; } diff --git a/tests/deviceio/deviceio.c b/tests/deviceio/deviceio.c index e58feb96..638ec66f 100644 --- a/tests/deviceio/deviceio.c +++ b/tests/deviceio/deviceio.c @@ -52,10 +52,6 @@ are specified the last one on the command line will have priority. #include "../common/common.c" #include "../../extras/backends/sdl2/miniaudio_sdl2.c" -#if defined(MA_TESTS_INCLUDE_PIPEWIRE) -#include "../../extras/backends/pipewire/miniaudio_pipewire.h" -#endif - #ifndef AUTO_CLOSE_TIME_IN_MILLISECONDS #define AUTO_CLOSE_TIME_IN_MILLISECONDS 5000 #endif @@ -170,6 +166,10 @@ ma_bool32 try_parse_backend(const char* arg, ma_device_backend_config* pBackends pBackends[backendCount++] = ma_device_backend_config_init(ma_device_backend_oss, NULL); goto done; } + if (strcmp(arg, "pipewire") == 0) { + pBackends[backendCount++] = ma_device_backend_config_init(ma_device_backend_pipewire, NULL); + goto done; + } if (strcmp(arg, "pulseaudio") == 0 || strcmp(arg, "pulse") == 0) { pBackends[backendCount++] = ma_device_backend_config_init(ma_device_backend_pulseaudio, NULL); goto done; @@ -202,14 +202,6 @@ ma_bool32 try_parse_backend(const char* arg, ma_device_backend_config* pBackends pBackends[backendCount++] = ma_device_backend_config_init(ma_device_backend_sdl2, NULL); goto done; } - if (strcmp(arg, "pipewire") == 0) { - #if defined(MA_TESTS_INCLUDE_PIPEWIRE) - pBackends[backendCount++] = ma_device_backend_config_init(ma_device_backend_pipewire, NULL); - #else - printf("ERROR: Attempting to use PipeWire, but it was not compiled in. Compile with MA_TESTS_INCLUDE_PIPEWIRE."); - #endif - goto done; - } done: if (*pBackendCount < backendCount) { @@ -306,15 +298,6 @@ void print_enabled_backends(void) printf(" SDL2\n"); } - #if defined(MA_TESTS_INCLUDE_PIPEWIRE) - { - if (ma_device_backend_pipewire != NULL) { - printf(" PipeWire\n"); - } - } - #endif - - printf("\n"); }