介绍流媒体协议

什么是流媒体协议?

协议是定义数据如何在网络中传输的规则和流程,千万要把它和格式分开,格式是定义音视频数据如何组织和封装,包括容器格式和编码格式的。

流媒体协议简要来说就是视频 / 音频在网络上 实时传输 的规则。包括规定了音视频数据如何被切分、打包、在网络中传输以及在接收端如何还原这种。

流媒体协议是视频在线传输的协议。

互联网上流媒体技术的变更

在过去的接近10多年时间里(从2004年到2018年),网页视频播放一直是 Flash 技术的天下,我们所熟悉的众多视频网站和新闻门户网站一直都在使用 Flash 技术来播放网页视频。

这里的主要原因是IE浏览器的高比例占有量和 Flash 插件在客户端的普及,还有就是主要流媒体服务器产品对 RTMP 协议和FLV 视频格式的广泛支持,这是这一个时期的视频生态系统。

在那个浏览器功能贫瘠的年代,Flash 插件几乎是网页视频的唯一救命稻草。RTMP 低延迟,而且 FLV 容器简单,非常适合流式传输。但是,他太封闭了,不管是服务端的生态 FMS 还是 客户端的 Flash Player,只有 Adobe 能决定 Flash 的未来

2010 年 乔布斯 发表《Thoughts on Flash》,拒绝在 iOS 支持 Flash,几乎是宣判了它的死刑。Chrome 浏览器在2020年彻底抛弃 Flash 技术,之后,包括 Adobe 自己都宣布将放弃 Flash。

随着IE浏览器的衰落和新型浏览器的崛起,Flash 视频播放进入了被淘汰的进程,HTML5 Video 正在成为视频播放的主流技术。Youtube 从2010年就开始尝试使用没有 Flash 的视频播放技术,到2018年前后完全实现了去 Flash 播放。包括2021年,就连 4399 也不再支持 Flash 了

但是变革不是一下子的,随着 Flash 倒下,行业急需一种不依赖插件、能穿透防火墙、且能利用现有 CDN(内容分发网络)的技术。苹果带着 HLS 出现了,它不发明新协议,而是把视频切成一个个 .ts 小片段,通过普通的 HTTP 下载。天然支持移动端,由于走 HTTP 80 端口,没有任何防火墙能挡住它。但是它延迟巨大,催发了 HTTP-FLV 的出现

HTTP-FLV 是国内直播平台,就包括早期的B站的过渡方案。它利用 HTTP 长连接传输 FLV 流,既保留了 RTMP 的低延迟,又规避了 RTMP 协议本身的复杂性。

当前流媒体生态中,网页视频播放不再是一个简单的 <video> 标签,而是一套复杂的 JavaScript 逻辑。

以前浏览器只能播放完整的 MP4 文件。有了 MSE,JavaScript 可以通过网络下载二进制流,像“喂食”一样流式的把数据塞给浏览器的解码器。B站开源的 flv.js,它们在 JS 层解析协议,在浏览器底层解码视频。

最后,一个个牛逼的协议出现了,DASH 类似 HLS 的工业标准,支持根据网速自动切换清晰度。WebRTC 解决延时,跳出了 HTTP 的范畴,使用 UDP 传输,将延迟压低到 500ms 以内。WebSocket 全双工通道,能够传输自定义的私有流格式,配合 Wasm 进行前端软解。

未来趋势是 Wasm 了,由于 H.265 等新编码的专利和浏览器支持不一,现在的顶级流媒体厂商普遍采用 Wasm 软解方案,彻底解决了浏览器支持什么格式的问题

主流流媒体协议有哪些?

流媒体协议 分为 控制协议(规定如何传输) 和 数据传输协议(实际上如何传输)两大类

HTTP-FLV

一般是 FLV 配合 WebSocket 配合使用,是作为 Flash 过渡的一个方案

这个不会展开描述

RTMP

Real Time Message Protocol,实时消息协议

