1

I'm attempting to stream my webcam locally from a webpage on my computer to an Android app (native WebRTC). I'm using WebRTC for the peer connection and NodeJS with Socket.io for signaling. When I initiate the video stream all sdp appears to be set correctly, but no track plays on my Andorid surface view. All I get is a black screen and this console output:

2020-08-19 13:23:19.667 30492-31575/com.example.demowebrtcclient I/org.webrtc.Logging: EglRenderer: video_viewDuration: 4050 ms. Frames received: 0. Dropped: 0. Rendered: 0. Render fps: .0. Average render time: NA. Average swapBuffer time: NA.

Here's my code, sorry there's so much I just don't know where the root of the problem is.

Peer 1 (Webpage, JS)

 navigator.mediaDevices.getUserMedia({audio: true, video: true})
    .then(function(s) {
      stream = s;
      video.srcObject = stream;
      video.play();
    });

  function startStream() {

    socket.emit('broadcaster');
  }

  socket.on('answer', function(id, description) {
    let RTCDescription = description;

    if (isAndroid) {
      RTCDescription = new RTCSessionDescription();
      RTCDescription.sdp = description;
      RTCDescription.type = "answer";
      console.log("Answer received");
    }

    console.log(RTCDescription);
    peerConnection.setRemoteDescription(RTCDescription); 
  });

  socket.on('watcher', function(id) {
    peerConnection = new RTCPeerConnection(config);
    // peerConnections[id] = peerConnection;
    stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
    peerConnection.createOffer()
    .then(function(sdp) {
      peerConnection.setLocalDescription(sdp);
    }) 
    .then(function() {
      socket.emit('offer', id, peerConnection.localDescription);
    })
    peerConnection.onicecandidate = function(event) {
      if (event.candidate) {
        socket.emit('candidate', id, event.candidate);
      }
    };
  });

  socket.on('candidate', function(id, candidate) {
    console.log("Candidate recieved");
    peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
  });

  socket.on('close', function(id) {
    peerConnection.close();
    delete peerConnection;
  });

Peer 2 (Android, Java)

private void initializePeerConnectionFactory() {
    PeerConnectionFactory.InitializationOptions initOptions = PeerConnectionFactory.InitializationOptions.builder(getApplicationContext())
        .setEnableInternalTracer(true)
        .setFieldTrials("WebRTC-H264HighProfile/Enabled/")
        .createInitializationOptions();

    PeerConnectionFactory.initialize(initOptions);
    VideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(rootEglBaseContext,  /* enableIntelVp8Encoder */true,  /* enableH264HighProfile */true);
    VideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(rootEglBaseContext);

    PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
    options.disableEncryption = true;
    options.disableNetworkMonitor = true;

    factory = PeerConnectionFactory.builder()
            .setVideoEncoderFactory(defaultVideoEncoderFactory)
            .setVideoDecoderFactory(defaultVideoDecoderFactory)
            .setOptions(options)
            .createPeerConnectionFactory();
}



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    rootEglBase = EglBase.create();
    rootEglBaseContext = rootEglBase.getEglBaseContext();
    videoView = findViewById(R.id.video_view);
    videoView.setMirror(true);
    videoView.setEnableHardwareScaler(true);
    videoView.init(rootEglBaseContext, null);

    videoSink = new ProxyVideoSink();

    iceServers = new ArrayList<>();

    stunServer = (PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
    iceServers.add(stunServer);



    executor = Executors.newSingleThreadScheduledExecutor();

    mediaConstraints = new MediaConstraints();
    mSocket.on(Socket.EVENT_CONNECT, onConnect);
    mSocket.on("offer", handleOffer);
    mSocket.on("broadcaster", onBroadcast);
    mSocket.on("candidate", onCandidate);

    mSocket.connect();

    initializePeerConnectionFactory();
}


