From c384e1470772722eee42305e66829ba40714e349 Mon Sep 17 00:00:00 2001 From: Seth Hampson Date: Tue, 6 Mar 2018 15:47:10 -0800 Subject: [PATCH] Add new PeerConnection APIs to the Java SDK. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds wrappers to the following native APIs: - SdpSemantics enum added to the RTCConfiguration - RtpTransceiver - PeerConnection.addTrack - PeerConnection.removeTrack - PeerConnection.addTransceiver - PeerConnection.getTransceivers These APIs are used with the new Unified Plan semantics. Bug: webrtc:8869 Change-Id: I19443f3ff7ffc91a139ad8276331f09e57cec554 Reviewed-on: https://webrtc-review.googlesource.com/57800 Commit-Queue: Seth Hampson Reviewed-by: Sami Kalliomäki Reviewed-by: Steve Anton Cr-Commit-Position: refs/heads/master@{#22317} --- sdk/android/BUILD.gn | 4 + sdk/android/api/org/webrtc/MediaStream.java | 12 +- .../api/org/webrtc/PeerConnection.java | 270 +++++++++++++++--- .../api/org/webrtc/RtpTransceiver.java | 215 ++++++++++++++ sdk/android/native_api/jni/java_types.cc | 6 + sdk/android/native_api/jni/java_types.h | 20 +- sdk/android/src/jni/pc/mediastream.cc | 6 +- sdk/android/src/jni/pc/peerconnection.cc | 115 +++++++- sdk/android/src/jni/pc/rtpparameters.cc | 26 +- sdk/android/src/jni/pc/rtpparameters.h | 4 + sdk/android/src/jni/pc/rtptransceiver.cc | 151 ++++++++++ sdk/android/src/jni/pc/rtptransceiver.h | 33 +++ 12 files changed, 799 insertions(+), 63 deletions(-) create mode 100644 sdk/android/api/org/webrtc/RtpTransceiver.java create mode 100644 sdk/android/src/jni/pc/rtptransceiver.cc create mode 100644 sdk/android/src/jni/pc/rtptransceiver.h diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 8aa39e228c..00ce2f0f94 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -423,6 +423,7 @@ generate_jni("generated_peerconnection_jni") { "api/org/webrtc/RtpParameters.java", "api/org/webrtc/RtpReceiver.java", "api/org/webrtc/RtpSender.java", + "api/org/webrtc/RtpTransceiver.java", "api/org/webrtc/SdpObserver.java", "api/org/webrtc/SessionDescription.java", "api/org/webrtc/StatsObserver.java", @@ -471,6 +472,8 @@ rtc_static_library("peerconnection_jni") { "src/jni/pc/rtpreceiver.h", "src/jni/pc/rtpsender.cc", "src/jni/pc/rtpsender.h", + "src/jni/pc/rtptransceiver.cc", + "src/jni/pc/rtptransceiver.h", "src/jni/pc/sdpobserver.cc", "src/jni/pc/sdpobserver.h", "src/jni/pc/sessiondescription.cc", @@ -669,6 +672,7 @@ rtc_android_library("libjingle_peerconnection_java") { "api/org/webrtc/RtpParameters.java", "api/org/webrtc/RtpReceiver.java", "api/org/webrtc/RtpSender.java", + "api/org/webrtc/RtpTransceiver.java", "api/org/webrtc/ScreenCapturerAndroid.java", "api/org/webrtc/SdpObserver.java", "api/org/webrtc/SessionDescription.java", diff --git a/sdk/android/api/org/webrtc/MediaStream.java b/sdk/android/api/org/webrtc/MediaStream.java index d66d844936..e04fabdd85 100644 --- a/sdk/android/api/org/webrtc/MediaStream.java +++ b/sdk/android/api/org/webrtc/MediaStream.java @@ -88,13 +88,19 @@ public class MediaStream { JniCommon.nativeReleaseRef(nativeStream); } + // TODO(shampson): Remove this when downstreams have moved to using id() instead. + @Deprecated public String label() { - return nativeGetLabel(nativeStream); + return nativeGetId(nativeStream); + } + + public String getId() { + return nativeGetId(nativeStream); } @Override public String toString() { - return "[" + label() + ":A=" + audioTracks.size() + ":V=" + videoTracks.size() + "]"; + return "[" + getId() + ":A=" + audioTracks.size() + ":V=" + videoTracks.size() + "]"; } @CalledByNative @@ -137,5 +143,5 @@ public class MediaStream { long stream, long nativeVideoTrack); private static native boolean nativeRemoveAudioTrack(long stream, long nativeAudioTrack); private static native boolean nativeRemoveVideoTrack(long stream, long nativeVideoTrack); - private static native String nativeGetLabel(long stream); + private static native String nativeGetId(long stream); } diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java index de1e57d787..603b36e371 100644 --- a/sdk/android/api/org/webrtc/PeerConnection.java +++ b/sdk/android/api/org/webrtc/PeerConnection.java @@ -341,6 +341,33 @@ public class PeerConnection { } } + /** + * Java version of webrtc::SdpSemantics. + * + * Configure the SDP semantics used by this PeerConnection. Note that the + * WebRTC 1.0 specification requires UNIFIED_PLAN semantics. The + * RtpTransceiver API is only available with UNIFIED_PLAN semantics. + * + *

PLAN_B will cause PeerConnection to create offers and answers with at + * most one audio and one video m= section with multiple RtpSenders and + * RtpReceivers specified as multiple a=ssrc lines within the section. This + * will also cause PeerConnection to ignore all but the first m= section of + * the same media type. + * + *

UNIFIED_PLAN will cause PeerConnection to create offers and answers with + * multiple m= sections where each m= section maps to one RtpSender and one + * RtpReceiver (an RtpTransceiver), either both audio or both video. This + * will also cause PeerConnection to ignore all but the first a=ssrc lines + * that form a Plan B stream. + * + *

For users who wish to send multiple audio/video streams and need to stay + * interoperable with legacy WebRTC implementations, specify PLAN_B. + * + *

