WebRTC录制视频原理

  • Post author:
  • Post category:其他


1. 录制视频原理简介

2. WebRTC中提供的录制视频api

webrtc提供了一个MediaRecoder类,听名字就知道它是用来录制媒体数据的。

事实上这个类的确就是用来实现媒体流的录制,它有很多的方法和事件,我们一个个来看,首先我们看一下基本格式,下面先简单看看它的构造方法:

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

在构造方法中,我们需要传入两个参数,其中第一个参数stream(流数据),也就是我们通过getUserMedia获取到的stream, 第二个参数是option选项,可以配置一些我们自己想要的约束条件。





本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓



MediaRecorder 的参数说明:


  • stream

    : 媒体流,可以从getUserMedia、、或获取

  • options

    : 限制选项,有mimeType, audioBitsPerSecond,videoBitsPerSecond,bitsPerSecond.

接下来,我们看看option的限制选项的具体含义如下:

选项类型

含义

说明

mimeType

媒体类型:(1)video/webm (2)audio/webm (3) video/webm;codescs=vp8 (4)video/webm;codecs=h264 (5)audio/webm;codecs=opus

mimeType指的是你要录制的是音频还是视频,录制的格式是什么,最常见的视频是video/mp4;视频编码vp8,音频是audio/mp3;编码ac;

audioBitsPerSecond

音频码率

audioBitsPerSecond指的是每秒音频的码率,码率是根据 编码的情况了,有的是64k有的是128k;

videoBitsPerSecond

视频码率

videoBitsPerSecond指的是视频的码率,视频的码率对于我们一般的存储文件都比较大,必须1M,分辨率比如是720P的,可能是2M多,当然了设置的越多清晰度就越高,如果设置的越小,清晰度也会差些。

bitsPerSecond

整体码率

bitsPerSecond表示总体的码率

除了上面的构造方法为,我们接下来看看还有一些常用的api :

api

用途

说明

start(timeslice)

开始录制

开始录制媒体,timeslice是可选的,如果没填也就是它所有的数据都会存储到一个大的buffer里面去,如果设置了会按时间切片存储数据,比如10秒是一块数据再10秒是另外一块数据。

stop()

停止录制

此时会触发包括最终Blob数据的dataavilable事件

pause()

暂停录制

resume()

恢复录制

isTypeSupported()

类型是否支持

检查支持录制的文件格式,如mp4、webp、mp3,这些都可以通过这个API检查一下是否支持

此外同样WebRTC中的MediaRecorder也提供了很多事件回调:


  • ondataavailable

    事件 :当数据有效的时候会触发这个事件,所以我们可以监听这个事件,当数据 有效了我们可以直接把这个数据存储到缓冲区里,在这个事件里面实际上会传过来一个event,在这个事件里面会有一个data,这个data就是真正录制后的数据,可以拿到这个数据之后进行存储。 每次记录一定时间的数据时(如果没有指定时间片,则记录整个数据时)会定期触发。

  • onerror

    事件 : 当有错误发生时,录制会自动的被停止

3. WebRTC中浏览器实现录制视频

上面简单介绍了一下关于WebRTC中录制视频的一些相关api, 这个Api看起来非常简单,就像苹果,安卓给我们提供的媒体播放器一样,通过简单的start,stop就可以实现媒体的播放一样,接下来我们实际演练一下。

整个html 的内容跟之前的章节相差不大,在开发之前我们将整个流程给大家梳理一下:

  • 首先我们需要加几个标签,第一个标签是video标签,也就是说当我们开启录制之后,通过第二个video标签将录制的视频播放出来,所以我们要加第二个video标签 ,
  • 另外我们要加三个button按钮,第一个button按钮就是录制,点击录制之后就可以将我们采集的音视频数据录制下来;第二个是play,当我们点击play之后,就是将我们录制的数据播放出来,第三个 按钮是download,当我们点击这个按钮,可以将录制的数据直接下载到本地。

废话不多说,直接上源码,index.html内容如下:

