[android] Allow to configure field trials per PeerConnectionFactory Provide webrtc::Environment to create AudioProcessing and AudioDeviceModule through java layer Bug: webrtc:369904700, webrtc:413413572 Change-Id: I79454ceb9706fb9c79c995dc7ce8d65924ff84d0 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/388620 Reviewed-by: ZoƩ Lepaul <xalep@webrtc.org> Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Cr-Commit-Position: refs/heads/main@{#44520}
diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index d9ca1e0..0d0f973 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn
@@ -117,6 +117,7 @@ ":base_jni", ":builtin_audio_codecs_jni", ":default_video_codec_factory_jni", + ":environment_jni", ":java_audio_device_module_jni", ":peerconnection_jni", ":video_jni", @@ -182,6 +183,15 @@ ] } + rtc_android_library("environment_java") { + visibility = [ "*" ] + sources = [ "api/org/webrtc/Environment.java" ] + deps = [ + ":generated_environment_jni_java", + "//third_party/androidx:androidx_annotation_annotation_java", + ] + } + rtc_android_library("audio_api_java") { visibility = [ "*" ] sources = [ @@ -330,6 +340,7 @@ ":base_java", ":builtin_audio_codecs_java", ":default_video_codec_factory_java", + ":environment_java", ":generated_base_jni_java", ":generated_peerconnection_jni_java", @@ -616,6 +627,18 @@ ] } + rtc_library("environment_jni") { + sources = [ "src/jni/environment.cc" ] + deps = [ + ":generated_environment_jni", + ":native_api_jni", + "../../api:field_trials", + "../../api/environment", + "../../api/environment:environment_factory", + "//third_party/jni_zero", + ] + } + rtc_library("builtin_audio_codecs_jni") { visibility = [ "*" ] allow_poison = [ "audio_codecs" ] @@ -805,6 +828,7 @@ "../../api/audio:builtin_audio_processing_builder", "../../api/audio_codecs:audio_codecs_api", "../../api/crypto:options", + "../../api/environment", "../../api/neteq:neteq_api", "../../api/rtc_event_log:rtc_event_log_factory", "../../api/task_queue:default_task_queue_factory", @@ -1319,6 +1343,12 @@ jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h" } + generate_jni("generated_environment_jni") { + sources = [ "api/org/webrtc/Environment.java" ] + namespace = "webrtc::jni" + jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h" + } + generate_jni("generated_metrics_jni") { sources = [ "api/org/webrtc/Metrics.java" ] namespace = "webrtc::jni"
diff --git a/sdk/android/api/org/webrtc/AudioProcessingFactory.java b/sdk/android/api/org/webrtc/AudioProcessingFactory.java index bd8fdb8..e0aa25b 100644 --- a/sdk/android/api/org/webrtc/AudioProcessingFactory.java +++ b/sdk/android/api/org/webrtc/AudioProcessingFactory.java
@@ -16,5 +16,14 @@ * Dynamically allocates a webrtc::AudioProcessing instance and returns a pointer to it. * The caller takes ownership of the object. */ - public long createNative(); + // TODO: bugs.webrtc.org/369904700 - Remove when implementations switch to + // another variant. + @Deprecated + public default long createNative() { + return 0; + } + + public default long createNative(long webrtcEnvRef) { + return createNative(); + } }
diff --git a/sdk/android/api/org/webrtc/Environment.java b/sdk/android/api/org/webrtc/Environment.java new file mode 100644 index 0000000..88cef4f --- /dev/null +++ b/sdk/android/api/org/webrtc/Environment.java
@@ -0,0 +1,53 @@ +/* + * Copyright 2025 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +package org.webrtc; + +import androidx.annotation.Nullable; + +/** Wrapper for the webrtc::Environment native class. */ +public final class Environment implements AutoCloseable { + private final long webrtcEnv; + + public static class Builder { + Builder setFieldTrials(String fieldTrials) { + this.fieldTrials = fieldTrials; + return this; + } + + Environment build() { + return new Environment(this.fieldTrials); + } + + private @Nullable String fieldTrials; + } + + public static Builder builder() { + return new Builder(); + } + + /** Returns non-owning non-null native pointer to the webrtc::Environment */ + public long ref() { + return webrtcEnv; + } + + @Override + public void close() { + nativeFree(webrtcEnv); + } + + private Environment(@Nullable String fieldTrials) { + this.webrtcEnv = nativeCreate(fieldTrials); + } + + private static native long nativeCreate(@Nullable String fieldTrials); + + private static native void nativeFree(long webrtcEnv); +}
diff --git a/sdk/android/api/org/webrtc/PeerConnectionFactory.java b/sdk/android/api/org/webrtc/PeerConnectionFactory.java index 2b2ea15..eac3bb0 100644 --- a/sdk/android/api/org/webrtc/PeerConnectionFactory.java +++ b/sdk/android/api/org/webrtc/PeerConnectionFactory.java
@@ -15,9 +15,6 @@ import androidx.annotation.Nullable; import java.util.List; import org.webrtc.Logging.Severity; -import org.webrtc.MediaStreamTrack; -import org.webrtc.PeerConnection; -import org.webrtc.RtpCapabilities; import org.webrtc.audio.AudioDeviceModule; import org.webrtc.audio.JavaAudioDeviceModule; @@ -166,6 +163,7 @@ public static class Builder { @Nullable private Options options; + private Environment.Builder envBuilder = Environment.builder(); @Nullable private AudioDeviceModule audioDeviceModule; private AudioEncoderFactoryFactory audioEncoderFactoryFactory = new BuiltinAudioEncoderFactoryFactory(); @@ -186,6 +184,11 @@ return this; } + public Builder setFieldTrials(String fieldTrials) { + envBuilder.setFieldTrials(fieldTrials); + return this; + } + public Builder setAudioDeviceModule(AudioDeviceModule audioDeviceModule) { this.audioDeviceModule = audioDeviceModule; return this; @@ -261,24 +264,30 @@ public PeerConnectionFactory createPeerConnectionFactory() { checkInitializeHasBeenCalled(); - if (audioDeviceModule == null) { - audioDeviceModule = JavaAudioDeviceModule.builder(ContextUtils.getApplicationContext()) - .createAudioDeviceModule(); + try (Environment env = envBuilder.build()) { + if (audioDeviceModule == null) { + audioDeviceModule = JavaAudioDeviceModule.builder(ContextUtils.getApplicationContext()) + .createAudioDeviceModule(); + } + return nativeCreatePeerConnectionFactory( + ContextUtils.getApplicationContext(), + options, + env.ref(), + audioDeviceModule.getNative(env.ref()), + audioEncoderFactoryFactory.createNativeAudioEncoderFactory(), + audioDecoderFactoryFactory.createNativeAudioDecoderFactory(), + videoEncoderFactory, + videoDecoderFactory, + audioProcessingFactory == null ? 0 : audioProcessingFactory.createNative(env.ref()), + fecControllerFactoryFactory == null ? 0 : fecControllerFactoryFactory.createNative(), + networkControllerFactoryFactory == null + ? 0 + : networkControllerFactoryFactory.createNativeNetworkControllerFactory(), + networkStatePredictorFactoryFactory == null + ? 0 + : networkStatePredictorFactoryFactory.createNativeNetworkStatePredictorFactory(), + neteqFactoryFactory == null ? 0 : neteqFactoryFactory.createNativeNetEqFactory()); } - return nativeCreatePeerConnectionFactory(ContextUtils.getApplicationContext(), options, - audioDeviceModule.getNativeAudioDeviceModulePointer(), - audioEncoderFactoryFactory.createNativeAudioEncoderFactory(), - audioDecoderFactoryFactory.createNativeAudioDecoderFactory(), videoEncoderFactory, - videoDecoderFactory, - audioProcessingFactory == null ? 0 : audioProcessingFactory.createNative(), - fecControllerFactoryFactory == null ? 0 : fecControllerFactoryFactory.createNative(), - networkControllerFactoryFactory == null - ? 0 - : networkControllerFactoryFactory.createNativeNetworkControllerFactory(), - networkStatePredictorFactoryFactory == null - ? 0 - : networkStatePredictorFactoryFactory.createNativeNetworkStatePredictorFactory(), - neteqFactoryFactory == null ? 0 : neteqFactoryFactory.createNativeNetEqFactory()); } } @@ -604,7 +613,7 @@ private static native void nativeStopInternalTracingCapture(); private static native PeerConnectionFactory nativeCreatePeerConnectionFactory(Context context, - Options options, long nativeAudioDeviceModule, long audioEncoderFactory, + Options options, long webrtcEnvRef, long nativeAudioDeviceModule, long audioEncoderFactory, long audioDecoderFactory, VideoEncoderFactory encoderFactory, VideoDecoderFactory decoderFactory, long nativeAudioProcessor, long nativeFecControllerFactory, long nativeNetworkControllerFactory,
diff --git a/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java b/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java index 5a0bf5c..ba5097d 100644 --- a/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java +++ b/sdk/android/api/org/webrtc/audio/AudioDeviceModule.java
@@ -22,7 +22,14 @@ * Returns a C++ pointer to a webrtc::AudioDeviceModule. Caller does _not_ take ownership and * lifetime is handled through the release() call. */ - long getNativeAudioDeviceModulePointer(); + @Deprecated + default long getNativeAudioDeviceModulePointer() { + return 0; + } + + public default long getNative(long webrtcEnvRef) { + return getNativeAudioDeviceModulePointer(); + } /** * Release resources for this AudioDeviceModule, including native resources. The object should not
diff --git a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java index a28b9c2..1e36fef 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java
@@ -62,4 +62,31 @@ .createInitializationOptions()); PeerConnectionFactory.shutdownInternalTracer(); } + + // Tests that the JNI glue between Java and C++ does not crash when creating a + // PeerConnectionFactory. + @Test + @SmallTest + public void testCreation() throws Exception { + PeerConnectionFactory.initialize( + PeerConnectionFactory.InitializationOptions.builder( + InstrumentationRegistry.getTargetContext()) + .setNativeLibraryName(TestConstants.NATIVE_LIBRARY) + .createInitializationOptions()); + + PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory(); + } + + @Test + @SmallTest + public void testCreationWithFieldTrials() throws Exception { + PeerConnectionFactory.initialize( + PeerConnectionFactory.InitializationOptions.builder( + InstrumentationRegistry.getTargetContext()) + .setNativeLibraryName(TestConstants.NATIVE_LIBRARY) + .createInitializationOptions()); + + PeerConnectionFactory factory = + PeerConnectionFactory.builder().setFieldTrials("").createPeerConnectionFactory(); + } }
diff --git a/sdk/android/src/jni/environment.cc b/sdk/android/src/jni/environment.cc new file mode 100644 index 0000000..adf5fbc --- /dev/null +++ b/sdk/android/src/jni/environment.cc
@@ -0,0 +1,40 @@ +/* + * Copyright 2025 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "api/environment/environment.h" + +#include <string> + +#include "api/environment/environment_factory.h" +#include "api/field_trials.h" +#include "sdk/android/generated_environment_jni/Environment_jni.h" +#include "sdk/android/native_api/jni/java_types.h" +#include "third_party/jni_zero/jni_zero.h" + +namespace webrtc { +namespace jni { + +void JNI_Environment_Free(JNIEnv* /*env*/, jlong webrtc_env) { + delete reinterpret_cast<Environment*>(webrtc_env); +} + +jlong JNI_Environment_Create( + JNIEnv* jni, + const jni_zero::JavaParamRef<jstring>& j_field_trials) { + std::unique_ptr<FieldTrialsView> field_trials; + if (!j_field_trials.is_null()) { + field_trials = + FieldTrials::CreateNoGlobal(JavaToNativeString(jni, j_field_trials)); + } + return NativeToJavaPointer( + new Environment(CreateEnvironment(std::move(field_trials)))); +} + +} // namespace jni +} // namespace webrtc
diff --git a/sdk/android/src/jni/pc/peer_connection_factory.cc b/sdk/android/src/jni/pc/peer_connection_factory.cc index f05259b..80b9204 100644 --- a/sdk/android/src/jni/pc/peer_connection_factory.cc +++ b/sdk/android/src/jni/pc/peer_connection_factory.cc
@@ -29,6 +29,7 @@ #include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_options.h" #include "api/enable_media.h" +#include "api/environment/environment.h" #include "api/fec_controller.h" #include "api/media_stream_interface.h" #include "api/neteq/neteq_factory.h" @@ -256,6 +257,7 @@ JNIEnv* jni, const jni_zero::JavaParamRef<jobject>& jcontext, const jni_zero::JavaParamRef<jobject>& joptions, + const Environment& env, scoped_refptr<AudioDeviceModule> audio_device_module, scoped_refptr<AudioEncoderFactory> audio_encoder_factory, scoped_refptr<AudioDecoderFactory> audio_decoder_factory, @@ -292,8 +294,8 @@ JavaToNativePeerConnectionFactoryOptions(jni, joptions); PeerConnectionFactoryDependencies dependencies; - // TODO(bugs.webrtc.org/13145): Also add socket_server.get() to the - // dependencies. + dependencies.env = env; + dependencies.socket_factory = socket_server.get(); dependencies.network_thread = network_thread.get(); dependencies.worker_thread = worker_thread.get(); dependencies.signaling_thread = signaling_thread.get(); @@ -347,6 +349,7 @@ JNIEnv* jni, const jni_zero::JavaParamRef<jobject>& jcontext, const jni_zero::JavaParamRef<jobject>& joptions, + jlong webrtc_env_ref, jlong native_audio_device_module, jlong native_audio_encoder_factory, jlong native_audio_decoder_factory, @@ -357,10 +360,12 @@ jlong native_network_controller_factory, jlong native_network_state_predictor_factory, jlong native_neteq_factory) { + const Environment* env = reinterpret_cast<Environment*>(webrtc_env_ref); + RTC_CHECK(env != nullptr); scoped_refptr<AudioProcessing> audio_processor( reinterpret_cast<AudioProcessing*>(native_audio_processor)); return CreatePeerConnectionFactoryForJava( - jni, jcontext, joptions, + jni, jcontext, joptions, *env, scoped_refptr<AudioDeviceModule>( reinterpret_cast<AudioDeviceModule*>(native_audio_device_module)), TakeOwnershipOfRefPtr<AudioEncoderFactory>(native_audio_encoder_factory),