一、前言

WebRTC 技术已经广泛在各个行业及场景中被应用,但对多数开发者来说,实时音视频及相关技术却是比较不常接触到的。

做为一名 Web 开发者,WebRTC 这块的概念着实花了不少时间才搞明白,一是 WebRTC 本身有较多的独有概念,二是虽然带“Web”字样,但依赖底层概念和网络却是 Web 开发很少接触到的;

本篇文章以 0 经验音视频开发者 视角,类比常用的 Web 技术,期望帮助您简单入门 WebRTC 技术,耐心看完本篇文章,你将:

  1. 了解什么是 WebRTC
  2. 掌握 WebRTC 通话原理
  3. 利用 Chrome debug WebRTC 应用

适合阅读对象:Web开发,有 js 基础,对 WebRTC 感兴趣的同学

二、使用示例

没有接触过 WebRTC 技术的同学,可以先体验下 ZEGO 的 GoEnjoy 产品,里面包含了 WebRTC 在浏览器中的标准使用方案,包括不限于:设备检测、兼容性检测、弱网断网应对策略等,应用是免费的,可戳--->示例 Demo 传送

在进入正文之前,让我们先对它有个基本的印象吧!

图 1

三、简单介绍

体验完 Demo 后,有必要再了解一下技术的发展历史、应用场景等,这些能让我们知道它为什么优秀,哪方面优秀,有哪些缺点等。

程序员经常用到 5W1H 分析法,那么本文就按照这个思路给大家做一下介绍:

What

WebRTC(Web Real-Time Communication),一个可以让用户用自己流量 实现音视频实时通信的框架(APIs),支持浏览器(Firefox、Chrome、safari)以及 iOS、Android 原生系统。

When

2017 年 12 月成为 W3C 草案,国内微信浏览器 19 年下半年才支持,国内手机自带浏览器目前还有不少兼容问题,2021 年 1 月 26 日,成为 W3C 正式标准。

Who

2011年 Google 收购多个子项目(GIPS,On2,VPx),成立了现在的 WebRTC 项目,目前是 Google 的一个开源项目。

Where

可应用在社交/娱乐/教育/工具 等需要实时音视频高效沟通的场景,例如:最近很火的元宇宙。

Why

W3C 标准,开源,插件化,整体效果佳。

How

也是本文重中之中,最终的目的也是让大家能知道如何使用。

在正式代码讲解之前,有一些概念需要先普及一下您也可以先看完代码后,再回来看这个段落,加深理解。):

  • MediaStream:流媒体对象,音/视频数据的一种封装格式,挂载到 video 或 audio 标签上播放;
  • RTCPeerConnection:会话控制,网络和媒体信息收发,作用类似 http 对象;
  • SDP :主要用于两个会话实体之间的媒体协商,作用类似 http 中的配置项。

结合下图类比会更容易理解:

图2

四、前置思考问题

在讲解代码前,还需要思考以下几个问题,否则会不清楚为什么代码中需要交换 SDP,cadidate等(您也可以先看完代码后,再回来看这个段落,加深理解)。

双方使用浏览器通信,浏览器能力,网络情况等不一致会对通信有很大影响,一起思考下下面 2 个问题:

1、视频编码能力不一样?

peer-A 和 peer-B 是视频互动的两边浏览器,他们通讯前必须在视频编码能力上先达成一致,如下图,最终协商出共同的H264,如果无法达成一致,则通讯失败。

图3

2 电脑之间,大多数是在某个局域网中,需要 NAT(Network Address Translation,网络地址转换),因此并不能直接通信 ;

显示情况如下图:

图4

通俗一点比喻: 阿宅今年 30 了(不是我,不要乱猜)被父母逼婚,他只能求助媒婆,才可能被另一个阿宅认识。

媒婆解决阿宅社恐问题,NAT 也需要一种方式绕过,双方才能建立通信,我们需要用到 STUN 和 TURN。

五、代码讲解

终于到我们的代码讲解部分了,下面的代码会按照推流段顺序,分阶段讲解每个步骤所需要用到的API(如果你是直接看代码,建议看完后再回去看第三、四 Part 的介绍,理解会更加深刻)。

步骤一:创建数据源

localStream 作为发送端本地预览画面:

// 创建数据源
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// 显示数据源,localVideo 是 html 中的 video 标签
localVideo.srcObject = localStream;

步骤二:创建发送数据实例

用于发送步骤一中创建的数据:

// 本地实例
const pc1 = new RTCPeerConnection();
// 对端实例
const pc2 = new RTCPeerConnection();

步骤三:配置实例

