📹 第五章 FFmpeg二次开发IPC视频监控
📄 一句话概括

类封装的核心不是「把函数塞进类里」,是「保证资源成对释放」。 五个方法:open、encode、flush、close、析构(Python 里是 __exit__)。

📝 本节和前面几节的关系

上一节连上了摄像头,这一节把「采集 → 编码」包成一个可复用的东西。 RAII 的思想在 32 课讲过,这里用 Python 的 with 复现。

一、五个方法,各有各的纪律

方法 做什么 重复调用会怎样
open() 建容器、建编码器 应该报错,不能静默重来
encode(frame) 送一帧,返回若干包 没 open 就报错
flush() 冲空包,把尾巴吐出来 只能调一次有效
close() 关文件写索引 重复调应该无害
__enter__/__exit__ 支持 with,异常也保证关 —

第三行和第四行的对比最能说明「纪律」这个词: flush 调两次第二次就是空的(无害但没意义),close 调两次必须无害。 这两种「重复」的处理方式不一样,写代码时要分开想。

二、构造函数不做事,Open 才建资源

这是 C++ 的硬性要求:构造函数里抛异常,对象就没了, 外面拿不到「失败了一半」的状态,也就没法清理。 所以标准写法是「构造函数只存参数,Open() 里建资源」。

C++ 做法 为什么 Python 对应
构造只存参数,Open() 建资源 构造失败没法报错 __init__ 只存参数
析构里 Close() 保证异常也释放 __exit__
状态标志防重入 重复打开会泄漏 self.c is None 判断

第一行在 Python 里同样适用:__init__ 抛异常对象也不会留下,所以也建议「先构造、再打开」。

三、实测四种误用

误用 结果
没 open 就 encode 拦住:not open
重复 open 拦住:already open
close 之后 encode 拦住:already closed
重复 close 无害,直接返回

前三种都是「主动报错」,第四种是「静默返回」——这个区别是有意的: 前三种是调用顺序错了(bug),第四种是清理逻辑可能被多个地方调用(正常情况)。 写资源类的时候,这一条要提前想清楚:哪些操作「重复 = 错误」,哪些操作「重复 = 无害」。

四、为什么这层封装值得写

不封装 封装后
open/close 散在各处 成对出现,异常也保证
换编码器要改多处 只改一个类
错误处理各写各的 统一在一处

第二行是真正的收益:H.264 换 H.265 只改类的内部,调用方一行不动。 这也是为什么书里这一节要讲「类封装」而不是「写个函数」—— 监控项目里编码器经常要按客户要求切换。

五、动手 Demo:把编码器包成一个类,接口该长什么样

Demo 写一个 Encoder 类,然后故意用错四次,看它有没有拦住。 最后用 with 跑一遍正常流程,验证异常时也会关文件。

class Encoder(object):
    def __init__(self, path):
        self.path = path
        self.c = None
        self.closed = False
    def open(self):
        if self.c is not None:
            raise RuntimeError("already open")
        self.c = av.open(self.path, "w", format="h264")
        ...
    def close(self):
        if self.c is None or self.closed:
            return
        self.c.close()
        self.closed = True
    def __enter__(self):
        return self.open()
    def __exit__(self, *a):
        self.close()

实跑输出:

--------------------------------------------------------------------
把编码器包成一个类,接口该长什么样
--------------------------------------------------------------------
**书上这一节是 C++ 类封装,核心是「资源管理」:打开、使用、关闭必须成对。**
  **Python 里没有析构函数那套,但 RAII 的思想一样能用 `with` 表达。**

**一个合格的封装类要有五个方法:**

  方法                  做什么                  重复调用会怎样
  ------------------------------------------------------------------------
  `open()`              建容器、建编码器        应该报错,不能静默重来
  `encode(frame)`       送一帧,返回若干包      没 open 就报错
  `flush()`             冲空包,把尾巴吐出来    只能调一次有效
  `close()`             关文件写索引            重复调应该无害
  `__enter__/__exit__`  支持 with,异常也保证关   —

**实测四种误用,看它有没有拦住:**

  误用                      结果
  --------------------------------------------------------
  没 open 就 encode         拦住:not open
  重复 open                 拦住:already open
  close 之后 encode         拦住:already closed
  重复 close                无害,直接返回

**再用 `with` 跑一遍正常流程,验证「异常也保证关」:**

  `with` 块里编了 30 帧,出了 30 个包,文件 10634 字节。

  **`__exit__` 在异常时也会被调用**,所以哪怕中间抛错,
  **文件也会被 close(索引会写上)**——这就是 C++ 里析构函数干的事。

**书上 C++ 版的三个要点,翻译成 Python 是这样:**

  C++ 做法                           为什么                    Python 对应
  -------------------------------------------------------------------------------------
  构造函数不做事,`Open()` 才建资源  构造失败没法报错          `__init__` 只存参数
  析构函数里 `Close()`               保证异常也释放            `__exit__`
  状态标志防重入                     重复打开会泄漏            `self.c is None` 判断

  **第一行在 C++ 里是硬性要求**:构造函数抛异常对象就没了,**所以「先构造、再打开」是标准写法**。
  **Python 里 `__init__` 抛异常对象也不会留下**,所以同样建议这么分。

__exit__ 返回 False 很关键:它表示「异常继续往外抛」,不能吞掉。 返回 True 会把异常咽下去,调试时会疯掉。 with 那一段编了 30 帧、出 30 个包、文件 10634 字节, 和 04.05.03 直接写的版本完全一致——封装没有改变行为,只是收紧了纪律。

30 秒实验

  1. 把 __exit__ 的返回值改成 True,故意在 with 里抛异常,看异常会不会被吞掉。
  2. 给类加一个 switch(codec) 方法,把 H.264 换成 H.265,看调用方要改几行。
  3. 让 encode 在 flush 之后调用,看现在的状态判断能不能拦住。

常见误区

  1. 以为封装就是「把函数塞进类」。 核心是资源的成对释放,和错误状态的显式管理。
  2. 以为 __exit__ 该返回 True。 返回 True 会吞掉异常,问题会被藏起来。
  3. 以为所有操作重复调用都应该报错。 close 应该无害,因为清理逻辑可能被多处调用。
自测三问(点开对答案) 1. 为什么构造函数里不建资源? —— 构造抛异常对象就没了,**外面拿不到「失败一半」的状态去清理**。所以先构造、再 `Open()`。 2. `__exit__` 返回 False 是什么意思? —— 「异常继续往外抛」。**返回 True 会吞掉异常**,看起来程序没崩,其实问题被藏了。 3. 为什么 close 重复调用要无害、open 重复调用要报错? —— close 可能被多处清理逻辑调用,**必须幂等**;open 重复是调用顺序错了,**必须暴露出来**。

下一章进协议:SOAP 与 gSOAP,ONVIF 的地基。

相关笔记