private Emitter.Listener handleOffer = new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        Log.d("socket", "Offer recieved");
        peerConnection = factory.createPeerConnection(iceServers, observer);
        String id = (String) args[0];
        JSONObject data = (JSONObject) args[1];
        peerConnection.setRemoteDescription(new SdpAdapter("setremote") {
            @Override
            public void onSetSuccess() {
                peerConnection.createAnswer(new SdpAdapter("createanswer") {
                    @Override
                    public void onCreateSuccess(final SessionDescription sdp) {
                        super.onCreateSuccess(sdp);
                        Log.d("socket", "Session description " + sdp.toString() + " created");
                        SdpAdapter local = new SdpAdapter("setlocal") {
                            @Override
                            public void onSetSuccess() {
                                super.onSetSuccess();
                                mSocket.emit("answer" , id, peerConnection.getLocalDescription().description);
                            }
                        };
                        peerConnection.setLocalDescription(local, sdp);
                    }
                }, mediaConstraints);
            }
        }, new SessionDescription(SessionDescription.Type.OFFER, data.optString("sdp")));
    }
};


private Emitter.Listener onConnect = new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        Log.d("socket", "Socket connected");
        mSocket.emit("watcher");
    }
};

private Emitter.Listener onBroadcast = new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        Log.d("socket", "Received broadcast request");
        mSocket.emit("watcher");
    }
};

private Emitter.Listener onCandidate = new Emitter.Listener() {
    @Override
    public void call(final Object... args) {
        Log.d("ice", "New ice candidate recieved");

        JSONObject data = (JSONObject) args[1];
        IceCandidate candidate = new IceCandidate(data.optString("sdpMid"), Integer.parseInt(data.optString("sdpMLineIndex")), data.optString("candidate"));
        peerConnection.addIceCandidate(candidate);
    }
};

public class SdpAdapter implements SdpObserver {
    private String name;

    public SdpAdapter(String name) {
        this.name = name;
    }

    @Override
    public void onCreateSuccess(SessionDescription sessionDescription) {
        Log.d("socket", "SDP create success for " + name );
    }

    @Override
    public void onSetSuccess() {
        Log.d("socket", "SDP set success for " + name);
    }

    @Override
    public void onCreateFailure(String s) {
        Log.d("socket", "SDP create failure for" + s);
    }

    @Override
    public void onSetFailure(String s) {
        Log.d("socket", "SDP set failure for" + s);
    }
};


public static class ProxyVideoSink implements VideoSink {
    private VideoSink mTarget;
    @Override
    synchronized public void onFrame(VideoFrame frame) {
        if (mTarget == null) {
            Log.d("socket", "Dropping frame in proxy because target is null.");
            return;
        }
        mTarget.onFrame(frame);
    }
    synchronized void setTarget(VideoSink target) {
        this.mTarget = target;
    }
}

Observer observer = new Observer() {
    @Override
    public void onIceCandidate(IceCandidate iceCandidate) {
        Log.d("ice", iceCandidate.toString());
        mSocket.emit("candidate", iceCandidate);

    }

    @Override
    public void onAddStream(MediaStream stream) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Log.d("socket", "Stream added");
                Log.d("tracks", "Getting  tracks");
                VideoTrack remoteVideoTrack = (VideoTrack)stream.videoTracks.get(0);
                AudioTrack remoteAudioTrack = (AudioTrack)stream.audioTracks.get(0);
                Log.d("tracks", "Enabling tracks");
                remoteAudioTrack.setEnabled(true);
                remoteVideoTrack.setEnabled(true);
                Log.d("tracks", "Adding sink");
                videoSink.setTarget(videoView);
                remoteVideoTrack.addSink(videoSink);
                peerConnection.getStats(reports -> {
                    for (StatsReport report : reports) {
                        Log.d("Stats", "Stats: " + report.toString());
                    }
                }, null);



            }
        });
    }



};

}

Matthew Kline
  • 71
  • 1
  • 6

1 Answers1

1

Disabling encryption in Android will break interoperability with the browser, unless you start them in a special (insecure) mode.

options.disableEncryption = true;

A good point to get started with the Android native WebRTC API is by looking at the example App code: https://chromium.googlesource.com/external/webrtc/+/refs/heads/master/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java

I'll recommend to add more logs and extend the description / question.

Recommended procedure when debugging a WebRTC issue:

  1. Did ICE succeed? (iceConnectionState is connected) If not, look at the gathered and received candidates. Check involved firewall configurations, NAT and TURN.
  2. Do you receive packets but no frames can be rendered? Check your codecs and encoders / decoders.
seb
  • 196
  • 2
  • 8