Web 音视频的发展史
刀耕火种的年代——早期 HTML
在早期的 HTML,由于带宽、技术等各种因素限制,网页主要以简单的静态内容为主,只支持一些文字图片内容和简单的排版,不支持在线观看音视频。
(图为 1994 年的 Yahoo!)
20 世纪初,随着互联网的发展,各种 Web 应用和门户网站不断出现,人们渴望在网页上看到更加丰富多彩的内容,比如视频、动画等等,于是 Flash 进入了人们的视野。
彼时的 Flash 没有像现在大家印象中的那么臃肿,刚诞生的 Flash 小巧、高效、跨平台,同时凭借几十 K 的体积做出放大也不会失真的各种矢量彩色动画,在还是拨号上网,带宽条件受限,加载一个在线视频需要好几分钟的年代脱颖而出,甚至可以做出各种令人沉迷的 Flash 小游戏。
(Flash 塑造了很多经典的小游戏角色,火柴人就是其中之一)
Flash 的兴起,得益于当时 HTML 对于媒体文件支持的匮乏。Flash 以插件的形式,干着平台才需要负担的繁重工作,并得益于 Adobe 的大力推广,Flash 先后增加了对 Javascrip、HTML、XML 的支持,并增强了影音方面的功能。同时由于 Flash 跨平台的特性,非常容易被移植,市面上稍微高端点的设备,也得乖乖地给 Adobe 交授权费。
然而 2007 年推出的 iPhone 并不买账,他们以增加续航、安全为由抛弃了 Flash,很多人一开始对此嗤之以鼻,但事实证明苹果对此确实有远见,大量低质量的 Flash 使当时续航本就有限的移动设备更加不堪重负。2012 年,Android 也宣布不再支持 Flash,Flash 在移动市场不再有立足之地。
在桌面市场上,Flash 的日子也并不好过。Chrome 从的 Chrome 42 开始,就已经强制把 Flash 装入沙箱,以 PPAPI 的形式运行;而从 Chromium 版本号 88 开始,已经彻底不再支持 Flash 技术了。微软的 Edge 浏览器也同步不支持 Flash。Chrome 的前辈 Firefox 更加激进,从 2016 年就已经默认禁止 Flash 运行了。
至于 Flash 为什么走向了淘汰,除了它的效率变低,不安全因素过多,稳定性不足外,还有一个重要原因:Web 音视频解决方案有了更好的替代品—— HTML5。
其实,对于 HTML5 是否可以真正替代 Flash,尤大在 2011 年已经给出了预言:
事实正如预言所预料,HTML5 在 2008 年发布后,经过不断改进完善,基本上能包办 Flash 所有能干的事情了。HTML5 引入了许多新特性和新功能,其中就包含了 video 和 audio 标签,也就是对音视频的支持。使用了支持 HTML5 标准的网络浏览器访问 HTML5 站点,用户无需在电脑上安装 Flash 插件就可以在线观看视频,摆脱了对 Flash 的依赖。
<!-- 一个简单的video标签 -->
<video src="movie.mp4" poster="movie.jpg" controls></video>
同时,各大视频门户网站也加入了对 HTML5 的支持,并默认推荐以 HTML5 的方式来播放视频。
2021 年 1 月 20 日,chrome 88 正式发布,彻底的禁止使用 Flash。自此,Flash 算是彻底退出了历史舞台。
视频,其实就是一系列连续播放的图片,如果一秒钟播放 24 张图片,那么人眼看到的就不再是一张张独立的图片,而是动起来的画面。其中一张图片称为一帧,1s 播放的图片数称为帧率。由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约 10-12 帧的时候,就会认为是连贯的;当看到帧率为 24 fps 以上时,大脑会认为这是流畅播放的视频。所以一般有声电影的拍摄及播放帧率大约为每秒 24 帧,欧美、日本那边由于电视制式不同,大约为 30 帧。
为什么 24 帧的电影比 30 帧的游戏要流畅许多?
这其中的原因就在于,电影和游戏的图像生成原理不同。
电影的 24 fps,是每 1/24 秒拍摄一副画面,如果你玩过相机的手动设置,你应该知道如果以 1/24 秒的快门速度拍摄一个运动的物体会“糊”掉,而正是这样“糊”掉的画面连起来才让我们的眼睛看上去很“流畅”。
而游戏画面不是按 1/24 秒快门拍出来的,而是每一幅画面都是独立渲染出来的,之所以跑成 24fps 是因为显卡处理能力不够而“丢弃”了其中的一些画面,这样一来每两幅画面之间就不连续了,自然看上去会“卡”。
举个例子,一个圆从左上角移动到右下角,如果是电影,第一帧与第二帧可能是类似下图这样的:
如果是游戏画面,第一帧与第二帧会类似下面这两张图:
此外,帧与帧之间间隔恒定:人眼对于动态视频的捕捉是非常敏感的,电影帧率是固定不变,肉眼很难察觉出异常。
而游戏的帧率却是很容易变化的——如果手动锁定帧数,显卡会默认渲染最高帧率。
玩家触发的很多剧情往往伴随剧烈的画面变动,这时显卡的帧率就会出现下降,前后不一致的帧率很容易被肉眼捕捉,这时我们就会觉得,游戏变“卡”了。
视频是由图片构成的,图片是由像素构成的,假设尺寸为 1980*1080。每个像素由 RGB 构成,每个 8 位,共 24 位。
假设帧率是 24,那么每秒钟的视频的尺寸如下:
一分钟视频的尺寸就是 9237888000 Bytes 已经是 8.8 个 G 了。
可以看到,如果是不对视频做任何处理,是非常不方便对于视频做传输与存储的,所以需要对视频进行压缩,也就是编码。
视频图像数据有很强的相关性,也就是说有大量的冗余信息。其中冗余信息可分为空域冗余信息和时域冗余信息。压缩技术就是将数据中的冗余信息去掉(去除数据之间的相关性),压缩技术包含帧内图像数据压缩技术、帧间图像数据压缩技术和熵编码压缩技术。
经过编码之后,视频由一帧帧的图片,变成了一串串让人看不懂的二进制代码,因为编码的方式(算法)的不同,所以就有了编码格式的区分。常见的编码格式有 H.264,MPEG-4,VP8 等。
我们前端开发只需要记住一点,主流浏览器支持的视频编码格式是 H.264。
CD 音质的音频,存放一分钟数据需要的大小为 10M,太大了,也需要压缩(编码)。
常见的编码方式有:WAV、MP3 和 AAC 格式。
音频的编码方式不像视频那样那么多,而且音频在各个浏览器基本上都可以播放。
具体的每种编码格式包含的音频是怎么构成的,这里就不讲了。
我们把视频数据、音频数据打包到一起,然后再添加一些基本信息,例如分辨率、时长、标题等,构成一个文件,这个文件称为封装格式。常见的封装格式有 MP4, AVI, RMVB 等。
可以看出,视频的封装格式和视频的编码格式往往是无关的,一个 mp4 文件,里面的视频流编码可以是 h264,也可以是 mpeg-4,所以就会出现,同样都是 mp4 文件,有的浏览器可以放,有的浏览器就放不了的问题,因为能不能放是由视频码流的编码格式决定的。
码率,也叫比特率,帧率是 1s 播放多少帧,类比一下,比特率就是 1s 的视频有多少 bit。
这个参数直接决定了视频的大小与清晰程度。
一般网上流传的电影 MKV(BDrip-1080P)的码率是 10Mb/s 左右,蓝光原盘是 20Mb/s 左右,这两者都是 H.264 编码的。另外一些 MV、PV、演示片什么的除了 H.264 编码,可能还有 MPEG-2 编码,码率大小不等,像 youtube 那些在线的 1080P 的视频,码率可能只有 5Mb/s,而一些 MV 的码率可以高到离谱,可以达到 110Mb/s 的,3 分多钟的 MV 差不多有 3GB 大小。
而一般的视频剪辑、后期软件,在输出序列的时候,都会有码率这个选项。
播放视频的基本流程是:解协议 → 解封装 → 解码 → 视音频同步。如果播放本地文件则不需要解协议。
解协议的作用,就是将流媒体协议的数据,解析为标准的相应的封装格式数据。视音频在网络上传播的时候,常常采用各种流媒体协议,例如 HTTP,RTMP,或是 MMS 等等。这些协议在传输视音频数据的同时,也会传输一些信令数据。这些信令数据包括对播放的控制(播放,暂停,停止),或者对网络状态的描述等。解协议的过程中会去除掉信令数据而只保留视音频数据。
解封装的作用,就是将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如 MP4,MKV,RMVB,TS,FLV,AVI 等等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV 格式的数据,经过解封装操作后,输出 H.264 编码的视频码流和 AAC 编码的音频码流。
解码的作用,就是将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含 AAC,MP3,AC-3 等等,视频的压缩编码标准则包含 H.264,MPEG2,VC-1 等等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如 YUV420P,RGB 等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如 PCM 数据。
视音频同步的作用,就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的视频和音频数据,并将视频音频数据送至系统的显卡和声卡播放出来。
如果我们碰到一些特殊机型或者特殊情况 HTML5 的 video 解决方案不是很好处理,也可以采用 Canvas 去播放这个视频。
使用 Canvas 播放视频主要是利用 ctx.drawImage(video, x, y, width, height)
来对视频当前帧的图像进行绘制,其中 video 参数就是页面中的 video 对象。所以如果我们按照特定的频率不断获取 video 当前画面,并渲染到 Canvas 画布上,就可以实现使用 Canvas 播放视频的功能。
<video id="video" controls="controls" style="display: none;">
<source src="https://xxx.com/vid_159411468092581" />
</video>
<canvas id="myCanvas" width="460" height="270" style="border: 1px solid blue;" ></canvas>
<div>
<button id="playBtn">播放</button>
<button id="pauseBtn">暂停</button>
</div>
const video = document.querySelector("#video");
const canvas = document.querySelector("#myCanvas");
const playBtn = document.querySelector("#playBtn");
const pauseBtn = document.querySelector("#pauseBtn");
const context = canvas.getContext("2d");
let timerId = null;
function draw() {
if (video.paused || video.ended) return;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(video, 0, 0, canvas.width, canvas.height);
timerId = setTimeout(draw, 0);
}
playBtn.addEventListener("click", () => {
if (!video.paused) return;
video.play();
draw();
});
pauseBtn.addEventListener("click", () => {
if (video.paused) return;
video.pause();
clearTimeout(timerId);
});
事实上,市面上已经有不少 Canvas 播放视频的解决方案,比较出名的是这个 JSMpeg[1]。它和 PIXI 一样,可以选择 WebGL 渲染视频也可以直接用 Canvas 渲染视频。
JSMpeg 是没有 npm 包的,但是社区上有开发者基于 JSMpeg 封装了一个 npm 包:https://github.com/cycjimmy/jsmpeg-player
在官网上是这么介绍的:
JSMpeg is a Video Player written in JavaScript. It consists of an MPEG-TS Demuxer, WebAssembly MPEG1 Video & MP2 Audio Decoders, WebGL & Canvas2D Renderers and WebAudio Sound Output. JSMpeg can load static files via Ajax and allows low latency streaming (~50ms) via WebSocktes.
由于它所支持的编码格式不是常规的 H.264,而是比较老的 MPEG1,并且解封装器为 MPEG-TS。所以一般我们使用它去渲染视频的格式为 TS。
TS 是日本高清摄像机拍摄下进行的封装格式,全称为 MPEG2-TS。它的特点就是要求从视频流的任一片段开始都是可以独立解码的。
TS 文件通常作为多个文件保存在 DVD 上,虽然它可以在高清摄像机、蓝光 DVD 中无需借助其他软件就能直接打开,但是 TS 视频文件与大多数的媒体播放器、便携式播放器或视频编辑工具都不兼容,所以这个时候,FFmpeg 就可以出场了。
FFmpeg[2] 是一个开源的软件,我们直接用 homebrew 就可以安装
brew install ffmpeg
如果我们想转换为 jsmpeg 所需的 ts 格式视频,可以执行
$ ffmpeg -i input.mp4 -f mpegts \
-codec:v mpeg1video -s 640x360 -b:v 1500k -r 25 -bf 0 \
-codec:a mp2 -ar 44100 -ac 1 -b:a 64k \
output.ts
-i
:指定输入文件,这里指定为 input.mp4
-f
指明输出文件的封装格式,这里为 jsmpeg 所需的 mpegts
-codec:v
指明输出文件的视频编码,这里指明为 jsmpeg 所需的 mpeg1video
-s
设置视频分辨率,参数格式为w*h
或w×h
-b:v
设置视频码率,一般如果想得到高清的效果,至少需要 4000k 以上,如果对视频体积有要求,可以视情况小一点
-r
设置帧率(fps),一般都为 25
-bf
bframe 数目控制,一般为 0
B 帧法(B frame)是双向预测的帧间压缩算法。当把一帧压缩成 B 帧时,它根据相邻的前一帧、本帧以及后一帧数据的不同点来压缩本帧,也即仅记录本帧与前后帧的差值。
-codec:a
指明输出文件的音频编码-ar
设置音频编码采样率,单位kHz
,一般网上的音频,大多为 44100音频采样率是指录音设备在单位时间内对模拟信号采样的多少,采样频率越高,机械波的波形就越真实越自然。
-ac
设置音频编码声道数-b:a
设置音频码率音频码率,指一个音频流中每秒钟能通过的数据量,码率越大的话,音质越好
FFmpeg 是一个非常强大的音视频转换工具,不仅可以视频转换,还可以视频尺寸裁剪、视频时长裁剪、视频拼接等等功能,目前很多在线视频剪辑工具基本是基于 FFmpeg 开发的。
国内学习音视频相关的开发,绕不过的一个大神是 雷霄骅[3],大佬已经去世了,但是留下的文章永垂不朽。
本文也是参考了 雷霄骅[3] 的部分博客,如果感兴趣,可以从这篇文章看起:\[总结\]视音频编解码技术零基础学习方法\_雷霄骅\(leixiaohua1020\)的专栏-CSDN 博客\_雷霄骅[4]。
对于直播 webrtc 感兴趣的,也可以看一下 Real time communication with WebRTC[5],国内慕课网上李超老师也有不错的教程:李超\_慕课网精英讲师[6]
对 ffmpeg 感兴趣的,可以看一下这里:https://github.com/leandromoreira/ffmpeg-libav-tutorial
[1]JSMpeg: https://github.com/phoboslab/jsmpeg
[2]FFmpeg: https://www.ffmpeg.org/
[3]雷霄骅: https://link.juejin.cn/?target=https://blog.csdn.net/leixiaohua1020
[4][总结]视音频编解码技术零基础学习方法_雷霄骅(leixiaohua1020)的专栏-CSDN 博客_雷霄骅: https://blog.csdn.net/leixiaohua1020/article/details/18893769
[5]Real time communication with WebRTC: https://codelabs.developers.google.com/codelabs/webrtc-web?spm=a2c6h.12873639.0.0.58004925DffaQU
[6]李超_慕课网精英讲师: https://www.imooc.com/t/4873493
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8