From d6a56a4e5f8dcdb8cda4953ea6b325080d784975 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 30 Nov 2025 04:54:09 -0800 Subject: [PATCH 1/4] Attempt to dynamically load pulse aduio on linux --- webrtc-jni/src/main/cpp/CMakeLists.txt | 3 +- .../media/audio/linux/PulseAudioLoader.h | 177 ++++++++++++++++++ .../audio/linux/PulseAudioDeviceManager.cpp | 133 +++++++------ 3 files changed, 250 insertions(+), 63 deletions(-) create mode 100644 webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h diff --git a/webrtc-jni/src/main/cpp/CMakeLists.txt b/webrtc-jni/src/main/cpp/CMakeLists.txt index a761f4c0..82f72ae9 100644 --- a/webrtc-jni/src/main/cpp/CMakeLists.txt +++ b/webrtc-jni/src/main/cpp/CMakeLists.txt @@ -110,7 +110,8 @@ elseif(LINUX) set(CXX_LIBS "-static-libgcc") endif() - target_link_libraries(${PROJECT_NAME} ${CXX_LIBS} pulse udev) + target_link_libraries(${PROJECT_NAME} ${CXX_LIBS} udev) + target_link_libraries(${PROJECT_NAME} dl) elseif(WIN32) target_link_libraries(${PROJECT_NAME} dwmapi.lib mf.lib mfreadwrite.lib mfplat.lib mfuuid.lib shcore.lib) endif() diff --git a/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h new file mode 100644 index 00000000..aaeb4b00 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h @@ -0,0 +1,177 @@ +/* + * Copyright 2019 Alex Andres + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PULSE_AUDIO_LOADER_H +#define PULSE_AUDIO_LOADER_H + +#include + +#include + +#include + +#include "Exception.h" + +namespace jni +{ + namespace avdev + { + typedef pa_threaded_mainloop * ( * pa_threaded_mainloop_new_t)(); + typedef void( * pa_threaded_mainloop_free_t)(pa_threaded_mainloop * m); + typedef int( * pa_threaded_mainloop_start_t)(pa_threaded_mainloop * m); + typedef void( * pa_threaded_mainloop_stop_t)(pa_threaded_mainloop * m); + typedef pa_mainloop_api * ( * pa_threaded_mainloop_get_api_t)(pa_threaded_mainloop * m); + typedef void( * pa_threaded_mainloop_lock_t)(pa_threaded_mainloop * m); + typedef void( * pa_threaded_mainloop_unlock_t)(pa_threaded_mainloop * m); + typedef void( * pa_threaded_mainloop_wait_t)(pa_threaded_mainloop * m); + typedef void( * pa_threaded_mainloop_signal_t)(pa_threaded_mainloop * m, int wait_for_accept); + typedef int( * pa_threaded_mainloop_in_thread_t)(pa_threaded_mainloop * m); + + typedef pa_context * ( * pa_context_new_t)(pa_mainloop_api * mainloop, + const char * name); + typedef void( * pa_context_unref_t)(pa_context * c); + typedef void( * pa_context_set_state_callback_t)(pa_context * c, pa_context_notify_cb_t cb, void * userdata); + typedef void( * pa_context_set_subscribe_callback_t)(pa_context * c, pa_context_subscribe_cb_t cb, void * userdata); + typedef int( * pa_context_connect_t)(pa_context * c, + const char * server, pa_context_flags_t flags, + const pa_spawn_api * api); + typedef void( * pa_context_disconnect_t)(pa_context * c); + typedef pa_context_state_t( * pa_context_get_state_t)(const pa_context * c); + typedef pa_operation * ( * pa_context_subscribe_t)(pa_context * c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_server_info_t)(pa_context * c, pa_server_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_source_info_by_name_t)(pa_context * c, + const char * name, pa_source_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_source_info_list_t)(pa_context * c, pa_source_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_source_info_by_index_t)(pa_context * c, uint32_t idx, pa_source_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_sink_info_by_name_t)(pa_context * c, + const char * name, pa_sink_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_sink_info_list_t)(pa_context * c, pa_sink_info_cb_t cb, void * userdata); + typedef pa_operation * ( * pa_context_get_sink_info_by_index_t)(pa_context * c, uint32_t idx, pa_sink_info_cb_t cb, void * userdata); + + typedef void( * pa_operation_unref_t)(pa_operation * o); + typedef pa_operation_state_t( * pa_operation_get_state_t)(const pa_operation * o); + typedef + const char * ( * pa_proplist_gets_t)(const pa_proplist * p, + const char * key); + + class PulseAudioLoader { + public: static PulseAudioLoader & instance() { + static PulseAudioLoader instance; + return instance; + } + + bool load() { + if (loaded) return true; + + lib_handle = dlopen("libpulse.so.0", RTLD_NOW); + if (!lib_handle) return false; + + #define LOAD_SYM(name)\ + name = (name # #_t) dlsym(lib_handle, #name);\ + if (!name) { + close(); + return false; + } + + LOAD_SYM(pa_threaded_mainloop_new); + LOAD_SYM(pa_threaded_mainloop_free); + LOAD_SYM(pa_threaded_mainloop_start); + LOAD_SYM(pa_threaded_mainloop_stop); + LOAD_SYM(pa_threaded_mainloop_get_api); + LOAD_SYM(pa_threaded_mainloop_lock); + LOAD_SYM(pa_threaded_mainloop_unlock); + LOAD_SYM(pa_threaded_mainloop_wait); + LOAD_SYM(pa_threaded_mainloop_signal); + LOAD_SYM(pa_threaded_mainloop_in_thread); + + LOAD_SYM(pa_context_new); + LOAD_SYM(pa_context_unref); + LOAD_SYM(pa_context_set_state_callback); + LOAD_SYM(pa_context_set_subscribe_callback); + LOAD_SYM(pa_context_connect); + LOAD_SYM(pa_context_disconnect); + LOAD_SYM(pa_context_get_state); + LOAD_SYM(pa_context_subscribe); + + LOAD_SYM(pa_context_get_server_info); + LOAD_SYM(pa_context_get_source_info_by_name); + LOAD_SYM(pa_context_get_source_info_list); + LOAD_SYM(pa_context_get_source_info_by_index); + LOAD_SYM(pa_context_get_sink_info_by_name); + LOAD_SYM(pa_context_get_sink_info_list); + LOAD_SYM(pa_context_get_sink_info_by_index); + + LOAD_SYM(pa_operation_unref); + LOAD_SYM(pa_operation_get_state); + LOAD_SYM(pa_proplist_gets); + + loaded = true; + return true; + } + + void close() { + if (lib_handle) { + dlclose(lib_handle); + lib_handle = nullptr; + } + loaded = false; + } + + bool isLoaded() const { + return loaded; + } + + pa_threaded_mainloop_new_t pa_threaded_mainloop_new; + pa_threaded_mainloop_free_t pa_threaded_mainloop_free; + pa_threaded_mainloop_start_t pa_threaded_mainloop_start; + pa_threaded_mainloop_stop_t pa_threaded_mainloop_stop; + pa_threaded_mainloop_get_api_t pa_threaded_mainloop_get_api; + pa_threaded_mainloop_lock_t pa_threaded_mainloop_lock; + pa_threaded_mainloop_unlock_t pa_threaded_mainloop_unlock; + pa_threaded_mainloop_wait_t pa_threaded_mainloop_wait; + pa_threaded_mainloop_signal_t pa_threaded_mainloop_signal; + pa_threaded_mainloop_in_thread_t pa_threaded_mainloop_in_thread; + + pa_context_new_t pa_context_new; + pa_context_unref_t pa_context_unref; + pa_context_set_state_callback_t pa_context_set_state_callback; + pa_context_set_subscribe_callback_t pa_context_set_subscribe_callback; + pa_context_connect_t pa_context_connect; + pa_context_disconnect_t pa_context_disconnect; + pa_context_get_state_t pa_context_get_state; + pa_context_subscribe_t pa_context_subscribe; + + pa_context_get_server_info_t pa_context_get_server_info; + pa_context_get_source_info_by_name_t pa_context_get_source_info_by_name; + pa_context_get_source_info_list_t pa_context_get_source_info_list; + pa_context_get_source_info_by_index_t pa_context_get_source_info_by_index; + pa_context_get_sink_info_by_name_t pa_context_get_sink_info_by_name; + pa_context_get_sink_info_list_t pa_context_get_sink_info_list; + pa_context_get_sink_info_by_index_t pa_context_get_sink_info_by_index; + + pa_operation_unref_t pa_operation_unref; + pa_operation_get_state_t pa_operation_get_state; + pa_proplist_gets_t pa_proplist_gets; + + private: PulseAudioLoader(): loaded(false), + lib_handle(nullptr) {} + bool loaded; + void * lib_handle; + }; + } // namespace avdev +} // namespace jni + +#endif \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp b/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp index dd1db433..de8efa77 100644 --- a/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp +++ b/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp @@ -15,6 +15,7 @@ */ #include "media/audio/linux/PulseAudioDeviceManager.h" +#include "media/audio/linux/PulseAudioLoader.h" #include "Exception.h" namespace jni @@ -23,41 +24,49 @@ namespace jni { PulseAudioDeviceManager::PulseAudioDeviceManager() { - mainloop = pa_threaded_mainloop_new(); + // Try to load PulseAudio symbols dynamically + if (!PulseAudioLoader::instance().load()) { + throw Exception("PulseAudio: Library not found on system."); + } + + // Use the loader instance for function calls + auto& pa = PulseAudioLoader::instance(); + + mainloop = pa.pa_threaded_mainloop_new(); if (mainloop == 0) { throw Exception("PulseAudio: Could not create threaded mainloop"); } - if (pa_threaded_mainloop_start(mainloop) != 0) { - pa_threaded_mainloop_free(mainloop); + if (pa.pa_threaded_mainloop_start(mainloop) != 0) { + pa.pa_threaded_mainloop_free(mainloop); throw Exception("PulseAudio: Could not start threaded mainloop"); } - pa_mainloop_api * mainloopApi = pa_threaded_mainloop_get_api(mainloop); - context = pa_context_new(mainloopApi, "MediaDevices"); + pa_mainloop_api * mainloopApi = pa.pa_threaded_mainloop_get_api(mainloop); + context = pa.pa_context_new(mainloopApi, "MediaDevices"); if (!context) { - pa_threaded_mainloop_free(mainloop); + pa.pa_threaded_mainloop_free(mainloop); throw Exception("PulseAudio: Could not create context"); } - pa_context_set_state_callback(context, stateCallback, mainloop); - pa_context_set_subscribe_callback(context, subscribeCallback, this); + pa.pa_context_set_state_callback(context, stateCallback, mainloop); + pa.pa_context_set_subscribe_callback(context, subscribeCallback, this); - pa_threaded_mainloop_lock(mainloop); + pa.pa_threaded_mainloop_lock(mainloop); - if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) { - pa_context_unref(context); - pa_threaded_mainloop_free(mainloop); + if (pa.pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) { + pa.pa_context_unref(context); + pa.pa_threaded_mainloop_free(mainloop); throw Exception("PulseAudio: Could not connect to the context"); } while (true) { - pa_context_state_t contextState = pa_context_get_state(context); + pa_context_state_t contextState = pa.pa_context_get_state(context); if (contextState == PA_CONTEXT_FAILED || contextState == PA_CONTEXT_TERMINATED) { dispose(); @@ -67,20 +76,20 @@ namespace jni break; } - pa_threaded_mainloop_wait(mainloop); + pa.pa_threaded_mainloop_wait(mainloop); } int mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; pa_subscription_mask_t mask_t = static_cast(mask); - pa_operation * op = pa_context_subscribe(context, mask_t, nullptr, nullptr); + pa_operation * op = pa.pa_context_subscribe(context, mask_t, nullptr, nullptr); - pa_threaded_mainloop_unlock(mainloop); + pa.pa_threaded_mainloop_unlock(mainloop); if (!op) { throw Exception("PulseAudio: Failed to subscribe context"); } - pa_operation_unref(op); + pa.pa_operation_unref(op); } PulseAudioDeviceManager::~PulseAudioDeviceManager() @@ -94,17 +103,17 @@ namespace jni return; } - pa_threaded_mainloop_lock(mainloop); + pa.pa_threaded_mainloop_lock(mainloop); if (context) { - pa_context_set_state_callback(context, nullptr, nullptr); - pa_context_disconnect(context); - pa_context_unref(context); + pa.pa_context_set_state_callback(context, nullptr, nullptr); + pa.pa_context_disconnect(context); + pa.pa_context_unref(context); context = nullptr; } - pa_threaded_mainloop_stop(mainloop); - pa_threaded_mainloop_free(mainloop); + pa.pa_threaded_mainloop_stop(mainloop); + pa.pa_threaded_mainloop_free(mainloop); mainloop = nullptr; } @@ -114,30 +123,30 @@ namespace jni throw Exception("PulseAudio: No operation to process"); } - while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(main_loop); + while (pa.pa_operation_get_state(op) == PA_OPERATION_RUNNING) { + pa.pa_threaded_mainloop_wait(main_loop); } - pa_operation_unref(op); + pa.pa_operation_unref(op); } AudioDevicePtr PulseAudioDeviceManager::getDefaultAudioCaptureDevice() { - if (!pa_threaded_mainloop_in_thread(mainloop)) - pa_threaded_mainloop_lock(mainloop); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) + pa.pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_server_info(context, serverInfoCallback, this); + pa_operation * op = pa.pa_context_get_server_info(context, serverInfoCallback, this); - if (!pa_threaded_mainloop_in_thread(mainloop)) + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) iterate(mainloop, op); - op = pa_context_get_source_info_by_name(context, defaultCaptureName.c_str(), getSourceInfoCallback, this); + op = pa.pa_context_get_source_info_by_name(context, defaultCaptureName.c_str(), getSourceInfoCallback, this); - if (!pa_threaded_mainloop_in_thread(mainloop)) + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) iterate(mainloop, op); - if (!pa_threaded_mainloop_in_thread(mainloop)) - pa_threaded_mainloop_unlock(mainloop); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) + pa.pa_threaded_mainloop_unlock(mainloop); AudioDevicePtr defaultDevice = std::make_shared(defaultCaptureDescName, defaultCaptureName); defaultDevice->directionType = AudioDeviceDirectionType::adtCapture; @@ -150,31 +159,31 @@ namespace jni return captureDevices.devices(); } - pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_source_info_list(context, getSourceCallback, this); + pa.pa_threaded_mainloop_lock(mainloop); + pa_operation * op = pa.pa_context_get_source_info_list(context, getSourceCallback, this); iterate(mainloop, op); - pa_threaded_mainloop_unlock(mainloop); + pa.pa_threaded_mainloop_unlock(mainloop); return captureDevices.devices(); } AudioDevicePtr PulseAudioDeviceManager::getDefaultAudioPlaybackDevice() { - if (!pa_threaded_mainloop_in_thread(mainloop)) - pa_threaded_mainloop_lock(mainloop); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) + pa.pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_server_info(context, serverInfoCallback, this); + pa_operation * op = pa.pa_context_get_server_info(context, serverInfoCallback, this); - if (!pa_threaded_mainloop_in_thread(mainloop)) + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) iterate(mainloop, op); - op = pa_context_get_sink_info_by_name(context, defaultPlaybackName.c_str(), getSinkInfoCallback, this); + op = pa.pa_context_get_sink_info_by_name(context, defaultPlaybackName.c_str(), getSinkInfoCallback, this); - if (!pa_threaded_mainloop_in_thread(mainloop)) + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) iterate(mainloop, op); - if (!pa_threaded_mainloop_in_thread(mainloop)) - pa_threaded_mainloop_unlock(mainloop); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) + pa.pa_threaded_mainloop_unlock(mainloop); AudioDevicePtr defaultDevice = std::make_shared(defaultPlaybackDescName, defaultPlaybackName); defaultDevice->directionType = AudioDeviceDirectionType::adtRender; @@ -187,10 +196,10 @@ namespace jni return playbackDevices.devices(); } - pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_sink_info_list(context, getSinkCallback, this); + pa.pa_threaded_mainloop_lock(mainloop); + pa_operation * op = pa.pa_context_get_sink_info_list(context, getSinkCallback, this); iterate(mainloop, op); - pa_threaded_mainloop_unlock(mainloop); + pa.pa_threaded_mainloop_unlock(mainloop); return playbackDevices.devices(); } @@ -200,7 +209,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last > 0) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -212,7 +221,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -226,7 +235,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -238,7 +247,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last > 0) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -250,7 +259,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -262,7 +271,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -309,7 +318,7 @@ namespace jni void PulseAudioDeviceManager::stateCallback(pa_context * ctx, void * userdata) { pa_threaded_mainloop * mainloop = static_cast(userdata); - pa_threaded_mainloop_signal(mainloop, 0); + pa.pa_threaded_mainloop_signal(mainloop, 0); } void PulseAudioDeviceManager::serverInfoCallback(pa_context * ctx, const pa_server_info * info, void * userdata) @@ -318,7 +327,7 @@ namespace jni engine->defaultCaptureName = info->default_source_name; engine->defaultPlaybackName = info->default_sink_name; - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); } void PulseAudioDeviceManager::subscribeCallback(pa_context * ctx, pa_subscription_event_type_t type, uint32_t idx, void * userdata) @@ -330,7 +339,7 @@ namespace jni if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { if (operation == PA_SUBSCRIPTION_EVENT_NEW) { - op = pa_context_get_source_info_by_index(ctx, idx, newSourceCallback, engine); + op = pa.pa_context_get_source_info_by_index(ctx, idx, newSourceCallback, engine); } if (operation == PA_SUBSCRIPTION_EVENT_REMOVE) { engine->removeDevice(engine->captureDevices, idx, true); @@ -338,7 +347,7 @@ namespace jni } if (facility == PA_SUBSCRIPTION_EVENT_SINK) { if (operation == PA_SUBSCRIPTION_EVENT_NEW) { - op = pa_context_get_sink_info_by_index(ctx, idx, newSinkCallback, engine); + op = pa.pa_context_get_sink_info_by_index(ctx, idx, newSinkCallback, engine); } if (operation == PA_SUBSCRIPTION_EVENT_REMOVE) { engine->removeDevice(engine->playbackDevices, idx, false); @@ -346,28 +355,28 @@ namespace jni } if (op) { - pa_operation_unref(op); + pa.pa_operation_unref(op); } } void PulseAudioDeviceManager::fillAdditionalTypes(AudioDevicePtr device, pa_proplist * proplist) { // all property values see here https://docs.rs/libpulse-sys/latest/libpulse_sys/proplist/ const char *formFactor; - formFactor = pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR); + formFactor = pa.pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR); std::string formFactorStr = ""; if (formFactor) { formFactorStr = std::string(formFactor); } const char *deviceTransport; - deviceTransport = pa_proplist_gets(proplist, PA_PROP_DEVICE_BUS); + deviceTransport = pa.pa_proplist_gets(proplist, PA_PROP_DEVICE_BUS); std::string deviceTransportStr = ""; if (deviceTransport) { deviceTransportStr = std::string(deviceTransport); } const char *propHDMI; - propHDMI = pa_proplist_gets(proplist, PA_PROP_DEVICE_BUS_PATH); + propHDMI = pa.pa_proplist_gets(proplist, PA_PROP_DEVICE_BUS_PATH); if (propHDMI && std::string(propHDMI).find("_hdmi") != std::string::npos) { deviceTransportStr = "hdmi"; } From 05e9579c2cb02c877e2021c7c5d15e76c78dce7c Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:25:18 -0800 Subject: [PATCH 2/4] Fix LOAD_SYM macro and skip workflow publish on forks --- .github/actions/build-macos-x86_64/action.yml | 14 ++- .github/actions/build/action.yml | 14 ++- .../media/audio/linux/PulseAudioLoader.h | 98 +++++++++---------- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/.github/actions/build-macos-x86_64/action.yml b/.github/actions/build-macos-x86_64/action.yml index 70647c55..7d164333 100644 --- a/.github/actions/build-macos-x86_64/action.yml +++ b/.github/actions/build-macos-x86_64/action.yml @@ -55,8 +55,20 @@ runs: run: arch -x86_64 mvn -B jar:jar surefire:test -Pmacos-cross-x86_64 shell: bash + - name: Archive webrtc-java + uses: actions/upload-artifact@v5 + with: + name: webrtc-java + path: webrtc/target/*.jar + + - name: Archive webrtc-java-jni + uses: actions/upload-artifact@v5 + with: + name: webrtc-java-${{ inputs.platform-name }} + path: webrtc-jni/target/*.jar + - name: Deploy - if: ${{ github.event_name != 'pull_request' }} + if: ${{ github.event_name != 'pull_request' && github.repository == 'devopvoid/webrtc-java' }} env: MAVEN_USERNAME: ${{ inputs.maven-username }} MAVEN_TOKEN: ${{ inputs.maven-password }} diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index ac9969b1..9ffcc63c 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -62,8 +62,20 @@ runs: run: mvn -B jar:jar surefire:test shell: bash + - name: Archive webrtc-java + uses: actions/upload-artifact@v5 + with: + name: webrtc-java + path: webrtc/target/*.jar + + - name: Archive webrtc-java-jni + uses: actions/upload-artifact@v5 + with: + name: webrtc-java-${{ inputs.platform-name }} + path: webrtc-jni/target/*.jar + - name: Deploy - if: ${{ github.event_name != 'pull_request' }} + if: ${{ github.event_name != 'pull_request' && github.repository == 'devopvoid/webrtc-java' }} env: MAVEN_USERNAME: ${{ inputs.maven-username }} MAVEN_TOKEN: ${{ inputs.maven-password }} diff --git a/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h index aaeb4b00..1ec01f49 100644 --- a/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h +++ b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h @@ -29,62 +29,54 @@ namespace jni { namespace avdev { - typedef pa_threaded_mainloop * ( * pa_threaded_mainloop_new_t)(); - typedef void( * pa_threaded_mainloop_free_t)(pa_threaded_mainloop * m); - typedef int( * pa_threaded_mainloop_start_t)(pa_threaded_mainloop * m); - typedef void( * pa_threaded_mainloop_stop_t)(pa_threaded_mainloop * m); - typedef pa_mainloop_api * ( * pa_threaded_mainloop_get_api_t)(pa_threaded_mainloop * m); - typedef void( * pa_threaded_mainloop_lock_t)(pa_threaded_mainloop * m); - typedef void( * pa_threaded_mainloop_unlock_t)(pa_threaded_mainloop * m); - typedef void( * pa_threaded_mainloop_wait_t)(pa_threaded_mainloop * m); - typedef void( * pa_threaded_mainloop_signal_t)(pa_threaded_mainloop * m, int wait_for_accept); - typedef int( * pa_threaded_mainloop_in_thread_t)(pa_threaded_mainloop * m); - - typedef pa_context * ( * pa_context_new_t)(pa_mainloop_api * mainloop, - const char * name); - typedef void( * pa_context_unref_t)(pa_context * c); - typedef void( * pa_context_set_state_callback_t)(pa_context * c, pa_context_notify_cb_t cb, void * userdata); - typedef void( * pa_context_set_subscribe_callback_t)(pa_context * c, pa_context_subscribe_cb_t cb, void * userdata); - typedef int( * pa_context_connect_t)(pa_context * c, - const char * server, pa_context_flags_t flags, - const pa_spawn_api * api); - typedef void( * pa_context_disconnect_t)(pa_context * c); - typedef pa_context_state_t( * pa_context_get_state_t)(const pa_context * c); - typedef pa_operation * ( * pa_context_subscribe_t)(pa_context * c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_server_info_t)(pa_context * c, pa_server_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_source_info_by_name_t)(pa_context * c, - const char * name, pa_source_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_source_info_list_t)(pa_context * c, pa_source_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_source_info_by_index_t)(pa_context * c, uint32_t idx, pa_source_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_sink_info_by_name_t)(pa_context * c, - const char * name, pa_sink_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_sink_info_list_t)(pa_context * c, pa_sink_info_cb_t cb, void * userdata); - typedef pa_operation * ( * pa_context_get_sink_info_by_index_t)(pa_context * c, uint32_t idx, pa_sink_info_cb_t cb, void * userdata); - - typedef void( * pa_operation_unref_t)(pa_operation * o); - typedef pa_operation_state_t( * pa_operation_get_state_t)(const pa_operation * o); - typedef - const char * ( * pa_proplist_gets_t)(const pa_proplist * p, - const char * key); + typedef pa_threaded_mainloop* (*pa_threaded_mainloop_new_t)(); + typedef void (*pa_threaded_mainloop_free_t)(pa_threaded_mainloop* m); + typedef int (*pa_threaded_mainloop_start_t)(pa_threaded_mainloop* m); + typedef void (*pa_threaded_mainloop_stop_t)(pa_threaded_mainloop* m); + typedef pa_mainloop_api* (*pa_threaded_mainloop_get_api_t)(pa_threaded_mainloop* m); + typedef void (*pa_threaded_mainloop_lock_t)(pa_threaded_mainloop* m); + typedef void (*pa_threaded_mainloop_unlock_t)(pa_threaded_mainloop* m); + typedef void (*pa_threaded_mainloop_wait_t)(pa_threaded_mainloop* m); + typedef void (*pa_threaded_mainloop_signal_t)(pa_threaded_mainloop* m, int wait_for_accept); + typedef int (*pa_threaded_mainloop_in_thread_t)(pa_threaded_mainloop* m); + + typedef pa_context* (*pa_context_new_t)(pa_mainloop_api* mainloop, const char* name); + typedef void (*pa_context_unref_t)(pa_context* c); + typedef void (*pa_context_set_state_callback_t)(pa_context* c, pa_context_notify_cb_t cb, void* userdata); + typedef void (*pa_context_set_subscribe_callback_t)(pa_context* c, pa_context_subscribe_cb_t cb, void* userdata); + typedef int (*pa_context_connect_t)(pa_context* c, const char* server, pa_context_flags_t flags, const pa_spawn_api* api); + typedef void (*pa_context_disconnect_t)(pa_context* c); + typedef pa_context_state_t (*pa_context_get_state_t)(const pa_context* c); + typedef pa_operation* (*pa_context_subscribe_t)(pa_context* c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_server_info_t)(pa_context* c, pa_server_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_source_info_by_name_t)(pa_context* c, const char* name, pa_source_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_source_info_list_t)(pa_context* c, pa_source_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_source_info_by_index_t)(pa_context* c, uint32_t idx, pa_source_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_sink_info_by_name_t)(pa_context* c, const char* name, pa_sink_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_sink_info_list_t)(pa_context* c, pa_sink_info_cb_t cb, void* userdata); + typedef pa_operation* (*pa_context_get_sink_info_by_index_t)(pa_context* c, uint32_t idx, pa_sink_info_cb_t cb, void* userdata); + + typedef void (*pa_operation_unref_t)(pa_operation* o); + typedef pa_operation_state_t (*pa_operation_get_state_t)(const pa_operation* o); + typedef const char* (*pa_proplist_gets_t)(const pa_proplist* p, const char* key); class PulseAudioLoader { - public: static PulseAudioLoader & instance() { + public: + static PulseAudioLoader& instance() { static PulseAudioLoader instance; return instance; } bool load() { if (loaded) return true; - + + // Try to open the library lib_handle = dlopen("libpulse.so.0", RTLD_NOW); if (!lib_handle) return false; - #define LOAD_SYM(name)\ - name = (name # #_t) dlsym(lib_handle, #name);\ - if (!name) { - close(); - return false; - } + #define LOAD_SYM(name) \ + name = (name##_t)dlsym(lib_handle, #name); \ + if (!name) { close(); return false; } LOAD_SYM(pa_threaded_mainloop_new); LOAD_SYM(pa_threaded_mainloop_free); @@ -96,7 +88,7 @@ namespace jni LOAD_SYM(pa_threaded_mainloop_wait); LOAD_SYM(pa_threaded_mainloop_signal); LOAD_SYM(pa_threaded_mainloop_in_thread); - + LOAD_SYM(pa_context_new); LOAD_SYM(pa_context_unref); LOAD_SYM(pa_context_set_state_callback); @@ -105,7 +97,7 @@ namespace jni LOAD_SYM(pa_context_disconnect); LOAD_SYM(pa_context_get_state); LOAD_SYM(pa_context_subscribe); - + LOAD_SYM(pa_context_get_server_info); LOAD_SYM(pa_context_get_source_info_by_name); LOAD_SYM(pa_context_get_source_info_list); @@ -113,7 +105,7 @@ namespace jni LOAD_SYM(pa_context_get_sink_info_by_name); LOAD_SYM(pa_context_get_sink_info_list); LOAD_SYM(pa_context_get_sink_info_by_index); - + LOAD_SYM(pa_operation_unref); LOAD_SYM(pa_operation_get_state); LOAD_SYM(pa_proplist_gets); @@ -130,9 +122,7 @@ namespace jni loaded = false; } - bool isLoaded() const { - return loaded; - } + bool isLoaded() const { return loaded; } pa_threaded_mainloop_new_t pa_threaded_mainloop_new; pa_threaded_mainloop_free_t pa_threaded_mainloop_free; @@ -166,10 +156,10 @@ namespace jni pa_operation_get_state_t pa_operation_get_state; pa_proplist_gets_t pa_proplist_gets; - private: PulseAudioLoader(): loaded(false), - lib_handle(nullptr) {} + private: + PulseAudioLoader() : loaded(false), lib_handle(nullptr) {} bool loaded; - void * lib_handle; + void* lib_handle; }; } // namespace avdev } // namespace jni From ce0733f666387f887448e775a7c692a85e2e8bb2 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 30 Nov 2025 19:05:33 -0800 Subject: [PATCH 3/4] Fix missing PulseAudioLoader refs --- .../audio/linux/PulseAudioDeviceManager.cpp | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp b/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp index de8efa77..bcdfd8c8 100644 --- a/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp +++ b/webrtc-jni/src/main/cpp/src/media/audio/linux/PulseAudioDeviceManager.cpp @@ -99,6 +99,8 @@ namespace jni void PulseAudioDeviceManager::dispose() { + auto& pa = PulseAudioLoader::instance(); + if (!mainloop) { return; } @@ -123,6 +125,8 @@ namespace jni throw Exception("PulseAudio: No operation to process"); } + auto& pa = PulseAudioLoader::instance(); + while (pa.pa_operation_get_state(op) == PA_OPERATION_RUNNING) { pa.pa_threaded_mainloop_wait(main_loop); } @@ -132,6 +136,8 @@ namespace jni AudioDevicePtr PulseAudioDeviceManager::getDefaultAudioCaptureDevice() { + auto& pa = PulseAudioLoader::instance(); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) pa.pa_threaded_mainloop_lock(mainloop); @@ -159,6 +165,8 @@ namespace jni return captureDevices.devices(); } + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_lock(mainloop); pa_operation * op = pa.pa_context_get_source_info_list(context, getSourceCallback, this); iterate(mainloop, op); @@ -169,6 +177,8 @@ namespace jni AudioDevicePtr PulseAudioDeviceManager::getDefaultAudioPlaybackDevice() { + auto& pa = PulseAudioLoader::instance(); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) pa.pa_threaded_mainloop_lock(mainloop); @@ -196,6 +206,8 @@ namespace jni return playbackDevices.devices(); } + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_lock(mainloop); pa_operation * op = pa.pa_context_get_sink_info_list(context, getSinkCallback, this); iterate(mainloop, op); @@ -208,6 +220,8 @@ namespace jni { PulseAudioDeviceManager * engine = reinterpret_cast(userdata); + auto& pa = PulseAudioLoader::instance(); + if (last > 0) { pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; @@ -221,6 +235,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { + auto& pa = PulseAudioLoader::instance(); pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -235,6 +250,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { + auto& pa = PulseAudioLoader::instance(); pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -247,6 +263,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last > 0) { + auto& pa = PulseAudioLoader::instance(); pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -259,6 +276,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { + auto& pa = PulseAudioLoader::instance(); pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -271,6 +289,7 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { + auto& pa = PulseAudioLoader::instance(); pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -283,7 +302,7 @@ namespace jni auto device = std::make_shared(name, desc); if (devices.insertDevice(device)) { - if (isCapture) { + if (isCapture) { device->directionType = AudioDeviceDirectionType::adtCapture; } else { device->directionType = AudioDeviceDirectionType::adtRender; @@ -304,11 +323,11 @@ namespace jni } if (devices.removeDevice(it->second)) { - if (isCapture) { - it->second->directionType = AudioDeviceDirectionType::adtCapture; - } else { - it->second->directionType = AudioDeviceDirectionType::adtRender; - } + if (isCapture) { + it->second->directionType = AudioDeviceDirectionType::adtCapture; + } else { + it->second->directionType = AudioDeviceDirectionType::adtRender; + } notifyDeviceDisconnected(it->second); deviceMap.erase(it); @@ -317,12 +336,15 @@ namespace jni void PulseAudioDeviceManager::stateCallback(pa_context * ctx, void * userdata) { + auto& pa = PulseAudioLoader::instance(); pa_threaded_mainloop * mainloop = static_cast(userdata); pa.pa_threaded_mainloop_signal(mainloop, 0); } void PulseAudioDeviceManager::serverInfoCallback(pa_context * ctx, const pa_server_info * info, void * userdata) { + auto& pa = PulseAudioLoader::instance(); + PulseAudioDeviceManager * engine = reinterpret_cast(userdata); engine->defaultCaptureName = info->default_source_name; engine->defaultPlaybackName = info->default_sink_name; @@ -336,6 +358,7 @@ namespace jni unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; pa_operation * op = nullptr; + auto& pa = PulseAudioLoader::instance(); if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { if (operation == PA_SUBSCRIPTION_EVENT_NEW) { @@ -362,6 +385,7 @@ namespace jni void PulseAudioDeviceManager::fillAdditionalTypes(AudioDevicePtr device, pa_proplist * proplist) { // all property values see here https://docs.rs/libpulse-sys/latest/libpulse_sys/proplist/ const char *formFactor; + auto& pa = PulseAudioLoader::instance(); formFactor = pa.pa_proplist_gets(proplist, PA_PROP_DEVICE_FORM_FACTOR); std::string formFactorStr = ""; if (formFactor) { From 5c834ab856e1c405ad39732b1c0f7b1d930bd977 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Sun, 30 Nov 2025 20:05:53 -0800 Subject: [PATCH 4/4] Fix artifact name conflicts --- .github/actions/build-macos-x86_64/action.yml | 4 ++-- .github/actions/build/action.yml | 4 ++-- .../src/main/cpp/include/media/audio/linux/PulseAudioLoader.h | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/actions/build-macos-x86_64/action.yml b/.github/actions/build-macos-x86_64/action.yml index 7d164333..92c87333 100644 --- a/.github/actions/build-macos-x86_64/action.yml +++ b/.github/actions/build-macos-x86_64/action.yml @@ -58,13 +58,13 @@ runs: - name: Archive webrtc-java uses: actions/upload-artifact@v5 with: - name: webrtc-java + name: webrtc-java-${{ inputs.platform-name }} path: webrtc/target/*.jar - name: Archive webrtc-java-jni uses: actions/upload-artifact@v5 with: - name: webrtc-java-${{ inputs.platform-name }} + name: webrtc-java-jni-${{ inputs.platform-name }} path: webrtc-jni/target/*.jar - name: Deploy diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 9ffcc63c..c68ac2d6 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -65,13 +65,13 @@ runs: - name: Archive webrtc-java uses: actions/upload-artifact@v5 with: - name: webrtc-java + name: webrtc-java-${{ inputs.platform-name }} path: webrtc/target/*.jar - name: Archive webrtc-java-jni uses: actions/upload-artifact@v5 with: - name: webrtc-java-${{ inputs.platform-name }} + name: webrtc-java-jni-${{ inputs.platform-name }} path: webrtc-jni/target/*.jar - name: Deploy diff --git a/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h index 1ec01f49..4ac742a3 100644 --- a/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h +++ b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h @@ -18,11 +18,8 @@ #define PULSE_AUDIO_LOADER_H #include - #include - #include - #include "Exception.h" namespace jni