webrtc 工作图

1.音视频设备检测

1.1 奈奎斯特定理

在进行模拟 / 数字信号的转换过程中,当采样率大于信号中最高频率的 2 倍时,采样之后的数字信号就完整地保留了原始信号中的信息。

1.2 设备检测代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
...

//判断浏览器是否支持这些 API
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
  return;
}

// 枚举 cameras and microphones.
navigator.mediaDevices.enumerateDevices()
.then(function(deviceInfos) {

  //打印出每一个设备的信息
  deviceInfos.forEach(function(deviceInfo) {
    console.log(deviceInfo);
    console.log(deviceInfo.kind + ": " + deviceInfo.label +
                " id = " + deviceInfo.deviceId);
  });
})
.catch(function(err) {
  console.log(err.name + ": " + err.message);
});

2.摄像头

2.1 非编码帧 与 编码帧

名字 定义
非编码帧 采集的原始视频数据叫做非编码帧,属于YUV格式或者RGB格式
编码帧 通过编码器(如 H264/H265、VP8/VP9)压缩后的帧称为编码帧

以 H264 为例,经过 H264 编码的帧包括以下三种类型

名字 定义
I帧 关键帧。压缩率低,可以单独解码成一幅完整的图像。
P帧 参考帧。压缩率较高,解码时依赖于前面已解码的数据。
B帧 前后参考帧。压缩率最高,解码时不光依赖前面已经解码的帧,而且还依赖它后面的 P 帧。换句话说就是,B 帧后面的 P 帧要优先于它进行解码,然后才能将 B 帧解码。

2.2 获取视频流代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'use strict'

//获取HTML页面中的video标签  
var videoplay = document.querySelector('video#player');

//播放视频流
function gotMediaStream(stream){
        videoplay.srcObject = stream;
}

function handleError(err){
        console.log('getUserMedia error:', err);
}

//对采集的数据做一些限制
var constraints = {
                        video : {
                                width: 1280,
                                height: 720,
                                frameRate:15,
                        },
                        audio : false
                   }

//采集音视频数据流
navigator.mediaDevices.getUserMedia(constraints)
                        .then(gotMediaStream)
                        .catch(handleError);

2.3拍照实现步骤

1.首先将摄像头的数据渲染到video标签上

1
2
3
navigator.mediaDevices.getUserMedia(constraints)
            .then(gotMediaStream)
            .catch(handleError);

2.将video的某一帧渲染在canvas上

1
picture.getContext('2d').drawImage(videoplay, 0, 0, picture.width, picture.height);

demo地址

3. 录制

3.1 分类

名字 优缺点
服务端录制 服务端录制:优点是不用担心客户因自身电脑问题造成录制失败(如磁盘空间不足),也不会因录制时抢占资源(CPU 占用率过高)而导致其他应用出现问题等;缺点是实现的复杂度很高
客户端录制 优点是方便录制方(如老师)操控,并且所录制的视频清晰度高,实现相对简单;其中最主要的缺点就是录制失败率高。因为客户端在进行录制时会开启第二路编码器,这样会特别耗 CPU。而 CPU 占用过高后,就很容易造成应用程序卡死。除此之外,它对内存、硬盘的要求也特别高

3.2 客户端录制

1.三个问题

录制的格式(flv等);播放场景;什么时候开始播放

2.二进制数据类型

WebRTC 录制音视频流之后,最终是通过 Blob 对象将数据保存成多媒体文件的

名字 使用
ArrayBuffer let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 bufferlet view = new Uint32Array(buffer);
ArrayBufferView ArrayBufferView 指的是 Int8Array、Uint8Array、DataView 等类型的总称,而这些类型都是使用 ArrayBuffer 类实现的
Blob WebRTC 最终就是使用它将录制好的音视频流保存成多媒体文件的。而它的底层是由上面所讲的 ArrayBuffer 对象的封装类实现的,即 Int8Array、Uint8Array 等类型

3.3 录制api

1.录制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function handleDataAvailable(e){
	if(e && e.data && e.data.size > 0){
	 	buffer.push(e.data);			
	}
}

.........

    buffer = [];

	var options = {
		mimeType: 'video/webm;codecs=vp8'
	}

	if(!MediaRecorder.isTypeSupported(options.mimeType)){
		console.error(`${options.mimeType} is not supported!`);
		return;	
	}

	try{
		mediaRecorder = new MediaRecorder(window.stream, options);
	}catch(e){
		console.error('Failed to create MediaRecorder:', e);
		return;	
	}

	mediaRecorder.ondataavailable = handleDataAvailable;
	mediaRecorder.start(10);

