第九 视频
视频是现代社会最重要的信息媒介之一。从电影电视到短视频直播,从视频会议到监控安防,视频技术深刻改变了人们获取和传播信息的方式。与静态图片不同,视频是随时间连续变化的图像序列,同时伴随音频流。本章将探讨视频的本质、编码原理、主流编码格式,以及如何使用 Rust 进行视频处理。
9.1 视频的本质
视频本质上是一系列静态图像(称为帧)按照一定速率连续播放,利用人眼的视觉暂留效应产生运动的错觉。衡量视频质量的核心参数有三个:帧率、分辨率和码率。
9.1.1 帧率
**帧率(Frame Rate)**表示每秒显示的图像帧数,单位是 FPS(Frames Per Second)。
- 24 FPS:电影标准,画面具有电影感。
- 25 FPS / 30 FPS:传统电视广播标准(PAL / NTSC)。
- 60 FPS:游戏和高清视频常用,运动画面更流畅。
- 120 FPS / 240 FPS:高帧率视频,用于慢动作回放和电竞。
帧率越高,运动画面越流畅,但数据量也越大。对于普通观影,24~30 FPS 已足够;对于快速运动场景(如体育赛事、游戏),60 FPS 能显著提升观感。
9.1.2 分辨率
分辨率表示视频画面的像素尺寸,决定了画面的清晰度。
| 分辨率 | 像素尺寸 | 说明 |
|---|---|---|
| SD(标清) | 720 × 480 / 720 × 576 | 传统 DVD 标准 |
| HD(高清) | 1280 × 720 | 720p |
| Full HD | 1920 × 1080 | 1080p,目前主流 |
| 2K | 2560 × 1440 | 1440p |
| 4K UHD | 3840 × 2160 | 超高清,四倍于 1080p |
| 8K UHD | 7680 × 4320 | 超高分辨率,未来趋势 |
分辨率越高,画面细节越丰富,但像素数量呈指数增长。4K 的像素数是 1080p 的 4 倍,数据量也相应大幅增加。
9.1.3 码率
**码率(Bitrate)**表示视频每秒传输的数据量,单位通常是 kbps 或 Mbps。码率直接决定视频的画质和文件大小。
- 恒定码率(CBR):码率保持不变,适合网络流媒体。
- 可变码率(VBR):根据画面复杂度动态调整码率,相同平均码率下画质更好。
以 1080p 视频为例:
| 码率 | 画质 | 一小时文件大小 |
|---|---|---|
| 5 Mbps | 一般 | 约 2.2 GB |
| 10 Mbps | 良好 | 约 4.4 GB |
| 25 Mbps | 优秀 | 约 11 GB |
| 50 Mbps | 接近无损 | 约 22 GB |
未经压缩的 1080p@60FPS 视频数据量极为惊人:
1920 × 1080 × 3(RGB)× 60 FPS = 373,248,000 字节/秒 ≈ 356 MB/s
一小时约 1.25 TB
这正是视频压缩编码技术存在的根本原因。
9.2 视频编码原理
视频编码的核心目标是在尽可能保持画质的前提下,大幅减少数据量。主要利用两种冗余:空间冗余和时间冗余。
9.2.1 帧内压缩
**帧内压缩(Intra-frame Compression)**利用单帧图像内部的空间冗余。相邻像素通常具有相似的颜色值,通过以下技术实现压缩:
- 色彩空间转换:将 RGB 转换为 YUV(亮度+色度),利用人眼对色度不敏感的特性降低色度采样精度(如 4:2:0)。
- 离散余弦变换(DCT):将图像块从空间域转换到频率域,能量集中在低频分量。
- 量化:对高频分量进行较大量化,丢弃人眼不敏感的细节。
- 熵编码:使用哈夫曼编码或算术编码进一步压缩数据。
只使用帧内压缩的帧称为 I 帧(Intra-coded Frame),可以独立解码,是视频随机访问的锚点。
9.2.2 帧间压缩
**帧间压缩(Inter-frame Compression)**利用相邻帧之间的时间冗余。视频相邻帧通常非常相似,只需记录变化部分即可。
- P 帧(Predicted Frame):参考前面的 I 帧或 P 帧,只编码差异部分和运动矢量。
- B 帧(Bidirectional Predicted Frame):同时参考前后帧,压缩率最高,但解码需要更多计算。
典型的视频帧序列如:I P B B P B B I P B B ...
帧间压缩是视频编码效率的关键。一个 I 帧可能占用数兆字节,而 P 帧和 B 帧通常只有几十到几百 KB。
9.2.3 运动估计与补偿
帧间压缩的核心技术是运动估计。编码器将画面划分为宏块(如 16×16 像素),在参考帧中搜索最佳匹配位置,记录运动矢量(位移偏移量)。解码时根据运动矢量从参考帧“搬运“像素块,再叠加残差数据还原当前帧。
运动估计的计算量巨大,是视频编码中最耗时的环节,也是硬件加速的重点。
9.3 视频编码格式
9.3.1 H.264 / AVC
H.264(又称 AVC,Advanced Video Coding)是目前最广泛使用的视频编码标准,由 ITU-T 和 ISO/IEC 联合制定。
- 发布时间:2003 年。
- 压缩效率:相比前代 MPEG-2,同等画质下码率降低约 50%。
- 特点:兼容性好、专利授权成熟、硬件解码支持最广泛。
- 应用场景:网络视频(YouTube、Bilibili)、蓝光光盘、视频监控、视频会议。
- Profile 等级:
- Baseline:低复杂度,适合视频通话和移动设备。
- Main:平衡画质和复杂度,用于标清广播。
- High:最高画质,用于高清和蓝光。
9.3.2 H.265 / HEVC
H.265(又称 HEVC,High Efficiency Video Coding)是 H.264 的继任者。
- 发布时间:2013 年。
- 压缩效率:同等画质下码率比 H.264 降低约 50%,即 1080p 的码率可看 4K 画质。
- 特点:支持更高分辨率(8K)、更高效的帧间预测、更大的编码单元(CTU 最大 64×64)。
- 缺点:专利授权复杂且昂贵,导致普及速度不及预期。
- 应用场景:4K 蓝光、Apple 设备、部分流媒体平台。
9.3.3 AV1
AV1(AOMedia Video 1)是由开放媒体联盟(AOMedia)主导开发的开源免版税视频编码格式。
- 发布时间:2018 年。
- 压缩效率:同等画质下码率比 H.265 低约 20%~30%,比 H.264 低约 50%。
- 特点:完全开源免版税、由 Google、Mozilla、Netflix 等巨头推动。
- 缺点:编码速度较慢(正在快速改善),硬件解码支持仍在普及中。
- 应用场景:YouTube、Netflix、WebRTC、浏览器视频。
| 编码格式 | 发布时间 | 压缩效率 | 专利授权 | 硬件支持 | 主要应用 |
|---|---|---|---|---|---|
| H.264 | 2003 | 基准 | 收费 | 最广泛 | 全网通用 |
| H.265 | 2013 | 比 H.264 提升 50% | 昂贵 | 较广泛 | 4K 内容 |
| AV1 | 2018 | 比 H.265 提升 20%~30% | 免版税 | growing | 流媒体/Web |
9.4 Rust 视频处理
Rust 在视频处理领域虽然不如 Python 或 C++ 生态成熟,但已有可用的库和绑定,特别是通过 FFI 调用 FFmpeg 这一业界标准工具。
9.4.1 FFmpeg 简介
FFmpeg 是开源多媒体处理的事实标准,支持几乎所有音视频格式的编解码、转码、流媒体处理。它由 C 语言编写,提供了命令行工具和 C 库(libavcodec、libavformat、libavutil 等)。
Rust 社区提供了 FFmpeg 的绑定库 ffmpeg-next,使得在 Rust 中调用 FFmpeg 功能成为可能。
在 Cargo.toml 中添加依赖:
[dependencies]
ffmpeg-next = "6.1"
9.4.2 使用 ffmpeg-next 读取视频信息
use ffmpeg_next as ffmpeg;
fn main() {
// 初始化 FFmpeg
ffmpeg::init().unwrap();
// 打开视频文件
let mut ictx = ffmpeg::format::input(&"video.mp4").unwrap();
// 遍历所有流,找到视频流
for (stream_index, stream) in ictx.streams().enumerate() {
let codec = stream.codec();
match codec.medium() {
ffmpeg::media::Type::Video => {
let video = codec.decoder().video().unwrap();
println!("视频流 #{}:", stream_index);
println!(" 编码格式: {:?}", video.id());
println!(" 分辨率: {}x{}", video.width(), video.height());
println!(" 帧率: {}/{}",
stream.rate().0, stream.rate().1);
println!(" 总帧数: {}", stream.frames());
}
ffmpeg::media::Type::Audio => {
let audio = codec.decoder().audio().unwrap();
println!("音频流 #{}:", stream_index);
println!(" 采样率: {}", audio.rate());
println!(" 声道数: {}", audio.channels());
}
_ => {}
}
}
}
9.4.3 使用 ffmpeg-next 解码视频帧
use ffmpeg_next as ffmpeg;
use ffmpeg::software::scaling::{context::Context, flag::Flags};
use ffmpeg::util::frame::video::Video;
fn main() {
ffmpeg::init().unwrap();
let mut ictx = ffmpeg::format::input(&"video.mp4").unwrap();
// 找到第一个视频流
let input_stream = ictx.streams()
.best(ffmpeg::media::Type::Video)
.expect("未找到视频流");
let stream_index = input_stream.index();
// 创建解码器
let context_decoder = ffmpeg::codec::context::Context::from_parameters(input_stream.parameters()).unwrap();
let mut decoder = context_decoder.decoder().video().unwrap();
// 创建缩放器,将帧转换为 RGB24 格式
let mut scaler = Context::get(
decoder.format(),
decoder.width(),
decoder.height(),
ffmpeg::format::Pixel::RGB24,
decoder.width(),
decoder.height(),
Flags::BILINEAR,
).unwrap();
// 读取数据包并解码
for (stream, packet) in ictx.packets() {
if stream.index() == stream_index {
decoder.send_packet(&packet).unwrap();
let mut decoded = Video::empty();
while decoder.receive_frame(&mut decoded).is_ok() {
let mut rgb_frame = Video::empty();
scaler.run(&decoded, &mut rgb_frame).unwrap();
println!("解码一帧: {}x{}", rgb_frame.width(), rgb_frame.height());
// 此处可以对 rgb_frame.data(0) 中的 RGB 像素数据进行处理
// 例如保存为图片、进行图像识别等
}
}
}
// 刷新解码器
decoder.send_eof().unwrap();
}
9.4.4 使用 ffmpeg-next 进行视频转码
use ffmpeg_next as ffmpeg;
fn main() {
ffmpeg::init().unwrap();
// 打开输入文件
let mut ictx = ffmpeg::format::input(&"input.mp4").unwrap();
// 创建输出上下文
let mut octx = ffmpeg::format::output(&"output.mkv").unwrap();
// 遍历输入流,在输出中添加对应流
for (stream_index, istream) in ictx.streams().enumerate() {
let codec_id = istream.codec().id();
let mut ostream = octx.add_stream(codec_id).unwrap();
ostream.set_parameters(istream.parameters()).unwrap();
}
octx.write_header().unwrap();
// 复制数据包
for (stream, mut packet) in ictx.packets() {
let istream_index = stream.index();
let ostream = octx.stream(istream_index).unwrap();
packet.set_stream(ostream.index());
packet.rescale_ts(stream.time_base(), ostream.time_base());
packet.write_interleaved(&mut octx).unwrap();
}
octx.write_trailer().unwrap();
println!("转码完成: input.mp4 -> output.mkv");
}
9.4.5 rav1e:Rust 原生的 AV1 编码器
rav1e 是由 Xiph.Org 基金会开发的 AV1 编码器,使用 Rust 和 Assembly 编写,是 Rust 视频编码领域的重要项目。
// rav1e 基本使用示例(API 可能随版本变化)
use rav1e::config::Config;
use rav1e::encoder::EncoderStatus;
fn main() {
// 创建编码配置
let mut cfg = Config::default();
cfg.enc.width = 1920;
cfg.enc.height = 1080;
cfg.enc.bit_depth = 8;
cfg.enc.time_base = rav1e::data::Rational { num: 1, den: 30 }; // 30 FPS
// 创建编码器
let mut ctx = cfg.new_context().unwrap();
// 发送帧进行编码
// ctx.send_frame(frame).unwrap();
// 刷新编码器
ctx.flush();
// 接收编码后的数据包
loop {
match ctx.receive_packet() {
Ok(packet) => {
println!("收到编码包,大小: {} 字节", packet.data.len());
// 将 packet.data 写入文件或传输
}
Err(EncoderStatus::LimitReached) => break,
Err(EncoderStatus::Encoded) => continue,
Err(e) => {
println!("编码错误: {:?}", e);
break;
}
}
}
}
rav1e 展示了 Rust 在性能敏感型多媒体处理领域的潜力,其内存安全特性对于处理大规模视频数据尤为重要。
9.4.6 相关 Rust 库
| 库名 | 说明 |
|---|---|
| ffmpeg-next | FFmpeg 的 Rust 绑定,功能全面 |
| rav1e | Rust 编写的 AV1 编码器 |
| image | 图像处理,可用于视频帧处理 |
| wgpu | 跨平台 GPU 计算,可用于视频渲染 |
9.5 本章小结
| 概念 | 说明 |
|---|---|
| 帧率 | 每秒显示帧数,电影 24 FPS,高清 60 FPS |
| 分辨率 | 画面像素尺寸,1080p、4K、8K 等 |
| 码率 | 每秒数据量,决定画质和文件大小 |
| I 帧 | 帧内压缩,可独立解码的完整帧 |
| P 帧 | 前向预测帧,参考前面帧编码 |
| B 帧 | 双向预测帧,参考前后帧,压缩率最高 |
| H.264 | 最广泛使用的视频编码标准 |
| H.265 | H.264 继任者,4K 时代主流 |
| AV1 | 开源免版税编码格式,未来趋势 |
| FFmpeg | 业界标准多媒体处理工具 |
| ffmpeg-next | FFmpeg 的 Rust 绑定库 |
| rav1e | Rust 原生 AV1 编码器 |
练习建议
- 使用 ffmpeg-next 编写程序,读取一个视频文件的完整元数据(分辨率、帧率、码率、编码格式、时长等)。
- 使用 ffmpeg-next 提取视频中的每一帧,保存为 PNG 图片序列。
- 对比同一视频分别用 H.264、H.265、AV1 编码后的文件大小和画质差异。
- 编写程序计算视频的平均码率和峰值码率。
- 了解 Rust 的 vpx-encode 或 x264 绑定,尝试进行视频编码。