当前位置:   article > 正文

[010]音视频直播系统构建(四) | 音视频数据录制_var blob = new blob()的option用于指定存储成的视频类型

var blob = new blob()的option用于指定存储成的视频类型
效果

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);
  • 1
  • 2

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

在上面的例子中,一开始生成的 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 );
  • 1

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

怎样录制本地音视频?
var mediaRecorder = new MediaRecorder(stream[, options]);
  • 1
  • 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);
	}
	...
  • 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

停止播放关键代码:

mediaRecorder.stop(10);
  • 1

下载关键代码:

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

完整代码:

<!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>
  • 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
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/195237
推荐阅读
  

闽ICP备14008679号