做这一步的目的是为了交换两端的信息:icecandidate 和 SDP

  • icecandidate:包含通信协议(TCP/UDP)和通信IP,STUN和TURN协议中描述网络信息的格式规范,解决双方网络链接问题;
  • SDP:浏览器能力,包括不限于音视频编码格式,带宽,流控策略等;解决前置思考中,双方能力不匹配问题,通过交换双方 SDP 浏览器会自动选择双方都支持的视频编码格式。
// 告诉对端,本端地址
pc1.addEventListener('icecandidate', async (e) => {
// 发送给对端
// 对端添加本端地址
if (e.candidate) {
await pc2.addIceCandidate(e.candidate);
}
}); pc2.addEventListener('icecandidate', async (e) => {
// 发送给本端
// 本端添加对端地址
if (e.candidate) {
await pc1.addIceCandidate(e.candidate);
}
}); // 创建本端SDP,告诉本端浏览器支持哪些能力
const offer = await pc1.createOffer();
pc1.setLocalDescription(offer);
// 创建远端SDP,告诉远端浏览器支持哪些能力
const answer = await pc2.createAnswer();
pc2.setLocalDescription(answer);
// 。。。。发送远端SDP给本端
// 接收远端sdp,告诉远端浏览器支持哪些能力
pc1.setRemoteDescription(answer);
// 接收客户端sdp,告诉远端浏览器支持哪些能力
pc2.setRemoteDescription(offer);

步骤四:发送数据

localStream.getTracks().forEach(
(track) => pc1.addTrack(track, localStream)
);

步骤五:完整精简版Typescript代码

注意,这里使用的 typescript 编写,实际运行需要先转成 js。

const pc1 = new RTCPeerConnection();
pc1.addEventListener('icecandidate', async (e) => {
if (e.candidate) {
await pc2.addIceCandidate(e.candidate);
}
});
pc1.addEventListener('iceconnectionstatechange', (e) => {
console.log('pc1: iceconnectionstatechange', e);
}); const pc2 = new RTCPeerConnection();
pc2.addEventListener('icecandidate', async (e) => {
if (e.candidate) {
await pc1.addIceCandidate(e.candidate);
}
}); pc2.addEventListener('iceconnectionstatechange', (e) => {
console.log('pc2: iceconnectionstatechange', e);
}); pc2.addEventListener('track', (e) => {
if (e.streams.length > 0) {
remoteVideo.srcObject = e.streams[0];
}
}); const remoteVideo = document.querySelector('#remoteVideo') as HTMLVideoElement;
const localVideo = document.querySelector('#localVideo') as HTMLVideoElement; async function pushStream(answer: RTCSessionDescriptionInit) {
pc1.setRemoteDescription(answer);
} async function pullStream(offer: RTCSessionDescriptionInit): Promise<void> {
pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
pc2.setLocalDescription(answer);
console.warn('answer', answer);
pushStream(answer);
} window.onload = async () => {
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
}); localVideo.srcObject = localStream;
localStream.getTracks().forEach((track) => pc1.addTrack(track, localStream)); const offer = await pc1.createOffer();
pc1.setLocalDescription(offer);
console.warn('pc1 offer', offer);
pullStream(offer);
};

六、应用举例

学习完理论知识,接下来我们一起再实践下,加深对知识的理解 —— 通过 Chrome浏览器 debug WebRTC应用。学会 debug 既可以加深理解,也是后续写代码必不可少的技能,千万不少跳过这一步哦:

1、点击打开示例DEMO

2、另打开一个tab页面,输入: chrome://webrtc-internals/

3、DEMO 中输入相关信息开始直播, 切回到 2 中的 tab 页面,如下图:

蓝色部分:对应的是代码中SDP的处理过程

绿色部分:对应的是网络链接情况

图5

4、继续下来,可以看到推流中实时数据变化:

蓝色部分:拉流实时数据,包括分辨率,码率,丢包率等

绿色部分:推流实时数据,包括分辨率,码率,丢包率等

图6

七、结尾

随着硬件网络的更新换代,我们经历了由文字->图片->视频 载体变更的过程。随着 5G 的普及,音视频技术融于无形正在成为现实,WebRTC 作为其中最重要框架之一,浏览器的支持成熟度也在快速完善当中,持续学习 WebRTC 技术,关注 ZEGO 即构科技!

附下作者常用的工具,推荐收藏:

1 ​​WebRTC samples​​. google 官网 demo,包含最新功能

2 ​​MediaDevices - Web APIs | MDN​

3 ​​WebRTC 1.0: Real-Time Communication Between Browsers​​( W3C标准介绍)

