「完全理解」video 标签到底能播放什么
本文的主要内容转载于QQQQQCY'S BLOG,少部分内容是自己补充的。
- 理解为什么有的 mp4 媒体文件(
也称 mp4 视频
)用 video 标签可以播放,有的 mp4 媒体文件不可以播放视频,但可以播放音频。 - 了解到什么是封装格式、视频编码、音频编码。
- 了解 FFmpeg 这个强大的工具。
0. 写在前面
笔者是某次发现 xx.mp4 文件在 Microsoft Edge 浏览器竟然不能播放,出乎我的意料,所以才有下面这篇文章。
现如今,越来越多的项目都希望在自己的页面中插入视频,如果现在哪个应用说不支持视频播放,就和 N 年前的应用里看不了图片一样无法想象
播放视频当然就离不开 HTML5 标准中的 video 元素。所以让我们从视频的本质开始,弄懂 video 标签的原理和表现
一、使用
MDN:HTML <video>
元素,用于在 HTML 或者 XHTML 文档中嵌入媒体播放器,用于支持文档内的视频播放。
<video controls width="250">
<source src="/media/cc0-videos/flower.webm"
type="video/webm">
<source src="/media/cc0-videos/flower.mp4"
type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
上面的例子展示了 <video>
元素的用法。和 <img>
元素的使用类似,在 src
属性里加入一个我们需要展示的视频地址,同时也可以用其他属性来定义视频的宽度高度、是否自动或者循环播放、是否展示浏览器默认的视频控件等信息。
在 <video></video>
标签中间的内容,是针对浏览器不支持此元素时候的降级处理。
浏览器并不是都支持相同的视频格式,所以你可以在 <source>
元素里提供多个视频源,然后浏览器将会使用它所支持的第一个源。
二、浏览器与播放器
用法很简单,值得注意的是在「浏览器并不是都支持相同的视频格式」这句话里,包含了两个重要信息
1. 不同浏览器支持的视频格式不同
或者换一种说法:因为不同浏览器内置的视频播放器不同,所以支持的视频格式不同。
video 元素是可替换元素。可替换元素就是浏览器根据元素的标签和属性,来决定元素的具体显示内容。
这些元素往往没有实际的内容,即是一个空元素。
简单来说就是不同浏览器里的视频播放器有不同的实现,可以抽象成,在 Chrome 中预置的播放器是 CPlayer,Safari 中则是 SPlayer。
不同播放器支持的视频格式当然也就不尽相同。不仅如此,在 UI、可控性和一些其他地方表现都各有差异
所以要谈 video 标签支持的视频格式,必须先指定浏览器
2. 浏览器只支持特定的视频格式
下图是关于主流浏览器对主流视频格式的支持情况
Browser | Operating System | H.264(MP4) | HEVC(MP4) | VP8(WebM) | VP9(WebM) | AV1(WebM) | Theora(Ogg) |
---|---|---|---|---|---|---|---|
Google Chrome | Unix-like, Android, macOS, iOS, and Windows | Since 3.0 | No | Since 6.0 | Since 29.0 | Since 70 | Since 3.0 |
Microsoft Edge | Windows | Since 12.0 | Needs hardware decoder | Since 17.0 (supports | Only enabled by default if hardware decoder presentSince 17.0 | Since 18.0 (with AV1 Video Extension) | Since 17.0 (with Web Media Extensions) |
Mozilla Firefox | Windows | Since 21.0 | No | Since 4.0 | Since 28.0 | Since 65.0 | Since 3.5 |
Mozilla Firefox | Android | Since 17.0 | No | Since 4.0 | Since 28.0 | in Nightly | Since 3.5 |
Mozilla Firefox | macOS | Since 34.0 | No | Since 4.0 | Since 28.0 | Since 66.0 | Since 3.5 |
Safari) | iOS | Since 3.1 | Since 11 | Since 12.1 (only supports WebRTC) | No | No | No |
Safari) | macOS | Since 3.1 | Since 11 | Since 12.1 (only supports WebRTC) | No | No | Via Xiph QuickTime Components(macOS 10.11and earlier) |
看表格中第一行 chrome 支持的格式可以发现,只有括号中的 Ogg、MP4 和 WebM 三种格式看起来比较眼熟。然而实际上真正决定浏览器能不能支持的,是 H.264、HEVC、VP8 这些一般人都没听过的格式
所以视频的格式到底是怎么一回事,还得要从视频的构成说起:
三、视频格式
视频的本质很简单,快速播放的图片而已。
几个简单的概念:
- 帧:每帧代表一幅静止的图像
- 帧率(fps):每秒显示的图片数。帧率越大,画面越流畅;帧率越小,画面越卡
- 12 fps:由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约10-12帧的时候,就会认为是连贯的
- 24 fps:有声电影的拍摄及播放帧率均为每秒24帧,对一般人而言已算可接受
- 30 fps:早期的高动态电子游戏,帧率少于每秒30帧的话就会显得不连贯,这是因为没有动态模糊使流畅度降低
- 60 fps:在实际体验中,60帧相对于30帧有着更好的体验
- 85 fps:一般而言,大脑处理视频的极限
1. 源文件
我们计算一下按照最简单定义的视频大小
- 用 RGB 来描述颜色,0~255 等于 256 种可能,刚好等于 1 个字节。所以一个像素
255*255*255
的大小为3B
- 分辨率
980*720
的普通清晰度图片需要980*720*3B ≈ 2M
- 正常视频帧率为 30,表示 1s 的时间里播放 30 张图片。也就是说一个 1 分钟的视频大小为
2M*30*60 ≈ 3.5G
帧率 30,时长 1 分钟的 980 分辨率纯视频大小约为 3.5G。
原始的 YUV、RGB 格式的视频源文件就是这么大。
2. 编码
这么大的视频显然没法用,按照现在的网速,一个几分钟的视频估计得下载半天。
所以首要任务就是压缩,专业术语叫编码(encode)。视频一般通过两种方式编码。
2.1 帧内编码
帧内编码就是压缩图片,比如把所有图片都压缩成 jpeg(原理是利用人眼对亮度的敏感程度要高于对色彩的敏感程度)。通常这一步之后就已经能减小视频 90% 的大小。文件格式系列科普之.JPEG/.JPG
2.2 帧间编码
帧间编码是利用帧与帧之间的相关性。例如下面的第二帧与第一帧对比,只有太阳与人改变了,那么第二帧只需要存储相应的变化(类似 git,只保存 diff)
经过压缩后的帧分为:I 帧、P 帧和 B 帧
- I 帧:关键帧,采用帧内压缩技术
- P 帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩技术
- B 帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术
除了 I/P/B 帧外,还有图像序列 GOP。
GOP:两个 I 帧之间是一个图像序列,在一个图像序列中只有一个 I 帧
推荐阅读:H.264基本原理与编码流程
3. 编码格式
编码思路大致都能归为这两种,具体怎么实现,那就大家各显神通了
不同公司、机构,不同年代都会有不同的方案。效率、成本也完全不同
常见的编码标准(大致按照编码效率从低到高,越下面的越好)
标准 | 推出机构 | 说明 |
---|---|---|
H.262、MPEG-2 part 2 | MPEG/ITU-T | 作为 MPEG-4 的前身,MPEG-2 是用于 DVD 和早期蓝光光盘的标准编解码器。它不常用于流媒体视频。 |
AVC (H.264)、MPEG-4 part 10 | MPEG/ITU-T | H.264 能够在低码率情况下提供高质量的视频图像。是目前最常见、主流的标准 |
VP8 | 类似于 H.264 | |
HEVC (H.265) | MPEG/ITU-T | H.265 旨在在有限带宽下传输更高质量的网络视频,与 H.264相比,同样的视觉质量的视频只占用一半的空间 |
VP9 | 类似于 H.265 | |
AV1 | AOM | AV1 具体的目标是,与 VP9 和 HEVE 相比,码率需要大幅度降低,目标是减少 30% 左右。解码是比较低的复杂度,目标大概是 VP9 的两倍 |
当然,这些都只是规范而已(就和我们前端的 es6、es10 一样)。就算再先进,没人去推广、使用也白搭
一个编码标准能否被推广和使用,不仅取决于效率,还和背后的组织、专利等一系列因素相关。目前最流行、广泛使用的是 H.264 编码规范
换种说法,目前最流行的视频格式就是 H.264
4. 封装格式
平常我们看到的 xxx.MP4、xxx.AVI 文件,准确来说叫做视频封装格式。是一个容器,容器中包含了视频编码(如 H.264)、音频编码(如 AAC),此外还可以包含同步信息、字幕和元数据(如标题)等
编码 Codec name (short) | 全称 Full codec name | 封装格式 Container support |
---|---|---|
AV1 | AOMedia Video 1 | MP4, WebM |
AVC (H.264) | Advanced Video Coding | 3GP, MP4, WebM,TS,FLV |
H.263 | H.263 Video | 3GP |
HEVC (H.265) | High Efficiency Video Coding | MP4 |
MP4V-ES | MPEG-4 Video Elemental Stream | 3GP, MP4 |
MPEG-1 | MPEG-1 Part 2 Visual | MPEG, QuickTime |
MPEG-2 | MPEG-2 Part 2 Visual | MP4, MPEG, QuickTime |
Theora | Theora | Ogg |
VP8 | Video Processor 8 | 3GP, Ogg, WebM |
VP9 | Video Processor 9 | MP4, Ogg, WebM |
所以再回看开始那个支持情况表格,用 Chrome 列举。H.264、H.265(HEVC)都能被封装到 MP4 中,但 Chrome 只支持前一种
5. 音频
一个完整的「视频」是包含了视频编码、音频编码、字幕等众多信息的。
视频编码和音频编码一般是一起存在的。和视频类似,音频也是从源文件到编码,再到封装。比对理解即可,此文不做其他解释
常见的音频编码标准
标准 | 推出机构 | 说明 |
---|---|---|
AAC | MPEG | 各个领域(新) |
AC-3 | Dolby Inc. | 电影 |
MP3 | MPEG | 各个领域(旧) |
WMA | Microsoft Inc. | 微软平台 |
目前使用最广泛的音频编码格式为 AAC
四、视频处理
从上面我们了解到,播放器能否播放一个视频,关键取决于其编码格式,但是封装格式也无法被忽略
以被广泛使用的 FLV 格式举例:
1. 封装与解包
H.264 和 AAC,可以被封装成 MP4 或 FLV。但是 Chrome 只能播放前者而不支持后者,细想其实很没道理。就好比两个东西被装在 A 盒子里的时候能拆开用,但是装在 B 盒子里就不行了
因为本质上重要的是东西(H.264、H.265),而不是盒子(MP4、FLV)
这是浏览器对不同视频编码的支持情况:
查表可知,Chrome 是支持 H.264 的。所以还是得明白东西与盒子之间是怎面转化的
把编码封装成文件的步骤叫做封装(remux),文件拆解成编码的步骤叫解包(demux)
封装,抽象的说其实就是把东西按不同排列码到一块
假设 H.264 的二进制文件是 123456789
,不同封装如下所示(数字之外的可以代指音频、字幕等其它玩意)
- MP4
ab
123456789
b
c
d
- FLV
a
b
123
c
456
d
789
所以看起来,我们完全可以把 FLV 先解包回 H.264 和音频、字幕登其他,再封包成 MP4
2. MSE
其实还有更简单的方法,我们根本不用再封包了
通过 Media Source Extensions API,解包出视频编码和音频编码之后,已经可以直接播放了
媒体源扩展 API(MSE) 提供了实现无插件且基于 Web 的流媒体的功能。使用 MSE,媒体串流能够通过 JavaScript 创建,并且能通过使用 <audio>
和 <video>
元素进行播放。
业界比较有名的相关库是 B 站前员工开源的 flv.js,可以把指定编码的 flv 解包成 H.264、ACC 并通过 MSE 播放。
目前除了 iOS 和 IE 之外,基本都支持 MSE
3. 其他
再深入一点可以想到,能用 js 解包,那么解码是不是也可以?
查看 video 的实现可以发现,浏览器是通过 FFmpeg 来解码的,实际上用 js 或者 Wasm 都可以替代这一步骤。
假设现在我们拿到一个浏览器不支持的视频格式。
- 先用 js 或者 Wasm 来对封装格式进行解包。
- 解包完发现得到的编码格式也不支持。
- 心一狠,通过 js 或者 Wasm 编译过的 FFmpeg 完成了解码,得到了视频源文件。
之后有两个思路
- 编码成 H.264 再通过 MSE 播放。
- 根据源文件 YUV、RGB,得到每一帧的原始图片。在 Canvas 中连续播放图片而形成视频,在 audio 中同步播放音频。
这两个方法业界也有对应的实现,不过都因为性能和兼容性问题等没有大规模被使用。
因此理论上来说,任何格式的视频都能被播放。
五、直播
以上说的都是播放一个完整视频。实际情况下一个几分钟的视频就肯定大于 10M,各大视频网站更多的是几十分钟以上的剧集、综艺,还有大小不固定的直播。
1. 视频传输
这些情况下肯定不能等视频完全下载完再播放,目前有两个主流方案处理大尺寸的视频数据:
- 切片文件:把视频分割成 3~10s 的视频片段,一段段下载播放
- 连续流:把视频做成流,按数据帧传输、播放(本质就是更小的分段)
常用的视频传输协议有如下 4 种:
协议 | http-flv | rtmp | hls | dash |
---|---|---|---|---|
传输方式 | http | tcp | http | http |
视频封装格式 | flv | flv、tag | Ts文件 | Mp4、3gp、webm 切片 |
延时 | 低 | 低 | 高 | 高 |
数据分段 | 连续流 | 连续流 | 切片文件 | 切片文件 |
HTML5 播放 | 可通过 html5 解包播放(flv.js) | 不支持 | 原生支持、或通过(hls.js) | 直接播放或者 HTML5 解封包播放 |
总结就是切片文件兼容性更好,更适合视频点播。连续流时效性更好,适合直播。
两个方案其实只解决了传输问题,例如一个 10s 的视频被分成 10 个 1s 的视频之后,每播放完 1s video 都得重新加载。
实际上就是每播放 1s,video 上的进度条都会从头跳到尾。代码层面则每过 1s,video 的初始化和结束都会被重新执行。这种体验肯定是无法满足正常使用。
2. 流媒体播放
有两种解决方案:
2.1 数据解包之后通过 MSE 播放
MSE 控制的二进制文件可以任意删减。所以通过流拿到新的数据之后直接添加到视频源上,video 也不会重新加载。
但是对 MSE 支持不佳的 iOS Safari 用不了此方案。
2.2 浏览器自主处理
第一个方案自己需要处理的部分如下:
- 持续发送请求获取最新视频
- 解包出视频、音频编码
- 推送编码文件给 MSE
HTTP Live Streaming(HLS),是 Apple 提出的直播流协议。所以在其平台的浏览器上原生就支持,只用把 HLS 地址直接传递给 video 的 src 属性,浏览器相当于把上述步骤全部完成了。而且最开始说过,video 是可替换元素。所以很多浏览器(尤其是国内的移动端浏览器)会使用自己开发过的,不同于标准的播放器。
- 坏处是不像标准的 video 那么可控。比如无法控制显示层级,永远在页面的最高层...
- 好处是会帮你处理很多视频甚至流媒体协议
例如 QQ 浏览器、UC 浏览器。可以直接播放 FLV、HLS 流
所以我整理了一份,一直使用最优方案的直播实现:
(极少数浏览器劫持后也不支持 HTTP-FLV 流,但是一定会支持 HLS 流,类似情况白名单切换即可)
六、FFmpeg
官网
:github 地址
推荐阅读图书:FFmpeg原理
一个很强大的工具,可以将很多类型的视频编码文件转换成你想要的视频编码文件。
Git 恰好与帧间编码相反,Git 保存的是每个 commit 的快照。保存差异是 SVN 的行为。