4、视频流基础:视频编码原理(H.264/H.265)、封装格式(MP4/RTSP/RTMP)、OpenCV读取视频流的基本操作

好,咱们进入第四章。这一章可以说是整个课程的「地基」——视频流基础。你想想看,做边缘端实时视频分析,如果连视频怎么编码、怎么封装、怎么用OpenCV读出来都不清楚,后面那些AI模型推理、性能优化根本无从谈起。

我个人习惯,在讲任何实战之前,先把「原材料」搞明白。视频流就是我们的原材料。今天咱们就把它拆开揉碎了讲清楚。

4.1 视频编码原理:H.264 vs H.265

先说编码。为什么需要编码?原始视频数据量太大了。举个例子,一张1080p的RGB图像,分辨率1920x1080,每个像素3字节,一帧就是6MB左右。30帧每秒,一秒就是180MB。这谁受得了?

所以必须压缩。H.264和H.265是目前最主流的两种编码标准。

4.1.1 H.264(AVC)

H.264也叫AVC(Advanced Video Coding),2003年推出的标准。到现在快二十年了,依然统治着大部分视频场景。我在项目中遇到过很多次,客户说「我摄像头输出的是H.264」,基本上十有八九都是它。

H.264的核心思想是「帧内预测 + 帧间预测」。说白了就是:

  • 帧内预测:利用同一帧内相邻像素的相似性来压缩。比如一块蓝天,我只存一个颜色和范围,不存每个像素。
  • 帧间预测:利用前后帧的相似性。比如一个人站着不动,背景不变,我只存变化的部分。

H.264定义了三种帧类型:

帧类型 全称 说明
I帧 关键帧 完整编码的一帧,不依赖其他帧。相当于「快照」
P帧 前向预测帧 参考前面的I帧或P帧,只存差异
B帧 双向预测帧 参考前后帧,压缩率最高,但计算量大
我的经验:在边缘端做实时分析时,我建议尽量少用B帧。B帧需要参考未来帧,会引入延迟。对于实时性要求高的场景,用IPPP结构(只有I帧和P帧)更靠谱。

4.1.2 H.265(HEVC)

H.265也叫HEVC(High Efficiency Video Coding),2013年推出的。它是H.264的升级版,目标很明确:在同等画质下,码率再降一半。

怎么做到的?核心改进包括:

  • 更大的编码块:H.264最大16x16像素,H.265最大64x64像素。大块编码效率更高。
  • 更灵活的划分:H.265支持四叉树划分,可以更精细地适配图像内容。
  • 更好的帧内预测:预测方向从H.264的9种增加到35种。

嗯,这里要注意:H.265虽然压缩率更好,但计算复杂度也更高。在边缘端设备上,比如树莓派、Jetson Nano,解码H.265可能需要硬件支持。我曾经在一个项目里,用纯CPU软解H.265的4K视频,结果帧率只有个位数……后来换了带硬件解码的模块才搞定。

选型建议:
  • 带宽有限、存储有限 → 选H.265
  • 边缘设备性能较弱 → 选H.264(硬件支持更广泛)
  • 兼容性优先 → 选H.264

4.2 封装格式:MP4 / RTSP / RTMP

编码是压缩视频数据,封装是把压缩后的数据打包成文件或流。很多人容易把编码和封装搞混,其实它们是两回事。

4.2.1 MP4

MP4是最常见的文件封装格式。它基于ISO Base Media File Format,可以装H.264、H.265、AAC音频等。MP4的特点是:

  • 支持随机访问(快进快退)
  • 有moov box存储元数据(时长、分辨率等)
  • 适合存储和点播

但MP4有个坑:它的moov box默认在文件末尾。如果你用OpenCV直接读一个没处理过的MP4文件,可能需要等它下载完才能开始播放。我建议用工具把moov box移到文件头,比如用qt-faststart

4.2.2 RTSP

RTSP(Real Time Streaming Protocol)是实时流传输协议。它本身不传输数据,而是充当「遥控器」,控制RTP/UDP或TCP来传输视频流。

RTSP的特点:

  • 支持实时传输,延迟低
  • 支持VCR操作(播放、暂停、快进)
  • 常用于IP摄像头、监控系统

在OpenCV中,RTSP地址通常长这样:rtsp://username:password@192.168.1.100:554/stream1

避坑指南:我曾经在项目中遇到RTSP流断连的问题。摄像头偶尔会丢包,导致OpenCV的read()卡死。解决方案是加一个超时重连机制,或者用GStreamer管道来增强稳定性。

