之前公司用的编解码用的是硬件模块 有寒武纪 海康 还有华为的等等,程序调用的是统一的接口 ,因为不用的硬件厂商的API不一样,所以需要做一层抽象封装,这样程序调用的时候可以不用关注到底是哪个硬件。

然后有一个需求 不用硬件板卡 直接用FFMPEG 借助CPU来进行编解码。差不多一周时间实现了记录下 支持WINDOWS  也支持 LINUX:

哦对了 其实是有两个模块 ,

一个是FFMPEG 拉RTSP的流

另一个是FFMPEG 编解码 两个独立 可以单独调用 也可以两个一起用。

先看下文件结构

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_编解码

ICodec.h是 统一调用接口的抽象接口定义 如下 后面程序调用的都是这个接口

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_02

然后这个create_codec 和destroy_codec 是创建和销毁ICodec的另一层封装。

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_数据_03

 下面来看一下FFMPEG 类的定义

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_04

 

继承了基类,然后需要实现基类的虚函数,这也是程序调用的接口

下面是一些内部的私有函数 看名字 就知道意思了

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_05

 m_decoder 和 m_encoder 分辨定义解码和编码需要的一些参数

解码的结构体定义如下:

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_数据_06

 编码的结构体定义如下:

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_音频编码解码_07

 不同的操作系统  系统调用的API 不一样 做了区分

m_configInfo 是程序调用的时候传进来的一些编码配置信息(分辨率 码率 等等) 以及编码之后的回调函数 如下图:

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_编解码_08

 

先看一下程序如何调用的,如下图:

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_音视频_09

 pFFCodec就是定义的一个ICodec * 指针变量 需要先通过create_codec new 一个对象

之后需要调用init方法 传入的info就是一个configinfo的结构

start之后  没有异常的话就会开始编解码了

通过feed_frame_data 给编解码模块喂数据 就是ffmpeg 拉RTSP 拉出来的H264或者H265裸流

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_音视频_10

 

销毁的部分如下:

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_音频编码解码_11

 下面看下代码  create和 destroy很简单 没啥 需要解释的 如下:new 和delete对象

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_编解码_12

接下来看下实际的实现部分 定义了一个静态变量m_instance 用来记录 编码器的实际创建数量

cpp文件中这个静态变量需要 声明一下 不然 link的时候会报错 我的环境是vs2022

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_数据_13

接下来  初始化部分  需要添加一个解码器 一个编码器 

实际上 我们是如果好几路RTSP流是相同的话,只是编出来不同的分辨率 Decoder解码器的部分是可以复用的,也就是一路Decoder对应多路Encoder

但是由于之前我们程序已经成型了 没办法 修改,所以这里 就不能复用了,这里直接一路Decoder对应一路Encoder 

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_音视频_14

 Init成功之后就是Start了 Start很简单 分别开启解码线程,编码线程 不同的操作系统做了区分

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_编解码_15

 decode_thread函数主要做的事就是 从解码队列里不断读取feed的raw packet 然后 解码成功之后通过decode_cb回调送入编码队列,实际过程中 feed data的时候的packet是没有pts信息的 ,这里解码的时候需要自己给packet打pts 否则ffmepg解码会有问题 ,这个pts要根据实际的fps 来打 如果25帧就是90000/25*packetcounter++   我没这么做 ,因为 传进来的时候不知道fps是多少

而是取了系统的实时time。这里就不贴出来  给自己留点干货

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_16

 看下decode_cb就是 把数据送入队列的操作

解码出来的是AVFrame的数据 这里需要注意下 

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_编解码_17

 另外之前说的 如果一个解码器对应多个编码器的话 这里 把数据送队列的时候 就用一个for循环 分别送入不用的编码器队列就可以了

接下来是编码线程 

编码线程做的事主要就是从刚刚编码队列中取出 刚刚解码送进来的数据 然后 判断下

编码分辨率是否和源分辨率一样 不一样的话需要调用sws_scale进行yuv图像的缩放

之后调用ffmpeg的api  编码   编码之后将编码出来的rawpacket数据送入回调函数中

至此 大概流程就走完了,

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_数据_18

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_19

 总的来说 并不是很复杂,这里不涉及到对yuv数据的具体操作,api都帮我们做好了

目前测试没遇到啥问题,后期测试yuv有很多种格式,可能部分格式需要具体处理 ,暂时不需要

实际开发中遇到个问题,如下 下面三行被我注释掉了,不然 会异常  还没时间查为什么,

注释掉,一般也够用了 。

ffmpeg 调用 Android MediaCodec 进行硬解码 ffmpeg调用硬件解码_ffmpeg_20