2.使用

通过对 buffer 进行转化,可以将视频流保存或者播放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 //播放

 var blob = new Blob(buffer, {type: 'video/webm'});
	recvideo.src = window.URL.createObjectURL(blob);
	recvideo.srcObject = null;
	recvideo.controls = true;
	recvideo.play();

//保存

var blob = new Blob(buffer, {type: 'video/webm'});
	var url = window.URL.createObjectURL(blob);
	var a = document.createElement('a');

	a.href = url;
	a.style.display = 'none';
	a.download = 'aaa.webm';
	a.click();

4.桌面共享

4.1 原理

对于共享者,每秒钟抓取多次屏幕(可以是 3 次、5 次等),每次抓取的屏幕都与上一次抓取的屏幕做比较,取它们的差值,然后对差值进行压缩;如果是第一次抓屏或切幕的情况,即本次抓取的屏幕与上一次抓取屏幕的变化率超过 80% 时,就做全屏的帧内压缩,其过程与 JPEG 图像压缩类似(有兴趣的可以自行学习)。最后再将压缩后的数据通过传输模块传送到观看端;数据到达观看端后,再进行解码,这样即可还原出整幅图片并显示出来

对于远程控制端,当用户通过鼠标点击共享桌面的某个位置时,会首先计算出鼠标实际点击的位置,然后将其作为参数,通过信令发送给共享端。共享端收到信令后,会模拟本地鼠标,即调用相关的 API,完成最终的操作。一般情况下,当操作完成后,共享端桌面也发生了一些变化,此时就又回到上面共享者的流程了

抓屏->压缩编码->传输->解码->显示->控制

4.2 远程桌面协议

桌面数据:包括了桌面的抓取 (采集)、编码(压缩)、传输、解码和渲染。

信令控制:包括键盘事件、鼠标事件以及接收到这些事件消息后的相关处理等

其实在 WebRTC 中也可以实现共享远程桌面的功能。但由于共享桌面与音视频处理的流程是类似的,且 WebRTC 的远程桌面又不需要远程控制,所以其处理过程使用了视频的方式,而非传统意义上的 RDP/VNC 等远程桌面协议,具体的区别如下

过程 内容
第一个环节,共享端桌面数据的采集 WebRTC 对于桌面的采集与 RDP/VNC 使用的技术是相同的,都是利用各平台所提供的相关 API 进行桌面的抓取
第二个环节,共享端桌面数据的编码 WebRTC 对桌面的编码使用的是视频编码技术,即 H264/VP8 等;但 RDP/VNC 则不一样,它们使用的是图像压缩技术。使用视频编码技术的好处是压缩率高,而坏处是在网络不好的情况下会有模糊等问题
第三个环节,传输 编码后的桌面数据会通过流媒体传输协议发送到观看端。对于 WebRTC 来说,当网络有问题时,数据是可以丢失的。但对于 RDP/VNC 来说,桌面数据一定不能丢失
第四个环节,观看端解码 WebRTC 对收到的桌面数据通过视频解码技术解码,而 RDP/VNC 使用的是图像解码技术
第五个环节,观看端渲染 一般会通过 OpenGL/D3D 等 GPU 进行渲染,这个 WebRTC 与 RDP/VNC 都是类似的

4.3 调用api

1.共享

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function gotMediaStream(stream){

	window.stream = stream;
	videoplay.srcObject = stream;

}

......

if(!navigator.mediaDevices ||!navigator.mediaDevices.getDisplayMedia){
	console.log('getDisplayMedia is not supported!');
	return;
}else{
    var constraints = {
		video : {
			width: 640,	
			height: 480,
			frameRate:15
		}, 
		audio : false 
	}

	navigator.mediaDevices.getDisplayMedia(constraints)
			.then(gotMediaStream)
			.catch(handleError);
}

2.记录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function handleDataAvailable(e){
	if(e && e.data && e.data.size > 0){
	 	buffer.push(e.data);			
	}
}

function startRecord(){
	
	buffer = [];

	var options = {
		mimeType: 'video/webm;codecs=vp8'
	}

	if(!MediaRecorder.isTypeSupported(options.mimeType)){
		console.error(`${options.mimeType} is not supported!`);
		return;	
	}

	try{
		mediaRecorder = new MediaRecorder(window.stream, options);
	}catch(e){
		console.error('Failed to create MediaRecorder:', e);
		return;	
	}

	mediaRecorder.ondataavailable = handleDataAvailable;
	mediaRecorder.start(10);

}