由 Adobe 开发,基于 TCP。它是经典直播推流协议,播放端逐渐被 HLS 替代,但在直播推流端依然是主流,它的特点是延迟较低

RTSP / RTP / RTCP

这一组协议通常配合使用,主要用于实时视频通信,如监控摄像头和视频会议系统。RTSP 负责控制,而实际数据传输通常通过 RTP协议 完成。

  • RTSP(Real-Time Streaming Protocol)实时流式协议

    它是一个控制协议,负责建立和控制媒体流,支持“播放”、“暂停”、“停止”等指令。它本身不传输音视频数据。

  • RTP(Real-Time Transport Protocol)实时运输协议

    它是数据传输协议。负责将音视频数据切片传输并加上时间戳和序列号,确保接收端能按顺序组装并同步播放。

    通常运行在 UDP 之上以保证实时性。

    RTP over TCP:由于 RTP 默认走 UDP,在某些严苛的防火墙环境下会被拦截。将 RTP 数据封装在 TCP 中传输,可以提高穿透性,但会牺牲一部分实时性。

  • RTCP (RTP Control Protocol)实时运输控制协议

    它算是一个质量监控方面的协议。它与 RTP 配合,负责收集传输统计信息,如丢包率、延迟抖动这种,反馈给发送端用于显示提示用户或者以便发送端动态调整编码质量。

SIP / SDP

这一组常见于 VoIP(网络电话)和运营商级音视频通话。

  • SIP(Session Initiation Protocol)会话初始协议

    负责“打招呼”和“挂断”。它管理会话的创建、修改和释放。你可以把它理解为建立通话前的拨号过程。

  • SDP(Session Description Protocol)会话描述协议

    媒体配置清单。它不传数据,而是发送方告诉自己媒体的各种相关信息。例如:“我支持 H.264 编码,分辨率是 1080P,我的 IP 地址是多少”。

HLS

HTTP Live Streaming

HLS 由苹果开发,是目前使用最广泛的流媒体协议之一。

HLS支持码率自适应,可以在不同网络条件下提供最佳的视频质量。

DASH

Dynamic Adaptive Streaming over HTTP,类似 HLS,但更标准、更灵活

WebRTC / WebSocket / WebAssembly

这一套构建了现代 Web 实时通信技术

  • WebRTC(Web Real-Time Communications)Web 实时通信协议

    W3C 标准,支持浏览器间无需插件的实时音视频通信,延迟极低

    它是一个完整的框架,包含了音视频采集、编解码、NAT 穿透和传输。它是唯一能实现浏览器端点对点(P2P)实时通话的技术。

  • WebSocket:

    WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

    它不是专门的流媒体协议,但常被用于传输私有的封装流,或者作为 WebRTC 的信令通道。

  • WebAssembly(Wasm):

    他并非一种单独针对流媒体的协议,它是浏览器内运行的高性能二进制指令

    W3C 制定的一个新的编码方式。是一种底层的二进制格式,旨在让代码像 C++ 一样在浏览器中高速运行,从而突破 JavaScript 的性能瓶颈。它允许开发者使用 C、C++、Rust 等高性能编程语言编写代码,并将其编译为浏览器可执行的机器码。

    比如浏览器原生不支持 H.265 编码,开发者可以用 C++ 编写解码库,编译成 Wasm 运行在浏览器里,实现高性能的前端视频渲染。

各个流媒体协议

Flash / RTMP

都过时很久了,为什么还要看。但是它曾经确实极其风靡,是流媒体技术的标准方案,玩过4399都知道是什么概念。因为要理解现在的流媒体技术,Flash 是一个绝对绕不开的坐标系。它不仅是一个播放器,更是一套闭环的生态系统。

