云手机同步效果
现在市面上云手机 基本都是基于arm服务器+docker容器技术来实现,其中最核心的是gpu虚拟化,然后就是 远程画面操作。
今天和大家分享下如何在Android上把视频画面以及音频通过webrtc传输到客户端或者pc上,以及如何实现远程遥控操
1.首先我们需要一个信令服务用于转发信令消息,在推流与收流端正常通讯后。使用webrtc发送音视频,其中nat服务器使用的是cotrun。
2.在推流端实现推流关键代码如下
private void start() {
videoCapturer = createScreenCapturer();
if (videoCapturer == null) {
return;
}
eglBaseContext = EglBase.create().getEglBaseContext();
surfaceViewRendererLocal.init(eglBaseContext, null);
surfaceViewRendererLocal.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
surfaceViewRendererLocal.setKeepScreenOn(true);
surfaceViewRendererRemote.init(eglBaseContext, null);
surfaceViewRendererRemote.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
surfaceViewRendererRemote.setKeepScreenOn(true);
peerConnectionFactory = createPeerConnectionFactory();
//使用硬件
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext);
DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true, true);
peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options).setVideoDecoderFactory(defaultVideoDecoderFactory).setVideoEncoderFactory(defaultVideoEncoderFactory).createPeerConnectionFactory();
MediaConstraints audioConstraints = new MediaConstraints();
// //回声消除
// audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
// //自动增益
// audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
// //高音过滤
// audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
// //噪音处理
// audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
AudioSource audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
audioTrack = peerConnectionFactory.createAudioTrack("101", audioSource);
surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
VideoSource videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());
videoTrack = peerConnectionFactory.createVideoTrack("102", videoSource);
videoTrack.addSink(surfaceViewRendererLocal);
btnCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
call();
}
});
webSocketChannel = new WebSocketChannel();
webSocketChannel.setWebSocketCallback(webSocketCallback);
webSocketChannel.connect(WEBSOCKET_URL);
}
3 收流端采用html我试过用腾讯的x5直接加载html也可
<!DOCTYPE html>
<html>
<head>
<title>webrtc demo</title>
</head>
<!--去除白边-->
<body style="border:0;margin:0;padding:0;">
<div>
<div style="position:absolute;z-index:321;left:50%">
<button style="background:#F00; color:#FFF" onclick="makeCall()">申请实例</button>
</div>
<div style="position:absolute;z-index:321;left:20%;top:10%">
<button style="background:#F00; color:#FFF" onclick="sendText()">点击一下通知云手机</button>
</div>
<video id="remoteVideo" autoplay playsinline controls="false"
style="width: 100%;height: 100%;object-fit: fill;position:relative;"/>
</div>
<script>
// 官网 https://webrtc.org/getting-started/peer-connections
const LOCAL_USER_ID = 'bbbb';
const RECEIVER_USER_ID = 'aaaa';
var webSocket = new WebSocket('ws://221.7.79.213:5000/websocket');
webSocket.onerror = function(event) {
console.log('open error');
};
webSocket.onopen = function(event) {
console.log("onopen---------->>>>fuck");
// 注册自己到服务端
webSocket.send(JSON.stringify({
'event':'register',
'userId': LOCAL_USER_ID
}));
};
webSocket.onclose = function(event) {
console.log('websocket closed');
};
webSocket.onmessage = function(event) {
console.log('onmessage '+event.data);
// 回调
onReceiveSocketMessage(JSON.parse(event.data));
};
// ice 服务器列表
const configuration = {'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
{'urls': 'stun:42.192.40.58:3478?transport=udp'},
{'urls': 'turn:42.192.40.58:3478?transport=udp','username':'ddssingsong','credential':'123456'},
{'urls': 'turn:42.192.40.58:3478?transport=tcp','username':'ddssingsong','credential':'123456'},
{'urls': 'turn:221.7.79.213:3478?transport=udp','username':'admin','credential':'123456'},
]}
// 管道
var peerConnection = new RTCPeerConnection(configuration); // 全局 PeerConnection
var dataChannel;//datachannel 发送消息
// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
console.log('local trickle '+event);
if (event.candidate) {
const trickle = {
event:'trickle',
sender:LOCAL_USER_ID,
receiver: RECEIVER_USER_ID,
candidate:{
sdpMid:event.candidate.sdpMid,
sdpMLineIndex:event.candidate.sdpMLineIndex,
sdp:event.candidate.candidate
}
};
webSocket.send(JSON.stringify(trickle));
// 发送本地 ICE candidate 到远端
console.log('send local ice candidate to remote '+RECEIVER_USER_ID);
}
});
// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
if (peerConnection.connectionState === 'connected') {
// Peers connected! 会话连接完成
console.log('p2p connected');
// 初始化管道
initDataChanel();
}
});
// 处理java 的 signaling 消息
async function onReceiveSocketMessage(message) {
if ('sdp'===message.event) {
if('offer'==message.type.toLowerCase()) {
// 收到 offer
console.log('收到 offer');
let offerSdp = {type:'offer', sdp: message.description};
peerConnection.setRemoteDescription(new RTCSessionDescription(offerSdp));
// 创建 answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
console.log('create answer and setLocalDescription');
// 发送 answer 到对端
webSocket.send(JSON.stringify({
"event": "sdp",
"type": 'answer',
"description":answer.sdp,
"sender":LOCAL_USER_ID,
"receiver":message.sender
}));
console.log('send answer to remote '+message.receiver);
} else if ('answer'==message.type.toLowerCase()) {
// 收到 answer
console.log('收到 answer from '+message.sender);
let answerSdp = {type:'answer', sdp: message.description};
const remoteDesc = new RTCSessionDescription(answerSdp);
await peerConnection.setRemoteDescription(remoteDesc);
console.log('setRemoteDescription after reveiver answer');
}
} else if('trickle'===message.event) {
let candidate = message.candidate;
await peerConnection.addIceCandidate(new RTCIceCandidate({
sdpMid:candidate.sdpMid,
sdpMLineIndex:candidate.sdpMLineIndex,
candidate:candidate.candidate
}))
console.log('add remote candidate');
}
};
async function addStreams() {
const remoteStream = new MediaStream();
const remoteVideo = document.querySelector('video#remoteVideo');
remoteVideo.srcObject = remoteStream;
// 监听远程流
peerConnection.addEventListener('track', async (event) => {
console.log('监听远程流');
remoteStream.addTrack(event.track, remoteStream);
});
}
// 添加流
addStreams();
// 拨打
async function makeCall() {
// create offer
const offer = await peerConnection.createOffer();
console.log('create offer');
console.log(offer);
await peerConnection.setLocalDescription(offer);
console.log('set setLocalDescription offer');
// 发送 offer 到对端
webSocket.send(JSON.stringify({
"event": "sdp",
"type": 'offer',
"description":offer.sdp,
"sender":LOCAL_USER_ID,
"receiver":RECEIVER_USER_ID
}));
console.log('send offer to remote '+RECEIVER_USER_ID);
}
// 状态改变
function dataChannelStateChange(){
var readyState=dataChannel.readyState;
console.log(' will send ttttttttt:'+readyState);
if(readyState==='open'){
sendText();
}else{
}
}
function dataChannelError(error){
console.log("Data Channel Error:", error);
}
//文本对方传过来的数据
function reveivemsg(e){
var msg = e.data;
console.log('recreived msg is :'+e.data);
if(msg){
chat.value += '->' + msg + '\r\n';
}else{
console.error('recreived msg is null');
}
}
// 初始化datachannel
function initDataChanel(){
var dataChannelOptions = {
ordered: true, //保证到达顺序
};
//文本聊天
dataChannel=peerConnection.createDataChannel('dataChannel',dataChannelOptions);
dataChannel.onmessage=reveivemsg; //收到消息
dataChannel.onopen = dataChannelStateChange; //状态改变
dataChannel.onclose = dataChannelStateChange; //
dataChannel.onerror = dataChannelError; //错误
}
//发送文本
function sendText(){
dataChannel.send('ttttttttt');
console.log('send ttttttttt');
}
</script>
</body>
</html>
当然原生的Android客户端也可以的
原生的Java收流端类似推流端 只是不发送本地音视频
以上就是公网的实时效果,由于时间限制所以没有录制视频