极客时间-从零打造音视频直播系统

极客时间-从零打造音视频直播系统学习笔记

WebRTC 1对1通话

WebRTC处理过程

img

音视频采集

我们使用 getUserMedia 方法进行音视频流对象的采集

1
2

var promise = navigator.mediaDevices.getUserMedia(constraints);

其中constraints参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

const mediaStreamContrains = {
video: {
frameRate: {min: 20},
width: {min: 640, ideal: 1280},
height: {min: 360, ideal: 720},
aspectRatio: 16/9
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
};

![image-20220327134534844](/Users/xaviershi/Library/Application Support/typora-user-images/image-20220327134534844.png)

Api和设备检测

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


//判断浏览器是否支持这些 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.kind + ": " + deviceInfo.label +
" id = " + deviceInfo.deviceId);
});
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
});

用摄像头给自己拍照并保存

首先获取视频流到video标签里面

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


'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);

然后将video标签给canvas做参数用于截图

1
2
3
4
5
6
7
8
9
10
11
12

...

var picture = document.querySelector('canvas#picture');
picture.width = 640;
picture.height = 480;

...

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

...

最后创建a标签用于下载

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

...

function downLoad(url){
var oA = document.createElement("a");
oA.download = 'photo';// 设置下载的文件名,默认是'下载'
oA.href = url;
document.body.appendChild(oA);
oA.click();
oA.remove(); // 下载之后把创建的元素删除
}

...
document.querySelector("button#save").onclick = function (){
downLoad(canvas.toDataURL("image/jpeg"));
}
....

如何录制本地音视频

1
2

var mediaRecorder = new MediaRecorder(stream[, options]);

stream,通过 getUserMedia 获取的本地视频流或通过RTCPeerConnection获取的远程视频流

options,可选项,指定视频格式、编解码器、码率等相关信息

1
2
3
4
5
6
7
8
9
10
11

<html>
...
<body>
...
<button id="record">Start Record</button>
<button id="recplay" disabled>Play</button>
<button id="download" disabled>Download</button>
...
</body>
</html>
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44

...

var buffer;

...

//当该函数被触发后,将数据压入到blob中
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);
}

...

回放录制的内容

1
2
3
4
5
6

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

下载录制好的文件

1
2
3
4
5
6
7
8
9
10
11

btnDownload.onclick = ()=> {
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();
}

共享桌面

WebRTC与传统RDP/VNC共享桌面协议不太一样,因为webrtc不需要远程控制,所以它采用的是视频采集传输编码解码那一套,而RDP/VNC则是采用的图片那一套+指令控制鼠标

  1. 数据采集,WebRTC采用的DirectX方式进行采集
  2. 编码,采用的H264/Vp8等视频编码方式
  3. 传输,流媒体传输协议,数据可丢
  4. 解码,视频解码技术解码
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
31
32
33
34
35
36
37
38
39
40
41

var promise = navigator.mediaDevices.getDisplayMedia(constraints);

...

//得到桌面数据流
function getDeskStream(stream){
localStream = stream;
}

//抓取桌面
function shareDesktop(){

//只有在 PC 下才能抓取桌面
if(IsPC()){
//开始捕获桌面数据
navigator.mediaDevices.getDisplayMedia({video: true})
.then(getDeskStream)
.catch(handleError);

return true;
}

return false;

}

...

...
<video autoplay playsinline id="deskVideo"></video>
...

...
var deskVideo = document.querySelect("video/deskVideo");
...
function getDeskStream(stream){
localStream = stream;
deskVideo.srcObject = stream;
}
...

不能再采集桌面的同时采集音频。

录制本地桌面

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
31
32
33
34
35
36
37
38
39
40

...

var buffer;

...

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(localStream, options);
}catch(e){
console.error('Failed to create MediaRecorder:', e);
return;
}

//当捕获到桌面数据后,该事件触发
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(10);
}

...
Author: XavierShi
Link: https://blog.xaviershi.com/2022/03/27/极客时间-从零打造音视频直播系统/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.