Flash 的风靡源于它提供了一套完整的端到端解决方案:

  • RTMP:实时消息传输协议。

    RTMP 基于 TCP,和众多 TCP 协议一样,使用了分块的思路,它将大块的音视频数据切成一个个极小的 Chunk 块

    它不仅能传视频,还能传控制命令,比如:告诉服务器我要切换清晰度

    它是长连接的,一旦握手成功,数据就源源不断地推过来,没有 HTTP 那种频繁的请求开销。

  • FLV: Flash Video 容器。它极其简单,就像一个带标签的数据流,不需要像 MP4 那样下载完整个索引表才能播放,非常适合边下边播。

  • ActionScript + Flash Player :客户端,这是当时唯一的跨浏览器运行环境。开发者写一次代码,在所有电脑上的 IE 浏览器上都能跑。

直接导致 Flash 消失的原因,其实闭源都是小事,主要是因为它极其耗电。乔布斯曾在《对 Flash 的思考》中直言:Flash 是为 PC 设计的,它对触摸屏极不友好,且严重消耗手机寿命。在移动互联网,Flash 就失去了根基。

而 RTMP 是 Adobe 的私有协议。如果你想开发一个支持 RTMP 的服务器,你得看 Adobe 的脸色。而现在的 WebRTCHLSDASH 都是开放标准,全世界的开发者都能参与改进。

Flash Player 几乎每个月都会被爆出高危漏洞,黑客可以通过网页视频直接控制用户的电脑。这对现代浏览器(Chrome 等)来说是不可接受的。

RTSP

RTSP(Real-Time Stream Protocol)协议是一个基于文本的多媒体播放控制协议,属于应用层。RTSP 以客户端方式工作,对流媒体提供播放、暂停、后退、前进等操作。

想象一下,当你需要欣赏网络中的某一段视频,通过HTTP协议访问其URL、开始下载、下载完成后播放,这几乎难以接受任何形式的在线。

RTSP 由 IETF MMusic 小组开发,已成为互联网建议标准。RTSP 本身并不传送数据,而仅仅是是媒体播放器能控制多媒体流的传送,暂停播放,快进快退等。实际媒体数据的传输可以用 RTP 协议或其他专用协议。

RTSP 用于在希望通讯的两端建立并控制媒体会话,客户端通过发出 VCR-style 命令如 playrecordpause 等来实时控制媒体流。

该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。但是很少单独使用 RTSP,都是配合 RTP 和 RTCP 等一起使用

image-20260319104253032

那么,整体来说

img
  • RTSP 承载与 RTP 和 RTCP 之上,RTSP 并不会发送媒体数据,而是使用 RTP 协议传输
    • RTSP 协议:负责服务器与客户端之间的请求与响应
    • RTP 协议:负责传输媒体数据
    • RTCP 协议:在 RTP 传输过程中提供传输信息

RTP

什么是RTP协议

流,Java 有,但是别急,我说的是网络上的内容,其定义非常广泛,主要是指通过网络传输多媒体数据的技术总称。

流式传输分为两种

  • 顺序流式传输
  • 实时流式传输:应用中数据的交付必须与数据的产生保持精确的时间关系,这样 RTP 和 RTCP 就相应的出现了

RTP (Real-time Transport Protocol),实时传输协议,专为 IP 网络传输音视频等实时媒体流设计的核心协议。它本身不保证可靠传输,但通过序列号、时间戳等机制,为数据提供时序恢复、流同步与丢包检测的能力,通常与 RTCP (RTP Control Protocol) 配合工作。

RTP 并没有规定发送方式,可以选择 UDP 发送或者 TCP 发送,但是为了实时性,基本都是 UDP,根据上面的协议图我们也知道,它是 UDP 之上的一层,所以优先 UDP

RTP协议原理

RTP 协议负责对流媒体数据进行封包并实现媒体流的实时传输,它按照 RPT 数据包格式来封装流媒体数据,并利用与它绑定的那一套进协议行数据包的传输

  • RTP在端口号 1025 到 65535 之间选择一个未使用的偶数 UDP 端口号,而在同一次会话中的 RTCP 则使用下一个基数 UDP 端口号。RTP 默认端口号 5004,所以 RTCP 端口号默认为 5005。