For users who wish to send multiple audio/video streams and/or wish to + * use the new RtpTransceiver API, specify UNIFIED_PLAN. + */ + public enum SdpSemantics { PLAN_B, UNIFIED_PLAN } + /** Java version of PeerConnectionInterface.RTCConfiguration */ // TODO(qingsi): Resolve the naming inconsistency of fields with/without units. public static class RTCConfiguration { @@ -386,6 +413,7 @@ public class PeerConnection { // Use "Unknown" to represent no preference of adapter types, not the // preference of adapters of unknown types. public AdapterType networkPreference; + public SdpSemantics sdpSemantics; // This is an optional wrapper for the C++ webrtc::TurnCustomizer. public TurnCustomizer turnCustomizer; @@ -423,6 +451,7 @@ public class PeerConnection { combinedAudioVideoBwe = null; enableDtlsSrtp = null; networkPreference = AdapterType.UNKNOWN; + sdpSemantics = SdpSemantics.PLAN_B; } @CalledByNative("RTCConfiguration") @@ -574,12 +603,18 @@ public class PeerConnection { AdapterType getNetworkPreference() { return networkPreference; } + + @CalledByNative("RTCConfiguration") + SdpSemantics getSdpSemantics() { + return sdpSemantics; + } }; private final List localStreams = new ArrayList<>(); private final long nativePeerConnection; private List senders = new ArrayList<>(); private List receivers = new ArrayList<>(); + private List transceivers = new ArrayList<>(); /** * Wraps a PeerConnection created by the factory. Can be used by clients that want to implement @@ -622,18 +657,24 @@ public class PeerConnection { nativeSetRemoteDescription(observer, sdp); } - // True if remote audio should be played out. Defaults to true. - // Note that even if playout is enabled, streams will only be played out if - // the appropriate SDP is also applied. The main purpose of this API is to - // be able to control the exact time when audio playout starts. + /** + * Enables/disables playout of received audio streams. Enabled by default. + * + * Note that even if playout is enabled, streams will only be played out if + * the appropriate SDP is also applied. The main purpose of this API is to + * be able to control the exact time when audio playout starts. + */ public void setAudioPlayout(boolean playout) { nativeSetAudioPlayout(playout); } - // True if local audio shall be recorded. Defaults to true. - // Note that even if recording is enabled, streams will only be recorded if - // the appropriate SDP is also applied. The main purpose of this API is to - // be able to control the exact time when audio recording starts. + /** + * Enables/disables recording of transmitted audio streams. Enabled by default. + * + * Note that even if recording is enabled, streams will only be recorded if + * the appropriate SDP is also applied. The main purpose of this API is to + * be able to control the exact time when audio recording starts. + */ public void setAudioRecording(boolean recording) { nativeSetAudioRecording(recording); } @@ -650,6 +691,11 @@ public class PeerConnection { return nativeRemoveIceCandidates(candidates); } + /** + * Adds a new MediaStream to be sent on this peer connection. + * Note: This method is not supported with SdpSemantics.UNIFIED_PLAN. Please + * use addTrack instead. + */ public boolean addStream(MediaStream stream) { boolean ret = nativeAddLocalStream(stream.nativeStream); if (!ret) { @@ -659,6 +705,11 @@ public class PeerConnection { return true; } + /** + * Removes the given media stream from this peer connection. + * This method is not supported with SdpSemantics.UNIFIED_PLAN. Please use + * removeTrack instead. + */ public void removeStream(MediaStream stream) { nativeRemoveLocalStream(stream.nativeStream); localStreams.remove(stream); @@ -666,15 +717,15 @@ public class PeerConnection { /** * Creates an RtpSender without a track. - *

- * This method allows an application to cause the PeerConnection to negotiate + * + *

This method allows an application to cause the PeerConnection to negotiate * sending/receiving a specific media type, but without having a track to * send yet. - *

- * When the application does want to begin sending a track, it can call + * + *

When the application does want to begin sending a track, it can call * RtpSender.setTrack, which doesn't require any additional SDP negotiation. - *

- * Example use: + * + *

