Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/actions/test-natives/action.yml
Original file line number Diff line number Diff line change
@@ -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,/<version>/s|.*<version>\([^<]*\)</version>.*|\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
40 changes: 40 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }}
3 changes: 2 additions & 1 deletion webrtc-jni/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
167 changes: 167 additions & 0 deletions webrtc-jni/src/main/cpp/include/media/audio/linux/PulseAudioLoader.h
Original file line number Diff line number Diff line change
@@ -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 <pulse/pulseaudio.h>

#include <dlfcn.h>

#include <string>

#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
Loading
Loading