从下图可看出 RTP 被划分在传输层,它建立在 UDP 上。同 UDP 协议一样,为了实现其实时传输功能,RTP 也有固定的封装形式。RTP 用来为端到端的实时传输提供时间信息和流同步,但并不保证服务质量。服务质量由 RTCP 来提供。

image-20260319111644946

RTP 协议传输使用的报文如下

img
  • 其中,时序与乱序恢复,通过序列号和时间戳来实现的
  • 音视频同步也是基于时间戳来是实现的,接收端按时间戳对齐播放
  • 序号不连续,会判定丢包(RTP 不自动重传,由上层或 RTCP 处理)

RTCP

RTCP,Real-time Transport Control Protocol,实时传输控制协议

RTCP 也是用 UDP 来传送的,毕竟为了实时性,但 RTCP 封装的仅仅是一些控制信息,不传媒体

  • RTP 负责传音视频数据
  • RTCP 负责 监控质量、同步、反馈、会话管理

因为 RTP 只做打包,本身没有任何反馈机制,也没有设计那种复杂的机制来保证信息的获取和重试,它不知道有没有丢包,不知道延迟、抖动多大,这些事情是有必要的,所以这些问题全部由 RTCP 解决

RTCP 封装的仅仅是一些控制信息,因而分组很短,所以可以将多个 RTCP 分组封装在一个 UDP 包中。

RTCP 有如下五种分组类型。

image-20260319193622592

SIP

了解 SIP 协议

SIP(Session Initiation Protocol),会话初始协议,是 IETF 制定的应用层信令协议,广泛用于IP网络中实时通信会话的建立、修改与终止。

SIP 是基于文本的,采用类 HTTP 的应用层协议,使用请求-响应交互模式。SIP 协议本身不传输媒体数据,而是通过与 SDP 等协议协同完成会话描述交换。

SIP 支持无状态代理转发与有状态会话控制的灵活组合,在性能与可靠性之间实现平衡,为现代 UC(统一通信)和 WebRTC 系统提供坚实基础。

SIP 用于创建、修改和终止语音、视频等多媒体通信会话,在VoIP和实时通信中起核心作用。

SIP 协议原理

SIP系统的典型部署包括终端用户代理、多种类型的服务器节点以及位于网络边界的防护设备。

  • 用户代理作为通信发起者和接收者,直接参与会话流程;

  • 代理服务器负责请求转发与路由决策;

    代理服务器是SIP信令路径上的中间节点,负责接收请求、执行路由决策并将其转发至下一跳。

  • 注册服务器用于绑定用户当前位置;

    注册服务器负责接收用户的 REGISTER 请求,验证身份后将当前联系地址与 AOR 进行绑定,并存储于位置数据库中。这一过程实现了 SIP 的 用户可达性 机制。

  • 重定向服务器提供目标地址建议;

    与代理服务器不同,重定向服务器不转发请求,而是向客户端返回 3xx 系列响应,告知其应尝试的新地址。

  • 会话边界控制器(SBC)则承担安全加固、NAT穿越和协议合规等关键任务。

    随着SIP应用向公网扩展,网络安全威胁日益严峻。传统的防火墙难以解析SIP信令中的动态端口信息,导致媒体流阻塞。同时,SIP Flood、注册欺骗等攻击频发。为此,会话边界控制器(SBC)成为不可或缺的安全网关。

SIP 协议采用请求-响应模型,因此每个 UA 都具备双重身份:既可以作为客户端发送请求(User Agent Client, UAC),也可以作为服务端接收并处理请求(User Agent Server, UAS)。这种角色不是固定的,而是动态切换的,取决于当前消息的方向。这种双工能力使得 SIP 能够支持对等会话模式,无需预设主从关系,贴合实时电话这种业务