WebRTC 简单入门与实践的更多相关文章

  1. Python编程从入门到实践笔记——变量和简单数据类型

    Python编程从入门到实践笔记——变量和简单数据类型 #coding=gbk #变量 message_1 = 'aAa fff' message_2 = 'hart' message_3 = &qu ...

  2. 初探Electron,从入门到实践

    本文由葡萄城技术团队于博客园原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.   在开始之前,我想您一定会有这样的困惑:标题里的Electron ...

  3. 《Github入门与实践》读书笔记 蟲咋先生的追求之旅(上)

    <Github入门与实践>作者: [日] 大塚弘记 译者:支鹏浩/刘斌   简介 本书从Git的基本知识和操作方法入手,详细介绍了GitHub的各种功能,GitHub与其他工具或服务的协作 ...

  4. Python编程从入门到实践笔记——类

    Python编程从入门到实践笔记——类 #coding=gbk #Python编程从入门到实践笔记——类 #9.1创建和使用类 #1.创建Dog类 class Dog():#类名首字母大写 " ...

  5. Python编程从入门到实践笔记——函数

    Python编程从入门到实践笔记——函数 #coding=gbk #Python编程从入门到实践笔记——函数 #8.1定义函数 def 函数名(形参): # [缩进]注释+函数体 #1.向函数传递信息 ...

  6. Docker 从入门到实践(一)Docker 简介

    读前须知:本教程大部分都是[Docker 从入门到实践 ]一书的知识,有兴趣可以直接观看书籍.同时,借鉴书籍的知识,如有侵权,请告知我,我会删除处理.谢谢. 一.什么是 Docker? Docker ...

  7. Python编程:从入门到实践(选记)

    本文参考< Python 编程:从入门到实践>一书,作者: [ 美 ] Eric Matthes 第1章 起步 1.1     搭建python环境 在不同的操作系统中, Python 存 ...

  8. Python 简单入门指北(二)

    Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...

  9. odoo:开源 ERP/CRM 入门与实践 -- 上海嘉冰信息技术公司提供咨询服务

    odoo:开源 ERP/CRM 入门与实践 看了这张图,或许你对odoo有了一些兴趣. 这次Chat就是和大家一起交流开源ERP/CRM系统:odoo 对以下读者有帮助:研发.产品.项目.市场.服务. ...

  10. Python编程-从入门到实践 Eric Matthes 著 袁国忠 译 - - 第二章 动手试一试

    因为第一章的动手试一试基本都是探索性的,所以直接进入第二章. # 2.2 动手试一试 # 2_1 简单消息: 将一条消息存储到变量中,再将其打印出来. message = 'python 编程从入门到 ...

随机推荐

  1. [oeasy]python0106 七段数码管_显示字母_BP机

    七位数码管进化 回忆上次内容 上次回顾了 7-seg 七位数码管 可以显示数字 甚至是十六进制数字   ​   添加图片注释,不超过 140 字(可选)   能否让 七位数码管 将26个字母 全部都显 ...

  2. linux date格式化获取时间

    转载请注明出处: 在编写shell脚本时,需要在shell脚本中格式化时间,特此整理下date命令相关参数的应用 root@controller1:~# date --help 用法:date [选项 ...

  3. Django 多数据库配置与使用总结

    Django 多数据库配置与使用总结 By:授客 QQ:103355122 #实践环境 Win 10 Python 3.5.4 Django-2.0.13.tar.gz 官方下载地址: https:/ ...

  4. NPIO在指定位置插入新列(附案例和代码)

    背景: I could be mistaken as I am not that familiar with NPOI, however, after a minor search, it appea ...

  5. 题解 WD与数列

    P5161 WD与数列 可以想到原条件是一个差分形式,所以我们对原数组差分.然后发现答案其实就是 \(\sum_{i<j} \min(lcp(i+1,j+1)+1,j-i)\). 这个东西先跑 ...

  6. OpenStack 基本命令

    keystone source /etc/keystone/admin-openrc.sh #登录 openstack user create --password ps1234 --email hq ...

  7. Fiddler篡改请求和响应数据

    Fiddler标记断点后,我们可以通过篡改请求或响应数据,来模拟客户端请求和服务器响应. 一.打断点的方式 1.1 工具栏设置断点 工具栏勾选断点类型进行断点,路径:Rules->Automat ...

  8. 【VMware VCF】VMware Cloud Foundation Part 06:部署 VI 工作负载域。

    VMware Cloud Foundation 标准架构中,管理域和 VI 工作负载域需要分开部署,管理域是初始构建(Bring-up)中部署的一个工作负载域并且只有一个,管理域专门用于承载管理相关组 ...

  9. 【Vue】14 UI库

    PC端: 第一梯队:基于JQuery实现的Dom操作,和一些简单CSS样式组成 Layui Bootstrap EasyUI 第二梯队:基于Vue2.0开发的UI库,组件化开发 ElementUI A ...

  10. python运行报警告:Cython directive 'language_level' not set, using '3str' for now (Py3)

    相关: https://stackoverflow.com/questions/34603628/how-to-specify-python-3-source-in-cythons-setup-py ...