时长16:59大小15.57M
对于很多从事 JavaScript 开发的同学来说,基本都认为 JavaScript 是专门做页面控制的。如果用 JavaScript 做音视频处理,那真是很难想象的事儿。你可能首先想到的问题是:JavaScript 或者浏览器的性能跟得上吗?
而 Google 却不这么认为。 Google 就是要做一些常人无法想象,又难以理解的事情,否则它就不是 Google 了。
“浏览器 + WebRTC”就是 Google 给出的答案。2011 年,Google 创立了 WebRTC 项目,其愿景就是可以在浏览器之间快速地实现音视频通信。
随着 WebRTC 1.0 规范的推出,现在主流浏览器 Chrome、Firefox、Safari 以及 Edge 都已经支持了 WebRTC 库。换句话说,在这些浏览器之间进行实时音视频通信已经很成熟了。
下面我就通过讲解 JavaScript/ 浏览器访问电脑上的音视频设备,向你展示通过现代浏览器访问音视频设备是何其简单。
在正式讲解如何通过浏览器采集音视频数据之前,我先向你介绍一下 WebRTC 实现一对一音视频实时通话的整个处理过程。对这个过程的了解,可以帮助你在阅读文章时,能清楚明了地知道所阅读的这篇文章、所要学习的知识点在整个处理过程中的位置。
上面这幅图是整个 WebRTC 1 对 1 音视频实时通话的过程图。通过这幅图,你可以看出要实现 1 对 1 音视频实时通话其过程还是蛮复杂的。
这幅图从大的方面可以分为 4 部分,即两个 WebRTC 终端(上图中的两个大方框)、一个 Signal(信令)服务器和一个 STUN/TURN 服务器。
接下来,我就向你描述一下WebRTC 进行音视频通话的大体过程。
当一端(WebRTC 终端)进入房间之前,它首先会检测自己的设备是否可用。如果此时设备可用,则进行音视频数据采集,这也是本篇我们要介绍的重点内容。
采集到的数据一方面可以做预览,也就是让自己可以看到自己的视频;另一方面,可以将其录制下来保存成文件,等到视频通话结束后,上传到服务器让用户回看之前的内容。
在获取音视频数据就绪后,WebRTC 终端要发送 “加入” 信令到 Signal 服务器。Signal 服务器收到该消息后会创建房间。在另外一端,也要做同样的事情,只不过它不是创建房间,而是加入房间了。待第二个终端成功加入房间后,第一个用户会收到 “另一个用户已经加入成功” 的消息。
此时,第一个终端将创建 “媒体连接” 对象,即RTCPeerConnection(该对象会在后面的文章中做详细介绍),并将采集到的音视频数据通过 RTCPeerConnection 对象进行编码,最终通过 P2P 传送给对端。
当然,在进行 P2P 穿越时很有可能失败。所以,当 P2P 穿越失败时,为了保障音视频数据仍然可以互通,则需要通过 TURN 服务器(TURN 服务会在后面文章中专门介绍)进行音视频数据中转。
这样,当音视频数据 “历尽千辛万苦” 来到对端后,对端首先将收到的音视频数据进行解码,最后再将其展示出来,这样就完成了一端到另一端的单通。如果双方要互通,那么,两方都要通过 RTCPeerConnection 对象传输自己一端的数据,并从另一端接收数据。
以上,就是这幅图大体所描述的含义。而本文要重点介绍的内容就是 WebRTC 终端中的音视频采集部分。
在正式介绍 JavaScript 采集音视频数据的 API 之前,你还需要了解一些基本概念。这些概念虽然都不难理解,但在后面讲解 API 时都会用到它们,很是重要,所以在这里我还是给你着重汇总和强调下。
有了上面这些基本概念,你就可以很容易理解后面所要讲的内容了。接下来,就让我们来具体看看在浏览器下采集音视频的 API 格式以及如何控制音视频的采集吧。
在浏览器中访问音视频设备非常简单,只要调用getUserMedia这个 API 即可。该 API 的基本格式如下:
var promise = navigator.mediaDevices.getUserMedia(constraints);复制代码
它返回一个Promise对象。
从上面的调用格式中可以看到,getUserMedia方法有一个输入参数constraints,其类型为 MediaStreamConstraints。它可以指定MediaStream中包含哪些类型的媒体轨(音频轨、视频轨),并且可为这些媒体轨设置一些限制。
下面我们就来详细看一下它包括哪些限制,这里我引用一下 WebRTC 1.0 规范对 MediaStreamConstraints的定义,其格式如下:
dictionary MediaStreamConstraints { (boolean or MediaTrackConstraints) video = false, (boolean or MediaTrackConstraints) audio = false };复制代码
从上面的代码中可以看出,该结构可以指定采集音频还是视频,或是同时对两者进行采集。
举个例子,比如你只想采集视频,则可以像下面这样定义 constraints:
const mediaStreamContrains = { video: true }; 复制代码
或者,同时采集音视和视频:
const mediaStreamContrains = { video: true, audio: true }; 复制代码
其实,你还可以通过 MediaTrackConstraints 进一步对每一条媒体轨进行限制,比如下面的代码示例:
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 } };复制代码
上面这个例子表示:视频的帧率最小 20 帧每秒;宽度最小是 640,理想的宽度是 1280;同样的,高度最小是 360,最理想高度是 720;此外宽高比是 16:9;对于音频则是开启回音消除、降噪以及自动增益功能。
除了上面介绍的这些参数来控制摄像头和麦克风外,当然还有其他一些参数可以设置,更详细的参数信息,可以跳到下面的参考部分。
通过上面的这些方式就可以很方便地控制音视频的设备了,是不是非常简单?
接下来,我们看一下如何使用上面介绍的 API 来采集视频数据吧。
下面的 HTML 代码非常简单,它引入一段 JavaScript 代码用于捕获音视频数据,然后将采集到的音视频数据通过 video 标签播放出来。
<!DOCTYPE html> <html> <head> <title>Realtime communication with WebRTC</title> <link rel="stylesheet", href="css/client.css" /> </head> <body> <h1>Realtime communication with WebRTC </h1> <video autoplay playsinline></video> <script src="js/client.js"></script> </body> </html>复制代码
为便于你更好地理解该部分的知识,上面这段代码中有两条代码我需要解释一下,一句是:
<video autoplay playsinline></video>复制代码
它是 HTML5 的视频标签,不仅可以播放多媒体文件,还可以用于播放采集到的数据。其参数含义如下:
另一句是:
<script src="js/client.js"></script>复制代码
它引入了外部的 JavaScript 代码,起到的作用就是获取视频数据。具体代码如下:
'use strict'; const mediaStreamContrains = { video: true }; const localVideo = document.querySelector('video'); function gotLocalMediaStream(mediaStream){ localVideo.srcObject = mediaStream; } function handleLocalMediaStreamError(error){ console.log('navigator.getUserMedia error: ', error); } navigator.mediaDevices.getUserMedia(mediaStreamContrains).then( gotLocalMediaStream ).catch( handleLocalMediaStreamError );复制代码
通过上面的代码,我们就可以采集到视频数据并将它展示在页面上了,很简单吧!接下来,我们来大体看一下它的逻辑。
JavaScript 代码中首先执行getUserMedia()方法,该方法会请求访问 Camera。如果是第一次请求 Camera,浏览器会向用户弹出提示窗口,让用户决定是否可以访问摄像头。如果用户允许访问,且设备可用,则调用 gotLocalMediaStream 方法。
在 gotLocalMediaStream 方法中,其输入参数为MediaStream对象,该对象中存放着getUserMedia方法采集到的音视频轨。我们将它作为视频源赋值给 HTML5 的 video 标签的 srcObject 属性。这样在 HTML 页面加载之后,就可以在该页面中看到摄像头采集到的视频数据了。
在这个例子中,getUserMedia方法的输入参数mediaStreamContraints限定了只采集视频数据。同样的,你也可以采集音频数据或同时采集音频和视频数据。
在 WebRTC 中,MediaTrack和MediaStream这两个概念特别重要,后续学习 WebRTC 的过程中,我们会反复用到,所以在这最开始你就要理解透这两个概念。举个例子,如果你想在一个房间里,同时共享视频、共享音频、共享桌面,该怎么做呢?如果你对 MediaTrack 和 MediaStream 真正理解了,就会觉得 WebRTC 处理这种情况太简单了。
另外,在本文中我重点介绍了getUserMedia这个 API,它是 WebRTC 几个核心 API 之一,你必须熟练掌握它。因为通过它,你可以对音视频设备做各种各样的控制,例如,是采集音频,还是采集视频?视频的分辨率是多少?帧率是多少?音频的采样率是多少?
当然,特别关键的一点是可以通过该 API 开启回音消除。回音消除问题是所有做实时互动直播系统最难解决的问题之一。对于 JavaScript 开发同学来说,现在只需要调用该 API 时,将回音消除选项打开就可以了,一下子解决了世界难题。
最后,我还通过一个例子向你具体展示了视频采集后的效果。相信通过这些讲解和展示,你应该已经感受到目前浏览器的强大,以及它可以做更多、更有意思的音视频相关的事情了。
这里你也可以看一下我做出来的效果图(没有美颜):
上面我们一起学习了如何通过getUserMedia获取到音视频数据。而在真实的场景中,我们往往不但要获取到默认设备的音视频数据,还要能获取到某个指定的设备的音视频数据。比如,手机上一般都有两个摄像头——前置摄像头和后置摄像头。那么,你有没有办法采集到指定摄像头的视频数据呢?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。
getUserMedia API 控制设备的参数及其含义如下:
作者回复: 谢谢,铁粉呀!
作者回复: 自己的音频没有mute 吧?把video 标签里加个muted 试试
作者回复: P2P,端与端直接进行连接,不需要服务器中转数据,这样可以节省服务器带宽,但并不意味着不需要服务器,服务器作为辅助功能
作者回复: 这个专栏中没有,其实学完这个专栏你自己可以用android 实现了,很简单
作者回复: 是的,可以在移动端上自己新手试一试!
作者回复: 有 demo, 下周就会放到github上,耐心等待
作者回复: 是的coturn就可以,大家都用它!
作者回复: freeswitch可以做混音服务器,或mcu。不过在直播中,一般用它做服务器混音。
作者回复: 函数没有调用成功,到catch之后报的具体错误是什么?
作者回复: 目前移动端的浏览器确实还存在很多问题,所以移动端一般还是使用native开发比较好。在PC和Mac上可以直接使用浏览器,因为Native与浏览器之间是可以互通的,所以这个问题可以通过这种方式很好的解决。
作者回复: 信令的控制要通过专门的信令通道(如 http/https ws/wss)与信令服务器交互。DataStream 主要用于传输二进制数据,所以这是两回事儿。这两个后面都会讲到。
作者回复: 目前新版本的浏览器都已经支持,IE 确实是个问题,有一些不是特别好的办法,如给 IE 编写的一控件安装上去,来解决这个问题。但更多的直播系统是通过多客户端来解决这个问题,如果用户浏览器实在不支持的话,就用native方案!
作者回复: 用https或http://localhost
作者回复: 没有讲义,文章已经写的足够细了
作者回复: Rtmp 底层用的tcp,wenrtc底层主要使用udp,使用tcp 就注定他在极端网络情况下没法实时通信
作者回复: 适合,无论你做少,只要对音视频感兴趣,想做音视频直播相关的工作都应该看看这个专栏哈
作者回复: 出于安全的原因,你只能用localhost 访问或https 访问时才能检测到mediaDevice
作者回复: 必须是https,主要出于安全的考虑,不能让随便一个网站就能访问你的摄像头不是?另外就是本地测试也是可以的!
作者回复: android 端可以直接用原生的方案哈,用原生的是不是更高效些?