用户代理的具体实现形式多样,主要分为三类

  • 软电话: 运行于通用操作系统之上,以桌面应用或移动App形式存在,就例如 QQ 电话
  • 硬终端:IP 话机,需要接网线的那种座机,内置SIP协议栈与嵌入式操作系统
  • 嵌入式:集成于IoT设备、智能家居网关、工业控制系统中,常以轻量级SIP栈

而正如 SIP 消息请求响应的特点而言,所有 SIP 消息均可分为两大类:请求消息,响应消息

无论哪一类,其结构均由三个主要部分组成:

  • 起始行:分为请求行,代表要做什么、找谁做,和状态行,代表请求处理结果

  • 消息头域

    头域 核心作用 关键特性
    Via 记录消息传输路径,确保响应沿原路返回 每经过一个代理加一行,branch 参数标识事务
    From/To 标识会话双方,From 带 tag(发起方标识),To 在响应中加 tag(接收方标识) From+To+Call-ID = 唯一对话(Dialog)
    Call-ID 唯一标识一次会话(无论多少请求 / 响应) 全局唯一,由客户端随机生成
    CSeq 命令序列(序号 + 方法),用于排序 / 去重 同一会话内序号递增,CANCEL 复用原 INVITE 序号
    Contact 提供终端 “直接联系地址”,避免后续消息经过代理(三角路由优化) 注册时声明 “用户当前位置”
    Max-Forwards 限制最大转发跳数,防止环路 每转发一次减 1,为 0 则返回错误
    Route/Record-Route 强制消息经过指定代理(Route)/ 记录路由(Record-Route),用于会话全程代理 带;lr 参数支持松散路由
  • 消息体

而 SIP 的核心价值是管理会话的生命周期,所有流程围绕「INVITE-200 OK-ACK」三次握手展开,分为 4 个阶段

  • 会话建立:请求-响应模型

    1
    2
    3
    4
    5
    6
    Alice(UAC)→ Bob(UAS):INVITE + SDP Offer(声明自己的媒体能力:编码、端口等)
    Bob → Alice:100 Trying(收到请求)
    Bob → Alice:180 Ringing(被叫振铃)
    Bob → Alice:200 OK + SDP Answer(接受呼叫,反馈自己的媒体能力)
    Alice → Bob:ACK(确认收到200 OK,完成三次握手)
    → 双方开始传输RTP媒体流(音频/视频)
  • 会话中期调整:会话建立后,可动态修改媒体参数

  • 会话正常终止:任一方向对方发送 BYE 请求,对方回复 200 OK 后,双方释放资源、停止媒体流

  • 异常终止:主叫在收到 200 OK 前取消呼叫

SIP 默认明文传输,易被窃听 / 篡改,核心安全加固分两层

  • 信令安全(SIP 消息加密)
    • TLS 加密:SIP 消息通过 TLS 传输(端口 5061),替代明文 UDP/TCP(5060),防止信令窃听;
    • HTTP Digest 认证:注册 / 呼叫时验证用户身份,避免密码明文传输(核心是哈希计算,而非明文)。
  • 媒体安全(RTP 流加密)
    • SRTP:加密 RTP 媒体流,防止音频 / 视频被嗅探;

SIP 的灵活性体现在可扩展,核心扩展方式:

  1. 扩展方法:除标准方法外,新增 INFO(传 DTMF)、REFER(呼叫转接)、SUBSCRIBE/NOTIFY(事件订阅,如在线状态);
  2. 扩展头域:P-Asserted-Identity(运营商身份断言)、Privacy(隐私保护);
  3. 扩展状态码:复用标准码 + Reason 头域(如 480 Temporarily Unavailable + Reason: “DND 模式”),避免自定义码导致兼容问题。

SDP

SDP,会话描述协议

在现代实时通信系统中,SIP负责会话的建立、修改和终止,而真正决定媒体如何传输的关键内容,如编码格式、端口、IP地址、带宽等,则由另一项关键协议承载

那就是 SDP 协议,典型的分离控制与描述的设计思路,其设计初衷是提供一种简洁、可读性强且平台无关的文本格式来描述多媒体会话属性。

