问题描述
我正在尝试将本地网络摄像头从计算机上的网页本地传输到Android应用(本机WebRTC)。我将WebRTC用于对等连接,将NodeJS与Socket.io用于信令。当我启动视频流时,所有sdp似乎都已正确设置,但是在我的Andorid表面视图上没有轨道播放。我得到的只是一个黑屏,该控制台输出:
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.
这是我的代码,抱歉,有太多我只是不知道问题的根源在何处。
对等1(网页,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',event.candidate);
}
};
});
socket.on('candidate',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",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);
}
});
}
};
}
解决方法
在 Android 中禁用加密会破坏与浏览器的互操作性,除非您以特殊(不安全)模式启动它们。
options.disableEncryption = true;
开始使用 Android 原生 WebRTC API 的一个好点是查看示例应用代码: https://chromium.googlesource.com/external/webrtc/+/refs/heads/master/examples/androidapp/src/org/appspot/apprtc/PeerConnectionClient.java
我会建议添加更多日志并扩展描述/问题。
调试 WebRTC 问题时的推荐程序:
- ICE 成功了吗? (
iceConnectionState
已连接)如果没有,请查看收集和接收的候选人。检查涉及的防火墙配置、NAT 和 TURN。 - 您是否收到数据包但无法渲染任何帧?检查您的编解码器和编码器/解码器。