2

How do I use @CalledByNative("...")? I need a callback from a webrtc lib.

If you know the class PeerConnection on it:

PeerConnection.java

Here is a old version of PeerConnection but it's almost same now

I call the function addStream, but I can't get the callback from it.

  • Please explain how calledbynative works!

PeerConnection.java

/*
 *  Copyright 2013 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.Collections;
import java.util.LinkedList;
import java.util.List;
/**
 * Java-land version of the PeerConnection APIs; wraps the C++ API
 * http://www.webrtc.org/reference/native-apis, which in turn is inspired by the
 * JS APIs: http://dev.w3.org/2011/webrtc/editor/webrtc.html and
 * http://www.w3.org/TR/mediacapture-streams/
 */
public class PeerConnection {
  static {
    System.loadLibrary("jingle_peerconnection_so");
  }
  /** Tracks PeerConnectionInterface::IceGatheringState */
  public enum IceGatheringState { NEW, GATHERING, COMPLETE }
  /** Tracks PeerConnectionInterface::IceConnectionState */
  public enum IceConnectionState {
    NEW,
    CHECKING,
    CONNECTED,
    COMPLETED,
    FAILED,
    DISCONNECTED,
    CLOSED
  }
  /** Tracks PeerConnectionInterface::SignalingState */
  public enum SignalingState {
    STABLE,
    HAVE_LOCAL_OFFER,
    HAVE_LOCAL_PRANSWER,
    HAVE_REMOTE_OFFER,
    HAVE_REMOTE_PRANSWER,
    CLOSED
  }
  /** Java version of PeerConnectionObserver. */
  public static interface Observer {
    /** Triggered when the SignalingState changes. */
    public void onSignalingChange(SignalingState newState);
    /** Triggered when the IceConnectionState changes. */
    public void onIceConnectionChange(IceConnectionState newState);
    /** Triggered when the ICE connection receiving status changes. */
    public void onIceConnectionReceivingChange(boolean receiving);
    /** Triggered when the IceGatheringState changes. */
    public void onIceGatheringChange(IceGatheringState newState);
    /** Triggered when a new ICE candidate has been found. */
    public void onIceCandidate(IceCandidate candidate);
    /** Triggered when some ICE candidates have been removed. */
    public void onIceCandidatesRemoved(IceCandidate[] candidates);
    /** Triggered when media is received on a new stream from remote peer. */
    public void onAddStream(MediaStream stream);
    /** Triggered when a remote peer close a stream. */
    public void onRemoveStream(MediaStream stream);
    /** Triggered when a remote peer opens a DataChannel. */
    public void onDataChannel(DataChannel dataChannel);
    /** Triggered when renegotiation is necessary. */
    public void onRenegotiationNeeded();
  }
  /** Java version of PeerConnectionInterface.IceServer. */
  public static class IceServer {
    public final String uri;
    public final String username;
    public final String password;
    /** Convenience constructor for STUN servers. */
    public IceServer(String uri) {
      this(uri, "", "");
    }
    public IceServer(String uri, String username, String password) {
      this.uri = uri;
      this.username = username;
      this.password = password;
    }
    public String toString() {
      return uri + "[" + username + ":" + password + "]";
    }
  }
  /** Java version of PeerConnectionInterface.IceTransportsType */
  public enum IceTransportsType { NONE, RELAY, NOHOST, ALL }
  /** Java version of PeerConnectionInterface.BundlePolicy */
  public enum BundlePolicy { BALANCED, MAXBUNDLE, MAXCOMPAT }
  /** Java version of PeerConnectionInterface.RtcpMuxPolicy */
  public enum RtcpMuxPolicy { NEGOTIATE, REQUIRE }
  /** Java version of PeerConnectionInterface.TcpCandidatePolicy */
  public enum TcpCandidatePolicy { ENABLED, DISABLED }
  /** Java version of PeerConnectionInterface.CandidateNetworkPolicy */
  public enum CandidateNetworkPolicy { ALL, LOW_COST }
  /** Java version of rtc::KeyType */
  public enum KeyType { RSA, ECDSA }
  /** Java version of PeerConnectionInterface.ContinualGatheringPolicy */
  public enum ContinualGatheringPolicy { GATHER_ONCE, GATHER_CONTINUALLY }
  /** Java version of PeerConnectionInterface.RTCConfiguration */
  public static class RTCConfiguration {
    public IceTransportsType iceTransportsType;
    public List<IceServer> iceServers;
    public BundlePolicy bundlePolicy;
    public RtcpMuxPolicy rtcpMuxPolicy;
    public TcpCandidatePolicy tcpCandidatePolicy;
    public CandidateNetworkPolicy candidateNetworkPolicy;
    public int audioJitterBufferMaxPackets;
    public boolean audioJitterBufferFastAccelerate;
    public int iceConnectionReceivingTimeout;
    public int iceBackupCandidatePairPingInterval;
    public KeyType keyType;
    public ContinualGatheringPolicy continualGatheringPolicy;
    public int iceCandidatePoolSize;
    public boolean pruneTurnPorts;
    public boolean presumeWritableWhenFullyRelayed;
    public RTCConfiguration(List<IceServer> iceServers) {
      iceTransportsType = IceTransportsType.ALL;
      bundlePolicy = BundlePolicy.BALANCED;
      rtcpMuxPolicy = RtcpMuxPolicy.NEGOTIATE;
      tcpCandidatePolicy = TcpCandidatePolicy.ENABLED;
      candidateNetworkPolicy = candidateNetworkPolicy.ALL;
      this.iceServers = iceServers;
      audioJitterBufferMaxPackets = 50;
      audioJitterBufferFastAccelerate = false;
      iceConnectionReceivingTimeout = -1;
      iceBackupCandidatePairPingInterval = -1;
      keyType = KeyType.ECDSA;
      continualGatheringPolicy = ContinualGatheringPolicy.GATHER_ONCE;
      iceCandidatePoolSize = 0;
      pruneTurnPorts = false;
      presumeWritableWhenFullyRelayed = false;
    }
  };
  private final List<MediaStream> localStreams;
  private final long nativePeerConnection;
  private final long nativeObserver;
  private List<RtpSender> senders;
  private List<RtpReceiver> receivers;
  PeerConnection(long nativePeerConnection, long nativeObserver) {
    this.nativePeerConnection = nativePeerConnection;
    this.nativeObserver = nativeObserver;
    localStreams = new LinkedList<MediaStream>();
    senders = new LinkedList<RtpSender>();
    receivers = new LinkedList<RtpReceiver>();
  }
  // JsepInterface.
  public native SessionDescription getLocalDescription();
  public native SessionDescription getRemoteDescription();
  public native DataChannel createDataChannel(String label, DataChannel.Init init);
  public native void createOffer(SdpObserver observer, MediaConstraints constraints);
  public native void createAnswer(SdpObserver observer, MediaConstraints constraints);
  public native void setLocalDescription(SdpObserver observer, SessionDescription sdp);
  public native void setRemoteDescription(SdpObserver observer, SessionDescription sdp);
  public native boolean setConfiguration(RTCConfiguration config);
  public boolean addIceCandidate(IceCandidate candidate) {
    return nativeAddIceCandidate(candidate.sdpMid, candidate.sdpMLineIndex, candidate.sdp);
  }
  public boolean removeIceCandidates(final IceCandidate[] candidates) {
    return nativeRemoveIceCandidates(candidates);
  }
  public boolean addStream(MediaStream stream) {
    boolean ret = nativeAddLocalStream(stream.nativeStream);
    if (!ret) {
      return false;
    }
    localStreams.add(stream);
    return true;
  }
  public void removeStream(MediaStream stream) {
    nativeRemoveLocalStream(stream.nativeStream);
    localStreams.remove(stream);
  }
  public RtpSender createSender(String kind, String stream_id) {
    RtpSender new_sender = nativeCreateSender(kind, stream_id);
    if (new_sender != null) {
      senders.add(new_sender);
    }
    return new_sender;
  }
  // Note that calling getSenders will dispose of the senders previously
  // returned (and same goes for getReceivers).
  public List<RtpSender> getSenders() {
    for (RtpSender sender : senders) {
      sender.dispose();
    }
    senders = nativeGetSenders();
    return Collections.unmodifiableList(senders);
  }
  public List<RtpReceiver> getReceivers() {
    for (RtpReceiver receiver : receivers) {
      receiver.dispose();
    }
    receivers = nativeGetReceivers();
    return Collections.unmodifiableList(receivers);
  }
  public boolean getStats(StatsObserver observer, MediaStreamTrack track) {
    return nativeGetStats(observer, (track == null) ? 0 : track.nativeTrack);
  }
  // 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.
  public void stopRtcEventLog() {
    nativeStopRtcEventLog();
  }
  // TODO(fischman): add support for DTMF-related methods once that API
  // stabilizes.
  public native SignalingState signalingState();
  public native IceConnectionState iceConnectionState();
  public native IceGatheringState iceGatheringState();
  public native void close();
  public void dispose() {
    close();
    for (MediaStream stream : localStreams) {
      nativeRemoveLocalStream(stream.nativeStream);
      stream.dispose();
    }
    localStreams.clear();
    for (RtpSender sender : senders) {
      sender.dispose();
    }
    senders.clear();
    for (RtpReceiver receiver : receivers) {
      receiver.dispose();
    }
    receivers.clear();
    freePeerConnection(nativePeerConnection);
    freeObserver(nativeObserver);
  }
  private static native void freePeerConnection(long nativePeerConnection);
  private static native void freeObserver(long nativeObserver);
  private native boolean nativeAddIceCandidate(
      String sdpMid, int sdpMLineIndex, String iceCandidateSdp);
  private native boolean nativeRemoveIceCandidates(final IceCandidate[] candidates);
  private native boolean nativeAddLocalStream(long nativeStream);
  private native void nativeRemoveLocalStream(long nativeStream);
  private native boolean nativeGetStats(StatsObserver observer, long nativeTrack);
  private native RtpSender nativeCreateSender(String kind, String stream_id);
  private native List<RtpSender> nativeGetSenders();
  private native List<RtpReceiver> nativeGetReceivers();
  private native boolean nativeStartRtcEventLog(int file_descriptor, int max_size_bytes);
  private native void nativeStopRtcEventLog();
}
colidyre
  • 4,170
  • 12
  • 37
  • 53