它不用于传输数据本身,也不参与控制流程,而是作为 元数据容器 嵌入到 SIP、RTSP 或 HTTP 等协议的消息体中,供通信双方进行能力协商。

SIP 通过 INVITE、200 OK、UPDATE 等方法传递会话请求与响应,但这些信令仅定义了“谁要通话”、“是否接受”,并未说明“用什么方式通话”。这就引出了 SDP 的核心使命:在 SIP 信令之上承载媒体协商信息。典型的 SIP/SDP 交互遵循请求响应模型:

  • Offer :主叫方在 INVITE 请求中携带 SDP Offer,声明自己支持的媒体类型、编码、端口等;
  • Answer :被叫方在 200 OK 响应中返回 SDP Answer,确认接受哪些参数并反馈自身能力;
  • 双方据此建立RTP流连接。

该流程确保了双向兼容性,避免因编解码不匹配导致通信失败。

SDP以纯文本形式组织,每行一个键值对,常见的字段如下

image-20260319195711861

HLS

了解 HLS

HLS,HTTP Live streaming,是基于 HTTP 的流媒体传输协议,由苹果公司所提出的一种由于传输音视频的协议交互方式,HLS 被广泛应用于视频点直播领域。

HLS 采用 HTTP 协议传输音视频数据,HLS通过将音视频流切割成一个个小的切片,基于 HTTP 的文件来下载,每次只下载一些,从而实现便下载边播放,类似于实时在线播放的效果。

由于传输层只采用 HTTP 协议,因此其具备 HTTP 的网传优势,比如可以方便的透过防火墙或者代理服务器,可简单的实现媒体流的负载均衡:当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。

可以方便的结合 CDN 进行媒体分发等,另外 HLS 协议本身可实现码率自适应,通过视频转码,切片成不同码率的TS文件,从而实现播放客户端根据网络带宽情况,自由的选择码流进行播放,但是 HLS 在直播时延时较大。

而 M3U8 就是 HLS 协议中的 索引文件。HLS 协议的主要内容是关于 M3U8这个文本协议的,文本协议非常简洁

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.000000,
hls0.ts
#EXTINF:10.000000,
hls1.ts
#EXTINF:10.000000,
hls2.ts

为了能够实现码率自适应,HLS存在二级索引结构,一级索引文件存放的是不同码率的HLS源的M3U8地址,也就是二级索引文件的地址,二级索引文件如上所示是真正记录了同一码率下TS切片序列的下载地址

HLS 协议原理

HLS 协议的架构如下

image-20260319200041430

HLS 的 架构 流程:

  • 采集与编码:将视频源(摄像头、文件)编码为 H.264/H.265 视频和 AAC 音频。
  • 切片(Slicing):使用 FFmpeg 等工具将视频流切割为短小的 .ts 文件(Transport Stream),并生成一个 .m3u8 索引文件。
  • 分发:将 .m3u8.ts 文件部署到普通的 Web 服务器(Nginx/Apache)或 CDN 上。
  • 播放:客户端请求 .m3u8,根据索引下载对应的 .ts 切片并连续播放。

在 Chrome 等非 Safari 浏览器上,我们需要使用 hls.js 这个库来解析 M3U8。这是一个基于 JavaScript 的库,利用 HTML5 Video 和 MediaSource Extensions 进行播放。

1
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>

