From a2974d743146dd41ad81db9c184de0266e8db159 Mon Sep 17 00:00:00 2001 From: SendableMetatype <263203301+SendableMetatype@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:32:29 +0200 Subject: [PATCH 1/6] ci: test cross compiled natives on native runners The linux_arm and linux_arm64 lanes skip their tests, because the natives cannot run on the x86_64 build host. Until now, no CI run loaded those natives. The two lanes now upload the classifier jar that they produced. A test job on an arm64 runner downloads that exact jar, installs it into the local repository, and runs the test suite of the webrtc module against it. The 32-bit lane uses a Liberica armv7 JDK and the armhf runtime libraries that the natives require. The composite action is generic, so a further cross compiled platform only needs a matrix entry. --- .github/actions/test-natives/action.yml | 88 +++++++++++++++++++++++++ .github/workflows/build.yml | 40 +++++++++++ 2 files changed, 128 insertions(+) create mode 100644 .github/actions/test-natives/action.yml diff --git a/.github/actions/test-natives/action.yml b/.github/actions/test-natives/action.yml new file mode 100644 index 00000000..6a63c6c2 --- /dev/null +++ b/.github/actions/test-natives/action.yml @@ -0,0 +1,88 @@ +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 + + - 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 libxfixes3:armhf libxrandr2:armhf libxcomposite1:armhf \ + libdbus-1-3:armhf libpulse0: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 libxfixes3 libxrandr2 libxcomposite1 \ + libdbus-1-3 libpulse0 libudev1 pipewire pipewire-pulse + 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 }} From c7606ca06478263957e3fda9293fbad5778be9c0 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 2/6] 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 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..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 97a252fef3b59e376f8f531ddd84629ac231630a Mon Sep 17 00:00:00 2001 From: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> Date: Sat, 22 Aug 2026 19:34:17 +0200 Subject: [PATCH 3/6] Fix LOAD_SYM macro Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --- .../media/audio/linux/PulseAudioLoader.h | 98 +++++++++---------- 1 file changed, 44 insertions(+), 54 deletions(-) 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 24731e59bdf901629ac21cd4e5c24b5630e8a9fe 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 4/6] 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 d251ea56ce0868a622868e08fc23aaeeab543c64 Mon Sep 17 00:00:00 2001 From: SendableMetatype <263203301+SendableMetatype@users.noreply.github.com> Date: Sat, 22 Aug 2026 22:32:30 +0200 Subject: [PATCH 5/6] ci: run the native tests without PulseAudio installed The natives now open libpulse at runtime, so the test runners do not install libpulse. A test lane that passes on a host without PulseAudio proves that the natives load on minimal systems. --- .github/actions/test-natives/action.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/actions/test-natives/action.yml b/.github/actions/test-natives/action.yml index 6a63c6c2..05e527e7 100644 --- a/.github/actions/test-natives/action.yml +++ b/.github/actions/test-natives/action.yml @@ -39,6 +39,8 @@ runs: 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: | @@ -46,7 +48,7 @@ runs: sudo apt-get update sudo apt-get install -y libc6:armhf libstdc++6:armhf zlib1g:armhf \ libx11-6:armhf libxfixes3:armhf libxrandr2:armhf libxcomposite1:armhf \ - libdbus-1-3:armhf libpulse0:armhf libudev1:armhf + libdbus-1-3:armhf libudev1:armhf shell: bash - name: Install runtime libraries @@ -54,7 +56,7 @@ runs: run: | sudo apt-get update sudo apt-get install -y libx11-6 libxfixes3 libxrandr2 libxcomposite1 \ - libdbus-1-3 libpulse0 libudev1 pipewire pipewire-pulse + libdbus-1-3 libudev1 shell: bash - name: Set up JDK ${{ inputs.java-version }} From 6d1d312d6293fce114082773a5608f8c6aa8f19a Mon Sep 17 00:00:00 2001 From: SendableMetatype <263203301+SendableMetatype@users.noreply.github.com> Date: Sat, 5 Sep 2026 16:24:11 +0200 Subject: [PATCH 6/6] ci: install the desktop capture runtime libraries on the test runners The Wayland and PipeWire desktop capture in 0.16.0 links gio, gbm and libdrm directly and added Xext, Xdamage and Xtst to the X11 set. The natives need those libraries at load time, so the test runners install them, in the armhf variants for the 32-bit lane. --- .github/actions/test-natives/action.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/actions/test-natives/action.yml b/.github/actions/test-natives/action.yml index 05e527e7..9fbd44e5 100644 --- a/.github/actions/test-natives/action.yml +++ b/.github/actions/test-natives/action.yml @@ -46,17 +46,14 @@ runs: 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 libxfixes3:armhf libxrandr2:armhf libxcomposite1:armhf \ - libdbus-1-3:armhf libudev1:armhf + 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 libxfixes3 libxrandr2 libxcomposite1 \ - libdbus-1-3 libudev1 + 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 }}