[010]音视频直播系统构建(五) | 共享桌面

效果

https://codepen.io/vicksiyi/pen/oKGJzL

共享桌面
  1. 共享者
  2. 远程控制者
  • 对于共享者,每秒钟抓取多次屏幕(可以是 3 次、5 次等),每次抓取的屏幕都与上一次抓取的屏幕做比较,取它们的差值,然后对差值进行压缩;如果是第一次抓屏或切幕的情况,即本次抓取的屏幕与上一次抓取屏幕的变化率超过 80% 时,就做全屏的帧内压缩,其过程与 JPEG 图像压缩类似(有兴趣的可以自行学习)。最后再将压缩后的数据通过传输模块传送到观看端;数据到达观看端后,再进行解码,这样即可还原出整幅图片并显示出来。
  • 对于远程控制端,当用户通过鼠标点击共享桌面的某个位置时,会首先计算出鼠标实际点击的位置,然后将其作为参数,通过信令发送给共享端。共享端收到信令后,会模拟本地鼠标,即调用相关的 API,完成最终的操作。一般情况下,当操作完成后,共享端桌面也发生了一些变化,此时就又回到上面共享者的流程了,我就不再赘述了。
处理过程
动作 备注
抓取(采集) 桌面数据
编码(压缩) 桌面数据
传输 桌面数据
解码 桌面数据
渲染 桌面数据
键盘、鼠标事件、处理事件 等 信令控制
常见协议
  • RDP(Remote Desktop Protocal) : Windows系统下的共享桌面协议
  • VNC(Virtual Network Console) : 不同操作系统上共享远程桌面
共享端桌面数据的采集

WebRTC 对于桌面的采集与 RDP/VNC 使用的技术是相同的,都是利用各平台所提供的相关 API 进行桌面的抓取。以 Windows 为例,可以使用下列 API 进行桌面的抓取。

  • BitBlt:XP 系统下经常使用,在 vista 之后,开启 DWM 模式后,速度极慢。
  • Hook:一种黑客技术,实现稍复杂。
  • DirectX:由于 DirectX 9/10/11 之间差别比较大,容易出现兼容问题。最新的 WebRTC 都是使用的这种方式
  • GetWindowDC:可以通过它来抓取窗口。
共享端桌面数据的编码

WebRTC 对桌面的编码使用的是视频编码技术,即 H264/VP8 等;但 RDP/VNC 则不一样,它们使用的是图像压缩技术。使用视频编码技术的好处是压缩率高,而坏处是在网络不好的情况下会有模糊等问题。

传输

编码后的桌面数据会通过流媒体传输协议发送到观看端。对于 WebRTC 来说,当网络有问题时,数据是可以丢失的。但对于 RDP/VNC 来说,桌面数据一定不能丢失。

观看端解码

WebRTC 对收到的桌面数据通过视频解码技术解码,而 RDP/VNC 使用的是图像解码技术(可对比第二个环节)。

观看端渲染

一般会通过 OpenGL/D3D 等 GPU 进行渲染,这个 WebRTC 与 RDP/VNC 都是类似的。

如何实现桌面共享

1、抓取桌面

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

注意:navigator.mediaDevices.getUserMedia(constraints)与上面的区别,getUserMedia()音频和视频都可以同时采集,但是getDisplayMedia()不能在采集桌面的同时采集音频

抓取桌面关键代码

	; (
			() => {
				// 只有在 PC 下才能抓取桌面
				if (ifMachine.isChrome && ifMachine.isPc) {
					// 开始捕获桌面数据
					navigator.mediaDevices.getDisplayMedia({
						video: {
							width: 640,
							height: 480,
							frameRate: 15,
							facingMode: 'enviroment'
						},
						audio: false
					})
						.then((stream) => {
							window.stream = stream;
							videoplay.srcObject = stream
						})
						.catch((err) => {
							console.log('getDisplayMedia error:', err);
						});

					return true;
				}
				return false;
			}
		)();

[010]音视频直播系统构建(五) | 共享桌面_第1张图片

录制关键代码

需参考:《音视频直播系统构建(四) | 音视频数据录制》

完整代码:

<!DOCTYPE html>
<html>

<head>
	<title>Realtime communication with WebRTC</title>
</head>

<style>
	.rowShow {
		display: flex;
		flex-direction: row;
	}
</style>

<body>
	<div class="rowShow">
		<div>
			<video autoplay playsinline id="player"></video>
		</div>
		<div>
			<video autoplay playsinline id="recvideo"></video>
		</div>
	</div>

	<button id="record">start Record</button>
	<button id="recplay" disabled>Play</button>
	<button id="download" disabled>Download</button>
</body>

<script>
	'use strict';
	let buffer;
	let mediaRecorder;
	let videoplay = document.querySelector("video#player")
	let record = document.querySelector("button#record")
	let recplay = document.querySelector('button#recplay');
	let btnDownload = document.querySelector('button#download');
	let recvideo = document.querySelector('video#recvideo')

	// 判断是否为PC端的Chrome
	let ifMachine = (() => {
		var ua = navigator.userAgent,
			isWindowsPhone = /(?:Windows Phone)/.test(ua),
			isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,
			isAndroid = /(?:Android)/.test(ua),
			isFireFox = /(?:Firefox)/.test(ua),
			isChrome = /(?:Chrome|CriOS)/.test(ua),
			isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),
			isPhone = /(?:iPhone)/.test(ua) && !isTablet,
			isPc = !isPhone && !isAndroid && !isSymbian;
		return {
			isChrome: isChrome,
			isPc: isPc
		};
	})();


	let handleDataAvailable = (e) => {
		if (e && e.data && e.data.size > 0) {
			buffer.push(e.data);
		}
	}

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

		record.disabled = true;
		recplay.disabled = false;
	}

	recplay.onclick = () => {
		mediaRecorder.stop(10);
		recplay.disabled = true;
		btnDownload.disabled = false;

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


	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 = new Date();
		a.click();
	}


		; (
			() => {
				// 只有在 PC 下才能抓取桌面
				if (ifMachine.isChrome && ifMachine.isPc) {
					// 开始捕获桌面数据
					navigator.mediaDevices.getDisplayMedia({
						video: {
							width: 640,
							height: 480,
							frameRate: 15,
							facingMode: 'enviroment'
						},
						audio: false
					})
						.then((stream) => {
							window.stream = stream;
							videoplay.srcObject = stream
						})
						.catch((err) => {
							console.log('getDisplayMedia error:', err);
						});

					return true;
				}
				return false;
			}
		)();

</script>

</html>

注意:录制桌面的 API,目前很多浏览器支持得还不够好,只有 Chrome 浏览器相对比较完善。

你可能感兴趣的:([010]音视频直播系统构建)