强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 最慢 最高 最高 存档

注意事项

  1. Sequence Header 必须优先发送:播放器需要先收到 AVC/HEVC Sequence Header 才能解码
  2. 关键帧间隔:直播场景建议 2 秒,过长会导致切换延迟
  3. Enhanced RTMP 兼容性:H.265 需要服务器支持 Enhanced RTMP,旧服务器不支持
  4. B 帧延迟:B 帧会增加 1-2 帧的编码延迟,低延迟场景建议关闭 B 帧(-bf 0
  5. CTO 为 0:当不使用 B 帧时,Composition Time Offset 始终为 0
  6. NALU 长度前缀:RTMP 中的 NALU 使用 4 字节长度前缀,而非 Annex B 的 start code

扩展阅读


上一章06 - 流操作 下一章08 - 音频编解码 — 了解 RTMP 中的音频编码处理