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

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 倍
-gGOP 大小(关键帧间隔)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 中的音频编码处理