一、前言

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. 使用@nuxtjs/sitemap给项目添加sitemap(网站地图)

    安装使用步骤参照:此博客内容转载博客地址:https://huangliangbo.com/2097 如何使用?(详细图文) 在项目根目录下使用yarn/npm/cnpm 安装 @nuxtjs/sit ...

  2. 流式查询1. mybatis的游标Cursor,分页大数据查询

    流式查询流式查询 指的是查询成功后不是返回一个集合而是返回一个迭代器,应用可以通过迭代器每次取一条查询结果.流式查询的好处是能够降低内存使用.例如我们想要从数据库取 1000 万条记录而又没有足够的内 ...

  3. Using temporary与Using filesort

    Using temporary Using temporary表示由于排序没有走索引.使用union.子查询连接查询.使用某些视图等原因(详见https://dev.mysql.com/doc/ref ...

  4. Django使用 DoesNotExist 异常和 Logger 来记录异常情况

    代码不仅处理了特定的异常类型,还可以添加更多的调试信息来帮助诊断问题.可以使用 DoesNotExist 异常和 Logger 来记录异常情况. from django.core.exceptions ...

  5. 02-springboot配置

    目录 1,前言 2,YAML介绍 3,获取yml配置文件内容 4,springboot的配置文件 5,springboot使用@Value实现映射 6,@PropertySource.@ImportR ...

  6. 第一作者解读|我们这篇Nature Communication背后的故事

    2024年7月16日,大暑将至,立秋不远.我们基于Python的转录组学全分析框架的文章--"OmicVerse: a framework for bridging and deepenin ...

  7. 2023/4/18 SCRUM个人博客

    1.我昨天的任务 初步学习dlib的安装,了解dlib的基础组件 2.遇到了什么困难 对pandas库了解不到位,需要学习其中的基础 3.我今天的任务 初步了解了pandas库,对series和dat ...

  8. STM32F103 SPI详解及示例代码

    1 SPI协议详解 SPI是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,也是一种单片机外设芯片串 ...

  9. Fiddler关于https抓包

    一.Fiddler默认只抓取HTTP请求 Fiddler安装后默认只抓取HTTP请求,如要抓取HTTPS请求需要进行证书安装 二.Fiddler导出HTTPS证书 1.勾选HTTPS 工具栏Tools ...

  10. web3的的入口,钱包,为什么说加密钱包是Web 3活动入口

    Web3.0让未来充满了想象力,或许超越当今人类所知的互联网.有可能彻底改变人类社交互动.商业往来和整个互联网经济.同时数字加密货币行业从业者对Web 3.0赋予了很高期待,希望通过结合后打破互联网巨 ...