Web 直播
直播有两个特点:
- 获取的是流数据
- 要求实时性(决定了数据源不可能在服务器上,而是在某一个客户端)
流数据。我们常接触的数据就是 ajax
从接口获取的 json
数据,特别一点的可能是文件上传。这些数据的特点是,它们都属于一次性就能拿到的数据。我们一个请求,一个响应,完整的数据就拿回来了。
但是流不一样,流数据获取是一帧一帧的,可以理解为是一小块一小块的。像直播流的数据,它并不是一个完整的视频片段,它就是很小的二进制数据,需要一点一点的拼接起来,才有可能输出一段视频。
直播整体流程
🤔 数据源在客户端,那么又是怎么到达其他客户端的呢?
一个完整的 Web 媒体串流应当具备 3 种角色
- 推流客户端(主播侧)
- 媒体服务器(MediaServer)
- 拉流客户端(观众侧)
视频流
流媒体:客户端是需要源源不断接受来自外界传输的音视频,并且需要播放已经接受到的部分
区分于 光盘、下载的场景
所有在线观看的网站都是流媒体平台,这个名词常用于国外,国内叫视频平台(AppleTv Netflix HBO+ Disney+ 等更纯粹...)
视频格式/后缀
容器格式(理解为将比特流按照一定顺序放进特定的盒子里)
常见的容器格式有: MP4、rmvb、rm、flv、AVI、mov、WMV、mkv
🤔 那选用不同格式来装视频有什么问题吗?
没有任何问题,但是我们需要知道如何将该盒子解开,并且能够找到对应的解码器进行解码。只要我有这些对应的解码器以及播放器。
将视频比特流放进一个盒子里面,如果其中某一段出现问题,那么最终生成的文件实际上是不可用的,因为这个盒子本身就是有问题的。
MP4,MKV 等等类似这种封装,必须拿到完整的音视频文件才能播放,因为里面的单个音视频数据块不带有时间戳信息,播放器不能将这些没有时间戳信息数据块连续起来,所以就不能实时的解码播放。
FLV
FLV 格式的流中,每一个音视频数据都被封装成了包含时间戳信息头的数据包。在传输时,只需要当播放器拿到这些数据包解包的时候能够根据时间戳信息把这些音视频数据和之前到达的音视频数据连续起来播放。
压缩技术(编码格式)
同一种封装格式中可以放不同编码的视频,不过一种视频容器格式一般是只支持某几类编码格式的视频。
国内常见的编码格式:
- AVC/H.264
- HEVC/H.265
- AV1
AVC/H.264
比较早期成熟的压缩技术,兼容性最好,但是相对的体积大
HEVC/H.265
、AV1
更强大的压缩技术,但是对设备要求高,体积小
🤔 为什么反感用高压缩技术?
HEVC/h.265
编码
AV1
编码
- 硬解:字面上理解就是用硬件来进行解码,是使用 GPU 的专门模块来解码。
- 软解:字面上理解就是用软件来进行解码,是使用 CPU 来运行视频编解码代码。
软硬解各有优缺点:
- 软解:在软解码过程中需要对大量的视频信息进行运算,所以对 CPU 性能的要求非常高,尤其是对高码率的视频来说巨大的运算量会造成转换效率低,发热量高等问题。不过软解码的过程中不需要复杂的硬件支持,兼容性高。即使是新出的视频编码格式,也可以为其编写新的解码程序;
- 硬解:硬解码调用 GPU 的专门模块来解码,拥有独特的计算方法,解码效率高。这样不但能够减轻 CPU 的负担,还有着低功耗,发热少等特点。但是由于硬解码起步相对晚,软件和驱动对他的支持度低,基本上硬解码内置什么样的模块就解码什么样的视频,面对各色各样的视频编码样式,兼容性没那么好。
解码方式 | 效率 | 功耗 | 兼容性 |
---|---|---|---|
软解 | 低 | 高 | 高 |
硬解 | 高 | 低 | 低 |
随着 Chrome 在 107 版本支持 H.265 的硬解,以及 Web 平台上 H.265 软解技术的成熟,在 Web 平台上规模化部署 H.265 视频的时机已经成熟。关于 H.265 编码格式以及它的好处,网上已经有非常多的介绍了。它最重要的好处是更低的部署成本,因此对于视频服务供应商来说,是尽量采用的
查看浏览器的 gpu 支持
- chrome://gpu
- chrome://media-internals
- 活动监听器
VTDecoderXPCService
前端解码-WASM(软解,任何平台): 基于 WASM
+ FFMPEG
编译实现,支持所有支持 WASM
的浏览器
收费问题
网络传输
RTMP(Real Time Messaging Protocol 实时信息控制协议)
RTMP 是 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议, 该协议在国内直播平台中较为普及。
RTMP 是一种基于 TCP 进行实时流媒体通信的网络协议,主要用来在 Flash 平台和支持 RTMP 协议的流媒体服务器之间进行音视频和数据通信。RTMP 协议下可以用来拉流,也可以进行退流。在浏览器中并不支持 RTMP 协议,只能通过 Flash 插件进行处理。RTMP 传输是所支持的媒体格式为 FLV。
HTTP-FLVs
让原本只能在 RTMP 中进行传输的 FLV 音视频流也能够在 HTTP 下进行传输
由于 HTML 的 Video 不直接支持 Flv 格式的音视频,有 Flash 插件才能够播放
HLS(HTTP Living Stream)
是一个由苹果公司提出的基于 HTTP
的流媒体网络传输协议。
HLS
的工作原理:把整个流分成一个个小的基于 HTTP
的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的 extended M3U (m3u8) playlist
文件,用于寻找可用的媒体流。
视频在 server
服务器上被转换成 HLS
格式的视频(既 TS
和 m3u8
文件)文件。客户端只需要访问一级 m3u8
文件的路径就会自动播放 HLS
视频流了
延迟问题:
由于 HLS
需要将采集到的音视频进行分片、客户端也需要对接受到的分片后的音视频进行合并处理,因此相对来时会存在比较大的延迟,大概会达到 10s 左右
兼容问题:
HLS
在 PC
端仅支持 safari
浏览器,而其他大部分 PC
浏览器使用 HTML5 video
标签由于无法解析 TS
所以不能直接播放(需要通过 hls.js
)
移动端不论是安卓还是 IOS 统统都原生支持 HLS
如果想要在 PC 非safari 浏览器上使用到 HLS,仍然需要使用其他技术手段才能实现
m3u8 + TS/m4s
m3u8
的命名来源是 m3u 文件 + utf-8
编码而来
m3u
实际上就是一个索引文件,其中可以记录 TS
文件地址,客户端会按照下载的顺序进行连续播放。
#EXTM3U // 声明文件为M3U,必须写在第一行
#EXT-X-PLAYLIST-TYPE:VOD // 当前播放类型为点播
#EXT-X-TARGETDURATION:10 //每个视频分段最大的时长(单位秒)
#EXTINF:10, //下面ts切片的播放时长
2000kbps-00001.ts //ts文件路径
#EXTINF:10,
2000kbps-00002.ts
#ZEN-TOTAL-DURATION:20
#ZEN-AVERAGE-BANDWIDTH:2190954
#ZEN-MAXIMUM-BANDWIDTH:3536205
#EXT-X-ENDLIST // m3u结束指令
当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率(通过切换m3u8的返回内容)
TS(Transport Stream)
视频流:国内大部分平台由于 flv
相对于 hls
延迟低很多,所以采用 flv
视频流来播放
视频播放
拉取
我们需要一种类似 websocket
的长连接来持续获取 flv
内容
fetch API
就天然支持
fetch("./a.flv").then((res) => {
const reader = res.body.getReader();
const pump = async () => {
const data = await reader.read(); // ✨ 长连接拉取
if (!data.done) pump();
};
pump();
});
可能大家还听过 WS-FLV
,这是使用 WebSocket
去拉 FLV
流,相比 HTTP-FLV
没啥优势,所以开始尽可能使用 HTTP-FLV
在我看来 WS-FLV
唯一的作用是兼容 IE 11
浏览器,因为 IE 11
是不支持 fetch
的,这时候只有用 WebSocket 去拉流
video 标签
使用原生
video
标签播放视频文件
<video controls autoplay>
<source src="your_video.mp4" type="video/mp4; codecs=hevc" />
<source src="your_video.mp4" type="video/mp4; codecs=av1" />
<source src="your_video.mp4" type="video/mp4; codecs=avc1" />
</video>
经过上面我们已经了解到了视频播放的步骤,我们可以理解为 video
原生对视频格式做了 解封转
和 解码
在以前浏览器端 <video />
标签没有办法支持播放H.265
视频,但是因为视频格式本身是连续图像画面的集合
最原始的支持,可以参考chromium
的源码及video标签
内部的实现原理,通过 <canvas />
+ Web Audio API
的结合来模拟实现一个虚拟的canvas版video标签
来实现播放器功能
转格式 flv -> FMP4
但是根据浏览器的不同,可能支持的视频格式也不同,但是 FMP4 格式所有的浏览器都支持
因此在 js 利用 MSE API 处理 flv
- 对 flv 进行解析,这个操作一般称为解封装(demux)
- 解析出来音视频等信息数据后,再封装(remux)成 fmp4 视频格式
- URL.createObjectURL 创建一个
DOMString
表示指定的File
对象或Blob(二进制大对象)
对象
flv.js
<script src="flv.min.js"></script>
<video id="videoElement"></video>
<script>
if (flvjs.isSupported()) {
var videoElement = document.getElementById("videoElement");
var flvPlayer = flvjs.createPlayer({
type: "flv",
url: "http://example.com/flv/video.flv",
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
</script>
bilibili 开源的 flv.js
只做了一件事,在获取到 FLV
格式的音视频数据后通过原生的 JS
去解码 FLV
数据,再通过 Media Source Extensions
API 喂给原生 HTML5 Video 标签。(HTML5 原生仅支持播放 fmp4/webm
等格式,不支持 FLV)
通过原生的 JS 去解码 FLV 数据, 二进制流的操作 arraybuffer
😱
由于 flv.js
是基于 MSE
制作的,所以一些低版本移动端流览器中是无法正常播放的,因此在考虑兼容性的情况下需要慎用。
MSE(Media Source Extensions)
MDN 示例:
var video = document.querySelector("video");
var assetURL = "frag_bunny.mp4";
// Need to be specific for Blink regarding codecs
// ./mp4info frag_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
if ("MediaSource" in window && MediaSource.isTypeSupported(mimeCodec)) {
var mediaSource = new MediaSource();
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener("sourceopen", sourceOpen);
} else {
console.error("Unsupported MIME type or codec: ", mimeCodec);
}
function sourceOpen(_) {
//console.log(this.readyState); // open
var mediaSource = this;
var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
fetchAB(assetURL, function (buf) {
sourceBuffer.addEventListener("updateend", function (_) {
mediaSource.endOfStream();
video.play();
//console.log(mediaSource.readyState); // ended
});
sourceBuffer.appendBuffer(buf);
});
}
function fetchAB(url, cb) {
console.log(url);
var xhr = new XMLHttpRequest();
xhr.open("get", url);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
}
视频流存储
nginx、cdn
视频采集
- 选择镜头/窗口(从前端采集设备中获取原始的音频和视频数据)
- 视频处理-抠图绿幕/美颜
- 视频推流(SDK)
OBS 推流
视频处理
🤔 node
服务端可以统一处理视频(转码、添加水印等)
FFmpeg
: 一款处理音视频非常有效的工具, 这款工具提供以命令行的方式去对视频进行转码、转封装格式、增加水印等等功能其中还包括了 RTMP
推流的功能。同时 FFmpeg
也为 Node
提供了一些控制的 Bridge
- 对音视频进行额外的处理,如音频的混音、降噪等处理;对视频进行水印、滤镜和时间戳等处理
- 按照相关规范要求对处理后的音视频数据进行转码多种格式
WebRTC
WebRTC更适合用来做Web视频会议的原因就是它能够实现浏览器和浏览器之间进行音视频的传输
如果依靠流媒体服务器:
3位参会者,服务器就需要接受3条推流和6条拉流,显然这样对流媒体服务器的压力过于庞大,并且还有不可忽视的延时问题
WebRTC (Web Real-Time Communications) 是一项由Google推行的实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输
总结
ios 手机端 H5 不支持延迟更低的 flv(不支持 MSE),因此需要兼容成 hls
其他端(安卓、PC) H5 用延迟更低的 flv 即可
当然,如果依靠原生 APP 能力,则移动端、PC 端统一使用 flv 即可
可以看出音视频播放领域,更多的技术难点在:
软硬件条件具备的情况下,用更高效的编码格式且体验不降级;条件不具备的情况下,能降级到低效的编码格式
愉快的直播吧