<html>
  <head>
    <title>WebRTC中浏览器实现录制视频--孔雨露 20200606</title>
		<style>
			.none {
				-webkit-filter: none;	
			}
 
			.blur {
        /* 特效模糊 */
				-webkit-filter: blur(3px);	
			}
 
			.grayscale {
        /* 特效灰度 */
				-webkit-filter: grayscale(1); 	
			}
 
			.invert {
        /* 翻转 */
				-webkit-filter: invert(1);	
			}
 
			.sepia {
        /* 特效褐色 */
				-webkit-filter: sepia(1);
			}
 
		</style>
  </head>
  <body>
		<div>
			<label>audio Source:</label>
			<select id="audioSource"></select>
		</div>
 
		<div>
			<label>audio Output:</label>
			<select id="audioOutput"></select>
		</div>
 
		<div>
			<label>video Source:</label>
			<select id="videoSource"></select>
    </div>
    <!-- 特效选择器 -->
    <div>
			<label>Filter:</label>
			<select id="filter">
				<option value="none">None</option>
				<option value="blur">blur</option>
				<option value="grayscale">Grayscale</option>
				<option value="invert">Invert</option>
				<option value="sepia">sepia</option>
			</select>
		</div>
    <!-- 
      我们创建一个video标签,这个标签就可以显示我们捕获的音视频数据 
      autoplay 表示当我们拿到视频源的时候直接播放
      playsinlin  表示在浏览器页面中播放而不是调用第三方工具
     -->
     <!-- 通过audio标签只获取音频 -->
     <!-- 
       controls  表示将暂停和播放按钮显示出来,否则它虽然播放声音,但是不会显示播放器窗口
       autoplay  默认自动播放
      -->
    <!-- <audio autoplay controls id='audioplayer'></audio> -->
		<table>
			<tr>
				<td><video autoplay playsinline id="player"></video></td>
				<!-- 添加标签 -->
				<td><video playsinline id="recplayer"></video></td>
				<td><div id='constraints' class='output'></div></td>
      </tr>
			<tr>
				<td><button id="record">Start Record</button></td>
				<td><button id="recplay" disabled>Play</button></td>
				<td><button id="download" disabled>Download</button></td>
			</tr>
		</table>
 
    <!-- 获取视频帧图片按钮 -->
		<div>
			<button id="snapshot">Take snapshot</button>
    </div>
    <!-- 获取视频帧图片显示在canvas里面 -->
		<div>
			<canvas id="picture"></canvas>
		</div>
    <!-- 引入 adapter.js库 来做 不同浏览器的兼容 -->
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="./js/client.js"></script>
  </body>
</html>

上面 html中的client.js 内容如下:

'use strict'
 
var audioSource = document.querySelector('select#audioSource');
var audioOutput = document.querySelector('select#audioOutput');
var videoSource = document.querySelector('select#videoSource');
// 获取video标签
var videoplay = document.querySelector('video#player');
// 获取音频标签
var audioplay = document.querySelector('audio#audioplayer');
 
//div
var divConstraints = document.querySelector('div#constraints');
 
// 定义二进制数组
var buffer;
var mediaRecorder;
 
//record 视频录制 播放 下载按钮
var recvideo = document.querySelector('video#recplayer');
var btnRecord = document.querySelector('button#record');
var btnPlay = document.querySelector('button#recplay');
var btnDownload = document.querySelector('button#download');
 
//filter 特效选择
var filtersSelect = document.querySelector('select#filter');
 
//picture 获取视频帧图片相关的元素
var snapshot = document.querySelector('button#snapshot');
var picture = document.querySelector('canvas#picture');
picture.width = 640;
picture.height = 480;
 
// deviceInfos是设备信息的数组
function gotDevices(deviceInfos){
  // 遍历设备信息数组, 函数里面也有个参数是每一项的deviceinfo, 这样我们就拿到每个设备的信息了
	deviceInfos.forEach(function(deviceinfo){
    // 创建每一项
		var option = document.createElement('option');
		option.text = deviceinfo.label;
		option.value = deviceinfo.deviceId;
	
		if(deviceinfo.kind === 'audioinput'){ // 音频输入
			audioSource.appendChild(option);
		}else if(deviceinfo.kind === 'audiooutput'){ // 音频输出
			audioOutput.appendChild(option);
		}else if(deviceinfo.kind === 'videoinput'){ // 视频输入
			videoSource.appendChild(option);
		}
	})
}
 
// 获取到流做什么, 在gotMediaStream方面里面我们要传人一个参数,也就是流,
// 这个流里面实际上包含了音频轨和视频轨,因为我们通过constraints设置了要采集视频和音频
// 我们直接吧这个流赋值给HTML中赋值的video标签
// 当时拿到这个流了,说明用户已经同意去访问音视频设备了
function gotMediaStream(stream){  
  	// audioplay.srcObject = stream;
  videoplay.srcObject = stream; // 指定数据源来自stream,这样视频标签采集到这个数据之后就可以将视频和音频播放出来
  // 通过stream来获取到视频的track 这样我们就将所有的视频流中的track都获取到了,这里我们只取列表中的第一个
  var videoTrack = stream.getVideoTracks()[0];
  // 拿到track之后我们就能调用Track的方法
  var videoConstraints = videoTrack.getSettings(); // 这样就可以拿到所有video的约束
  // 将这个对象转化成json格式
  // 第一个是videoConstraints, 第二个为空, 第三个表示缩进2格
  divConstraints.textContent = JSON.stringify(videoConstraints, null, 2);
  
  window.stream = stream;
 
  // 当我们采集到音视频的数据之后,我们返回一个Promise
  return navigator.mediaDevices.enumerateDevices();
}
 
