《WebRTC系列》实战 Web 端支持 h265 硬解

1、背景

Web 端实时预览 H.265 需求一直存在,但由于之前 Chrome 本身不支持 H.265 硬解,软解性能消耗大,仅能支持一路播放,该需求被搁置。

去年 9 月份,Chrome 发布 M106 版本,默认开启 H.265 硬解,使得实时预览支持 H.265 硬解具备可行性。

然而 WebRTC 本身支持的视频编码格式仅包括 VP8、VP9、H.264、AV1,并不包含 H.265。根据 w3c 发布的 2023 WebRTC Next Version Use Cases 来看,近期也没有打算支持 H.265 的迹象,因而决定自研实现 WebRTC 对 H.265 的支持。

2、DataChannel

背景说到 chrome 支持了 h265 的硬解,但 WebRTC 并不支持直接传输 h265 视频流。但可以通过 datachannel 来绕过这个限制

WebRTC 的数据通道 DataChannel 是专门用来传输除音视频数据之外的任何数据的(但并不意味着不可以传输音视频数据,本质上它就是一条 socket 通道),如短消息、实时文字聊天、文件传输、远程桌面、游戏控制、P2P加速等。

1)SCTP协议

DataChannel 使用的协议是 SCTP(Stream Control Transport Protocol) (是一种与TCP、UDP同级的传输协议),可以直接在 IP 协议之上运行。

但在 WebRTC 的情况下,SCTP 通过安全的 DTLS 隧道进行隧道传输,该隧道本身在 UDP 之上运行,同时支持流控、拥塞控制、按消息传输、传输模式可配置等特性。需注意单次发送消息大小不能超过 maxMessageSize(只读, 默认65535字节)。

2)可配置传输模式

DataChannel 可以配置在不同模式中,一种是使用重传机制的可靠传输模式(默认模式),可以确保数据成功传输到对等端;另一种是不可靠传输模式,该模式下可以通过设置 maxRetransmits 指定最大传输次数,或通过 maxPacketLife 设置传输间隔时间实现;

这两种配置项是互斥的,不可同时设置,当同为null 时使用可靠传输模式,有一个值不为 null 时开启不可靠传输模式。

3)支持数据类型

数据通道支持 string 类型或 ArrayBuffer 类型,即二进制流或字符串数据。

后续两种方案,都是基于 datachannel 来做

3、方案一 WebCodecs

官方文档: github.com/w3c/webcode…

思路: DataChannel 传输 H.265 裸流 + Webcodecs 解码 + Canvas 渲染。即 WebRTC 的音视频传输通道(PeerConnection) 不支持 H.265 编码格式,但可采用其数据通道(DataChannel)来传输 H.265数据,前端收到后使用 Wecodecs 解码、Canvas 渲染。

优点:

  • 直接传输 H.265 裸码流,无需额外封装,实现简单方便;无冗余数据,传输效率高

  • Wecodecs 解码延迟低,实时性很高

缺点:

  • 音频需额外单独传输、解码和播放,需处理音视频同步问题

  • 既有 sdk 基于 video 封装,webcodes 方案依赖 canvas,既有 video 相关操作,需要全部重写,比如截图,录像等操作

  • 由于线上各项目等历史原因,既有 sdk 改动大,时间上不允许

4、方案二 MSE

官方例子: github.com/bitmovin/ms…

思路:Fmp4封装 + DataChannel 传输 + MSE 解码播放。即先将 H.265 视频数据封装成 Fmp4 格式,再通过 WebRTC DataChannel 通道进行传输,前端收到后采用 MSE 解码, video 进行播放。

优点:

  • 复用 video 标签播放,无需单独实现渲染

  • 音视频已封装到 Fmp4 中,web 端无需考虑音视频同步问题

  • 整体工作量相比 Wecodecs 小,可快速上线

缺点:

  • 设备端实现 Fmp4 封装可能存在性能问题,因此需要云端转发实时进行解封装,或者前端解封装

  • MSE 解码实时性不好(云端首次切片会有 1~2 秒延迟)