4.2.3 RTMP

RTMP(Real-Time Messaging Protocol)是Adobe推出的,主要用于直播推流。它基于TCP,延迟比RTSP稍高,但穿透防火墙的能力更强。

RTMP的特点:

  • 推流为主,拉流也可以
  • 需要Flash或专用播放器(现在逐渐被HLS/WebRTC取代)
  • 常用于直播平台、视频会议

在边缘端,如果你需要把分析结果推送到云端或直播平台,RTMP是个不错的选择。但要注意,RTMP默认只支持H.264和AAC。

4.3 OpenCV读取视频流的基本操作

好,理论讲完了,咱们来点实际的。OpenCV读取视频流,核心就两个类:VideoCaptureVideoWriter

4.3.1 读取本地视频文件

import cv2

# 打开视频文件
cap = cv2.VideoCapture('test.mp4')

# 检查是否打开成功
if not cap.isOpened():
    print("无法打开视频文件")
    exit()

# 获取视频属性
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

print(f"帧率: {fps}, 分辨率: {width}x{height}, 总帧数: {total_frames}")

# 逐帧读取
while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # 在这里做你的AI分析
    # process_frame(frame)
    
    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

4.3.2 读取RTSP摄像头流

import cv2

# RTSP地址
rtsp_url = "rtsp://admin:123456@192.168.1.100:554/stream1"

# 创建VideoCapture对象
cap = cv2.VideoCapture(rtsp_url)

# 设置缓冲区大小(减少延迟)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)

# 设置解码后端(推荐使用FFMPEG)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('H', '2', '6', '4'))

while True:
    ret, frame = cap.read()
    if not ret:
        print("读取失败,尝试重连...")
        cap.release()
        cap = cv2.VideoCapture(rtsp_url)
        continue
    
    # 显示帧率
    cv2.putText(frame, f"FPS: {cap.get(cv2.CAP_PROP_FPS):.2f}", 
                (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
    
    cv2.imshow('RTSP Stream', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
我的经验:读取RTSP流时,CAP_PROP_BUFFERSIZE这个参数很关键。默认缓冲区可能很大,导致你读到的是几秒前的画面。设成1可以降低延迟,但要注意网络波动时可能会丢帧。我一般设成2-3,平衡一下。

4.3.3 常用属性设置

属性 OpenCV常量 说明
帧宽 CAP_PROP_FRAME_WIDTH 设置/获取视频宽度
帧高 CAP_PROP_FRAME_HEIGHT 设置/获取视频高度
帧率 CAP_PROP_FPS 获取视频帧率(只读)
缓冲区大小 CAP_PROP_BUFFERSIZE 设置内部缓冲区帧数
解码器 CAP_PROP_FOURCC 设置编码格式(如H264)
超时 CAP_PROP_OPEN_TIMEOUT_MSEC 设置打开超时(毫秒)

4.3.4 性能优化小技巧

在边缘端做实时分析,性能是命根子。我总结了几条经验:

  • 降低分辨率:如果AI模型不需要全分辨率,先resize再推理。比如1080p缩放到640x360,计算量直接降到1/9。
  • 跳帧处理:如果模型推理速度跟不上视频帧率,可以每N帧处理一次。比如30fps的视频,每3帧处理一次,相当于10fps的推理频率。
  • 多线程读取:把视频读取和AI推理放在不同线程,避免I/O阻塞推理。
  • 使用硬件解码:如果边缘设备支持,用GStreamer或FFMPEG的硬件加速后端。
# 多线程读取示例(简化版)
import threading
import queue

frame_queue = queue.Queue(maxsize=10)

def read_frames(cap):
    while True:
        ret, frame = cap.read()
        if not ret:
            break
        if not frame_queue.full():
            frame_queue.put(frame)

# 主线程
cap = cv2.VideoCapture(rtsp_url)
reader_thread = threading.Thread(target=read_frames, args=(cap,))
reader_thread.start()

while True:
    if not frame_queue.empty():
        frame = frame_queue.get()
        # 在这里做AI推理
        # result = model.predict(frame)
        cv2.imshow('Stream', frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
注意:多线程读取时,一定要控制队列大小。如果推理速度跟不上,队列会爆满,导致内存溢出。我一般设成10-20帧的缓冲区就够了。

好了,这一章的内容就到这里。视频编码、封装格式、OpenCV读取,这三块是后面所有实战的基础。下一章我们会讲如何用OpenCV做视频预处理,包括缩放、裁剪、颜色空间转换等。到时候咱们再聊。