RTMP 协议精讲 / 07 - 视频编解码
视频编解码(Video Codecs)
7.1 RTMP 视频架构
RTMP 中的视频数据以 FLV Video Tag 格式封装,通过 Type 9 消息传输。理解 RTMP 视频需要掌握三层知识:
┌─────────────────────────────────────────────────────┐
│ 应用层: 编码器输出 │
│ ┌─────────────────────────────────────────────────┐│
│ │ H.264/H.265 编码的帧数据 (NAL Units) ││
│ └─────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────┤
│ 封装层: FLV Video Tag │
│ ┌──────┬──────────┬───────────────────────────────┐│
│ │Video │ AVC/HEVC │ Video Data ││
│ │Header│ Packet │ (Sequence Header / NALU) ││
│ │(1B) │ Header │ ││
│ └──────┴──────────┴───────────────────────────────┘│
├─────────────────────────────────────────────────────┤
│ 传输层: RTMP Message (Type 9) │
│ ┌─────────────────────────────────────────────────┐│
│ │ 拆分为多个 Chunk 传输 ││
│ └─────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────┘
7.2 FLV Video Tag 结构
视频头(第一个字节)
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
│Frame │Codec │
│Type │ID │
│(4 bit)│(4 bit)│
+-+-+-+-+-+-+-+-+
Frame Type:
┌──────┬────────────────────┬──────────────────────────────────┐
│ 值 │ 名称 │ 说明 │
├──────┼────────────────────┼──────────────────────────────────┤
│ 1 │ Keyframe │ 关键帧(可独立解码) │
│ 2 │ Inter Frame │ 帧间帧(需要参考帧) │
│ 3 │ Disposable Inter │ 可丢弃的帧间帧 │
│ 4 │ Generated Keyframe │ 生成的关键帧 │
│ 5 │ Video Info/Command │ 视频信息/命令(Enhanced RTMP) │
└──────┴────────────────────┴──────────────────────────────────┘
Codec ID:
┌──────┬────────────────────┬──────────────────────────────────┐
│ 值 │ 名称 │ 说明 │
├──────┼────────────────────┼──────────────────────────────────┤
│ 2 │ Sorenson H.263 │ 早期 Flash 视频编码 │
│ 3 │ Screen Video │ 屏幕录制编码 │
│ 4 │ VP6 │ On2 VP6 编码 │
│ 5 │ VP6 with Alpha │ 带透明通道的 VP6 │
│ 6 │ Screen Video v2 │ 屏幕录制编码 v2 │
│ 7 │ AVC (H.264) │ 最常用的编码 │
│ 12 │ HEVC (H.265) │ Enhanced RTMP 扩展 │
│ 13 │ AV1 │ Enhanced RTMP 扩展 │
└──────┴────────────────────┴──────────────────────────────────┘
7.3 H.264 (AVC) 编码
7.3.1 AVC 视频数据结构
当 Codec ID = 7 时,视频数据以 AVC 格式封装:
FLV Video Tag Body:
┌────────────────┬────────────────┬──────────────────────────┐
│ Video Header │ AVC Packet │ AVC Data │
│ (1 byte) │ Type (1 byte) │ │
│ 0x17 (key) │ │ Sequence Header: │
│ 0x27 (inter) │ 0=SeqHeader │ AVCDecoderConfiguration │
│ │ 1=NALU │ Record (SPS/PPS) │
│ │ 2=End of Seq │ │
│ │ │ NALU: │
│ │ │ Composition Time (3B) │
│ │ │ + NALU Data │
└────────────────┴────────────────┴──────────────────────────┘
注意:AVC Packet Type 后面有一个 3 字节的 Composition Time Offset(CTO),用于 B 帧时间偏移:
┌─────────────────────┬──────────────────────────────┐
│ Composition Time │ 3 bytes, big-endian │
│ Offset (CTO) │ 有符号整数(毫秒) │
│ │ 0 = 无 B 帧 │
│ │ >0 = B 帧需要延迟显示的时间 │
└─────────────────────┴──────────────────────────────┘
时间计算:
DTS (解码时间戳) = RTMP Message Timestamp
PTS (显示时间戳) = DTS + CTO
7.3.2 AVC Sequence Header (AVCDecoderConfigurationRecord)
Sequence Header 包含 H.264 解码器初始化所需的 SPS 和 PPS:
AVCDecoderConfigurationRecord:
┌───────────────────────────────┬────────────────────────────────────┐
│ configurationVersion │ 1 byte, 值为 1 │
│ AVCProfileIndication │ 1 byte, 如 100=High, 77=Main │
│ profile_compatibility │ 1 byte, 兼容性标志 │
│ AVCLevelIndication │ 1 byte, 如 40=Level 4.0 │
│ lengthSizeMinusOne │ 1 byte, 低 2 位 + 1 = NALU 长度 │
│ │ 前缀字节数(通常 4) │
│ numOfSequenceParameterSets │ 1 byte, 低 5 位 = SPS 数量 │
│ ┌─ SPS #1 ──────────────┐ │ │
│ │ spsLength (2 bytes) │ │ SPS 数据长度 │
│ │ spsData (variable) │ │ SPS NALU 数据 │
│ └────────────────────────┘ │ │
│ numOfPictureParameterSets │ 1 byte, PPS 数量 │
│ ┌─ PPS #1 ──────────────┐ │ │
│ │ ppsLength (2 bytes) │ │ PPS 数据长度 │
│ │ ppsData (variable) │ │ PPS NALU 数据 │
│ └────────────────────────┘ │ │
└───────────────────────────────┴────────────────────────────────────┘
7.3.3 NAL Unit 结构
H.264 编码数据以 NAL Unit(Network Abstraction Layer Unit)为单位:
NAL Unit 头(1 byte):
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
│F│NRI│ Type │
+-+-+-+-+-+-+-+-+
┌──────┬──────────────────────────────────────────────┐
│ Type │ 名称 │
├──────┼──────────────────────────────────────────────┤
│ 1 │ Non-IDR Slice (非关键帧切片) │
│ 2 │ Slice Part A │
│ 5 │ IDR Slice (关键帧切片) │
│ 6 │ SEI (补充增强信息) │
│ 7 │ SPS (序列参数集) │
│ 8 │ PPS (图像参数集) │
│ 9 │ Access Unit Delimiter │
└──────┴──────────────────────────────────────────────┘
FLV 中的 NALU 格式:
原始 H.264 Annex B 格式 (start code):
00 00 00 01 | NALU Header | NALU Data
FLV 中的格式 (length-prefixed):
NALU Length (4 bytes) | NALU Header | NALU Data
↑
lengthSizeMinusOne + 1 字节
7.4 H.265 (HEVC) 编码 — Enhanced RTMP
传统的 FLV 规范不支持 H.265。2023 年后社区推出了 Enhanced RTMP 扩展,通过新的包格式支持 H.265、AV1 等编码。
7.4.1 Enhanced RTMP 视频头
Enhanced FLV Video Tag (CodecID = 12 for HEVC):
┌──────────────────────────────────────────────────────┐
│ Video Header (1 byte): │
│ FrameType (4 bits) = 1-4 (同传统) │
│ CodecID (4 bits) = 12 (HEVC) │
├──────────────────────────────────────────────────────┤
│ VideoPacketType (1 byte): │
│ 0 = Sequence Start (VPS/SPS/PPS) │
│ 1 = Coded Frames (编码帧) │
│ 2 = Sequence End │
│ 3 = Coded Frames (XPS) │
│ 4 = Metadata │
├──────────────────────────────────────────────────────┤
│ CompositionTime (3 bytes, big-endian) │
├──────────────────────────────────────────────────────┤
│ Video Data (variable) │
└──────────────────────────────────────────────────────┘
7.4.2 HEVC Sequence Header
HEVCDecoderConfigurationRecord (ISO 14496-15):
┌───────────────────────────────┬────────────────────────────────────┐
│ configurationVersion │ 1 byte, 值为 1 │
│ general_profile_space │ 2 bits │
│ general_tier_flag │ 1 bit │
│ general_profile_idc │ 5 bits │
│ general_profile_compatibility│ 4 bytes │
│ general_constraint_indicator │ 6 bytes │
│ general_level_idc │ 1 byte │
│ min_spatial_segmentation_idc │ 2 bytes (12 bits 有效) │
│ parallelismType │ 1 byte (2 bits 有效) │
│ chromaFormat │ 1 byte (2 bits 有效) │
│ bitDepthLumaMinus8 │ 1 byte (3 bits 有效) │
│ bitDepthChromaMinus8 │ 1 byte (3 bits 有效) │
│ avgFrameRate │ 2 bytes │
│ constantFrameRate │ 2 bits │
│ numTemporalLayers │ 3 bits │
│ temporalIdNested │ 1 bit │
│ lengthSizeMinusOne │ 2 bits │
│ numOfArrays │ 1 byte │
│ ┌─ Array #1 (VPS) ─────────┐ │ │
│ │ NAL_unit_type (1 byte) │ │ 32 = VPS │
│ │ numNalus (2 bytes) │ │ │
│ │ nalUnitLength (2 bytes) │ │ │
│ │ nalUnitData (variable) │ │ │
│ └──────────────────────────┘ │ │
│ ┌─ Array #2 (SPS) ─────────┐ │ │
│ │ NAL_unit_type (1 byte) │ │ 33 = SPS │
│ │ ... │ │ │
│ └──────────────────────────┘ │ │
│ ┌─ Array #3 (PPS) ─────────┐ │ │
│ │ NAL_unit_type (1 byte) │ │ 34 = PPS │
│ │ ... │ │ │
│ └──────────────────────────┘ │ │
└───────────────────────────────┴────────────────────────────────────┘
7.5 关键帧处理
关键帧(Keyframe / IDR Frame)是视频流中最关键的数据:
关键帧的重要性
视频帧序列:
I ──→ P ──→ P ──→ B ──→ P ──→ B ──→ I ──→ P ──→ ...
↑ ↑
IDR IDR
关键帧 关键帧
(可独立解码) (可独立解码)
P 帧 = 前向预测帧(依赖前一帧)
B 帧 = 双向预测帧(依赖前后帧)
从任意位置开始播放:必须从关键帧开始!
关键帧间隔
关键帧间隔 (GOP = Group of Pictures):
短 GOP (1-2 秒):
I P P P I P P P I P P P ...
├── 1s ──┤
优点: 低延迟、快速切换
缺点: 码率较高
长 GOP (4-8 秒):
I P P P P P P P P P P P I P P P ...
├──── 4 秒 ────────────┤
优点: 压缩效率高
缺点: 切换延迟大
关键帧在 RTMP 中的处理
class KeyframeManager:
"""关键帧管理器 — 用于 GOP Cache"""
def __init__(self, gop_size: int = 10):
self.gop_size = gop_size # 保留的 GOP 数
self.gops: dict[int, list] = {} # stream_id -> [frames]
self.current_gop: list = []
self.gop_count = 0
def on_video_frame(self, stream_id: int, data: bytes, timestamp: int, is_keyframe: bool):
"""处理视频帧"""
if stream_id not in self.gops:
self.gops[stream_id] = []
if is_keyframe:
# 保存上一个完整的 GOP
if self.current_gop:
self.gops[stream_id].append(self.current_gop)
# 只保留最近 N 个 GOP
if len(self.gops[stream_id]) > self.gop_size:
self.gops[stream_id].pop(0)
self.current_gop = [(data, timestamp, True)]
else:
self.current_gop.append((data, timestamp, False))
def get_gop_for_new_viewer(self, stream_id: int) -> list:
"""为新观众获取最近的完整 GOP"""
if stream_id in self.gops and self.gops[stream_id]:
return self.gops[stream_id][-1]
return []
7.6 Composition Time 与 B 帧
时间轴示例(有 B 帧):
编码顺序 (DTS): I(0) P(3) B(1) B(2) P(6) B(4) B(5)
显示顺序 (PTS): I(0) B(1) B(2) P(3) B(4) B(5) P(6)
CTO = PTS - DTS:
I: CTO = 0 - 0 = 0
P: CTO = 3 - 3 = 0
B: CTO = 1 - 3 = -2 (注意是负数)
实际 FLV 中的 Composition Time:
I: 0x000000 (0ms)
P: 0x000000 (0ms)
B: 0xFFFFFE (-2ms, 有符号 24 位)
def calculate_pts(dts: int, composition_time: int) -> int:
"""
计算 PTS (Presentation Timestamp)
composition_time: 3 字节有符号整数
"""
# 3 字节有符号:如果最高位为 1,则为负数
if composition_time & 0x800000:
composition_time -= 0x1000000
return dts + composition_time
7.7 FFmpeg 视频编码命令
常用推流命令
# 基础推流(H.264)
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 2000k \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/stream
# 使用 NVENC 硬件编码
ffmpeg -re -i input.mp4 \
-c:v h264_nvenc -preset p4 -b:v 4000k \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/stream
# 推送 H.265(Enhanced RTMP)
ffmpeg -re -i input.mp4 \
-c:v libx265 -preset fast -b:v 1500k \
-c:a aac -b:a 128k \
-f hevc rtmp://localhost:1935/live/stream
# 设置关键帧间隔(2 秒)
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 2000k \
-g 60 -keyint_min 60 \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/stream
# 多码率输出
ffmpeg -re -i input.mp4 \
-c:v libx264 -s 1920x1080 -b:v 4000k -g 60 \
-c:v libx264 -s 1280x720 -b:v 2000k -g 60 \
-c:v libx264 -s 640x360 -b:v 800k -g 60 \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/stream_{0}
从摄像头推流
# Linux (V4L2)
ffmpeg -f v4l2 -i /dev/video0 -f alsa -i hw:0 \
-c:v libx264 -preset veryfast -b:v 2000k \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/camera
# macOS (AVFoundation)
ffmpeg -f avfoundation -i "0:0" \
-c:v libx264 -preset veryfast -b:v 2000k \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/camera
# Windows (DirectShow)
ffmpeg -f dshow -i video="Integrated Camera":audio="Microphone" \
-c:v libx264 -preset veryfast -b:v 2000k \
-c:a aac -b:a 128k \
-f flv rtmp://localhost:1935/live/camera
7.8 视频参数详解
| 参数 | 说明 | 推荐值 |
|---|---|---|
-c:v | 视频编码器 | libx264, h264_nvenc, libx265 |
-preset | 编码速度/质量权衡 | veryfast, fast, medium |
-b:v | 视频码率 | 1000k-6000k |
-maxrate | 最大码率 | b:v 的 1.5 倍 |
-bufsize | 缓冲区大小 | maxrate 的 2 倍 |
-g | GOP 大小(关键帧间隔) | fps * 2 (2 秒) |
-keyint_min | 最小关键帧间隔 | 同 -g |
-s | 分辨率 | 1920x1080, 1280x720 |
-r | 帧率 | 25, 30, 60 |
-pix_fmt | 像素格式 | yuv420p |
编码 Preset 对比
| Preset | 编码速度 | 压缩质量 | CPU 使用 | 适用场景 |
|---|---|---|---|---|
| ultrafast | 最快 | 最低 | 低 | 实时编码 |
| superfast | 很快 | 低 | 较低 | 低延迟直播 |
| veryfast | 快 | 较低 | 中等 | 直播推流 |
| fast | 较快 | 中等 | 较高 | 直播推流 |
| medium | 中等 | 较高 | 高 | 存储/点播 |
| slow | 慢 | 高 | 很高 | 存储/点播 |
| veryslow | 最慢 | 最高 | 最高 | 存档 |
注意事项
- Sequence Header 必须优先发送:播放器需要先收到 AVC/HEVC Sequence Header 才能解码
- 关键帧间隔:直播场景建议 2 秒,过长会导致切换延迟
- Enhanced RTMP 兼容性:H.265 需要服务器支持 Enhanced RTMP,旧服务器不支持
- B 帧延迟:B 帧会增加 1-2 帧的编码延迟,低延迟场景建议关闭 B 帧(
-bf 0) - CTO 为 0:当不使用 B 帧时,Composition Time Offset 始终为 0
- NALU 长度前缀:RTMP 中的 NALU 使用 4 字节长度前缀,而非 Annex B 的 start code
扩展阅读
- FLV File Format Specification
- Enhanced RTMP Specification
- H.264/AVC Standard
- FFmpeg H.264 Encoding Guide
上一章:06 - 流操作 下一章:08 - 音频编解码 — 了解 RTMP 中的音频编码处理