5、方案抉择

第一版本先以 MSE 上线。云端,前端开发量相对少,roi 高。

计划第二版上 wecodecs,不仅低延迟,而且可以避免云端耗流量的问题,节省成本。假设在第二版期间,WebRTC 官方支持了 H.265,那么直接兼容官方方案即可。

5.1 细说 Mse 及第一版 sdk 改造

Media Source Extensions, 媒体源扩展。官方文档: developer.mozilla.org/zh-CN/docs/…

引入 MSE 之后,支持 HTML5 的 Web 浏览器就变成了能够解析流协议的播放器了。

从另一个角度来说,通过引入 MSE,HTML5 标签不仅可以直接播放其默认支持的 mp4、m3u8、webm、ogg 等格式,还可以支持能够被 (具备MSE功能的)JS 处理的视频流格式。如此一来,我们就可以通过 (具备MSE功能的)JS,把一些原本不支持的视频流格式,转化为其支持的格式(如 H.264 的 mp4,H.265 的 fmp4)。

比如 B站开源的 flv.js 就是一个典型应用场景。B站的 HTML5 播放器,通过使用 MSE 技术,将 FLV源用 JS(flv.js) 实时转码成 HTML5 支持的视频流编码格式,提供给 HTML5 播放器播放。

// 此 demo 来自下面链接的官方示例, 可以直接跑起来,比较直观
// https://github.com/bitmovin/mse-demo/blob/main/index.html
​
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>MSE Demo</title>
</head>
<body><h1>MSE Demo</h1><div><video controls width="80%"></video></div>
​<script type="text/javascript">(function() {var baseUrl = 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/video/720_2400000/dash/';var initUrl = baseUrl + 'init.mp4';var templateUrl = baseUrl + 'segment_$Number$.m4s';var sourceBuffer;var index = 0;var numberOfChunks = 52;var video = document.querySelector('video');
​if (!window.MediaSource) {console.error('No Media Source API available');return;}// 初始化 msevar ms = new MediaSource();video.src = window.URL.createObjectURL(ms);ms.addEventListener('sourceopen', onMediaSourceOpen);
​function onMediaSourceOpen() {// codecs,初始化 sourceBuffersourceBuffer = ms.addSourceBuffer('video/mp4; codecs="avc1.4d401f"');sourceBuffer.addEventListener('updateend', nextSegment);
​GET(initUrl, appendToBuffer);// 播放video.play();}
​function nextSegment() {var url = templateUrl.replace('$Number$', index);GET(url, appendToBuffer);index++;if (index > numberOfChunks) {sourceBuffer.removeEventListener('updateend', nextSegment);}}
​function appendToBuffer(videoChunk) {if (videoChunk) {// 二进制流转换为 Uint8Array,sourceBuffer 进行消费sourceBuffer.appendBuffer(new Uint8Array(videoChunk));}}
​function GET(url, callback) {var xhr = new XMLHttpRequest();xhr.open('GET', url);xhr.responseType = 'arraybuffer';
​xhr.onload = function(e) {if (xhr.status != 200) {console.warn('Unexpected status code ' + xhr.status + ' for ' + url);return false;}// 获取 mp4 二进制流callback(xhr.response);};
​xhr.send();}})();</script>
</body>
</html>

通过上面的 demo,以及测试(将 dmeo 中的 fmp4 片段换成我们自己的 IPC 设备(摄像头),H.265 类型的)得知,chrome 可以硬解 H.265 类型的 fmp4 片段。So,事情变得明朗了起来。大方向有了,无非就是 H.265 裸流,转换成 fmp4 片段,chrome 底层硬解。

5.2 fmp4 前端实时解封装

H.265 裸流解封装 fmp4,调研下来,如果纯 js 进行封装,工作量挺大。尝试用 wasm 调 c++ 的库,发现即使解封装性能也不大好。所以放在前端被 pass 掉了。

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

5.3 fmp4 云端实时解封装

性能好,对前端 0 侵入。确定了云端解封装,接下来讲讲这段时间开发遇到的核心链路演变,及最终的流程方案。

6、阶段一

云端实时解封装 Fmp4,写死 codecs(音视频编码类型) -> 前端 MSE 解码播放 -> 播放几秒后,失败,MSE 会抛异常,大概意思就是你的数据不对了,前后衔接不上。

排查下来,是 MSE 处于 updating 的时候,不能进行消费,数据直接被丢掉,导致后续数据衔接不上。那既然不能丢,我们就缓存下来。具体可以看下面的代码注释。

具体可以看代码注释:

const updating = this.sourceBuffer?.updating === true;
const bufferQueueEmpty = this.bufferQueue.length === 0;
​if (!updating) {if (bufferQueueEmpty) {// 缓存队列为空: 仅消费本次 bufferthis.appendBuffer(curBuffer);} else {// 缓存队列不为空: 消费队列 + 本次 bufferthis.bufferQueue.push(curBuffer);
​// 队列中多个 buffer 的合并const totalBufferByteLen = this.bufferQueue.reduce((pre, cur) => pre + cur.byteLength,0);const combinedBuffer = new Uint8Array(totalBufferByteLen);let offset = 0;this.bufferQueue.forEach((array, index) => {offset += index > 0 ? this.bufferQueue[index - 1].length : 0;combinedBuffer.set(array, offset);});
​this.appendBuffer(combinedBuffer);this.bufferQueue = [];}} else {// mse 还在消费上一次 buffer(处于 updating 中), 缓存本次 buffer, 否则会有丢帧问题this.bufferQueue.push(curBuffer);}

考虑到 Fmp4 数据每一帧都不可丢失,因此 datachannel 走的是可靠传输。

但是测试下来,发现了新的问题。随着时间的增长,延迟会累积增大。因为丢包后,网络层会进行重试,重试的时间会累积进延时。我们测试下来,网络情况不好的时候,延迟会高达 30 秒及以上,理论上会一直增加,如果你拉流时间足够久的话

7、阶段二

ok,换个思路,既然不丢帧 + 可靠传输带来的延时问题完全不能接受,那么如果换用不可靠传输呢?

不可靠传输,意味着会丢帧。调研下来,Fmp4 可以丢掉一整个切片(一个切片包含多帧),既然如此,我们可以设计一套丢帧算法,只要判断到一个切片是不完整的,我们就把整个切片丢掉。

这样的话,理论上来讲,最多只会有一个切片的延迟,大概在2秒左右,业务层可以接受。

丢帧算法设计思路:在每一帧数据头部增加 4 个字节的数据,用来标识每一帧的具体信息。

  • segNum: 2个字节,大端模式,Fmp4片段序列号,从1开始,每次加1

  • fragCount: 1个字节,Fmp4片段分片总数,最小为1

  • fragSeq: 1个字节,Fmp4片段分片序列号,从1开始

前端拿到每帧数据后,对前 4 个字节进行解析,就能获取到每帧数据的详细信息。举个例子,假如我要判断当前帧是否为最后一帧,只需要判断 fragCount 是否等于 fragSeq 即可。

算法大致流程图:

具体解释一下:

  • frameQueue, 用来缓存每一帧的数据,用来跟后面一帧数据进行对比,判断是否为完整帧

  • bufferQueue, 此队列中的数据,都是完整的切片数据,保证 MSE 进行消费时,数据没有缺失

  /*** fmp4 切片队列 frameQueue,处理丢帧,生产 bufferQueue 内容** @param frameObj 每一帧的相关数据*      每来一帧进行判断*      buffer中加上当前帧是否为连续帧(从第一帧开始的连续帧)*        是*          当前帧是否为最后一帧*            是 拼接buffer帧以及当前帧,组成完整帧,放入另外一个待消费 buffer*            否 当前帧入 buffer*        否 清空 buffer,当前帧入 buffer*/
​
const frameQueueLen = this.frameQueue.length;
const frameQueueEmpty = frameQueueLen === 0;
​// 单一完整分片帧单独处理,直接进行消费if (frameObj.fragCount === 1) {if (!frameQueueEmpty) {this.frameQueue = [];}this.bufferQueue.push(frameObj.value);return;}
​if (frameQueueEmpty) {this.frameQueue.push(frameObj);return;}
​// 是否为首帧let isFirstFragSeq = this.frameQueue[0].fragSeq === 1;// 当前帧加上queue帧是否为连续帧let isContinuousFragSeq = true;for (let i = 0; i < frameQueueLen; i++) {const isLast = i === frameQueueLen - 1;
​const curFragSeq = this.frameQueue[i].fragSeq;const nextFragSeq = isLast? frameObj.fragSeq: this.frameQueue[i + 1].fragSeq;
​const curSegNum = this.frameQueue[i].segNum;const nextSeqNum = isLast? frameObj.segNum: this.frameQueue[i + 1].segNum;
​if (curFragSeq + 1 !== nextFragSeq || curSegNum !== nextSeqNum) {isContinuousFragSeq = false;break;}}
​if (isFirstFragSeq && isContinuousFragSeq) {// 是否为最后一帧const isLastFrame = frameObj.fragCount === frameObj.fragSeq;if (isLastFrame) {this.frameQueue.forEach((item) => {this.bufferQueue.push(item.value);});this.frameQueue = [];this.bufferQueue.push(frameObj.value);} else {this.frameQueue.push(frameObj);}} else {// 丢帧则清空 frameQueue,则代表直接丢弃整个 segment 切片this.emit(EVENTS_ERROR.frameDropError);this.frameQueue = [];this.frameQueue.push(frameObj);}

原本以为大功告成,结果意想不到的事情发生了。

当出现丢帧时,通过上面的算法,确实是把整个切片的数据丢弃掉了,但是 MSE 此时居然再次异常了,意思也是说数据序列不对,导致解析失败。

可是用 ffplay 在本地测试(丢掉一整个切片后,是可以继续播放的),陷入僵局,继续排查。

8、阶段三

话说最近 chatgpt 不是挺火,尝试着用了下,确实找到了原因。MSE 在消费 fmp4 数据时,需要根据内部序列号进行索引标识,因此即使是丢掉整个切片数据,还是会播放失败。怎么办?难道要回到不可靠传输?

经过一番权衡,最终决定,当出现丢帧时,前端通知云端,重新进行切片,并且此时前端重新初始化 MSE。

改造下来发现,效果还不错,我们把不可靠传输,datachannel 重传次数设置为 5。

出现丢帧的概率大大减小,就算出现丢帧,也只会有不到 2 秒的 loading,然后继续出画面,业务层可以接受。

最终,经过上面 3 个阶段的改造,就有了整个链路图。当然其实还有很多细节,没有讲到,比如利用 mp4box 获取 codec, 前端定时检查 datachannel 状态等,就不展开细说了。有兴趣的可以留言讨论

完整的链路图,简单画了下。

9、总结

目前 datachannel + MSE 的方案已经上线,测试下来,线上同时硬解 16 路没有性能问题。

后续会尝试用 webcodes 来进行 H.265 的解析,并处理音视频同步等问题。彻底解决掉延时的问题。

下一篇准备写日常排查 WebRTC 问题的一些思路,也欢迎评论区聊一下日常遇到的一些问题,下篇一起汇总。

原文链接:《WebRTC系列》实战 Web 端支持 h265 硬解 - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24086.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

极客公园对话 Zilliz 星爵:大模型时代,需要新的「存储基建」

大模型在以「日更」进展的同时&#xff0c;不知不觉也带来一股焦虑情绪&#xff1a;估值 130 亿美元的 AI 写作工具 Grammarly 在 ChatGPT 发布后网站用户直线下降&#xff1b;AI 聊天机器人独角兽公司 Character.AI 的自建大模型在 ChatGPT 进步之下&#xff0c;被质疑能否形成…

云平台的ChatGLM部署

最近ChatGPT很火&#xff0c;国内清华也发布了ChatGLM&#xff0c;于是想在云平台上实现一下小型的ChatGLM。目前准备在趋动云这个平台上试试ChatGLM-6B-int8。 目前ChatGLM-6B-int8显存最少需要10G 可以参考GitHub - THUDM/ChatGLM-6B: ChatGLM-6B: An Open Bilingual Dialo…

高通Ziad Asghar:AI处理的重心从云端向边缘侧转移,智能手机是最佳平台 | MEET 2023...

萧箫 整理自 MEET 2023量子位 | 公众号 QbitAI 从Stable Diffusion到ChatGPT&#xff0c;这半年AI算法应用可谓突飞猛进。 但对于硬件领域而言&#xff0c;AI计算的下一个突破口或未来趋势究竟是什么&#xff1f; 尤其是AI应用最大的领域之一——移动端&#xff0c;大量AI算法在…

Stable Diffusion免费(三个月)通过阿里云轻松部署服务

温馨提示&#xff1a;划重点&#xff0c;活动入口在这里喔&#xff0c;不要迷路了。 其实我就在AIGC_有没有一种可能&#xff0c;其实你早就在AIGC了&#xff1f;阿里云邀请你&#xff0c;体验一把AIGC级的毕加索、达芬奇、梵高等大师作画的快感。阿里云将提供免费云产品资源&…

如何通过限制 IP 相关信息 | 控制用户访问站点频率

文章目录 通过 IP 限制反爬实验介绍知识点课程环境 IP 限制实战用 Nginx 限制特定 IP关于 allow 和 deny 的使用说明Nginx 限制 IP 访问频率Python Flask 模拟 IP 黑名单 实验总结 通过 IP 限制反爬 实验介绍 在常规的反爬手段中&#xff0c;IP 限制是应用广泛且比较有效的&a…

win11 报错 你的IT管理员已经限制对此应用一些区域的访问 解决方法

你的IT管理员已经限制对此应用一些区域的访问,你尝试访问的项目不可用。有关详细,请与你的IT支持人员联系。 1.按下wins&#xff0c;在框中输入cmd&#xff0c;右键管理员身份运行 2.在命令提示符中输入 reg add “HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Def…

wordpress开放注册和邮件问题解决

1开放注册 WordPress后台,设置-常规,勾选任何人都可以注册前面的复选框,新用户角色改为作者&#xff0c;保存即可开启。 2新用户注册收不到邮件问题解决 wordpress配置SMTP服务发送邮件(以qq邮箱为例) 第一步、配置邮箱&#xff08;这里介绍qq邮箱&#xff09; 我试过多个…

SLAM基础知识汇总【长期更新】

SLAM基础知识汇总 特征点相关 特征点由关键点和描述子构成&#xff1a; 关键点&#xff1a;特征点在图像里的位置描述子&#xff1a;通常是一个向量&#xff0c;描述了该关键点周围的信息&#xff0c;朝向大小等 [ORB-SLAM2] ORB-SLAM中的ORB特征&#xff08;提取&#xff…

国科大数字图像处理(复习与整理)

图像处理复习笔记&#xff1a; 1、证明一个系统是线性系统2、证明函数卷积的傅里叶变换等于函数傅氏变换后的乘积3、采样定理与混叠4、直方图均衡化第一节课知识点第二节课知识点第三节课知识点第四节课知识点第五节课知识点第六节课知识点第七节课知识点第八节课知识点第九节课…

3D视觉感知新SOTA BEVFormer复现nuscenes数据集测试demo

0 写在前面 分享最近在BEV感知方面的工作&#xff0c;欢迎自动驾驶同行交流学习&#xff0c;助力自动驾驶早日落地。 1.概述 对于自动驾驶而言&#xff0c;BEV&#xff08;鸟瞰图&#xff09;下的目标检测是一项十分重要的任务。尽管这项任务已经吸引了大量的研究投入&#…

CCF认证202305-1重复局面

题目背景 国际象棋在对局时&#xff0c;同一局面连续或间断出现3次或3次以上&#xff0c;可由任意一方提出和棋。 问题描述 国际象棋每一个局面可以用大小为 的字符数组来表示&#xff0c;其中每一位对应棋盘上的一个格子。六种棋子王、后、车、象、马、兵分别用字母 k、q、…

地鼠君黑盒测试--小白如何梳理需求,告别听不懂

没有需求文档的痛苦 刚开始作黑盒&#xff08;功能&#xff09;测试时&#xff0c;小白难免会遇到这种情况&#xff0c;就是需求梳理不清晰&#xff0c;没有需求文档或者需求文档太简单。这种一开始没人带时&#xff0c;不容易发觉后续测试多痛苦。 笔者一开始时&#xff0c;就…

记一次购买海外服务器的经历和python包管理小记

概述 最近在研究ChatGPT&#xff0c;需要有个服务器一直挂着&#xff0c;刚好看到raksmart在搞活动&#xff1b; 可以关注下&#xff0c;他们的活动页面 https://billing.raksmart.com/whmcs/index.php?rp%2Fannouncements&languagechinese-cn 刚好四月&#xff0c;有便…

No signature of method: build_*.android() is applicable for argument types

意思很直观&#xff1a;就是build的时候&#xff0c;android()的参数错误。 更新android studio 后出现这种问题&#xff0c;主要是新版本的生成的app和module模版有所变化引起的。 Android Studio Electric Eel | 2022.1.1 Patch 1 Build #AI-221.6008.13.2211.9514443, built…

01 Faster R-CNN系列

目录 一、 R-CNN 1. R-CNN流程&#xff08;4个步骤 &#xff09; 2. RP的确定 3. 预训练模型微调&#xff08;backbone&#xff09; 4. SVM的分类 5. bbox regression的训练 6. NMS 二、 Fast R-CNN 1. Fast R-CNN算法流程 2. 候选区域生成 3. 预训练模型微调&#…

ChatGPT API 遇见 Tistory:自动化英语学习博客

这是通过集成 ChatGPT API 创建自动化英语学习内容系列中的第三部分。 转发: ChatGPT API Meets Tistory: The Automated English Learning Blogs 项目介绍 介绍 这是通过集成 ChatGPT API 创建自动化英语学习内容系列中的第三部分。 我正在尝试整合各种平台。 第一个是 Se…

cahtgpt算法压力测试(丁真版,更新gpt4(暴风哭泣了已经))

更新 补充了gpt-4版本的答案&#xff0c;只能说牛逼&#xff0c;我收回之前的替代不了高级科研工作者的结论&#xff0c;话不多说上答案&#xff1a; 可以看到这里已经吊打got3.5了&#xff0c;它能把这个问题解释的很清楚了 那么关于GPF算法和varimax的关系呢 可以看到gpt3…

连音乐都可以创作!Google AI部门推出交互式体验

机器学习也能应用在音乐上吗&#xff1f;Google AI部门的Magenta研究项目PAIR计划团队打造出第一个由AI驱动的Doodle服务&#xff0c;Doodle为一项交互式体验&#xff0c;让用户自行创造一段旋律&#xff0c;按下和声演奏&#xff08;Harmonized&#xff09;的按钮后&#xff0…

现在程序员的工资是不是被高估了?

图片来源&#xff1a;AIGC 文章来源&#xff1a;www.zhihu.com/question/295009798 韩冬 不是程序员的工资被高估了。而是在中国&#xff0c;IT行业是少有的劳动者议价能力强&#xff0c;能够比较公平的和资方分配公司收入的行业。 最近三十年我国经济突飞猛进&#xff0c;GDP从…

im即时通讯开发/聊天软件系统/社交APP源码搭建/私有化部署聊天原生开发源码快速搭建

由IM技术专家打造的基于 Java 实现的即时通讯&#xff08;IM&#xff09;项目 我们提供私有化即时通讯解决方案&#xff0c;独立部署在您自己的服务器上、代码可以开源、支持二次开发、苹果端上线指导&#xff0c;源码出售&#xff0c;提供远程技术指导&#xff0c;全程指导服务…