2 Answers2

5

@CallByNative is an annotation used by the Google WebRtc Development team to specify mappings between Java and Native (C/C++) Stacks of WebRtc.

As said on there official WebRtc Chromium Git representation such as:

@CalledByNative is used by the JNI generator to create the necessary JNI bindings and expose this method to native code.

Now if there is a question that how is it happening? How WebRtc Java code is inter-linked with WebRtc Native Code?

For this, I will suggest looking into the Native Code of WebRtc will help you most. You will learn more informative and relevant stuff from the Comments inside the native stack. On the other hand, you should also have the basic knowledge of

  • How Java Native Interface (JNI) works
  • How WebRtc Java and Native Stack is interrelated

To get detailed intuition of Java Native Interface, you can visit these references:

Here I am giving you an example from PeerConnection.java. Let's say, we have a method inside PeerConnection.java class such as:

@CalledByNative("IceGatheringState")
static IceGatheringState fromNativeIndex(int nativeIndex) {
  return values()[nativeIndex];
}

Now this method is mapped inside src/sdk/android/src/jni/pc/peer_connection.cc class such as:

static ScopedJavaLocalRef<jobject> JNI_PeerConnection_IceGatheringState(
    JNIEnv* env,
    const JavaParamRef<jobject>& j_pc) {
  return Java_IceGatheringState_fromNativeIndex(
      env, ExtractNativePC(env, j_pc)->ice_gathering_state());
}

