编码
原始的媒体文件的数据量都是十分庞大的,而为了便于传输和存储,我们通常会编码来对媒体文件进行压缩,这里就涉及到了
编码格式的概念。 我们常见的视频编码格式有 H.264、H.265、MJPEG等
-
H.264 (AVC) 是当前应用最广泛、兼容性最好的视频编码标准,是"主流"。
- 块划分: H.264 可以将画面分割成不同大小的块,并为每个块选择最适合的编码方式,提高了效率。
- 帧内预测: 利用同一帧内相邻块的信息来预测当前块,减少冗余。
- 帧间预测: 利用过去或未来的帧中的相似块来预测当前块(运动补偿),这是视频压缩的核心技术之一。
-
H.265 (HEVC) 是 H.264 的进化版,在压缩效率上大幅提升,特别适合高分辨率视频,是未来的趋势,相对于H.264来说,在相同图像质量下,可以提供约50%的码率降低。但对硬件要求更高且兼容性不如 H.264。
媒体封装格式
一个视频内除了包含编码压缩后的视频流,音频流,还能够包含章节信息、字幕等数据,把这些数据都包装到一个文件容器内,就是封装的过程。我们常用的封装格式有: .mp4, .mkv, .avi, .mov等。
视频播放器的工作原理

- 解封装
识别出不同的媒体流,并将它们分离成独立的、编码后的视频数据包、音频数据包和字幕数据包。
- 解码
将经过压缩编码的音视频数据解码成原始的数据
3.音视频同步及渲染
通常,音频作为主时钟。播放器会尝试将视频的播放速度调整到与音频时钟保持一致。
将解码后的图像数据绘制到屏幕上。由 GPU 进行最终的渲染和合成,然后显示到屏幕上。
解码器类型1-WebAssembly(wasm)(软解)

WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。
WebAssembly 可以看作是一种中间表示,它以紧凑的二进制形式存在。
- 从高级语言到
.wasm
文件的过程,涉及到将高级语义编码成 WebAssembly 指令。 - 从
.wasm
文件到实际执行的过程,涉及到将二进制指令解码并转换为机器可执行的指令。
WebAssembly的由来
JavaScript代码在引擎中会经历什么?
- JavaScript文件会被下载下来。
- 然后进入Parser,Parser主要任务是词法分析 (Lexical Analysis) 和语法分析 (Syntactic Analysis)。会把代码转化成AST(abstract syntax tree 抽象语法树)。词法分析: 将原始的字符流分解成有意义的词法单元 (Tokens) ,例如关键字 (
function
,var
,if
)、标识符 (myVariable
)、运算符 (+
,=
)、字面量 ("hello"
,123
) 等。语法分析: 接收这些 Tokens,并根据 JavaScript 的语法规则,构建一个抽象语法树 (AST) 。AST 是代码结构的树状表示,它反映了代码的逻辑和层级关系。 - 然后根据抽象语法树AST,Bytecode Compiler字节码编译器会生成引擎能够直接阅读、执行的中间表示,通常为**字节码 (Bytecode) **。
- 字节码进入翻译器,将字节码一行一行的翻译成效率十分高的Machine Code.
- 解释器解释执行字节码, 在解释执行过程中,监测"热点"字节码。
- 当识别出"热点"时,JIT 编译器会将这些字节码编译成机器码。
- 引擎直接执行优化后的机器码。
性能瓶颈
在业务需求越来越复杂的现在,前端的开发逻辑越来越复杂,相应的代码量随之变的越来越多。相应的,整个项目的起步的时间越来越长。在性能不好的电脑上,启动一个前端的项目甚至要花上十多秒。
但是除了逻辑复杂、代码量大,还有另一个原因是JavaScript这门语言本身的缺陷,JavaScript没有静态变量类型。在JS中,变量类型不是预先声明的,而是可以根据赋值类型随时改变的。例如,let x = 5; x = "hello";
现代JavaScript引擎非常依赖JIT(Just In Time)编译技术。JIT编译器会在代码执行过程中,识别出"热点"代码(经常被执行的代码),然后将其从解释执行转换为高度优化的机器码。
如果有静态类型,JIT编译器在编译代码时就能确切地知道变量和函数参数的类型。例如,如果知道 a
和 b
总是数字,那么 a + b
就可以直接编译成一条高效的整数加法指令。但是,由于JavaScript的动态类型,JIT编译器很难做出这样的保证。
asm.js出现
所以为了解决这个问题,WebAssembly的前身,asm.js诞生了。asm.js强制静态类型
asm.js不能解决所有的问题
无论asm.js对静态类型的问题做的再好,它始终逃不过要经过Parser,要经过ByteCode Compiler,而这两步是JavaScript代码在引擎执行过程当中消耗时间最多的两步。而WebAssembly不用经过这两步。这就是WebAssembly比asm.js更快的原因。
WebAssembly与JavaScript很实际的一个性能对比。几乎稳定的是JavaScript的两倍
什么时候使用WebAssembly?
说了这么多,我到底什么时候该使用它呢?总结下来,大部分情况分两个点。
- 对性能有很高要求的App/Module/游戏
- 在Web中使用C/C++/Rust/Go的库(wasm能将他们转换成目标平台(例如 Windows x86、macOS ARM、Linux x64)特定的机器码)
解码器类型2-MSE(Media Source Extensions)
URL.createObjectURL()
他允许在浏览器中直接处理和展示内存中的二进制数据,而无需将这些数据先上传到服务器或进行复杂的编码转换。
传统做法(没有 createObjectURL
技术时)的问题:
-
上传到服务器再下载:
-
场景: 用户通过
<input type="file">
选择了一张图片,想在网页上立即预览这张图片。 -
旧方法: 以前你可能需要将这张图片(通过 AJAX 提交)上传到服务器。服务器接收到图片后,将其保存起来,然后返回一个该图片在服务器上的 URL(例如
http://yourserver.com/images/user_upload_123.jpg
)。然后,你才能将这个 URL 设置给<img>
标签的src
属性进行显示。 -
缺点:
- 网络延迟: 数据需要上传和下载,涉及网络传输,速度慢。
- 服务器负担: 服务器需要接收、存储、管理这些临时文件。
- 复杂性: 需要前端和后端都编写代码来处理文件上传和管理。
- 隐私风险: 用户的数据在上传过程中可能会有隐私泄露的风险。
-
-
复杂的编码转换(例如Base64):
-
场景: 你想在不上传服务器的情况下显示图片,但又不能直接引用本地文件路径(出于安全考虑,浏览器不允许直接访问本地文件系统)。
-
一种替代方案: 将二进制图片数据转换为 Base64 编码的字符串。Base64 编码可以将二进制数据表示为纯文本字符。然后,你可以将这个 Base64 字符串直接嵌入到 HTML 元素的
src
属性中,例如<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." />
。 -
缺点:
- 数据膨胀: Base64 编码会使数据体积增大约 33%。对于大文件来说,这会消耗更多内存和网络带宽(即使是本地内存中),加载速度也可能变慢。
- CPU开销: 进行 Base64 编码和解码需要 CPU 资源。
- 处理不便: 并非所有二进制数据都能方便地转换为 Base64 并直接使用(例如音频/视频流)。
-
使用该方法:
js
const fileInput = document.getElementById('fileInput');
const imagePreview = document.getElementById('imagePreview');
let objectURL; // 存储生成的URL以便后续释放
fileInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
// 如果之前有生成的URL,先释放掉
if (objectURL) {
URL.revokeObjectURL(objectURL);
}
objectURL = URL.createObjectURL(file);
imagePreview.src = objectURL;
}
});
但是要注意,浏览器并不知道你何时不再需要这个通过 createObjectURL
创建的 URL。它会一直将对应的文件数据保留在内存中,直到:
- 你明确调用
URL.revokeObjectURL()
来释放它。 - 页面被卸载(浏览器会清除所有与该页面相关的对象 URL)。
Media Source Extensions
虽然浏览器自带的播放器标签已经拥有解封装和解码的功能,我们只需要提供一个MP4或者是WEBM格式的视频地址给标签,标签就能播放这个视频。
但是标签支持的媒体封装格式是十分有限的(W3C标准中只支持MP4),同时它也只能满足一次播放整个曲目的需要,无法实现拆分/合并数个缓冲文件。
MSE 提供了将单个媒体文件的 src 值替换成引用 MediaSource 对象(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer 对象(代表多个组成整个串流的不同媒体块)的元素。
MSE 的核心是 MediaSource
对象。 MediaSource
对象代表一个媒体资源,它可以包含多个 SourceBuffer
对象。 SourceBuffer
对象用于存储媒体数据(例如,视频帧、音频帧)。
对于 MSE,你不能直接将 MediaSource
对象传递给 <video>
或 <audio>
元素的 src
属性。 你需要使用 URL.createObjectURL()
方法创建URL喂给标签
原始视频/音频 -> 编码器 -> 容器封装器 -> 分段后的容器数据 -> MediaSource → SourceBuffer -> 浏览器媒体播放器。

Webcodecs(硬解)
一、什么是WebCodecs
WebCodecs是浏览器提供的一套音视频编解码的API。相比较于我们自行编写的wasm进行软编软解,Web Codecs可以利用浏览器自带的FFmpeg,其执行效率是高于webassembly的,而且可以充分利用GPU。但其缺点就是编解码的兼容性较差,对于浏览器未支持或者未开放的编解码格式,都无法进行处理。关于WebCodecs的使用其实非常简单,基本上就是配置一个编码器(或者解码器),喂给它视频帧(或者编码块),得到编码块(或者视频帧)。
二、Web编解码器API出现的原因
现在已经有很多 Web API 进行媒体操作:Media Stream API、Media Recording API、Media Source API、WebRTC API,但是没有提供一些底层 API 给到 Web 开发者进行帧操作或者对已经编码的视频进行解封装操作。
很多音视频编辑器为了解决这个问题,使用了 WebAssembly 把音视频编解码带到了浏览器,但是有个问题是现在的浏览器很多已经在底层支持了音视频编解码,并且还进行了很多硬件加速的调优,如果使用 WebAssembly 重新打包这些能力,似乎浪费人力和计算机资源。
而虽然MSE 允许开发者将自定义的媒体数据(如分段编码的视频)注入到播放器中,但最终的解码过程仍然是由浏览器内部处理,开发者无法直接控制使用硬件解码器还是软件解码器,也无法对解码过程进行细粒度干预。对于一些需要低延迟、高效率的实时通信(如视频会议、直播推流)场景,MSE 的这种"黑箱"式解码不够灵活。
所以就诞生了WebCodecs API,暴露媒体 API 来使用浏览器已经有的一些能力,例如:
- 视频和音频解码
- 视频和音频编码
- 原始视频帧
- 图像解码器
三、WebCodecs的视频编解码处理流程
frames
是视频处理的核心,因此 WebCodecs 大多数类要么消耗 frames
要么生产 frames
。Video encoders 把 frames
转换为 encoded chunks
,Video Decoders 把 encoded chunks
转换为 frames
。这一切都在非主线程异步处理,所以可以保证主线程速度。
当前,在 WebCodecs 中,在页面上显示 frame
的唯一方法是将其转换为 ImageBitmap并在 canvas 上绘制位图或将其转换为WebGLTexture。

音视频录制和播放
音视频录制原理

音视频播放原理

参考