diff --git a/.github/actions/test-natives/action.yml b/.github/actions/test-natives/action.yml new file mode 100644 index 00000000..9fbd44e5 --- /dev/null +++ b/.github/actions/test-natives/action.yml @@ -0,0 +1,87 @@ +name: 'Test Natives' + +description: 'Run the test suite on a native runner against natives that a cross compiling lane built' + +inputs: + platform-name: + description: 'The platform name of the lane that built the natives.' + required: true + + classifier: + description: 'The Maven classifier of the natives.' + required: true + + profile: + description: 'The Maven profile that selects the classifier.' + required: true + + java-version: + description: 'The Java version.' + required: true + default: '17' + + java-distribution: + description: 'The setup-java distribution. It must provide a JDK for the runner architecture.' + required: true + default: 'temurin' + + java-architecture: + description: 'The JDK architecture, when it differs from the runner architecture.' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: Download natives + uses: actions/download-artifact@v4 + with: + name: natives-${{ inputs.platform-name }} + path: natives + + # PulseAudio is deliberately absent on the test runners: the natives open + # it at runtime, and the tests prove that they load without it. + - name: Install 32-bit runtime libraries + if: runner.os == 'Linux' && inputs.java-architecture == 'armv7' + run: | + sudo dpkg --add-architecture armhf + sudo apt-get update + sudo apt-get install -y libc6:armhf libstdc++6:armhf zlib1g:armhf libx11-6:armhf libxext6:armhf libxfixes3:armhf libxdamage1:armhf libxtst6:armhf libxrandr2:armhf libxcomposite1:armhf libglib2.0-0:armhf libgbm1:armhf libdrm2:armhf libdbus-1-3:armhf libudev1:armhf + shell: bash + + - name: Install runtime libraries + if: runner.os == 'Linux' && inputs.java-architecture != 'armv7' + run: | + sudo apt-get update + sudo apt-get install -y libx11-6 libxext6 libxfixes3 libxdamage1 libxtst6 libxrandr2 libxcomposite1 libglib2.0-0 libgbm1 libdrm2 libdbus-1-3 libudev1 + shell: bash + + - name: Set up JDK ${{ inputs.java-version }} + uses: actions/setup-java@v5 + with: + java-version: ${{ inputs.java-version }} + distribution: ${{ inputs.java-distribution }} + architecture: ${{ inputs.java-architecture }} + + - name: Ensure Maven + run: | + if ! command -v mvn >/dev/null 2>&1; then + curl -sSLo maven.zip https://archive.apache.org/dist/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.zip + unzip -q maven.zip -d "$RUNNER_TEMP/maven" + echo "$RUNNER_TEMP/maven/apache-maven-3.9.9/bin" >> "$GITHUB_PATH" + fi + shell: bash + + - name: Install the natives into the local repository + run: | + VERSION=$(sed -n '0,//s|.*\([^<]*\).*|\1|p' pom.xml) + JAR=$(ls natives/webrtc-java-*.jar | head -n 1) + echo "Installing $JAR as webrtc-java:$VERSION:${{ inputs.classifier }}" + mvn -B -q install:install-file -Dfile="$JAR" -DgroupId=dev.onvoid.webrtc \ + -DartifactId=webrtc-java -Dversion="$VERSION" -Dpackaging=jar \ + -Dclassifier=${{ inputs.classifier }} + shell: bash + + - name: Test + run: mvn -B -pl webrtc test -P${{ inputs.profile }} + shell: bash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03a2c2b0..89180ced 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,6 +76,14 @@ jobs: maven-username: ${{ secrets.MAVEN_USERNAME }} maven-password: ${{ secrets.MAVEN_TOKEN }} + - name: Upload natives for native testing + if: matrix.platform.name == 'linux_arm' || matrix.platform.name == 'linux_arm64' + uses: actions/upload-artifact@v4 + with: + name: natives-${{ matrix.platform.name }} + path: webrtc-jni/target/webrtc-java-*.jar + if-no-files-found: error + build-macos: strategy: fail-fast: false @@ -114,3 +122,35 @@ jobs: platform-name: ${{ matrix.platform.name }} maven-username: ${{ secrets.MAVEN_USERNAME }} maven-password: ${{ secrets.MAVEN_TOKEN }} + + test-natives: + needs: build-linux + strategy: + fail-fast: false + matrix: + platform: + - name: linux_arm64 + runs-on: ubuntu-22.04-arm + classifier: linux-aarch64 + profile: linux-aarch64 + java-distribution: temurin + java-architecture: '' + - name: linux_arm + runs-on: ubuntu-22.04-arm + classifier: linux-aarch32 + profile: linux-aarch32 + java-distribution: liberica + java-architecture: armv7 + runs-on: ${{ matrix.platform.runs-on }} + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Test natives + uses: ./.github/actions/test-natives + with: + platform-name: ${{ matrix.platform.name }} + classifier: ${{ matrix.platform.classifier }} + profile: ${{ matrix.platform.profile }} + java-distribution: ${{ matrix.platform.java-distribution }} + java-architecture: ${{ matrix.platform.java-architecture }} diff --git a/webrtc-jni/src/main/cpp/CMakeLists.txt b/webrtc-jni/src/main/cpp/CMakeLists.txt index 5051ace4..05d4aa12 100644 --- a/webrtc-jni/src/main/cpp/CMakeLists.txt +++ b/webrtc-jni/src/main/cpp/CMakeLists.txt @@ -106,7 +106,8 @@ if(APPLE) elseif(LINUX) set(CXX_LIBS "-static-libgcc -stdlib=libc++ -lc++ -lc++abi") - 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..1ec01f49 --- /dev/null +++ b/webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h @@ -0,0 +1,167 @@ +/* + * 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; + + // 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; } + + 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..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 @@ -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() @@ -90,21 +99,23 @@ namespace jni void PulseAudioDeviceManager::dispose() { + auto& pa = PulseAudioLoader::instance(); + if (!mainloop) { 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 +125,34 @@ namespace jni throw Exception("PulseAudio: No operation to process"); } - while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(main_loop); + auto& pa = PulseAudioLoader::instance(); + + 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); + auto& pa = PulseAudioLoader::instance(); + + 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 +165,35 @@ namespace jni return captureDevices.devices(); } - pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_source_info_list(context, getSourceCallback, this); + 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); - 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); + auto& pa = PulseAudioLoader::instance(); - pa_operation * op = pa_context_get_server_info(context, serverInfoCallback, this); + if (!pa.pa_threaded_mainloop_in_thread(mainloop)) + pa.pa_threaded_mainloop_lock(mainloop); - if (!pa_threaded_mainloop_in_thread(mainloop)) + pa_operation * op = pa.pa_context_get_server_info(context, serverInfoCallback, this); + + 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 +206,12 @@ namespace jni return playbackDevices.devices(); } - pa_threaded_mainloop_lock(mainloop); - pa_operation * op = pa_context_get_sink_info_list(context, getSinkCallback, this); + 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); - pa_threaded_mainloop_unlock(mainloop); + pa.pa_threaded_mainloop_unlock(mainloop); return playbackDevices.devices(); } @@ -199,8 +220,10 @@ namespace jni { PulseAudioDeviceManager * engine = reinterpret_cast(userdata); + auto& pa = PulseAudioLoader::instance(); + if (last > 0) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -212,7 +235,8 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -226,7 +250,8 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -238,7 +263,8 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last > 0) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -250,7 +276,8 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -262,7 +289,8 @@ namespace jni PulseAudioDeviceManager * engine = reinterpret_cast(userdata); if (last) { - pa_threaded_mainloop_signal(engine->mainloop, 0); + auto& pa = PulseAudioLoader::instance(); + pa.pa_threaded_mainloop_signal(engine->mainloop, 0); return; } @@ -274,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; @@ -295,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); @@ -308,17 +336,20 @@ namespace jni void PulseAudioDeviceManager::stateCallback(pa_context * ctx, void * userdata) { + auto& pa = PulseAudioLoader::instance(); 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) { + auto& pa = PulseAudioLoader::instance(); + PulseAudioDeviceManager * engine = reinterpret_cast(userdata); 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) @@ -327,10 +358,11 @@ 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) { - 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 +370,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 +378,29 @@ 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); + auto& pa = PulseAudioLoader::instance(); + 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"; }