If you follow the same technique by looking into both Java and Native Class of PeerConnection, you must found some other linkages of that kind. Now, what @CallByNative did here?

If you look, after loading library, @CallByNative basically do these two things such as:

  • Tells which inner class the method belongs to
  • Expose this Java method to the Native Code
  • Make JNI Bindings with Java and Native Code

These types of annotations are provided by Google Developers to make JNI easy and understandable while working with Pre-built libraries or your own compiled libraries such as libwebrtc.aar.

Muhammad Usman Bashir
  • 1,441
  • 2
  • 14
  • 43
3

I did some investigation on this topic lately and I would like to share my results. I realized that it might have to do something with @Override methods and the "super" keyword in Java. I suppose when we try to call e.g. the override method onIceCandidate or onAddStream, which is located in the PeerConnection.java file, from the MainActivity.java class we write:

public class MainActivity extends AppCompatActivity implements View.OnClickListener, SignallingClient.SignalingInterface {
...
private void createPeerConnection() {
...
            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                onIceCandidateReceived(iceCandidate);
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                showToast("Received Remote stream");
                super.onAddStream(mediaStream);
                gotRemoteStream(mediaStream);
            }
...
}
...
}

Furthermore, the onAddStream and onIceCandidate are both override methods. If we trace those methods back by the help of the super keyword we get into the following file CustomPeerConnectionObserver.java:

class CustomPeerConnectionObserver implements PeerConnection.Observer {
...
    @Override
    public void onIceCandidate(IceCandidate iceCandidate) {
        Log.d(logTag, "onIceCandidate() called with: iceCandidate = [" + iceCandidate + "]");
    }
...
    @Override
    public void onAddStream(MediaStream mediaStream) {
        Log.d(logTag, "onAddStream() called with: mediaStream = [" + mediaStream + "]");
    }
...
}

At this point when we take a look at the class we see that it "implements" the PeerConnection.Observer. If we keep investigating a bit further, we realize that we are guided to our file PeerConnection.java:

public class PeerConnection {
...
public interface Observer {
...
        @CalledByNative("Observer")
        void onIceCandidate(IceCandidate var1);
...
        @CalledByNative("Observer")
        void onAddStream(MediaStream var1);
...
 }
...
}

We get into the "interface" called Observer. So far I took the example from GitHub and I tried to comprehend how the application works. It is for Android, but I suppose we use Java, so there should not be a huge difference, correct me if I am wrong.

In conclusion, I suppose it somehow gets called automatically when a special event occurs and/or when it is mentioned/written as a @Override method, where we need it to be called. I also assume that the @CalledByNative tag could have impact on the method calls, i.e. addStream, also respectively in the example I provided above:

  • onIceCandidate
  • onAddStream.

I hope I was able to at least give you some reference points about my investigation on this behaviour, where you could pick up the trace and make further research. I am also looking forward that an expert could answer this good question on how @CalledByNative works.

colidyre
  • 4,170
  • 12
  • 37
  • 53
Fermit
  • 61
  • 6
  • There are some more details available [here](https://github.com/adobe/chromium/blob/master/base/android/java/org/chromium/base/CalledByNative.java). `CalledByNative` is documented as "// This annotation is used by the JNI generator to create the necessary JNI // bindings and expose this method to native code." – nanofarad Apr 20 '20 at 02:55