diff --git a/docs/guide/audio/dtmf-sender.md b/docs/guide/audio/dtmf-sender.md index 0b2b7bf8..c3283cf7 100644 --- a/docs/guide/audio/dtmf-sender.md +++ b/docs/guide/audio/dtmf-sender.md @@ -139,7 +139,13 @@ When you're done with the DTMF sender, you should unregister any observers: dtmfSender.unregisterObserver(); ``` -Note that you don't need to explicitly dispose of the DTMF sender, as it will be cleaned up when the associated RTP sender is disposed. +`RTCDtmfSender` has no `dispose()` method of its own; it is tied to the native sender's lifetime. +The `RTCRtpSender` it came from, however, is not owned by the peer connection, so make sure to +dispose that once you're done with it: + +```java +sender.dispose(); +``` ## Best Practices diff --git a/docs/guide/audio/headless-audio.md b/docs/guide/audio/headless-audio.md index 35a5132e..c2d9292d 100644 --- a/docs/guide/audio/headless-audio.md +++ b/docs/guide/audio/headless-audio.md @@ -73,7 +73,7 @@ PeerConnectionFactory factory = new PeerConnectionFactory(adm); // Use a custom or built-in AudioSource to provide audio frames CustomAudioSource source = new CustomAudioSource(); AudioTrack senderTrack = factory.createAudioTrack("audio0", source); -peerConnection.addTrack(senderTrack, Collections.singletonList("stream0")); +RTCRtpSender sender = peerConnection.addTrack(senderTrack, Collections.singletonList("stream0")); // Push PCM frames into the CustomAudioSource (10 ms chunks work well) byte[] pcm = new byte[480 /* frames */ * 2 /* ch */ * 2 /* bytes */]; @@ -81,6 +81,9 @@ source.pushAudio(pcm, 16, 48000, 2, 480); // ... later, stop adm.stopRecording(); +// addTrack() returns an RTCRtpSender that is not owned by the peer +// connection, so dispose it explicitly. +sender.dispose(); adm.dispose(); factory.dispose(); ``` diff --git a/docs/guide/get-started.md b/docs/guide/get-started.md index 776d030d..99ed5cd5 100644 --- a/docs/guide/get-started.md +++ b/docs/guide/get-started.md @@ -274,12 +274,13 @@ Audio devices can be specified via the `AudioDeviceModule`, see the guide on [Au ```java import java.util.ArrayList; import java.util.List; +import dev.onvoid.webrtc.RTCRtpSender; // Add tracks to the peer connection List streamIds = new ArrayList<>(); streamIds.add("stream1"); -peerConnection.addTrack(videoTrack, streamIds); -peerConnection.addTrack(audioTrack, streamIds); +RTCRtpSender videoSender = peerConnection.addTrack(videoTrack, streamIds); +RTCRtpSender audioSender = peerConnection.addTrack(audioTrack, streamIds); ``` ## Cleanup @@ -287,6 +288,10 @@ peerConnection.addTrack(audioTrack, streamIds); Always properly dispose of resources when you're done: ```java +// Dispose of senders (not owned by the peer connection) +videoSender.dispose(); +audioSender.dispose(); + // Dispose of tracks videoTrack.dispose(); audioTrack.dispose(); diff --git a/docs/guide/media/directionality.md b/docs/guide/media/directionality.md index e6e73c26..ae0f9bcd 100644 --- a/docs/guide/media/directionality.md +++ b/docs/guide/media/directionality.md @@ -50,7 +50,8 @@ init.direction = RTCRtpTransceiverDirection.RECV_ONLY; RTCRtpTransceiver transceiver = pc.addTransceiver(videoTrack, init); // Access the receiving track and attach a sink -MediaStreamTrack track = transceiver.getReceiver().getTrack(); +RTCRtpReceiver receiver = transceiver.getReceiver(); +MediaStreamTrack track = receiver.getTrack(); if (track instanceof dev.onvoid.webrtc.media.video.VideoTrack vTrack) { vTrack.addSink(frame -> { // Handle incoming frames @@ -58,6 +59,12 @@ if (track instanceof dev.onvoid.webrtc.media.video.VideoTrack vTrack) { frame.release(); }); } + +// receiver and transceiver are not owned by the peer connection; dispose +// them once the track has been retrieved. The track itself is unaffected +// and keeps delivering frames to its sink. +receiver.dispose(); +transceiver.dispose(); ``` ::: info @@ -89,6 +96,10 @@ RTCRtpTransceiver transceiver = pc.addTransceiver(audioTrack, init); // Optionally verify assert transceiver.getDirection() == RTCRtpTransceiverDirection.SEND_ONLY; + +// transceiver is not owned by the peer connection; dispose it once you no +// longer need this reference (e.g. together with the peer connection). +transceiver.dispose(); ``` When you create the offer with this setup, the SDP will include a=sendonly for the audio m= section. diff --git a/docs/guide/video/camera-capture.md b/docs/guide/video/camera-capture.md index d3ea7726..2fffe069 100644 --- a/docs/guide/video/camera-capture.md +++ b/docs/guide/video/camera-capture.md @@ -90,6 +90,7 @@ Add the video track to your peer connection: import java.util.ArrayList; import java.util.List; import dev.onvoid.webrtc.RTCPeerConnection; +import dev.onvoid.webrtc.RTCRtpSender; // Assuming you already have a configured RTCPeerConnection RTCPeerConnection peerConnection = factory.createPeerConnection(config, observer); @@ -97,9 +98,15 @@ RTCPeerConnection peerConnection = factory.createPeerConnection(config, observer // Add the track to the peer connection List streamIds = new ArrayList<>(); streamIds.add("stream1"); -peerConnection.addTrack(videoTrack, streamIds); +RTCRtpSender sender = peerConnection.addTrack(videoTrack, streamIds); ``` +::: info +`addTrack` returns an `RTCRtpSender`. It is not owned by the peer connection, so dispose it +yourself (`sender.dispose()`) once you no longer need it — typically together with the peer +connection when the call ends. +::: + ## Additional Features The `VideoDeviceSource` provides additional methods for controlling the camera capture: @@ -161,6 +168,7 @@ To receive frames from a remote peer, you need to add a sink to the remote video ```java import dev.onvoid.webrtc.PeerConnectionObserver; +import dev.onvoid.webrtc.RTCRtpReceiver; import dev.onvoid.webrtc.RTCRtpTransceiver; import dev.onvoid.webrtc.media.MediaStreamTrack; import dev.onvoid.webrtc.media.video.VideoTrack; @@ -172,7 +180,8 @@ public class MyPeerConnectionObserver implements PeerConnectionObserver { @Override public void onTrack(RTCRtpTransceiver transceiver) { - MediaStreamTrack track = transceiver.getReceiver().getTrack(); + RTCRtpReceiver receiver = transceiver.getReceiver(); + MediaStreamTrack track = receiver.getTrack(); String kind = track.getKind(); if (kind.equals(MediaStreamTrack.VIDEO_TRACK_KIND)) { @@ -180,6 +189,12 @@ public class MyPeerConnectionObserver implements PeerConnectionObserver { videoTrack.addSink(remoteVideoSink); System.out.println("Added sink to remote video track"); } + + // The receiver and transceiver are query results you own; dispose + // them once the track has been retrieved. The track itself is + // unaffected and keeps delivering frames to its sink. + receiver.dispose(); + transceiver.dispose(); } // Make sure to clean up when done diff --git a/docs/guide/video/desktop-capture.md b/docs/guide/video/desktop-capture.md index 6f4e86bb..a0ebc5e9 100644 --- a/docs/guide/video/desktop-capture.md +++ b/docs/guide/video/desktop-capture.md @@ -92,6 +92,7 @@ Add the video track to your peer connection: import java.util.ArrayList; import java.util.List; import dev.onvoid.webrtc.RTCPeerConnection; +import dev.onvoid.webrtc.RTCRtpSender; // Assuming you already have a configured RTCPeerConnection RTCPeerConnection peerConnection = factory.createPeerConnection(config, observer); @@ -99,9 +100,15 @@ RTCPeerConnection peerConnection = factory.createPeerConnection(config, observer // Add the track to the peer connection List streamIds = new ArrayList<>(); streamIds.add("stream1"); -peerConnection.addTrack(videoTrack, streamIds); +RTCRtpSender sender = peerConnection.addTrack(videoTrack, streamIds); ``` +::: info +`addTrack` returns an `RTCRtpSender`. It is not owned by the peer connection, so dispose it +yourself (`sender.dispose()`) once you no longer need it — typically together with the peer +connection when the call ends. +::: + ## Additional Features The `VideoDesktopSource` provides additional methods for controlling the desktop capture: diff --git a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/DesktopVideoExample.java b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/DesktopVideoExample.java index ee95938e..0fad48c0 100644 --- a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/DesktopVideoExample.java +++ b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/DesktopVideoExample.java @@ -32,6 +32,7 @@ import dev.onvoid.webrtc.RTCPeerConnection; import dev.onvoid.webrtc.RTCPeerConnectionState; import dev.onvoid.webrtc.RTCRtpReceiver; +import dev.onvoid.webrtc.RTCRtpSender; import dev.onvoid.webrtc.RTCRtpTransceiver; import dev.onvoid.webrtc.RTCSignalingState; import dev.onvoid.webrtc.media.MediaStream; @@ -95,6 +96,7 @@ private static class LocalPeer implements PeerConnectionObserver { private final RTCPeerConnection peerConnection; private final VideoDesktopSource videoSource; + private final RTCRtpSender videoSender; public LocalPeer(PeerConnectionFactory factory) { @@ -172,7 +174,7 @@ else if (!windows.isEmpty()) { // Add the tracks to the peer connection. List streamIds = new ArrayList<>(); streamIds.add("stream1"); - peerConnection.addTrack(videoTrack, streamIds); + videoSender = peerConnection.addTrack(videoTrack, streamIds); System.out.println("LocalPeer: Created with a desktop video track"); } @@ -186,6 +188,11 @@ public void dispose() { videoSource.stop(); videoSource.dispose(); } + // RTCRtpSender is not owned by the peer connection; the + // application must dispose it once it is no longer needed. + if (videoSender != null) { + videoSender.dispose(); + } if (peerConnection != null) { peerConnection.close(); } @@ -234,18 +241,30 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) { System.out.println("LocalPeer: Track added: " + receiver.getTrack().getKind()); + + // The receiver is a query result the application owns; dispose it + // once its track has been retrieved. + receiver.dispose(); } @Override public void onRemoveTrack(RTCRtpReceiver receiver) { System.out.println("LocalPeer: Track removed: " + receiver.getTrack().getKind()); + + receiver.dispose(); } @Override public void onTrack(RTCRtpTransceiver transceiver) { - MediaStreamTrack track = transceiver.getReceiver().getTrack(); + RTCRtpReceiver receiver = transceiver.getReceiver(); + MediaStreamTrack track = receiver.getTrack(); System.out.println("LocalPeer: Transceiver track added: " + track.getKind()); + + // The receiver and transceiver are query results the application + // owns; dispose them once the track has been retrieved. + receiver.dispose(); + transceiver.dispose(); } } } \ No newline at end of file diff --git a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/PeerConnectionExample.java b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/PeerConnectionExample.java index 2c670cdf..52c411ed 100644 --- a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/PeerConnectionExample.java +++ b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/PeerConnectionExample.java @@ -32,6 +32,7 @@ import dev.onvoid.webrtc.RTCPeerConnection; import dev.onvoid.webrtc.RTCPeerConnectionState; import dev.onvoid.webrtc.RTCRtpReceiver; +import dev.onvoid.webrtc.RTCRtpSender; import dev.onvoid.webrtc.RTCRtpTransceiver; import dev.onvoid.webrtc.RTCSignalingState; import dev.onvoid.webrtc.media.MediaStream; @@ -99,6 +100,8 @@ private static class LocalPeer implements PeerConnectionObserver { private final RTCPeerConnection peerConnection; private final AudioTrack audioTrack; private final VideoTrack videoTrack; + private final RTCRtpSender audioSender; + private final RTCRtpSender videoSender; private final AudioFrameLogger audioFrameLogger = new AudioFrameLogger(); private final VideoFrameLogger videoFrameLogger = new VideoFrameLogger(); @@ -130,8 +133,8 @@ public LocalPeer(PeerConnectionFactory factory) { // Add the tracks to the peer connection. List streamIds = new ArrayList<>(); streamIds.add("stream1"); - peerConnection.addTrack(audioTrack, streamIds); - peerConnection.addTrack(videoTrack, streamIds); + audioSender = peerConnection.addTrack(audioTrack, streamIds); + videoSender = peerConnection.addTrack(videoTrack, streamIds); System.out.println("LocalPeer: Created with audio and video tracks"); } @@ -146,6 +149,14 @@ public void dispose() { if (videoTrack != null) { videoTrack.removeSink(videoFrameLogger); } + // RTCRtpSender instances are not owned by the peer connection; the + // application must dispose them once they are no longer needed. + if (audioSender != null) { + audioSender.dispose(); + } + if (videoSender != null) { + videoSender.dispose(); + } if (peerConnection != null) { peerConnection.close(); } @@ -194,16 +205,23 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) { System.out.println("LocalPeer: Track added: " + receiver.getTrack().getKind()); + + // The receiver is a query result the application owns; dispose it + // once its track has been retrieved. + receiver.dispose(); } @Override public void onRemoveTrack(RTCRtpReceiver receiver) { System.out.println("LocalPeer: Track removed: " + receiver.getTrack().getKind()); + + receiver.dispose(); } @Override public void onTrack(RTCRtpTransceiver transceiver) { - MediaStreamTrack track = transceiver.getReceiver().getTrack(); + RTCRtpReceiver receiver = transceiver.getReceiver(); + MediaStreamTrack track = receiver.getTrack(); String kind = track.getKind(); if (kind.equals(MediaStreamTrack.AUDIO_TRACK_KIND)) { @@ -216,6 +234,12 @@ public void onTrack(RTCRtpTransceiver transceiver) { } System.out.println("LocalPeer: Transceiver track added: " + kind); + + // The receiver and transceiver are query results the application + // owns; dispose them once the track has been retrieved. The track + // itself is unaffected and keeps delivering frames to its sink. + receiver.dispose(); + transceiver.dispose(); } } diff --git a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/WhepExample.java b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/WhepExample.java index 46a49c9c..f8fdae3d 100644 --- a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/WhepExample.java +++ b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/WhepExample.java @@ -161,7 +161,8 @@ private void createPeerConnection() { RTCRtpTransceiver transceiver = peerConnection.addTransceiver(videoTrack, transceiverInit); // Set up a sink to handle incoming video frames. - MediaStreamTrack track = transceiver.getReceiver().getTrack(); + RTCRtpReceiver receiver = transceiver.getReceiver(); + MediaStreamTrack track = receiver.getTrack(); if (track instanceof VideoTrack vTrack) { vTrack.addSink(videoFrame -> { System.out.println("Received video frame: " + videoFrame); @@ -170,6 +171,12 @@ private void createPeerConnection() { videoFrame.release(); }); } + + // The receiver and transceiver are query results this example owns; + // dispose them once the track has been retrieved. The track itself + // is unaffected and keeps delivering frames to its sink. + receiver.dispose(); + transceiver.dispose(); } private void createOffer() { @@ -314,6 +321,10 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) { System.out.println("Track added."); + + // The receiver is a query result the application owns; dispose it + // once it is no longer needed. + receiver.dispose(); } } } \ No newline at end of file diff --git a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/web/connection/PeerConnectionManager.java b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/web/connection/PeerConnectionManager.java index fbece89c..167f8b87 100644 --- a/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/web/connection/PeerConnectionManager.java +++ b/webrtc-examples/src/main/java/dev/onvoid/webrtc/examples/web/connection/PeerConnectionManager.java @@ -16,6 +16,7 @@ package dev.onvoid.webrtc.examples.web.connection; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -33,6 +34,7 @@ import dev.onvoid.webrtc.RTCPeerConnection; import dev.onvoid.webrtc.RTCPeerConnectionState; import dev.onvoid.webrtc.RTCRtpReceiver; +import dev.onvoid.webrtc.RTCRtpSender; import dev.onvoid.webrtc.RTCRtpTransceiver; import dev.onvoid.webrtc.RTCSdpType; import dev.onvoid.webrtc.RTCSessionDescription; @@ -66,6 +68,7 @@ public class PeerConnectionManager implements PeerConnectionSignalingHandler { private final PeerConnectionFactory factory; private final RTCPeerConnection peerConnection; + private final List senders = new ArrayList<>(); private Consumer onLocalDescriptionCreated; private Consumer onIceCandidateGenerated; @@ -99,7 +102,9 @@ public PeerConnectionManager() { * @param streamIds The stream IDs to associate with the track. */ public void addTrack(MediaStreamTrack track, List streamIds) { - peerConnection.addTrack(track, streamIds); + // Keep the returned RTCRtpSender around so it can be disposed in + // close(); it is not owned by the peer connection. + senders.add(peerConnection.addTrack(track, streamIds)); LOG.info("Added track: {}", track.getKind()); } @@ -146,6 +151,11 @@ public VideoTrack createVideoTrack(VideoTrackSource source, String label) { * Closes the peer connection. */ public void close() { + for (RTCRtpSender sender : senders) { + sender.dispose(); + } + senders.clear(); + if (peerConnection != null) { peerConnection.close(); } @@ -407,23 +417,35 @@ public void onRenegotiationNeeded() { @Override public void onAddTrack(RTCRtpReceiver receiver, MediaStream[] mediaStreams) { LOG.info("Track added: {}", receiver.getTrack().getKind()); + + // The receiver is a query result the application owns; dispose it + // once its track has been retrieved. + receiver.dispose(); } - + @Override public void onRemoveTrack(RTCRtpReceiver receiver) { LOG.info("Track removed: {}", receiver.getTrack().getKind()); + + receiver.dispose(); } - + @Override public void onTrack(RTCRtpTransceiver transceiver) { - MediaStreamTrack track = transceiver.getReceiver().getTrack(); + RTCRtpReceiver receiver = transceiver.getReceiver(); + MediaStreamTrack track = receiver.getTrack(); String kind = track.getKind(); - + LOG.info("{} track added to transceiver", kind); - + if (onTrackReceived != null) { onTrackReceived.accept(track); } + + // The receiver and transceiver are query results the application + // owns; dispose them once the track has been retrieved. + receiver.dispose(); + transceiver.dispose(); } } } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/dependencies/jni-voithos/include/JavaUtils.h b/webrtc-jni/src/main/cpp/dependencies/jni-voithos/include/JavaUtils.h index db287978..dd273ef5 100644 --- a/webrtc-jni/src/main/cpp/dependencies/jni-voithos/include/JavaUtils.h +++ b/webrtc-jni/src/main/cpp/dependencies/jni-voithos/include/JavaUtils.h @@ -117,4 +117,36 @@ void SetHandle(JNIEnv * env, jobject obj, T * t) SetHandle(env, obj, "nativeHandle", t); } +// Replaces the native observer stored in the `handleName` field on `obj` +// with `observer`, deleting whatever was stored there before. Use this +// together with ClearNativeObserver() wherever a Java object registers a +// heap-allocated native observer against a "replace the previous one" +// register() API, so the previous observer's lifetime can never be +// forgotten and leaked -- as opposed to reimplementing the same +// get-old/delete/store-new sequence by hand in each JNI_Foo.cpp. +template +void ReplaceNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName, T * observer) +{ + T * old = GetHandle(env, obj, handleName); + + delete old; + + SetHandle(env, obj, handleName, observer); +} + +// Deletes and clears the native observer stored in the `handleName` field +// on `obj`, if any. Safe to call when no observer is currently registered +// (e.g. from an unregisterObserver()/dispose() implementation that isn't +// sure whether one was ever set). +template +void ClearNativeObserver(JNIEnv * env, jobject obj, const std::string & handleName) +{ + T * observer = GetHandle(env, obj, handleName); + + if (observer) { + SetHandle(env, obj, handleName, nullptr); + delete observer; + } +} + #endif \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpReceiver.h b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpReceiver.h index 3ff3658f..2a16ba70 100644 --- a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpReceiver.h +++ b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpReceiver.h @@ -47,6 +47,14 @@ extern "C" { JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_getSynchronizationSources (JNIEnv *, jobject); + /* + * Class: dev_onvoid_webrtc_RTCRtpReceiver + * Method: dispose + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_dispose + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h index 83f23ffc..eec94f3f 100644 --- a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h +++ b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h @@ -63,6 +63,14 @@ extern "C" { JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getDtmfSender (JNIEnv *, jobject); + /* + * Class: dev_onvoid_webrtc_RTCRtpSender + * Method: dispose + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_dispose + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpTransceiver.h b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpTransceiver.h index d0b74192..3379b6fc 100644 --- a/webrtc-jni/src/main/cpp/include/JNI_RTCRtpTransceiver.h +++ b/webrtc-jni/src/main/cpp/include/JNI_RTCRtpTransceiver.h @@ -87,6 +87,14 @@ extern "C" { JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_setCodecPreferences (JNIEnv *, jobject, jobject); + /* + * Class: dev_onvoid_webrtc_RTCRtpTransceiver + * Method: dispose + * Signature: ()V + */ + JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_dispose + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h b/webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h index 87f0f931..166f8144 100644 --- a/webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h +++ b/webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h @@ -49,6 +49,33 @@ namespace jni return objectArray; } + // Same as createObjectArray, but transfers the reference each element of + // `vector` holds into the corresponding Java wrapper instead of letting + // it evaporate when `vector` goes out of scope. Use this when the Java + // wrapper type owns/releases its native reference (e.g. is disposable) + // and nothing else already guarantees the pointer outlives the call -- + // callers of the returned array elements are then responsible for + // disposing them. + template + JavaLocalRef createOwningObjectArray(JNIEnv * env, std::vector> vector) + { + jsize vectorSize = static_cast(vector.size()); + + JavaLocalRef objectArray = JavaFactories::createArray(env, vectorSize); + + if (objectArray.get() == nullptr) { + throw Exception("Create object array of type [%s] failed", typeid(T).name()); + } + + for (jsize i = 0; i < vectorSize; i++) { + JavaLocalRef obj = JavaFactories::create(env, vector[i].release()); + + env->SetObjectArrayElement(objectArray, i, obj.get()); + } + + return objectArray; + } + std::string RTCErrorToString(const webrtc::RTCError & error); } diff --git a/webrtc-jni/src/main/cpp/src/JNI_MediaStreamTrack.cpp b/webrtc-jni/src/main/cpp/src/JNI_MediaStreamTrack.cpp index 011e6ac0..e23695df 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_MediaStreamTrack.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_MediaStreamTrack.cpp @@ -35,13 +35,16 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_MediaStreamTrack_dispose webrtc::RefCountReleaseStatus status = track->Release(); + // Our reference is gone the moment Release() returns, regardless of + // whether it was the last one, so the handle must not be reused either + // way -- otherwise a retry would Release() a reference this object no + // longer owns. + SetHandle(env, caller, nullptr); + track = nullptr; + if (status != webrtc::RefCountReleaseStatus::kDroppedLastRef) { env->Throw(jni::JavaError(env, "Native object was not deleted. A reference is still around somewhere.")); } - else { - SetHandle(env, caller, nullptr); - track = nullptr; - } } JNIEXPORT jstring JNICALL Java_dev_onvoid_webrtc_media_MediaStreamTrack_getKind diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCDataChannel.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCDataChannel.cpp index 53b21e6c..eef0a8a1 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCDataChannel.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCDataChannel.cpp @@ -35,7 +35,14 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDataChannel_registerObserver webrtc::DataChannelInterface * channel = GetHandle(env, caller); CHECK_HANDLE(channel); - channel->RegisterObserver(new jni::RTCDataChannelObserver(env, jni::JavaGlobalRef(env, jObserver))); + // Unregister and free a previously registered observer before replacing + // it, otherwise it would leak along with its JNI global reference. + channel->UnregisterObserver(); + + auto observer = new jni::RTCDataChannelObserver(env, jni::JavaGlobalRef(env, jObserver)); + ReplaceNativeObserver(env, caller, "observerHandle", observer); + + channel->RegisterObserver(observer); } JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDataChannel_unregisterObserver @@ -45,6 +52,8 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDataChannel_unregisterObserver CHECK_HANDLE(channel); channel->UnregisterObserver(); + + ClearNativeObserver(env, caller, "observerHandle"); } JNIEXPORT jstring JNICALL Java_dev_onvoid_webrtc_RTCDataChannel_getLabel @@ -152,6 +161,10 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDataChannel_dispose webrtc::DataChannelInterface * channel = GetHandle(env, caller); CHECK_HANDLE(channel); + channel->UnregisterObserver(); + + ClearNativeObserver(env, caller, "observerHandle"); + webrtc::RefCountReleaseStatus status = channel->Release(); if (status != webrtc::RefCountReleaseStatus::kDroppedLastRef) { diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp index 04454fb2..4d331dc9 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp @@ -76,7 +76,14 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_registerObserver webrtc::DtlsTransportInterface * transport = GetHandle(env, caller); CHECK_HANDLE(transport); - transport->RegisterObserver(new jni::RTCDtlsTransportObserver(env, jni::JavaGlobalRef(env, observer))); + // Unregister and free a previously registered observer before replacing + // it, otherwise it would leak along with its JNI global reference. + transport->UnregisterObserver(); + + auto newObserver = new jni::RTCDtlsTransportObserver(env, jni::JavaGlobalRef(env, observer)); + ReplaceNativeObserver(env, caller, "observerHandle", newObserver); + + transport->RegisterObserver(newObserver); } JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_unregisterObserver @@ -86,4 +93,6 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_unregisterObserve CHECK_HANDLE(transport); transport->UnregisterObserver(); + + ClearNativeObserver(env, caller, "observerHandle"); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp index e6dd5c7b..8db7b672 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp @@ -74,7 +74,14 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_registerObserver webrtc::DtmfSenderInterface * sender = GetHandle(env, caller); CHECK_HANDLE(sender); - sender->RegisterObserver(new jni::RTCDtmfSenderObserver(env, jni::JavaGlobalRef(env, jObserver))); + // Unregister and free a previously registered observer before replacing + // it, otherwise it would leak along with its JNI global reference. + sender->UnregisterObserver(); + + auto observer = new jni::RTCDtmfSenderObserver(env, jni::JavaGlobalRef(env, jObserver)); + ReplaceNativeObserver(env, caller, "observerHandle", observer); + + sender->RegisterObserver(observer); } JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_unregisterObserver @@ -84,4 +91,6 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_unregisterObserver CHECK_HANDLE(sender); sender->UnregisterObserver(); + + ClearNativeObserver(env, caller, "observerHandle"); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCPeerConnection.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCPeerConnection.cpp index 82215244..455306b6 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCPeerConnection.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCPeerConnection.cpp @@ -54,7 +54,7 @@ JNIEXPORT jobjectArray JNICALL Java_dev_onvoid_webrtc_RTCPeerConnection_getSende jni::JavaLocalRef objectArray; try { - objectArray = jni::createObjectArray(env, pc->GetSenders()); + objectArray = jni::createOwningObjectArray(env, pc->GetSenders()); } catch (...) { ThrowCxxJavaException(env); @@ -72,7 +72,7 @@ JNIEXPORT jobjectArray JNICALL Java_dev_onvoid_webrtc_RTCPeerConnection_getRecei jni::JavaLocalRef objectArray; try { - objectArray = jni::createObjectArray(env, pc->GetReceivers()); + objectArray = jni::createOwningObjectArray(env, pc->GetReceivers()); } catch (...) { ThrowCxxJavaException(env); @@ -90,7 +90,7 @@ JNIEXPORT jobjectArray JNICALL Java_dev_onvoid_webrtc_RTCPeerConnection_getTrans jni::JavaLocalRef objectArray; try { - objectArray = jni::createObjectArray(env, pc->GetTransceivers()); + objectArray = jni::createOwningObjectArray(env, pc->GetTransceivers()); } catch (...) { ThrowCxxJavaException(env); @@ -578,12 +578,11 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCPeerConnection_close SetHandle(env, caller, nullptr); - auto observer = GetHandle(env, caller, "observerHandle"); + ClearNativeObserver(env, caller, "observerHandle"); - if (observer) { - SetHandle(env, caller, "observerHandle", nullptr); - delete observer; - } + // Drop the owning reference taken when the PeerConnection was handed + // to Java in PeerConnectionFactory::createPeerConnection. + pc->Release(); } catch (...) { ThrowCxxJavaException(env); diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp index 7e74734d..41a9ca0d 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp @@ -96,4 +96,20 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_getSynchronizati auto list = jni::JavaList::toArrayList(env, ssrc, jni::RTCRtpSynchronizationSource::toJava); return list.release(); +} + +JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_dispose +(JNIEnv * env, jobject caller) +{ + webrtc::RtpReceiverInterface * receiver = GetHandle(env, caller); + CHECK_HANDLE(receiver); + + // Unlike e.g. MediaStreamTrack, an RTCRtpReceiver is not exclusively + // owned by one Java wrapper: the owning RtpTransceiver keeps its own + // reference, and other Java wrappers may have been obtained via separate + // getReceivers()/getReceiver() calls. Dropping our reference here is + // expected to leave others around, so it is not reported as an error. + receiver->Release(); + + SetHandle(env, caller, nullptr); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp index 2899121e..634abae1 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp @@ -120,4 +120,20 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getDtmfSender SetHandle(env, jDtmfSender, dtmfSender.get()); return jDtmfSender.release(); +} + +JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_dispose +(JNIEnv * env, jobject caller) +{ + webrtc::RtpSenderInterface * sender = GetHandle(env, caller); + CHECK_HANDLE(sender); + + // Unlike e.g. MediaStreamTrack, an RTCRtpSender is not exclusively owned + // by one Java wrapper: the owning RtpTransceiver keeps its own reference, + // and other Java wrappers may have been obtained via separate + // getSenders()/getSender() calls. Dropping our reference here is + // expected to leave others around, so it is not reported as an error. + sender->Release(); + + SetHandle(env, caller, nullptr); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpTransceiver.cpp b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpTransceiver.cpp index d752bb6e..be116e24 100644 --- a/webrtc-jni/src/main/cpp/src/JNI_RTCRtpTransceiver.cpp +++ b/webrtc-jni/src/main/cpp/src/JNI_RTCRtpTransceiver.cpp @@ -45,7 +45,10 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_getSender webrtc::scoped_refptr sender = transceiver->sender(); - return jni::JavaFactories::create(env, sender.get()).release(); + // Transfer the reference this local scoped_refptr holds into the Java + // wrapper, which owns and disposes it -- otherwise the wrapper would be + // left with a pointer nothing on the Java/JNI side keeps alive. + return jni::JavaFactories::create(env, sender.release()).release(); } JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_getReceiver @@ -56,7 +59,10 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_getReceiver webrtc::scoped_refptr receiver = transceiver->receiver(); - return jni::JavaFactories::create(env, receiver.get()).release(); + // Transfer the reference this local scoped_refptr holds into the Java + // wrapper, which owns and disposes it -- otherwise the wrapper would be + // left with a pointer nothing on the Java/JNI side keeps alive. + return jni::JavaFactories::create(env, receiver.release()).release(); } JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_getDirection @@ -150,4 +156,20 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_setCodecPreferen catch (...) { ThrowCxxJavaException(env); } +} + +JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpTransceiver_dispose +(JNIEnv * env, jobject caller) +{ + webrtc::RtpTransceiverInterface * transceiver = GetHandle(env, caller); + CHECK_HANDLE(transceiver); + + // Unlike e.g. MediaStreamTrack, an RTCRtpTransceiver is not exclusively + // owned by one Java wrapper: the owning PeerConnection keeps its own + // reference, and other Java wrappers may have been obtained via separate + // getTransceivers() calls. Dropping our reference here is expected to + // leave others around, so it is not reported as an error. + transceiver->Release(); + + SetHandle(env, caller, nullptr); } \ No newline at end of file diff --git a/webrtc-jni/src/main/cpp/src/api/AudioTrackSink.cpp b/webrtc-jni/src/main/cpp/src/api/AudioTrackSink.cpp index 54705428..957668f9 100644 --- a/webrtc-jni/src/main/cpp/src/api/AudioTrackSink.cpp +++ b/webrtc-jni/src/main/cpp/src/api/AudioTrackSink.cpp @@ -30,6 +30,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + const jbyte * buffer = static_cast(data); jsize dataSize = static_cast(frames * channels * (bitsPerSample / 8)); diff --git a/webrtc-jni/src/main/cpp/src/api/CreateSessionDescriptionObserver.cpp b/webrtc-jni/src/main/cpp/src/api/CreateSessionDescriptionObserver.cpp index e01591e5..52d89755 100644 --- a/webrtc-jni/src/main/cpp/src/api/CreateSessionDescriptionObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/CreateSessionDescriptionObserver.cpp @@ -32,6 +32,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef javaDesc = jni::RTCSessionDescription::toJava(env, desc); env->CallVoidMethod(observer, javaClass->onSuccess, javaDesc.get()); @@ -43,6 +47,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef errorMessage = JavaString::toJava(env, RTCErrorToString(error)); env->CallVoidMethod(observer, javaClass->onFailure, errorMessage.get()); diff --git a/webrtc-jni/src/main/cpp/src/api/PeerConnectionObserver.cpp b/webrtc-jni/src/main/cpp/src/api/PeerConnectionObserver.cpp index 6c752129..c89f88bf 100644 --- a/webrtc-jni/src/main/cpp/src/api/PeerConnectionObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/PeerConnectionObserver.cpp @@ -40,6 +40,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto jState = JavaEnums::toJava(env, state); env->CallVoidMethod(observer, javaClass->onConnectionChange, jState.get()); @@ -51,6 +55,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto jState = JavaEnums::toJava(env, state); env->CallVoidMethod(observer, javaClass->onSignalingChange, jState.get()); @@ -62,7 +70,15 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); - auto jTransceiver = JavaFactories::create(env, transceiver.get()); + if (env == nullptr) { + return; + } + + // transceiver is passed by value, so this call owns one reference on + // it; transfer that reference into the Java wrapper, which disposes + // it, instead of releasing it right back when transceiver goes out + // of scope and leaving the wrapper's pointer unowned. + auto jTransceiver = JavaFactories::create(env, transceiver.release()); env->CallVoidMethod(observer, javaClass->onTrack, jTransceiver.get()); @@ -73,12 +89,20 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef streamArray; try { streamArray = createObjectArray(env, streams); - auto jReceiver = JavaFactories::create(env, receiver.get()); + // receiver is passed by value, so this call owns one reference on + // it; transfer that reference into the Java wrapper, which + // disposes it, instead of releasing it right back when receiver + // goes out of scope and leaving the wrapper's pointer unowned. + auto jReceiver = JavaFactories::create(env, receiver.release()); env->CallVoidMethod(observer, javaClass->onAddTrack, jReceiver.get(), streamArray.get()); } @@ -93,7 +117,15 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); - auto jReceiver = JavaFactories::create(env, receiver.get()); + if (env == nullptr) { + return; + } + + // receiver is passed by value, so this call owns one reference on + // it; transfer that reference into the Java wrapper, which disposes + // it, instead of releasing it right back when receiver goes out of + // scope and leaving the wrapper's pointer unowned. + auto jReceiver = JavaFactories::create(env, receiver.release()); env->CallVoidMethod(observer, javaClass->onRemoveTrack, jReceiver.get()); @@ -104,6 +136,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto jDataChannel = JavaFactories::create(env, channel.release()); env->CallVoidMethod(observer, javaClass->onDataChannel, jDataChannel.get()); @@ -115,6 +151,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + env->CallVoidMethod(observer, javaClass->onRenegotiationNeeded); ExceptionCheck(env); @@ -124,6 +164,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto jState = JavaEnums::toJava(env, state); env->CallVoidMethod(observer, javaClass->onIceConnectionChange, jState.get()); @@ -135,6 +179,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto jState = JavaEnums::toJava(env, state); env->CallVoidMethod(observer, javaClass->onIceGatheringChange, jState.get()); @@ -146,6 +194,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef jCandidate = RTCIceCandidate::toJava(env, candidate); env->CallVoidMethod(observer, javaClass->onIceCandidate, jCandidate.get()); @@ -157,6 +209,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef event = RTCPeerConnectionIceErrorEvent::toJava(env, address, port, url, error_code, error_text); env->CallVoidMethod(observer, javaClass->onIceCandidateError, event.get()); @@ -168,6 +224,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + env->CallVoidMethod(observer, javaClass->onIceConnectionReceivingChange, receiving); ExceptionCheck(env); @@ -177,6 +237,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + const webrtc::Candidate & remote = event.selected_candidate_pair.remote_candidate(); std::string ip = remote.address().ipaddr().ToString(); diff --git a/webrtc-jni/src/main/cpp/src/api/PortAllocatorConfig.cpp b/webrtc-jni/src/main/cpp/src/api/PortAllocatorConfig.cpp index 8f5f9a6a..e4c24cf7 100644 --- a/webrtc-jni/src/main/cpp/src/api/PortAllocatorConfig.cpp +++ b/webrtc-jni/src/main/cpp/src/api/PortAllocatorConfig.cpp @@ -28,14 +28,19 @@ namespace jni { const auto javaClass = JavaClasses::get(env); - jobject jpac = env->NewObject(javaClass->cls, javaClass->ctor); + JavaLocalRef jpac(env, env->NewObject(javaClass->cls, javaClass->ctor)); - JavaObject obj(env, JavaLocalRef(env, jpac)); + // Keep jpac alive for the whole function instead of wrapping the + // raw jobject in two separate JavaLocalRef instances: the first + // one's destructor would delete the local ref at the end of its + // full expression, leaving the second (returned) one wrapping an + // already-deleted reference. + JavaObject obj(env, jpac); obj.setInt(javaClass->minPort, cfg.min_port); obj.setInt(javaClass->maxPort, cfg.max_port); obj.setInt(javaClass->flags, cfg.flags); - return JavaLocalRef(env, jpac); + return jpac; } JavaPortAllocatorConfigClass::JavaPortAllocatorConfigClass(JNIEnv * env) diff --git a/webrtc-jni/src/main/cpp/src/api/RTCDataChannelObserver.cpp b/webrtc-jni/src/main/cpp/src/api/RTCDataChannelObserver.cpp index 5c0258ea..6333bd06 100644 --- a/webrtc-jni/src/main/cpp/src/api/RTCDataChannelObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/RTCDataChannelObserver.cpp @@ -32,6 +32,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + env->CallVoidMethod(observer, javaClass->onStateChange); ExceptionCheck(env); @@ -41,6 +45,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef jBuffer = bufferFactory->create(env, &buffer); env->CallVoidMethod(observer, javaClass->onMessage, jBuffer.get()); @@ -52,6 +60,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + env->CallVoidMethod(observer, javaClass->onBufferedAmountChange, static_cast(sent_data_size)); ExceptionCheck(env); diff --git a/webrtc-jni/src/main/cpp/src/api/RTCDtlsTransportObserver.cpp b/webrtc-jni/src/main/cpp/src/api/RTCDtlsTransportObserver.cpp index 7ebdf739..f72d350f 100644 --- a/webrtc-jni/src/main/cpp/src/api/RTCDtlsTransportObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/RTCDtlsTransportObserver.cpp @@ -34,6 +34,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + auto state = JavaEnums::toJava(env, info.state()); env->CallVoidMethod(observer, javaClass->onStateChange, state.get()); @@ -45,6 +49,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef errorMessage = JavaString::toJava(env, RTCErrorToString(error)); env->CallVoidMethod(observer, javaClass->onError, errorMessage.get()); diff --git a/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp index 92cc9986..a0a54960 100644 --- a/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/RTCDtmfSenderObserver.cpp @@ -32,6 +32,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef jTone = JavaString::toJava(env, tone); JavaLocalRef jToneBuffer = JavaString::toJava(env, tone_buffer); diff --git a/webrtc-jni/src/main/cpp/src/api/RTCStatsCollectorCallback.cpp b/webrtc-jni/src/main/cpp/src/api/RTCStatsCollectorCallback.cpp index c6e152b2..5bfe61c0 100644 --- a/webrtc-jni/src/main/cpp/src/api/RTCStatsCollectorCallback.cpp +++ b/webrtc-jni/src/main/cpp/src/api/RTCStatsCollectorCallback.cpp @@ -31,6 +31,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef javaReport = jni::RTCStatsReport::toJava(env, report); env->CallVoidMethod(callback, javaClass->onStatsDelivered, javaReport.get()); diff --git a/webrtc-jni/src/main/cpp/src/api/SetSessionDescriptionObserver.cpp b/webrtc-jni/src/main/cpp/src/api/SetSessionDescriptionObserver.cpp index d7bb1a22..1ea9ac00 100644 --- a/webrtc-jni/src/main/cpp/src/api/SetSessionDescriptionObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/api/SetSessionDescriptionObserver.cpp @@ -32,6 +32,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + env->CallVoidMethod(observer, javaClass->onSuccess); ExceptionCheck(env); @@ -41,6 +45,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + JavaLocalRef errorMessage = JavaString::toJava(env, RTCErrorToString(error)); env->CallVoidMethod(observer, javaClass->onFailure, errorMessage.get()); diff --git a/webrtc-jni/src/main/cpp/src/api/VideoTrackSink.cpp b/webrtc-jni/src/main/cpp/src/api/VideoTrackSink.cpp index 97d4e7e8..0c343d04 100644 --- a/webrtc-jni/src/main/cpp/src/api/VideoTrackSink.cpp +++ b/webrtc-jni/src/main/cpp/src/api/VideoTrackSink.cpp @@ -35,6 +35,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + webrtc::scoped_refptr buffer = frame.video_frame_buffer(); webrtc::scoped_refptr i420Buffer = buffer->ToI420(); @@ -53,7 +57,8 @@ namespace jni env->CallVoidMethod(sink, javaClass->onFrame, jFrame); ExceptionCheck(env); - env->DeleteLocalRef(jBuffer); + // jBuffer is a JavaLocalRef and deletes its own local reference on + // scope exit; deleting it again here would be a double free. env->DeleteLocalRef(jFrame); } diff --git a/webrtc-jni/src/main/cpp/src/media/DeviceChangeListener.cpp b/webrtc-jni/src/main/cpp/src/media/DeviceChangeListener.cpp index ea5b8513..a7abaa7a 100644 --- a/webrtc-jni/src/main/cpp/src/media/DeviceChangeListener.cpp +++ b/webrtc-jni/src/main/cpp/src/media/DeviceChangeListener.cpp @@ -33,6 +33,11 @@ namespace jni void DeviceChangeListener::deviceConnected(avdev::DevicePtr device) { JNIEnv * env = AttachCurrentThread(); + + if (env == nullptr) { + return; + } + JavaLocalRef jdevice = nullptr; if (dynamic_cast(device.get())) { @@ -51,6 +56,11 @@ namespace jni void DeviceChangeListener::deviceDisconnected(avdev::DevicePtr device) { JNIEnv * env = AttachCurrentThread(); + + if (env == nullptr) { + return; + } + JavaLocalRef jdevice = nullptr; if (dynamic_cast(device.get())) { diff --git a/webrtc-jni/src/main/cpp/src/media/MediaStreamTrackObserver.cpp b/webrtc-jni/src/main/cpp/src/media/MediaStreamTrackObserver.cpp index 762683a4..faba1f5d 100644 --- a/webrtc-jni/src/main/cpp/src/media/MediaStreamTrackObserver.cpp +++ b/webrtc-jni/src/main/cpp/src/media/MediaStreamTrackObserver.cpp @@ -35,6 +35,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + // Check state changes. if (eventType == MediaStreamTrackEvent::mute) { diff --git a/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSink.cpp b/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSink.cpp index 1a517690..48fb6170 100644 --- a/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSink.cpp +++ b/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSink.cpp @@ -39,6 +39,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return 0; + } + const jbyte * buffer = static_cast(audioSamples); jsize dataSize = static_cast(nSamples * nBytesPerSample); diff --git a/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSource.cpp b/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSource.cpp index 3b1672db..dca821b8 100644 --- a/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSource.cpp +++ b/webrtc-jni/src/main/cpp/src/media/audio/AudioTransportSource.cpp @@ -40,6 +40,11 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + nSamplesOut = 0; + return 0; + } + *elapsed_time_ms = 0; *ntp_time_ms = 0; diff --git a/webrtc-jni/src/main/cpp/src/media/video/desktop/DesktopCaptureCallback.cpp b/webrtc-jni/src/main/cpp/src/media/video/desktop/DesktopCaptureCallback.cpp index ac29ddb5..2abebde3 100644 --- a/webrtc-jni/src/main/cpp/src/media/video/desktop/DesktopCaptureCallback.cpp +++ b/webrtc-jni/src/main/cpp/src/media/video/desktop/DesktopCaptureCallback.cpp @@ -43,6 +43,10 @@ namespace jni { JNIEnv * env = AttachCurrentThread(); + if (env == nullptr) { + return; + } + if (result != webrtc::DesktopCapturer::Result::SUCCESS) { // Propagate the failure to Java instead of silently dropping it — // callers waiting for a frame otherwise have to rely on timeouts. @@ -115,7 +119,8 @@ namespace jni env->CallVoidMethod(callback, javaClass->onCaptureResult, jresult.get(), jFrame); ExceptionCheck(env); - env->DeleteLocalRef(jBuffer); + // jBuffer is a JavaLocalRef and deletes its own local reference on + // scope exit; deleting it again here would be a double free. env->DeleteLocalRef(jFrame); } diff --git a/webrtc/pom.xml b/webrtc/pom.xml index 00946f51..a758f783 100644 --- a/webrtc/pom.xml +++ b/webrtc/pom.xml @@ -31,6 +31,53 @@ + + + jni-check + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + -Xcheck:jni + --add-opens webrtc.java/dev.onvoid.webrtc=ALL-UNNAMED + --add-opens webrtc.java/dev.onvoid.webrtc.logging=ALL-UNNAMED + --add-opens webrtc.java/dev.onvoid.webrtc.media=ALL-UNNAMED + --add-opens webrtc.java/dev.onvoid.webrtc.media.audio=ALL-UNNAMED + --add-opens webrtc.java/dev.onvoid.webrtc.media.video=ALL-UNNAMED + + + + + + +