HLS 流媒体播放,通常包含 直播 Live 和 VOD (点播)两种

  • 不管是 Live 还是 VOD,HLS 都采用 两级索引 架构

  • VOD 的核心特征是所有 TS 文件已生成完毕,二级 index 文件包含完整的 TS 列表且末尾有#EXT-X-ENDLIST标志,客户端只需一次性获取索引,即可按序下载播放。

    • 客户端请求一级索引文件,服务器响应返回的是包含多码率二级索引的 m3u8 文件,根据这个索引文件,服务器返回完整的 TS 列表,客户端解析出所有 TS 文件的地址,按顺序缓存。客户端顺序下载 TS 文件并播放

      那么拖拽如何实现的

      • 客户端根据时间点计算对应的 TS 序号(如总时长 28 秒,拖拽到 15 秒→对应video_001.ts);
      • 直接请求该 TS 文件,无需重新下载 index 文件;
      • 从该 TS 开始继续顺序播放。

      如果不切换码率等,index 文件仅需下载 1 次,所有 TS 文件地址一次性获取

  • Live 的核心特征是TS 文件实时生成,二级 index 文件无#EXT-X-ENDLIST,且会被服务器持续重写(删除旧 TS、添加新 TS),客户端需循环请求 index 文件以获取最新的 TS 列表。

    • 客户端请求一级索引文件,逻辑同 VOD,然后客户端首次请求二级索引文件,服务器会返回当前已生成的 TS 列表,客户端下载 TS 文件并播放,但是注意,Live 有 时延 设计,客户端不会播放最新的 TS。然后客户端会循环请求二级索引文件,服务器会返回已更新的 index 文件,重复 “请求 index→下载新 TS→播放” 循环,直到直播结束

WebSocket

这个太重量级了我必须单开一篇

WebRTC

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,既然是实时,它默认也是优先使用 UDP,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。

首先,从概念可以看出,WebRTC 通讯过程不需要中间媒介,所以说,该如何进行连接?不同环境下会有什么问题?

这里两者进行连接就需要找到对方,当然是需要通过 IP + 端口号,毕竟只有通过 IP 才能找到对应的设备

既然是点对点(P2P),且不需要中间媒介传输数据,那两个位于世界不同角落、或者两台设备的网络环境是局域网、还有那些躲在防火墙和路由器后面的浏览器,是怎么发现对方并 握手 成功的?如果通讯的两台设备建立连接后,无法解析对方的音视频格式该怎么办?

实际上,WebRTC 虽然在数据传输阶段是 P2P 的,但在建立连接阶段,必须依赖中间服务器。

要实现 P2P,WebRTC 必须解决三个问题:交换媒体信息穿透网络隔离实时传输数据

  • WebRTC 标准并没有规定信令的具体实现。你需要自己用 WebSocketHTTP 搭建一个中转。

    就像相亲前交换照片和电话。浏览器 A 生成一个包含自己音视频能力的描述文件,这里一般是 SDP,通过信令服务器传给浏览器 B。

  • 而极大多数电脑没有公网 IP,是藏在路由器(NAT)后面。

    这步就可以使用内网映射的方式解决:浏览器向 STUN 服务器发送请求,STUN 回复:“你的公网 IP 是 1.2.3.4,端口是 10001”。这个过程叫打洞。也就是,内网穿透

    某些企业防火墙或移动运营商网络使用 对称型 NAT。这种 NAT 每次连接不同的目标都会更换端口,导致 STUN 拿到的端口和实际连接时的端口不一致,导致穿透失败。必须部署 TURN 服务器来中转流量。

  • 媒体协议层的加密与同步

    一旦路径通了,就轮到协议上场去做具体的传输和加密等了

WebAssembly

MDN 官方对于 Wasm 的文档:https://developer.mozilla.org/zh-CN/docs/WebAssembly

什么是WebAssembly

2019 年 12 月 5 日,WebAssembly正式加入 HTML、CSS 和 JavaScript 的 Web 标准大家庭。

很多事情都会受益于这一全新的标准,并且它在浏览器中的性能表现是空前的。

WebAssembly 是一种使用非 JavaScript 代码,并使其在浏览器中运行的方法。这些代码可以是 C、C++ 或 Rust 等,它们会被编译进你的浏览器,在你的 CPU 上以接近原生的速度运行。而且这些代码是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,你可以直接在 JavaScript 中将它们当作模块来用。

对于 WebAssembly 的编译产生的二进制文件,很类似与 Java 中的字节码,它的存在除了统一不同高级语言,也兼顾跨平台性和性能的。

