Skip to content

Web 直播

直播有两个特点:

  1. 获取的是流数据
  2. 要求实时性(决定了数据源不可能在服务器上,而是在某一个客户端)

流数据。我们常接触的数据就是 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.265AV1 更强大的压缩技术,但是对设备要求高,体积小

看个 B 站搞得电脑风扇狂转? 网友们这次炸了

🤔 为什么反感用高压缩技术?

HEVC/h.265 编码

AV1 编码

  1. 硬解:字面上理解就是用硬件来进行解码,是使用 GPU 的专门模块来解码。
  2. 软解:字面上理解就是用软件来进行解码,是使用 CPU 来运行视频编解码代码。

软硬解各有优缺点:

  • 软解:在软解码过程中需要对大量的视频信息进行运算,所以对 CPU 性能的要求非常高,尤其是对高码率的视频来说巨大的运算量会造成转换效率低,发热量高等问题。不过软解码的过程中不需要复杂的硬件支持,兼容性高。即使是新出的视频编码格式,也可以为其编写新的解码程序;
  • 硬解:硬解码调用 GPU 的专门模块来解码,拥有独特的计算方法,解码效率高。这样不但能够减轻 CPU 的负担,还有着低功耗,发热少等特点。但是由于硬解码起步相对晚,软件和驱动对他的支持度低,基本上硬解码内置什么样的模块就解码什么样的视频,面对各色各样的视频编码样式,兼容性没那么好。
解码方式效率功耗兼容性
软解
硬解

随着 Chrome 在 107 版本支持 H.265 的硬解,以及 Web 平台上 H.265 软解技术的成熟,在 Web 平台上规模化部署 H.265 视频的时机已经成熟。关于 H.265 编码格式以及它的好处,网上已经有非常多的介绍了。它最重要的好处是更低的部署成本,因此对于视频服务供应商来说,是尽量采用的

查看浏览器的 gpu 支持

  1. chrome://gpu
  2. chrome://media-internals
  3. 活动监听器 VTDecoderXPCService

前端解码-WASM(软解,任何平台): 基于 WASM + FFMPEG 编译实现,支持所有支持 WASM 的浏览器

收费问题

腾讯云AV1编码

网络传输

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 格式的视频(既 TSm3u8 文件)文件。客户端只需要访问一级 m3u8 文件的路径就会自动播放 HLS 视频流了

延迟问题:

由于 HLS 需要将采集到的音视频进行分片、客户端也需要对接受到的分片后的音视频进行合并处理,因此相对来时会存在比较大的延迟,大概会达到 10s 左右

兼容问题:

HLSPC 端仅支持 safari 浏览器,而其他大部分 PC 浏览器使用 HTML5 video 标签由于无法解析 TS 所以不能直接播放(需要通过 hls.js)

移动端不论是安卓还是 IOS 统统都原生支持 HLS

如果想要在 PC 非safari 浏览器上使用到 HLS,仍然需要使用其他技术手段才能实现

m3u8 + TS/m4s

m3u8 的命名来源是 m3u 文件 + utf-8 编码而来

m3u 实际上就是一个索引文件,其中可以记录 TS 文件地址,客户端会按照下载的顺序进行连续播放。

txt
#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 视频流来播放

视频播放

抖音直播

斗鱼直播

虎牙直播

b 站直播

拉取

我们需要一种类似 websocket 的长连接来持续获取 flv 内容

fetch API 就天然支持

js
fetch("./a.flv").then((res) => {
  const reader = res.body.getReader();
  const pump = async () => {
    const data = await reader.read(); // ✨ 长连接拉取
    if (!data.done) pump();
  };
  pump();
});

res.body.getReader -mdn

可能大家还听过 WS-FLV,这是使用 WebSocket 去拉 FLV 流,相比 HTTP-FLV 没啥优势,所以开始尽可能使用 HTTP-FLV

在我看来 WS-FLV 唯一的作用是兼容 IE 11 浏览器,因为 IE 11 是不支持 fetch 的,这时候只有用 WebSocket 去拉流

video 标签

使用原生 video 标签播放视频文件

html
<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标签来实现播放器功能

淘系 DEMO

转格式 flv -> FMP4

但是根据浏览器的不同,可能支持的视频格式也不同,但是 FMP4 格式所有的浏览器都支持

因此在 js 利用 MSE API 处理 flv

  • 对 flv 进行解析,这个操作一般称为解封装(demux)
  • 解析出来音视频等信息数据后,再封装(remux)成 fmp4 视频格式
  • URL.createObjectURL 创建一个 DOMString 表示指定的 File 对象或 Blob(二进制大对象) 对象

flv.js

html
<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 示例:

js
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 即可

可以看出音视频播放领域,更多的技术难点在:

软硬件条件具备的情况下,用更高效的编码格式且体验不降级;条件不具备的情况下,能降级到低效的编码格式

愉快的直播吧

参考