Example use: *

    * {@code
    * audioSender = pc.createSender("audio", "stream1");
@@ -686,12 +737,15 @@ public class PeerConnection {
    * videoSender.setTrack(videoTrack, false);
    * }
    * 
- * Note: This corresponds most closely to "addTransceiver" in the official + *

Note: This corresponds most closely to "addTransceiver" in the official * WebRTC API, in that it creates a sender without a track. It was * implemented before addTransceiver because it provides useful * functionality, and properly implementing transceivers would have required * a great deal more work. * + *

Note: This is only available with SdpSemantics.PLAN_B specified. Please use + * addTransceiver instead. + * * @param kind Corresponds to MediaStreamTrack kinds (must be "audio" or * "video"). * @param stream_id The ID of the MediaStream that this sender's track will @@ -702,15 +756,18 @@ public class PeerConnection { * @return A new RtpSender object if successful, or null otherwise. */ public RtpSender createSender(String kind, String stream_id) { - RtpSender new_sender = nativeCreateSender(kind, stream_id); - if (new_sender != null) { - senders.add(new_sender); + RtpSender newSender = nativeCreateSender(kind, stream_id); + if (newSender != null) { + senders.add(newSender); } - return new_sender; + return newSender; } - // Note that calling getSenders will dispose of the senders previously - // returned (and same goes for getReceivers). + /** + * Gets all RtpSenders associated with this peer connection. + * Note that calling getSenders will dispose of the senders previously + * returned. + */ public List getSenders() { for (RtpSender sender : senders) { sender.dispose(); @@ -719,6 +776,11 @@ public class PeerConnection { return Collections.unmodifiableList(senders); } + /** + * Gets all RtpReceivers associated with this peer connection. + * Note that calling getReceivers will dispose of the receivers previously + * returned. + */ public List getReceivers() { for (RtpReceiver receiver : receivers) { receiver.dispose(); @@ -727,35 +789,162 @@ public class PeerConnection { return Collections.unmodifiableList(receivers); } + /** + * Gets all RtpTransceivers associated with this peer connection. + * Note that calling getTransceivers will dispose of the transceivers previously + * returned. + * Note: This is only available with SdpSemantics.UNIFIED_PLAN specified. + */ + public List getTransceivers() { + for (RtpTransceiver transceiver : transceivers) { + transceiver.dispose(); + } + transceivers = nativeGetTransceivers(); + return Collections.unmodifiableList(transceivers); + } + + /** + * Adds a new media stream track to be sent on this peer connection, and returns + * the newly created RtpSender. If streamIds are specified, the RtpSender will + * be associated with the streams specified in the streamIds list. + * + * @throws IllegalStateException if an error accors in C++ addTrack. + * An error can occur if: + * - A sender already exists for the track. + * - The peer connection is closed. + */ + public RtpSender addTrack(MediaStreamTrack track) { + return addTrack(track, Collections.emptyList()); + } + + public RtpSender addTrack(MediaStreamTrack track, List streamIds) { + if (track == null || streamIds == null) { + throw new NullPointerException("No MediaStreamTrack specified in addTrack."); + } + RtpSender newSender = nativeAddTrack(track.nativeTrack, streamIds); + if (newSender == null) { + throw new IllegalStateException("C++ addTrack failed."); + } + senders.add(newSender); + return newSender; + } + + /** + * Stops sending media from sender. The sender will still appear in getSenders. Future + * calls to createOffer will mark the m section for the corresponding transceiver as + * receive only or inactive, as defined in JSEP. Returns true on success. + */ + public boolean removeTrack(RtpSender sender) { + if (sender == null) { + throw new NullPointerException("No RtpSender specified for removeTrack."); + } + return nativeRemoveTrack(sender.nativeRtpSender); + } + + /** + * Creates a new RtpTransceiver and adds it to the set of transceivers. Adding a + * transceiver will cause future calls to CreateOffer to add a media description + * for the corresponding transceiver. + * + *

The initial value of |mid| in the returned transceiver is null. Setting a + * new session description may change it to a non-null value. + * + *

https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver + * + *

If a MediaStreamTrack is specified then a transceiver will be added with a + * sender set to transmit the given track. The kind + * of the transceiver (and sender/receiver) will be derived from the kind of + * the track. + * + *

If MediaType is specified then a transceiver will be added based upon that type. + * This can be either MEDIA_TYPE_AUDIO or MEDIA_TYPE_VIDEO. + * + *

Optionally, an RtpTransceiverInit structure can be specified to configure + * the transceiver from construction. If not specified, the transceiver will + * default to having a direction of kSendRecv and not be part of any streams. + * + *

Note: These methods are only available with SdpSemantics.UNIFIED_PLAN specified. + * @throws IllegalStateException if an error accors in C++ addTransceiver + */ + public RtpTransceiver addTransceiver(MediaStreamTrack track) { + return addTransceiver(track, new RtpTransceiver.RtpTransceiverInit()); + } + + public RtpTransceiver addTransceiver( + MediaStreamTrack track, RtpTransceiver.RtpTransceiverInit init) { + if (track == null) { + throw new NullPointerException("No MediaStreamTrack specified for addTransceiver."); + } + if (init == null) { + init = new RtpTransceiver.RtpTransceiverInit(); + } + RtpTransceiver newTransceiver = nativeAddTransceiverWithTrack(track.nativeTrack, init); + if (newTransceiver == null) { + throw new IllegalStateException("C++ addTransceiver failed."); + } + transceivers.add(newTransceiver); + return newTransceiver; + } + + public RtpTransceiver addTransceiver(MediaStreamTrack.MediaType mediaType) { + return addTransceiver(mediaType, new RtpTransceiver.RtpTransceiverInit()); + } + + public RtpTransceiver addTransceiver( + MediaStreamTrack.MediaType mediaType, RtpTransceiver.RtpTransceiverInit init) { + if (mediaType == null) { + throw new NullPointerException("No MediaType specified for addTransceiver."); + } + if (init == null) { + init = new RtpTransceiver.RtpTransceiverInit(); + } + RtpTransceiver newTransceiver = nativeAddTransceiverOfType(mediaType, init); + if (newTransceiver == null) { + throw new IllegalStateException("C++ addTransceiver failed."); + } + transceivers.add(newTransceiver); + return newTransceiver; + } + // Older, non-standard implementation of getStats. @Deprecated public boolean getStats(StatsObserver observer, MediaStreamTrack track) { return nativeOldGetStats(observer, (track == null) ? 0 : track.nativeTrack); } - // Gets stats using the new stats collection API, see webrtc/api/stats/. These - // will replace old stats collection API when the new API has matured enough. + /** + * Gets stats using the new stats collection API, see webrtc/api/stats/. These + * will replace old stats collection API when the new API has matured enough. + */ public void getStats(RTCStatsCollectorCallback callback) { nativeNewGetStats(callback); } - // Limits the bandwidth allocated for all RTP streams sent by this - // PeerConnection. Pass null to leave a value unchanged. + /** + * Limits the bandwidth allocated for all RTP streams sent by this + * PeerConnection. Pass null to leave a value unchanged. + */ public boolean setBitrate(Integer min, Integer current, Integer max) { return nativeSetBitrate(min, current, max); } - // Starts recording an RTC event log. Ownership of the file is transfered to - // the native code. If an RTC event log is already being recorded, it will be - // stopped and a new one will start using the provided file. Logging will - // continue until the stopRtcEventLog function is called. The max_size_bytes - // argument is ignored, it is added for future use. + /** + * Starts recording an RTC event log. + * + * Ownership of the file is transfered to the native code. If an RTC event + * log is already being recorded, it will be stopped and a new one will start + * using the provided file. Logging will continue until the stopRtcEventLog + * function is called. The max_size_bytes argument is ignored, it is added + * for future use. + */ public boolean startRtcEventLog(int file_descriptor, int max_size_bytes) { return nativeStartRtcEventLog(file_descriptor, max_size_bytes); } - // Stops recording an RTC event log. If no RTC event log is currently being - // recorded, this call will have no effect. + /** + * Stops recording an RTC event log. If no RTC event log is currently being + * recorded, this call will have no effect. + */ public void stopRtcEventLog() { nativeStopRtcEventLog(); } @@ -780,14 +969,14 @@ public class PeerConnection { /** * Free native resources associated with this PeerConnection instance. - *

+ * * This method removes a reference count from the C++ PeerConnection object, * which should result in it being destroyed. It also calls equivalent * "dispose" methods on the Java objects attached to this PeerConnection * (streams, senders, receivers), such that their associated C++ objects * will also be destroyed. - *

- * Note that this method cannot be safely called from an observer callback + * + *

Note that this method cannot be safely called from an observer callback * (PeerConnection.Observer, DataChannel.Observer, etc.). If you want to, for * example, destroy the PeerConnection after an "ICE failed" callback, you * must do this asynchronously (in other words, unwind the stack first). See @@ -808,6 +997,10 @@ public class PeerConnection { for (RtpReceiver receiver : receivers) { receiver.dispose(); } + for (RtpTransceiver transceiver : transceivers) { + transceiver.dispose(); + } + transceivers.clear(); receivers.clear(); nativeFreeOwnedPeerConnection(nativePeerConnection); } @@ -854,6 +1047,13 @@ public class PeerConnection { private native RtpSender nativeCreateSender(String kind, String stream_id); private native List nativeGetSenders(); private native List nativeGetReceivers(); + private native List nativeGetTransceivers(); + private native RtpSender nativeAddTrack(long track, List streamIds); + private native boolean nativeRemoveTrack(long sender); + private native RtpTransceiver nativeAddTransceiverWithTrack( + long track, RtpTransceiver.RtpTransceiverInit init); + private native RtpTransceiver nativeAddTransceiverOfType( + MediaStreamTrack.MediaType mediaType, RtpTransceiver.RtpTransceiverInit init); private native boolean nativeStartRtcEventLog(int file_descriptor, int max_size_bytes); private native void nativeStopRtcEventLog(); } diff --git a/sdk/android/api/org/webrtc/RtpTransceiver.java b/sdk/android/api/org/webrtc/RtpTransceiver.java new file mode 100644 index 0000000000..d77ecb408e --- /dev/null +++ b/sdk/android/api/org/webrtc/RtpTransceiver.java @@ -0,0 +1,215 @@ +/* + * Copyright 2018 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.webrtc.RtpParameters.Encoding; + +/** + * Java wrapper for a C++ RtpTransceiverInterface. + * + *

The RTCRtpTransceiver maps to the RTCRtpTransceiver defined by the WebRTC + * specification. A transceiver represents a combination of an RTCRtpSender + * and an RTCRtpReceiver that share a common mid. As defined in JSEP, an + * RTCRtpTransceiver is said to be associated with a media description if its + * mid property is non-nil; otherwise, it is said to be disassociated. + * JSEP: https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24 + * + *

Note that RTCRtpTransceivers are only supported when using + * RTCPeerConnection with Unified Plan SDP. + * + *

WebRTC specification for RTCRtpTransceiver, the JavaScript analog: + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver + */ +@JNINamespace("webrtc::jni") +public class RtpTransceiver { + /** Java version of webrtc::RtpTransceiverDirection - the ordering must be kept in sync. */ + public enum RtpTransceiverDirection { + SEND_RECV(0), + SEND_ONLY(1), + RECV_ONLY(2), + INACTIVE(3); + + private final int nativeIndex; + + private RtpTransceiverDirection(int nativeIndex) { + this.nativeIndex = nativeIndex; + } + + @CalledByNative("RtpTransceiverDirection") + int getNativeIndex() { + return nativeIndex; + } + + @CalledByNative("RtpTransceiverDirection") + static RtpTransceiverDirection fromNativeIndex(int nativeIndex) { + for (RtpTransceiverDirection type : RtpTransceiverDirection.values()) { + if (type.getNativeIndex() == nativeIndex) { + return type; + } + } + throw new IllegalArgumentException( + "Uknown native RtpTransceiverDirection type" + nativeIndex); + } + } + + /** + * Tracks webrtc::RtpTransceiverInit. https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiverinit + * A structure for initializing an RtpTransceiver in a call to addTransceiver. + * Note: This does not contain a list of encoding parameters, because they are currently + * not being used natively. + */ + public static final class RtpTransceiverInit { + private final RtpTransceiverDirection direction; + private final List streamIds; + + public RtpTransceiverInit() { + this(RtpTransceiverDirection.SEND_RECV); + } + + public RtpTransceiverInit(RtpTransceiverDirection direction) { + this(direction, Collections.emptyList()); + } + + public RtpTransceiverInit(RtpTransceiverDirection direction, List streamIds) { + this.direction = direction; + this.streamIds = new ArrayList(streamIds); + } + + @CalledByNative("RtpTransceiverInit") + int getDirectionNativeIndex() { + return direction.getNativeIndex(); + } + + @CalledByNative("RtpTransceiverInit") + List getStreamIds() { + return new ArrayList(this.streamIds); + } + } + + private final long nativeRtpTransceiver; + private RtpSender cachedSender; + private RtpReceiver cachedReceiver; + + @CalledByNative + protected RtpTransceiver(long nativeRtpTransceiver) { + this.nativeRtpTransceiver = nativeRtpTransceiver; + cachedSender = nativeGetSender(nativeRtpTransceiver); + cachedReceiver = nativeGetReceiver(nativeRtpTransceiver); + } + + /** + * Media type of the transceiver. Any sender(s)/receiver(s) will have this + * type as well. + */ + public MediaStreamTrack.MediaType getMediaType() { + return nativeGetMediaType(nativeRtpTransceiver); + } + + /** + * The mid attribute is the mid negotiated and present in the local and + * remote descriptions. Before negotiation is complete, the mid value may be + * null. After rollbacks, the value may change from a non-null value to null. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-mid + */ + public String getMid() { + return nativeGetMid(nativeRtpTransceiver); + } + + /** + * The sender attribute exposes the RtpSender corresponding to the RTP media + * that may be sent with the transceiver's mid. The sender is always present, + * regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-sender + */ + public RtpSender getSender() { + return cachedSender; + } + + /** + * The receiver attribute exposes the RtpReceiver corresponding to the RTP + * media that may be received with the transceiver's mid. The receiver is + * always present, regardless of the direction of media. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-receiver + */ + public RtpReceiver getReceiver() { + return cachedReceiver; + } + + /** + * The stopped attribute indicates that the sender of this transceiver will no + * longer send, and that the receiver will no longer receive. It is true if + * either stop has been called or if setting the local or remote description + * has caused the RtpTransceiver to be stopped. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stopped + */ + public boolean isStopped() { + return nativeStopped(nativeRtpTransceiver); + } + + /** + * The direction attribute indicates the preferred direction of this + * transceiver, which will be used in calls to CreateOffer and CreateAnswer. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ + public RtpTransceiverDirection getDirection() { + return nativeDirection(nativeRtpTransceiver); + } + + /** + * The current_direction attribute indicates the current direction negotiated + * for this transceiver. If this transceiver has never been represented in an + * offer/answer exchange, or if the transceiver is stopped, the value is null. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-currentdirection + */ + public RtpTransceiverDirection getCurrentDirection() { + return nativeCurrentDirection(nativeRtpTransceiver); + } + + /** + * Sets the preferred direction of this transceiver. An update of + * directionality does not take effect immediately. Instead, future calls to + * CreateOffer and CreateAnswer mark the corresponding media descriptions as + * sendrecv, sendonly, recvonly, or inactive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-direction + */ + public void setDirection(RtpTransceiverDirection rtpTransceiverDirection) { + nativeSetDirection(nativeRtpTransceiver, rtpTransceiverDirection); + } + + /** + * The Stop method irreversibly stops the RtpTransceiver. The sender of this + * transceiver will no longer send, the receiver will no longer receive. + * https://w3c.github.io/webrtc-pc/#dom-rtcrtptransceiver-stop + */ + public void stop() { + nativeStop(nativeRtpTransceiver); + } + + public void dispose() { + cachedSender.dispose(); + cachedReceiver.dispose(); + JniCommon.nativeReleaseRef(nativeRtpTransceiver); + } + + private static native MediaStreamTrack.MediaType nativeGetMediaType(long rtpTransceiver); + private static native String nativeGetMid(long rtpTransceiver); + private static native RtpSender nativeGetSender(long rtpTransceiver); + private static native RtpReceiver nativeGetReceiver(long rtpTransceiver); + private static native boolean nativeStopped(long rtpTransceiver); + private static native RtpTransceiverDirection nativeDirection(long rtpTransceiver); + private static native RtpTransceiverDirection nativeCurrentDirection(long rtpTransceiver); + private static native void nativeStop(long rtpTransceiver); + private static native void nativeSetDirection( + long rtpTransceiver, RtpTransceiverDirection rtpTransceiverDirection); +} diff --git a/sdk/android/native_api/jni/java_types.cc b/sdk/android/native_api/jni/java_types.cc index 86ada91b1f..5f4ebba390 100644 --- a/sdk/android/native_api/jni/java_types.cc +++ b/sdk/android/native_api/jni/java_types.cc @@ -200,6 +200,12 @@ ScopedJavaLocalRef NativeToJavaInteger( return optional_int ? NativeToJavaInteger(jni, *optional_int) : nullptr; } +ScopedJavaLocalRef NativeToJavaString( + JNIEnv* jni, + const rtc::Optional& str) { + return str ? NativeToJavaString(jni, *str) : nullptr; +} + ScopedJavaLocalRef NativeToJavaBooleanArray( JNIEnv* env, const std::vector& container) { diff --git a/sdk/android/native_api/jni/java_types.h b/sdk/android/native_api/jni/java_types.h index d9d2b1a3e5..fb660635d6 100644 --- a/sdk/android/native_api/jni/java_types.h +++ b/sdk/android/native_api/jni/java_types.h @@ -150,6 +150,21 @@ std::vector JavaToNativeVector(JNIEnv* env, return container; } +template +std::vector JavaListToNativeVector(JNIEnv* env, + const JavaRef& j_list, + Convert convert) { + std::vector native_list; + if (!j_list.is_null()) { + for (ScopedJavaLocalRef& j_item : Iterable(env, j_list)) { + native_list.emplace_back( + convert(env, static_java_ref_cast(env, j_item))); + } + CHECK_EXCEPTION(env) << "Error during JavaListToNativeVector"; + } + return native_list; +} + template std::map JavaToNativeMap(JNIEnv* env, const JavaRef& j_map, @@ -182,6 +197,9 @@ ScopedJavaLocalRef NativeToJavaString(JNIEnv* jni, ScopedJavaLocalRef NativeToJavaInteger( JNIEnv* jni, const rtc::Optional& optional_int); +ScopedJavaLocalRef NativeToJavaString( + JNIEnv* jni, + const rtc::Optional& str); // Helper function for converting std::vector into a Java array. template @@ -282,7 +300,7 @@ inline std::string JavaToStdString(JNIEnv* jni, jstring j_string) { return JavaToStdString(jni, JavaParamRef(j_string)); } -// Deprecated. Use JavaToNativeVector instead. +// Deprecated. Use JavaListToNativeVector instead. // Given a List of (UTF-16) jstrings // return a new vector of UTF-8 native strings. std::vector JavaToStdVectorStrings(JNIEnv* jni, diff --git a/sdk/android/src/jni/pc/mediastream.cc b/sdk/android/src/jni/pc/mediastream.cc index e733c8c6fb..5f36414eed 100644 --- a/sdk/android/src/jni/pc/mediastream.cc +++ b/sdk/android/src/jni/pc/mediastream.cc @@ -135,9 +135,9 @@ static jboolean JNI_MediaStream_RemoveVideoTrack(JNIEnv* jni, } static ScopedJavaLocalRef -JNI_MediaStream_GetLabel(JNIEnv* jni, const JavaParamRef&, jlong j_p) { - return NativeToJavaString( - jni, reinterpret_cast(j_p)->label()); +JNI_MediaStream_GetId(JNIEnv* jni, const JavaParamRef&, jlong j_p) { + return NativeToJavaString(jni, + reinterpret_cast(j_p)->id()); } } // namespace jni diff --git a/sdk/android/src/jni/pc/peerconnection.cc b/sdk/android/src/jni/pc/peerconnection.cc index f954a4330c..048d22490c 100644 --- a/sdk/android/src/jni/pc/peerconnection.cc +++ b/sdk/android/src/jni/pc/peerconnection.cc @@ -36,6 +36,7 @@ #include "api/peerconnectioninterface.h" #include "api/rtpreceiverinterface.h" #include "api/rtpsenderinterface.h" +#include "api/rtptransceiverinterface.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" #include "rtc_base/ptr_util.h" @@ -45,8 +46,10 @@ #include "sdk/android/src/jni/pc/datachannel.h" #include "sdk/android/src/jni/pc/icecandidate.h" #include "sdk/android/src/jni/pc/mediaconstraints.h" +#include "sdk/android/src/jni/pc/mediastreamtrack.h" #include "sdk/android/src/jni/pc/rtcstatscollectorcallbackwrapper.h" #include "sdk/android/src/jni/pc/rtpsender.h" +#include "sdk/android/src/jni/pc/rtptransceiver.h" #include "sdk/android/src/jni/pc/sdpobserver.h" #include "sdk/android/src/jni/pc/sessiondescription.h" #include "sdk/android/src/jni/pc/statsobserver.h" @@ -86,19 +89,35 @@ PeerConnectionInterface::IceServers JavaToNativeIceServers( ScopedJavaLocalRef tls_elliptic_curves = Java_IceServer_getTlsEllipticCurves(jni, j_ice_server); PeerConnectionInterface::IceServer server; - server.urls = JavaToStdVectorStrings(jni, urls); - server.username = JavaToStdString(jni, username); - server.password = JavaToStdString(jni, password); + server.urls = JavaListToNativeVector( + jni, urls, &JavaToNativeString); + server.username = JavaToNativeString(jni, username); + server.password = JavaToNativeString(jni, password); server.tls_cert_policy = tls_cert_policy; - server.hostname = JavaToStdString(jni, hostname); - server.tls_alpn_protocols = JavaToStdVectorStrings(jni, tls_alpn_protocols); - server.tls_elliptic_curves = - JavaToStdVectorStrings(jni, tls_elliptic_curves); + server.hostname = JavaToNativeString(jni, hostname); + server.tls_alpn_protocols = JavaListToNativeVector( + jni, tls_alpn_protocols, &JavaToNativeString); + server.tls_elliptic_curves = JavaListToNativeVector( + jni, tls_elliptic_curves, &JavaToNativeString); ice_servers.push_back(server); } return ice_servers; } +SdpSemantics JavaToNativeSdpSemantics(JNIEnv* jni, + const JavaRef& j_sdp_semantics) { + std::string enum_name = GetJavaEnumName(jni, j_sdp_semantics); + + if (enum_name == "PLAN_B") + return SdpSemantics::kPlanB; + + if (enum_name == "UNIFIED_PLAN") + return SdpSemantics::kUnifiedPlan; + + RTC_NOTREACHED(); + return SdpSemantics::kPlanB; +} + } // namespace void JavaToNativeRTCConfiguration( @@ -123,6 +142,8 @@ void JavaToNativeRTCConfiguration( Java_RTCConfiguration_getTurnCustomizer(jni, j_rtc_config); ScopedJavaLocalRef j_network_preference = Java_RTCConfiguration_getNetworkPreference(jni, j_rtc_config); + ScopedJavaLocalRef j_sdp_semantics = + Java_RTCConfiguration_getSdpSemantics(jni, j_rtc_config); rtc_config->type = JavaToNativeIceTransportsType(jni, j_ice_transports_type); rtc_config->bundle_policy = JavaToNativeBundlePolicy(jni, j_bundle_policy); @@ -193,6 +214,7 @@ void JavaToNativeRTCConfiguration( jni, Java_RTCConfiguration_getEnableDtlsSrtp(jni, j_rtc_config)); rtc_config->network_preference = JavaToNativeNetworkPreference(jni, j_network_preference); + rtc_config->sdp_semantics = JavaToNativeSdpSemantics(jni, j_sdp_semantics); } rtc::KeyType GetRtcConfigKeyType(JNIEnv* env, @@ -380,7 +402,7 @@ static ScopedJavaLocalRef JNI_PeerConnection_CreateDataChannel( DataChannelInit init = JavaToNativeDataChannelInit(jni, j_init); rtc::scoped_refptr channel( ExtractNativePC(jni, j_pc)->CreateDataChannel( - JavaToStdString(jni, j_label), &init)); + JavaToNativeString(jni, j_label), &init)); return WrapNativeDataChannel(jni, channel); } @@ -469,8 +491,8 @@ static jboolean JNI_PeerConnection_AddIceCandidate( const JavaParamRef& j_sdp_mid, jint j_sdp_mline_index, const JavaParamRef& j_candidate_sdp) { - std::string sdp_mid = JavaToStdString(jni, j_sdp_mid); - std::string sdp = JavaToStdString(jni, j_candidate_sdp); + std::string sdp_mid = JavaToNativeString(jni, j_sdp_mid); + std::string sdp = JavaToNativeString(jni, j_candidate_sdp); std::unique_ptr candidate( CreateIceCandidate(sdp_mid, j_sdp_mline_index, sdp, nullptr)); return ExtractNativePC(jni, j_pc)->AddIceCandidate(candidate.get()); @@ -507,8 +529,8 @@ static ScopedJavaLocalRef JNI_PeerConnection_CreateSender( const JavaParamRef& j_pc, const JavaParamRef& j_kind, const JavaParamRef& j_stream_id) { - std::string kind = JavaToStdString(jni, j_kind); - std::string stream_id = JavaToStdString(jni, j_stream_id); + std::string kind = JavaToNativeString(jni, j_kind); + std::string stream_id = JavaToNativeString(jni, j_stream_id); rtc::scoped_refptr sender = ExtractNativePC(jni, j_pc)->CreateSender(kind, stream_id); return NativeToJavaRtpSender(jni, sender); @@ -528,6 +550,75 @@ static ScopedJavaLocalRef JNI_PeerConnection_GetReceivers( &NativeToJavaRtpReceiver); } +static ScopedJavaLocalRef JNI_PeerConnection_GetTransceivers( + JNIEnv* jni, + const JavaParamRef& j_pc) { + return NativeToJavaList(jni, ExtractNativePC(jni, j_pc)->GetTransceivers(), + &NativeToJavaRtpTransceiver); +} + +static ScopedJavaLocalRef JNI_PeerConnection_AddTrack( + JNIEnv* jni, + const JavaParamRef& j_pc, + const jlong native_track, + const JavaParamRef& j_stream_labels) { + RTCErrorOr> result = + ExtractNativePC(jni, j_pc)->AddTrack( + reinterpret_cast(native_track), + JavaListToNativeVector(jni, j_stream_labels, + &JavaToNativeString)); + if (!result.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add track: " << result.error().message(); + return nullptr; + } else { + return NativeToJavaRtpSender(jni, result.MoveValue()); + } +} + +static jboolean JNI_PeerConnection_RemoveTrack( + JNIEnv* jni, + const JavaParamRef& j_pc, + jlong native_sender) { + return ExtractNativePC(jni, j_pc)->RemoveTrack( + reinterpret_cast(native_sender)); +} + +static ScopedJavaLocalRef JNI_PeerConnection_AddTransceiverWithTrack( + JNIEnv* jni, + const JavaParamRef& j_pc, + jlong native_track, + const JavaParamRef& j_init) { + RTCErrorOr> result = + ExtractNativePC(jni, j_pc)->AddTransceiver( + reinterpret_cast(native_track), + JavaToNativeRtpTransceiverInit(jni, j_init)); + if (!result.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add transceiver: " + << result.error().message(); + return nullptr; + } else { + return NativeToJavaRtpTransceiver(jni, result.MoveValue()); + } +} + +static ScopedJavaLocalRef JNI_PeerConnection_AddTransceiverOfType( + JNIEnv* jni, + const JavaParamRef& j_pc, + const JavaParamRef& j_media_type, + const JavaParamRef& j_init) { + RTCErrorOr> result = + ExtractNativePC(jni, j_pc)->AddTransceiver( + JavaToNativeMediaType(jni, j_media_type), + JavaToNativeRtpTransceiverInit(jni, j_init)); + if (!result.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add transceiver: " + << result.error().message(); + return nullptr; + } else { + return NativeToJavaRtpTransceiver(jni, result.MoveValue()); + } +} + static jboolean JNI_PeerConnection_OldGetStats( JNIEnv* jni, const JavaParamRef& j_pc, diff --git a/sdk/android/src/jni/pc/rtpparameters.cc b/sdk/android/src/jni/pc/rtpparameters.cc index 9630e1d6aa..6a312b4865 100644 --- a/sdk/android/src/jni/pc/rtpparameters.cc +++ b/sdk/android/src/jni/pc/rtpparameters.cc @@ -40,6 +40,21 @@ ScopedJavaLocalRef NativeToJavaRtpCodecParameter( } // namespace +RtpEncodingParameters JavaToNativeRtpEncodingParameters( + JNIEnv* jni, + const JavaRef& j_encoding_parameters) { + RtpEncodingParameters encoding; + encoding.active = Java_Encoding_getActive(jni, j_encoding_parameters); + ScopedJavaLocalRef j_bitrate = + Java_Encoding_getMaxBitrateBps(jni, j_encoding_parameters); + encoding.max_bitrate_bps = JavaToNativeOptionalInt(jni, j_bitrate); + ScopedJavaLocalRef j_ssrc = + Java_Encoding_getSsrc(jni, j_encoding_parameters); + if (!IsNull(jni, j_ssrc)) + encoding.ssrc = JavaToNativeLong(jni, j_ssrc); + return encoding; +} + RtpParameters JavaToNativeRtpParameters(JNIEnv* jni, const JavaRef& j_parameters) { RtpParameters parameters; @@ -49,15 +64,8 @@ RtpParameters JavaToNativeRtpParameters(JNIEnv* jni, Java_RtpParameters_getEncodings(jni, j_parameters); for (const JavaRef& j_encoding_parameters : Iterable(jni, j_encodings)) { - RtpEncodingParameters encoding; - encoding.active = Java_Encoding_getActive(jni, j_encoding_parameters); - ScopedJavaLocalRef j_bitrate = - Java_Encoding_getMaxBitrateBps(jni, j_encoding_parameters); - encoding.max_bitrate_bps = JavaToNativeOptionalInt(jni, j_bitrate); - ScopedJavaLocalRef j_ssrc = - Java_Encoding_getSsrc(jni, j_encoding_parameters); - if (!IsNull(jni, j_ssrc)) - encoding.ssrc = JavaToNativeLong(jni, j_ssrc); + RtpEncodingParameters encoding = + JavaToNativeRtpEncodingParameters(jni, j_encoding_parameters); parameters.encodings.push_back(encoding); } diff --git a/sdk/android/src/jni/pc/rtpparameters.h b/sdk/android/src/jni/pc/rtpparameters.h index da7c995c03..ae79793b1a 100644 --- a/sdk/android/src/jni/pc/rtpparameters.h +++ b/sdk/android/src/jni/pc/rtpparameters.h @@ -19,6 +19,10 @@ namespace webrtc { namespace jni { +RtpEncodingParameters JavaToNativeRtpEncodingParameters( + JNIEnv* env, + const JavaRef& j_encoding_parameters); + RtpParameters JavaToNativeRtpParameters(JNIEnv* jni, const JavaRef& j_parameters); ScopedJavaLocalRef NativeToJavaRtpParameters( diff --git a/sdk/android/src/jni/pc/rtptransceiver.cc b/sdk/android/src/jni/pc/rtptransceiver.cc new file mode 100644 index 0000000000..f142d5868e --- /dev/null +++ b/sdk/android/src/jni/pc/rtptransceiver.cc @@ -0,0 +1,151 @@ +/* + * Copyright 2018 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 "sdk/android/src/jni/pc/rtptransceiver.h" + +#include + +#include "sdk/android/generated_peerconnection_jni/jni/RtpTransceiver_jni.h" +#include "sdk/android/native_api/jni/java_types.h" +#include "sdk/android/src/jni/jni_helpers.h" +#include "sdk/android/src/jni/pc/mediastreamtrack.h" +#include "sdk/android/src/jni/pc/rtpparameters.h" +#include "sdk/android/src/jni/pc/rtpreceiver.h" +#include "sdk/android/src/jni/pc/rtpsender.h" + +namespace webrtc { +namespace jni { + +namespace { + +ScopedJavaLocalRef NativeToJavaRtpTransceiverDirection( + JNIEnv* jni, + RtpTransceiverDirection rtp_transceiver_direction) { + return Java_RtpTransceiverDirection_fromNativeIndex( + jni, static_cast(rtp_transceiver_direction)); +} + +} // namespace + +RtpTransceiverInit JavaToNativeRtpTransceiverInit( + JNIEnv* jni, + const JavaRef& j_init) { + RtpTransceiverInit init; + + // Convert the direction. + init.direction = static_cast( + Java_RtpTransceiverInit_getDirectionNativeIndex(jni, j_init)); + + // Convert the stream ids. + ScopedJavaLocalRef j_stream_ids = + Java_RtpTransceiverInit_getStreamIds(jni, j_init); + init.stream_ids = JavaListToNativeVector( + jni, j_stream_ids, &JavaToNativeString); + return init; +} + +ScopedJavaLocalRef NativeToJavaRtpTransceiver( + JNIEnv* env, + rtc::scoped_refptr transceiver) { + if (!transceiver) { + return nullptr; + } + // Transceiver will now have shared ownership by the Java object. + return Java_RtpTransceiver_Constructor( + env, jlongFromPointer(transceiver.release())); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_GetMediaType( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + return NativeToJavaMediaType( + jni, reinterpret_cast(j_rtp_transceiver_pointer) + ->media_type()); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_GetMid( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + rtc::Optional mid = + reinterpret_cast(j_rtp_transceiver_pointer) + ->mid(); + return NativeToJavaString(jni, mid); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_GetSender( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + return NativeToJavaRtpSender( + jni, reinterpret_cast(j_rtp_transceiver_pointer) + ->sender()); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_GetReceiver( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + return NativeToJavaRtpReceiver( + jni, reinterpret_cast(j_rtp_transceiver_pointer) + ->receiver()); +} + +jboolean JNI_RtpTransceiver_Stopped(JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + return reinterpret_cast(j_rtp_transceiver_pointer) + ->stopped(); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_Direction( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + return NativeToJavaRtpTransceiverDirection( + jni, reinterpret_cast(j_rtp_transceiver_pointer) + ->direction()); +} + +ScopedJavaLocalRef JNI_RtpTransceiver_CurrentDirection( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + rtc::Optional direction = + reinterpret_cast(j_rtp_transceiver_pointer) + ->current_direction(); + return direction ? NativeToJavaRtpTransceiverDirection(jni, *direction) + : nullptr; +} + +void JNI_RtpTransceiver_Stop(JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer) { + reinterpret_cast(j_rtp_transceiver_pointer)->Stop(); +} + +void JNI_RtpTransceiver_SetDirection( + JNIEnv* jni, + const base::android::JavaParamRef&, + jlong j_rtp_transceiver_pointer, + const base::android::JavaParamRef& j_rtp_transceiver_direction) { + if (IsNull(jni, j_rtp_transceiver_direction)) { + return; + } + RtpTransceiverDirection direction = static_cast( + Java_RtpTransceiverDirection_getNativeIndex(jni, + j_rtp_transceiver_direction)); + reinterpret_cast(j_rtp_transceiver_pointer) + ->SetDirection(direction); +} + +} // namespace jni +} // namespace webrtc diff --git a/sdk/android/src/jni/pc/rtptransceiver.h b/sdk/android/src/jni/pc/rtptransceiver.h new file mode 100644 index 0000000000..9017c2fcc2 --- /dev/null +++ b/sdk/android/src/jni/pc/rtptransceiver.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 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. + */ + +#ifndef SDK_ANDROID_SRC_JNI_PC_RTPTRANSCEIVER_H_ +#define SDK_ANDROID_SRC_JNI_PC_RTPTRANSCEIVER_H_ + +#include + +#include "api/rtptransceiverinterface.h" +#include "sdk/android/native_api/jni/scoped_java_ref.h" + +namespace webrtc { +namespace jni { + +RtpTransceiverInit JavaToNativeRtpTransceiverInit( + JNIEnv* jni, + const JavaRef& j_init); + +ScopedJavaLocalRef NativeToJavaRtpTransceiver( + JNIEnv* env, + rtc::scoped_refptr transceiver); + +} // namespace jni +} // namespace webrtc + +#endif // SDK_ANDROID_SRC_JNI_PC_RTPTRANSCEIVER_H_