对于 Web 平台而言,WebAssembly 具有巨大的意义——它提供了一条使得以多种语言编写的代码都可以接近原生的速度在 Web 中运行的途径,使得以前无法在 Web 上运行的客户端应用程序得以在 Web 上运行。

它是如何工作的?

WebAssembly 的核心思想是通过编译高性能的其他语言,生成一种中间格式的二进制文件,类似 Java 中的字节码,然后通过 JavaScript 将这些文件加载到网页中执行。因为 WebAssembly 的二进制格式直接面向机器,效率更高。

这使得复杂的计算任务可以交给 WebAssembly 处理,从而使得 JavaScript 专注于负责与页面元素的交互。

例如,在开发一个在线视频编辑器时,WebAssembly 可以处理视频剪辑等高计算任务,而 JavaScript 负责用户界面交互。

首先,使用 C、C++ 或其他语言编写一段源代码,这段代码可以解决对于 JS 来说某个过于复杂,性能影响大的流程。比如 FFmpeg 音视频处理逻辑

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
// image_process.cpp
#include <emscripten/emscripten.h>
#include <cmath>

// 保持 C 语言的导出名称,防止 C++ 的名称修饰(Name Mangling)
#ifdef __cplusplus
extern "C" {
#endif

// EMSCRIPTEN_KEEPALIVE 告诉编译器:不要移除这个函数,即使它看起来没被调用。
// 这个函数将被 JavaScript 调用。
EMSCRIPTEN_KEEPALIVE
void make_grayscale(unsigned char* data, int width, int height) {
// 图像数据通常是 RGBA 格式,每个像素占 4 个字节
int total_bytes = width * height * 4;

for (int i = 0; i < total_bytes; i += 4) {
// 获取 RGBA 分量
unsigned char r = data[i];
unsigned char g = data[i + 1];
unsigned char b = data[i + 2];
// data[i + 3] 是 Alpha 通道,我们不改变它

// 使用加权平均法计算灰度值 (符合人眼对不同颜色的敏感度)
// 这是一个相对耗时的浮点运算
unsigned char gray = static_cast<unsigned char>(0.299f * r + 0.587f * g + 0.114f * b);

// 将 RGB 三个通道都设置为灰度值
data[i] = gray; // R
data[i + 1] = gray; // G
data[i + 2] = gray; // B
}
}

#ifdef __cplusplus
}
#endif

然后,使用 Emscripten 将你的源代码编译为 WebAssembly,这一步完成时,你将得到一个 wasm文件。

1
2
3
4
5
6
7
emcc image_process.cpp -o image_process.js \
-O3 \
-s EXPORTED_FUNCTIONS='["_make_grayscale", "_malloc", "_free"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap"]' \
-s ALLOW_MEMORY_GROWTH=1 \
-s MODULARIZE=1 \
-s EXPORT_NAME="'createImageProcessor'"
  • 其中,emcc image_process.cpp是输入 C++ 文件。
  • -o image_process.js这告诉 Emscripten 生成两个文件:
    • image_process.wasm:实际的二进制代码。
    • image_process.js:这是一个“胶水代码”文件。它负责加载 Wasm,设置内存,并将 C++ 函数封装成 JavaScript 可以轻松调用的形式。
  • -s MODULARIZE=1-s EXPORT_NAME="'createImageProcessor'":这两项合起来,将生成的 JS 胶水代码封装成一个 Promise 化的工厂函数,方便我们像现代 ES6 模块一样异步加载和初始化。

最后,你将在网页上使用这个 wasm文件,将来你可以像其他 ES6 模块一样加载这个文件。

  • 现在你拥有了 image_process.wasmimage_process.js。创建一个 HTML 文件来运行它们。
  • Wasm 的核心难点在于内存管理,JS 不能直接把自己的对象传给 C++,必须把数据复制到 Wasm 的专用内存堆(Heap)中,让 Wasm 编译后的 C++ 处理,然后再复制出来。