[010]音视频直播系统构建(四) | 音视频数据录制

本文详细介绍了使用WebRTC技术进行音视频录制的方法,包括客户端与服务端录制的优缺点对比,录制原理,以及如何利用MediaRecorder API实现音视频流的录制、停止与下载。同时,还探讨了音视频流的存储格式选择与播放方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果

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

录制
  1. 服务端录制
  2. 客户端录制

服务端录制: 优点是不用担心客户因自身电脑问题造成录制失败(如磁盘空间不足),也不会因录制时抢占资源(CPU 占用率过高)而导致其他应用出现问题等;缺点是实现的复杂度很高。

客户端录制: 优点是方便录制方(如老师)操控,并且所录制的视频清晰度高,实现相对简单。这里可以和服务端录制做个对比,一般客户端摄像头的分辨率都非常高的(如 1280x720),所以客户端录制可以录制出非常清晰的视频;但服务端录制要做到这点就很困难了,本地高清的视频在上传服务端时由于网络带宽不足,视频的分辨率很有可能会被自动缩小到了 640x360,这就导致用户回看时视频特别模糊,用户体验差。不过客户端录制也有很明显的缺点,其中最主要的缺点就是录制失败率高。 因为客户端在进行录制时会开启第二路编码器,这样会特别耗 CPU。而 CPU 占用过高后,就很容易造成应用程序卡死。除此之外,它对内存、硬盘的要求也特别高。

基本原理

在了解基本原理之前首先得搞清楚以下问题:

  1. 录制后音视频流的存储格式是什么呢?(存储格式的选择对于录制后的回放很重要!!!)
  2. 录制下来的音视频流如何播放?(普通播放器播放 or 私有播放器 等)
  3. 怎样停止录制?

在接入正题之前,首先我们得思考一下:JS存储二进制数据类型(ArrayBuffer、ArrayBufferView、Blob)之间关系

1. ArrayBuffer

ArrayBuffer 对象表示通用的、固定长度的二进制数据缓冲区。因此,你可以直接使用它存储图片、视频等内容。

但你并不能直接对 ArrayBuffer 对象进行访问,类似于 Java 语言中的抽象类,在物理内存中并不存在这样一个对象,必须使用其封装类进行实例化后才能进行访问。

也就是说, ArrayBuffer 只是描述有这样一块空间可以用来存放二进制数据,但在计算机的内存中并没有真正地为其分配空间。只有当具体类型化后,它才真正地存在于内存中。如下所示:

let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
let view = new Uint32Array(buffer);

let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);

在上面的例子中,一开始生成的 buffer 是不能被直接访问的。只有将 buffer 做为参数生成一个具体的类型的新对象时(如 Uint32Array 或 DataView),这个新生成的对象才能被访问。

2. ArrayBufferView

ArrayBufferView 并不是一个具体的类型,而是代表不同类型的 Array 的描述。这些类型包括:Int8Array、Uint8Array、DataView 等。也就是说 Int8Array、Uint8Array 等才是 JavaScript 在内存中真正可以分配的对象。

以 Int8Array 为例,当你对其实例化时,计算机就会在内存中为其分配一块空间,在该空间中的每一个元素都是 8 位的整数。再以 Uint8Array 为例,它表达的是在内存中分配一块每个元素大小为 8 位的无符号整数的空间。

通过这上面的描述,你现在应该知道 ArrayBuffer 与 ArrayBufferView 的区别了吧?ArrayBufferView 指的是 Int8Array、Uint8Array、DataView 等类型的总称,而这些类型都是使用 ArrayBuffer 类实现的,因此才统称他们为 ArrayBufferView。

3. Blob

Blob(Binary Large Object)是 JavaScript 的大型二进制对象类型,WebRTC 最终就是使用它将录制好的音视频流保存成多媒体文件的。而它的底层是由上面所讲的 ArrayBuffer 对象的封装类实现的,即 Int8Array、Uint8Array 等类型。

var aBlob = new Blob( array, options );

其中,array 可以是ArrayBuffer、ArrayBufferView、Blob、DOMString等类型 ;option,用于指定存储成的媒体类型。

怎样录制本地音视频?
var mediaRecorder = new MediaRecorder(stream[, options]);
  • stream,通过 getUserMedia 获取的本地视频流或通过 RTCPeerConnection 获取的远程视频流。
  • options,可选项,指定视频格式、编解码器、码率等相关信息,如 mimeType: 'video/webm;codecs=vp8'

MediaRecorder 对象还有一个特别重要的事件,即 ondataavailable 事件。当 MediaRecoder 捕获到数据时就会触发该事件。通过它,我们才能将音视频数据录制下来。

开始录制关键代码:

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

	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);
	}
	...

停止播放关键代码:

mediaRecorder.stop(10);

下载关键代码:

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 = 'newBuffer.webm';
		a.click();
}

完整代码:

<!DOCTYPE html>
<html>

<head>
	<title>WebRTC Learing</title>
</head>

<body>
	<div>
		<video autoplay playsinline id="player"></video>
	</div>
	<div>
		<video id="recvideo"></video>
	</div>
	<button id="record">Start Record</button>
	<button id="recplay" disabled>Play</button>
	<button id="download" disabled>Download</button>
</body>

<script>
	'use strict';
	var buffer;
	var mediaRecorder
	//是否已截图
	var isSelectPicture = false;

	var videoplay = document.querySelector('video#player');
	var record = document.querySelector('button#record');
	var recplay = document.querySelector('button#recplay');
	var btnDownload = document.querySelector('button#download');
	var recvideo = document.querySelector('video#recvideo')


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

	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;

		var 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 = 'newBuffer.webm';
		a.click();
	}

		; (
			() => {
				if (!navigator.mediaDevices ||
					!navigator.mediaDevices.getUserMedia) {

					console.log('getUserMedia is not supported!');
					return;

				} else {
					var constraints = {
						video: {
							width: 640,
							height: 480,
							frameRate: 15,
							facingMode: 'enviroment'
						},
						audio: false
					}

					navigator.mediaDevices.getUserMedia(constraints)
						.then((stream) => {
							window.stream = stream
							videoplay.srcObject = stream;
						})
						.catch((err) => {
							console.log('getUserMedia error:', err);
						});
				}
			}
		)();

</script>

</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值