function handleError(err){
	console.log('getUserMedia error:', err);
}
function start() {
// 判断浏览器是否支持
if(!navigator.mediaDevices ||
  !navigator.mediaDevices.getUserMedia){
  console.log('getUserMedia is not supported!');
}else{
  // 获取到deviceId
  var deviceId = videoSource.value; 
  // 这里是约束参数,正常情况下我们只需要是否使用视频是否使用音频
  // 对于视频就可以按我们刚才所说的做一些限制
  var constraints = { // 表示同时采集视频金和音频
    video : {
      width: 640,	// 宽带
      height: 480,  // 高度
      frameRate:15, // 帧率
      facingMode: 'enviroment', //  设置为后置摄像头
      deviceId : deviceId ? deviceId : undefined // 如果deviceId不为空直接设置值,如果为空就是undefined
    }, 
    audio : true // 将声音获取设为true
  }
  //  从指定的设备中去采集数据
  navigator.mediaDevices.getUserMedia(constraints)
    .then(gotMediaStream)  // 使用Promise串联的方式,获取流成功了
    .then(gotDevices)
    .catch(handleError);
}
}
 
start();
 
// 当我选择摄像头的时候,他可以触发一个事件,
// 当我调用start之后我要改变constraints
videoSource.onchange = start;
 
// 选择特效的方法
filtersSelect.onchange = function(){
	videoplay.className = filtersSelect.value;
}
 
// 点击按钮获取视频帧图片
snapshot.onclick = function() {
  picture.className = filtersSelect.value;
  // 调用canvas API获取上下文,图片是二维的,所以2d,这样我们就拿到它的上下文了
  // 调用drawImage绘制图片,第一个参数就是视频,我们这里是videoplay,
  // 第二和第三个参数是起始点 0,0
  // 第四个和第五个参数表示图片的高度和宽度
	picture.getContext('2d').drawImage(videoplay, 0, 0, picture.width, picture.height);
}
// 
function handleDataAvailable(e){  // 5、获取数据的事件函数 当我们点击录制之后,数据就会源源不断的从这个事件函数中获取到
	if(e && e.data && e.data.size > 0){
     buffer.push(e.data);  // 将e.data放入二进制数组里面
    //  这个buffer应该是我们在开始录制的时候创建这个buffer
	}
}
 
// 2、录制方法
function startRecord(){
	buffer = []; // 定义数组
	var options = {
		mimeType: 'video/webm;codecs=vp8' // 录制视频 编码vp8
	}
	if(!MediaRecorder.isTypeSupported(options.mimeType)){ // 判断录制的视频 mimeType 格式浏览器是否支持
		console.error(`${options.mimeType} is not supported!`);
		return;	
	}
  try{ // 防止录制异常
    // 5、先在上面定义全局对象mediaRecorder,以便于后面停止录制的时候可以用到
		mediaRecorder = new MediaRecorder(window.stream, options); // 调用录制API // window.stream在gotMediaStream中获取
	}catch(e){
		console.error('Failed to create MediaRecorder:', e);
		return;	
  }
  // 4、调用事件 这个事件处理函数里面就会收到我们录制的那块数据 当我们收集到这个数据之后我们应该把它存储起来
  mediaRecorder.ondataavailable = handleDataAvailable; 
	mediaRecorder.start(10); // start方法里面传入一个时间片,每隔一个 时间片存储 一块数据
}
// 3、停止录制
function stopRecord(){
  // 6、调用停止录制
	mediaRecorder.stop();
}
 
// 1、录制视频 
btnRecord.onclick = ()=>{
	if(btnRecord.textContent === 'Start Record'){ // 开始录制
		startRecord();	// 调用startRecord方法开启录制
		btnRecord.textContent = 'Stop Record'; // 修改button的文案
		btnPlay.disabled = true; // 播放按钮状态禁止
		btnDownload.disabled = true; // 下载按钮状态禁止
	}else{ // 结束录制
		stopRecord(); // 停止录制
		btnRecord.textContent = 'Start Record';
		btnPlay.disabled = false; // 停止录制之后可以播放
		btnDownload.disabled = false; // 停止录制可以下载
 
	}
}
// 点击播放视频
btnPlay.onclick = ()=> {
	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 = 'kyl111.webm';
	a.click();
}

JS 知识补充

在做录制视频的时候,我们会用到js中的存储数据方法,这里介绍几种常用的存储数据方法: 我们最常用的就是Blob,最终是生成一个url或者一个文件,是使用Blob,如果是底层的数据,一般是ArrayBuffer或者是带类型的ArrayBufferView.


  • Blob

    : Blob相当于一块非常高效的存储区域,其他的类型的buffer可以放到这个Blob,那它有什么好处,它可以将整个缓冲区写到文件里去非常的方便,通过JavaScript,一般在写文件之前把它放到Blob里,它的底层实际上就是字节的Array一个无类型的数据缓冲区,在它的上层又封装了很多方法,我们可以调用这种方法很方便的去做各种操作。

  • ArrayBuffer

    :Blob实际上就依赖于ArrayBuffer,ArrayBuffer也可以使用各种各样的数据,通过上层的Blob我们可以对这个ArrayBuffer做各种操作,等于是Blob是对ArrayBuffer的封装,使得操作ArrayBuffer更高效。

  • ArrayBufferView

    :实际上就是各种各样类型的Buffer,比如整型的、double型的、字符型的等各种各样的buffer,这些都可以当作Blob的一个参数 ,它都会在底层做相关的处理





本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓





版权声明:本文为m0_60259116原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。