之前公司用的编解码用的是硬件模块 有寒武纪 海康 还有华为的等等,程序调用的是统一的接口 ,因为不用的硬件厂商的API不一样,所以需要做一层抽象封装,这样程序调用的时候可以不用关注到底是哪个硬件。
然后有一个需求 不用硬件板卡 直接用FFMPEG 借助CPU来进行编解码。差不多一周时间实现了记录下 支持WINDOWS 也支持 LINUX:
哦对了 其实是有两个模块 ,
一个是FFMPEG 拉RTSP的流
另一个是FFMPEG 编解码 两个独立 可以单独调用 也可以两个一起用。
先看下文件结构
ICodec.h是 统一调用接口的抽象接口定义 如下 后面程序调用的都是这个接口
然后这个create_codec 和destroy_codec 是创建和销毁ICodec的另一层封装。
下面来看一下FFMPEG 类的定义
继承了基类,然后需要实现基类的虚函数,这也是程序调用的接口
下面是一些内部的私有函数 看名字 就知道意思了
m_decoder 和 m_encoder 分辨定义解码和编码需要的一些参数
解码的结构体定义如下:
编码的结构体定义如下:
不同的操作系统 系统调用的API 不一样 做了区分
m_configInfo 是程序调用的时候传进来的一些编码配置信息(分辨率 码率 等等) 以及编码之后的回调函数 如下图:
先看一下程序如何调用的,如下图:
pFFCodec就是定义的一个ICodec * 指针变量 需要先通过create_codec new 一个对象
之后需要调用init方法 传入的info就是一个configinfo的结构
start之后 没有异常的话就会开始编解码了
通过feed_frame_data 给编解码模块喂数据 就是ffmpeg 拉RTSP 拉出来的H264或者H265裸流
销毁的部分如下:
下面看下代码 create和 destroy很简单 没啥 需要解释的 如下:new 和delete对象
接下来看下实际的实现部分 定义了一个静态变量m_instance 用来记录 编码器的实际创建数量
cpp文件中这个静态变量需要 声明一下 不然 link的时候会报错 我的环境是vs2022
接下来 初始化部分 需要添加一个解码器 一个编码器
实际上 我们是如果好几路RTSP流是相同的话,只是编出来不同的分辨率 Decoder解码器的部分是可以复用的,也就是一路Decoder对应多路Encoder
但是由于之前我们程序已经成型了 没办法 修改,所以这里 就不能复用了,这里直接一路Decoder对应一路Encoder
Init成功之后就是Start了 Start很简单 分别开启解码线程,编码线程 不同的操作系统做了区分
decode_thread函数主要做的事就是 从解码队列里不断读取feed的raw packet 然后 解码成功之后通过decode_cb回调送入编码队列,实际过程中 feed data的时候的packet是没有pts信息的 ,这里解码的时候需要自己给packet打pts 否则ffmepg解码会有问题 ,这个pts要根据实际的fps 来打 如果25帧就是90000/25*packetcounter++ 我没这么做 ,因为 传进来的时候不知道fps是多少
而是取了系统的实时time。这里就不贴出来 给自己留点干货
看下decode_cb就是 把数据送入队列的操作
解码出来的是AVFrame的数据 这里需要注意下
另外之前说的 如果一个解码器对应多个编码器的话 这里 把数据送队列的时候 就用一个for循环 分别送入不用的编码器队列就可以了
接下来是编码线程
编码线程做的事主要就是从刚刚编码队列中取出 刚刚解码送进来的数据 然后 判断下
编码分辨率是否和源分辨率一样 不一样的话需要调用sws_scale进行yuv图像的缩放
之后调用ffmpeg的api 编码 编码之后将编码出来的rawpacket数据送入回调函数中
至此 大概流程就走完了,
总的来说 并不是很复杂,这里不涉及到对yuv数据的具体操作,api都帮我们做好了
目前测试没遇到啥问题,后期测试yuv有很多种格式,可能部分格式需要具体处理 ,暂时不需要
实际开发中遇到个问题,如下 下面三行被我注释掉了,不然 会异常 还没时间查为什么,
注释掉,一般也够用了 。