嵌入式 视频编码(H264)

时间:2022-02-03 01:38:16

这几天在编写视频录制模块,所以,闲暇之余,又粗粗的整理了一下,主要是API,以备不时之用   嵌入式 视频编码(H264)

摄像头获取的模拟信号通过经芯片处理(我们使用的是CX25825),将模拟信号转成数字信号,产生标准的ITU 656 YUV格式的数字信号以帧为单位送到编码卡上的DSP和内存中。分别供视频实时预览、移动侦测处理以及编码等使用。其中编码的作用是将编码卡内存中的YUV数据送到H264编码器中,进过H.264编码产生压缩好的码流,送到主机内存中,供录像或网络传输使用。编码模块完成各个协议编码,协调 MD、VPP
相关模块的管理、同步和控制,配合软件调度和硬件共同完成视频编码相关功能。

一、重要概念

主次码流

主次码流是指硬件逻辑单元启动一次同时产生的 2 路码流,即 1 路主码流和
1 路次码流
。主码流和次码流可以为不同的编码协议,但其宽高比例都必须满足 1:1、1:2 或 1:4,次码流不能单独存在(必须和 1 路主码流在同一个通道组中) 。

双码流

双码流是指硬件逻辑单元启动 2 次分时产生的 2 个码流,即 2 路主码流。双码流可以为不同的编码协议,双码流之间的大小比例没有约束关系。

通道组

通道组是指芯片能够同时处理的编码通道的集合,相当于一个容器。一个通道组最多可同时包含 1 路主码流(H.264/MJPEG) 、1路次码流(H.264/MJPEG) ,或者仅包含 1 路 JPEG抓拍(即 JPEG抓拍时,不允许包含任何其他通道) ,或者 1 路MPEG4 编码通道。

H264

    H.264 的功能分为两层:视频编码层(VCL,
VideoCoding Layer)和网络提取层(NAL, NetworkAbstraction Layer
。VCL数据即编码处理的输出,它表示被压缩编码后的视频数序列。在VCL数据传输或存储之前,这些编码的VCL数据,先被映射或封装进 NAL单元中。每个NAL单元包括一个原始字节序列负荷(RBSP,
Raw Byte SequencePayload)、一组对应于视频编码的 NAL 头信息。RBSP 的基本结构是:在原始编码数据的后面填加了结尾比特。一个bit“1”若干比特“0”,以便字节对齐。H.264的编码视频序列包括一系列的NAL单元,每个 NAL单元包含一个RBSP编码片(包括数据分割片 IDR片)和序列RBSP结束符被定义为VCL
NAL单元,其余为 NAL 单元。典型的 RBSP 单元序列如图 2 所示。每个单元都按独立的 NAL 单元传送。单元的信息头(一个字节)定义了RBSP 单元的类型,NAL单元的其余部分为 RBSP 数据。

二、相关结构

1.定义编码通道属性结构体:

typedef structhiVENC_CHN_ATTR_S

{

PAYLOAD_TYPE_E enType; //编码协议类型

HI_VOID  *pValue;           //编码属性指针

}VENC_CHN_ATTR_S;

2.定义 H.264编码属性结构体:

typedef structhiVENC_ATTR_H264_S

{

HI_U32 u32Priority;           //通道优先级。 目前未使用,取值不限。

HI_U32 u32PicWidth;         //编码图像宽度。 取值范围:[160, 2048],以像素为单位。
静态属性。

HI_U32 u32PicHeight;        //编码图像高度。 取值范围:[112, 1536],以像素为单位。静态属性。

HI_U32 u32ViFramerate;   //VI 输入的帧率(原始帧率)。 取值范围: P制:(0, 25],以帧为单位。  N制:(0, 30],以帧为单位。静态属性。

HI_BOOL bMainStream;      //主次码流标识。 取值范围:{HI_TRUE, HI_FALSE}。 HI_TRUE:主码流。 HI_FALSE:次码流。 静态属性。

HI_BOOL bVIField;             //输入图像的帧场标志。 取值范围:{HI_TRUE, HI_FALSE}。 HI_TRUE:场。 
HI_FALSE:帧。 静态属性。目前未使用。

HI_BOOL bField;                //帧场编码模式。 取值范围:{HI_TRUE, HI_FALSE}。
HI_TRUE:场编码。 HI_FALSE:帧编码。 静态属性。

    //推荐值:一幅YUV420编码图像大小。以编码 D1 图像为例,推荐值为 704×576×1.5 byte。 最小值:一幅 YUV420编码图像大小的的
1/2。 最大值:无限制,但是会消耗更多的内存。

HI_U32 u32BufSize;               // 码流 buffer大小。 取值范围:[Min,
Max],以 byte 为单位。静态属性。

HI_BOOL bByFrame;               //帧/包模式获取码流。 取值范围:{HI_TRUE, HI_FALSE}。
HI_TRUE:按帧获取。  HI_FALSE:按包获取。静态属性。

HI_U32 u32TargetFramerate;// 目标帧率。取值范围: P制:[1/16, 25],N制:[1/16, 30],以帧/秒为单位。整数:高 16bit 为0。 分数:高 16bit 为分母,低
16bit 为分子。 动态属性。   

HI_U32 u32Gop;                   //I帧间隔
取值范围:[0, 1000],以帧为单位。 动态属性

HI_U32 u32MaxDelay;          // 最大延迟。取值范围:最大延迟,以帧为单位。目前未使用。  动态属性。

RC_MODE_E enRcMode;       //码率控制模式。 取值范围:[0, 3]。 0:VBR 模式。 1:CBR 模式。 2:ABR 模式。 3:FIXQP。 动态属性。

HI_U32 u32Bitrate;              //CBR/ABR 模式,表示平均码率。 VBR
模式,表示最大码率。 FIXQP 模式,该值无效。 取值范围:[1, 20000],单位 Kbps动态属性。

HI_U32 u32PicLevel;           //图像等级,仅 VBR/CBR模式下有效。 VBR 模式下,表示图像的质量等级。 取值范围:[0, 5],值越小,图像质量越好。

HI_S32 s32QpI;                 //I帧 QP。FIXQP 模式下有效。
取值范围:[10, 50]。

HI_S32 s32QpP;                //P帧 QP。FIXQP 模式下有效。
取值范围:[10, 50]。

HI_S32 s32Minutes;           //码率统计时段。ABR 模式下有效。
ABR,即码率短时间波动,长时间平稳。长时间码率的统计,以此时间为准。

}VENC_ATTR_H264_S;

3.定义编码的数字水印的结构体:

#define   DWM_KEY_LEN 8       //密钥字符的最大个数

#define   DWM_CHAR_LEN 16   //水印字符个数

typedef structhiVENC_WM_ATTR_S

{

HI_U8 au8Key[DWM_KEY_LEN];  //数字水印的密钥字符串。最多 8 个字符,不满 8 个字符填充 0。

HI_U8 au8User[DWM_CHAR_LEN];  //数字水印用户字符。个数最多不超过 DWM_CHAR_LEN。

}VENC_WM_ATTR_S;

4.定义编码通道的状态结构体:

typedef structhiVENC_CHN_STAT_S

{

HI_BOOL bRegistered;               //注册到通道组标志:取值范围:{HI_TRUE, HI_FALSE}。

HI_U32 u32LeftPics;                  //待编码的图像数。

HI_U32 u32LeftStreamBytes;      //码流 buffer剩余的 byte数。

HI_U32 u32CurPacks;               //当前帧的码流包个数。

}VENC_CHN_STAT_S;

5.定义帧码流类型结构体:  

typedef structhiVENC_STREAM_S

{

VENC_PACK_S *pstPack;  //帧码流包结构。

HI_U32 u32PackCount;    //一帧码流的所有包的个数。

HI_U32 u32Seq;             //码流序列号。 按帧获取帧序号;按包获取包序号。

}VENC_STREAM_S;

6.定义帧码流包结构体:

typedef structhiVENC_PACK_S

{

HI_U32   u32PhyAddr[2];  //码流包首地址。

HI_U8   *pu8Addr[2];      //码流包物理地址。

HI_U32   u32Len[2];        //码流包长度。

VENC_DATA_TYPE_U DataType; //码流类型。

HI_U64   u64PTS; //时间戳。单位:us。

HI_BOOL   bFieldEnd;    //结束标识。 取值范围: HI_TRUE:该码流包是该场的最后一个包。 HI_FALSE:该码流包不是该场的最后一个包。

HI_BOOL   bFrameEnd; //结束标识。 取值范围: HI_TRUE:该码流包是该帧的最后一个包。 HI_FALSE:该码流包不是该场的最后一个包。

}VENC_PACK_S;

7.定义码流结果类型

typedef unionhiVENC_DATA_TYPE_U

{

H264E_NALU_TYPE_E enH264EType;  //H.264 码流包类型

JPEGE_PACK_TYPE_E enJPEGEType;

MPEG4E_PACK_TYPE_E enMPEG4EType;

}VENC_DATA_TYPE_U;

8.定义 JPEG码流的 PACK类型

typedef enumhiJPEGE_PACK_TYPE_E

{

JPEGE_PACK_ECS = 5, //ECS类型

JPEGE_PACK_APP = 6,

JPEGE_PACK_VDO = 7,

JPEGE_PACK_PIC = 8,

JPEGE_PACK_BUTT

JPEGE_PACK_TYPE_E;

 

9.定义 MPEG4码流的PACK类型

typedef enumhiMPEG4E_PACK_TYPE_E

{

MPEG4E_PACK_VO  = 1, //VO类型

MPEG4E_PACK_VOS = 2,

MPEG4E_PACK_VOL = 3,

MPEG4E_PACK_VOP = 4,

MPEG4E_PACK_SLICE = 5

MPEG4E_PACK_TYPE_E;

10.定义 H.264码流 NALU类型

typedef enumhiH264E_NALU_TYPE_E

{

H264E_NALU_PSLICE = 1, //PSLICE 类型

H264E_NALU_ISLICE = 5,

H264E_NALU_SEI = 6,

H264E_NALU_SPS = 7,

H264E_NALU_PPS = 8,

H264E_NALU_BUTT

H264E_NALU_TYPE_E;

11.定义 H.264编码的 NALU大小设置结构体

typedef structhiVENC_ATTR_H264_NALU_S

{

HI_BOOL bNaluSplitEnable;   //是否打开 NALU划分,HI_TURE:打开。

HI_U32 u32NaluSize;  //NALU划分使能的情况下指定 NALU的大小,以字节为单位,在关闭使能的情况下,此参数无效。 必须满足 128 <=u32NaluSize <= 图象大小(包括色度)。

VENC_ATTR_H264_NALU_S;

12.定义 H.264编码的码率控制模式

typedef enum hiRC_MODE_E

{

RC_MODE_VBR = 0,    //可变码率模式。该模式下,码率波动大,图像质量稳定

RC_MODE_CBR,          //恒定码率模式。该模式下,码率始终保持平稳。

RC_MODE_ABR,          //平均码率模式。该模式下,码率长时间平稳,短时间内波动。

RC_MODE_FIXQP,       //固定 QP模式。该模式下,使用固定的 QP分别编码 I帧和 P帧。

RC_MODE_BUTT,

RC_MODE_E;

三、API 参考

视频编码功能实际包含 VENC(视频编码)和 GROUP(通道组管理)两个重要的部分,主要提供视频编码通道组的创建和销毁、通道组 GROUP 与视频输入通道的绑定和解绑定、视频编码通道的创建和销毁、注册和反注册到通道组、开启和停止接收图像、设置和获取编码通道属性、获取和释放码流、设置和获取数字水印属性、启用和禁用数字水印、视频编码通道属性的设置和查询等功能。

1.创建/销毁编码通道组

HI_S32 HI_MPI_VENC_CreateGroup(VENC_GRP VeGroup); 

HI_S32 HI_MPI_VENC_DestroyGroup(VENC_GRP VeGroup); 

    A.本文档中含有通道组号的接口的通道组号的取值范围为[0, VENC_MAX_GRP_NUM),否则返回 HI_ERR_VENC_INVALID_CHNID。

    B.编码通道组是指芯片能够同时处理的编码通道的集合,一个通道组最多可同时包含 1 路主码流(H.264/MJPEG)和一路次码流(H.264/MJPEG) ,或者包含 1 路JPEG抓拍,或者仅包含 1 路 MPEG4通道。

   C.如果指定的通道组已经存在,则返回错误码 HI_ERR_VENC_EXIST。 

   D.销毁通道组时,必须保证通道组为空,即没有任何通道在通道组中注册,否则会返回错误码 HI_ERR_VENC_NOT_PERM。

   E.销毁并不存在的通道组,返回错误码 HI_ERR_VENC_UNEXIST。

2.绑定/解绑定 VI 到通道组

HI_S32 HI_MPI_VENC_BindInput(VENC_GRP VeGroup, VI_DEV ViDevId, VI_CHN ViChn);

HI_S32 HI_MPI_VENC_UnbindInput(VENC_GRP VeGroup);

   A.绑定并不存在的通道组,则返回错误码 HI_ERR_VENC_UNEXIST。

   B.如果 VI 设备或者 VI 通道超出范围,则返回 HI_ERR_VENC_INVALID_DEVID或者 HI_ERR_VENC_INVALID_CHNID。 

   C.此接口并不判断 VI 的状态,ViDevId 和 ViChn 可以对应实际的 VI 设备,也可以对应虚拟的 VI 设备,对应虚拟的 VI 设备主要用于用户手动发送图像编码, HI_MPI_VENC_SendFrame 会对此作   出详细的说明。

   D.如果通道组已经绑定了某个 VI 通道,则返回错误码HI_ERR_VENC_NOT_PERM。 

   E.一个通道组只能绑定一个 VI 通道,但一个 VI 通道可以被多个通道组绑定。

   F.在编码过程中,可以动态解绑定和绑定 VI,达到编码不同图像源的目的。

   G.解绑定并不存在的通道组,返回错误码 HI_ERR_VENC_UNEXIST。

   H.解绑定之后,VI 通道如果满足条件,可以再绑定到其他任意通道组。

   I.可以重复解绑定,返回 HI_SUCCESS。

3.创建/销毁编码通道

HI_S32 HI_MPI_VENC_CreateChn(VENC_CHN VeChn, const VENC_CHN_ATTR_S *pstAttr, const VENC_WM_ATTR_S *pstWm);

HI_S32 HI_MPI_VENC_DestroyChn(VENC_CHN VeChn); 

   A.Hi3520 支持对主次码流进行编码。在创建编码通道的时候必须指定该通道是主码流还是次码流。 

   B.在创建编码通道的时候,编码通道属性除需要输入各个协议的特有的编码属性之外,一般还需要输入主次码流(MPEG4 编码协议无此属性) 、编码协议、编码的帧场模式、输入图像的帧场模式、获取码流的方式(按帧还是按包获取码流)、编码图像大小属性,这些属性受表 6-1约束,并且这些属性都为静态属性,不允许动态设置。 

   C.若输入图像大于大码流的宽高,但相差 16 像素以内(含 16像素) ,则大码流编码图像通过输入图像做切边得到。 

   D.若输入图像小于大码流的宽高,会丢弃这些图像,而不会对其放大进行编码。该出错信息会在 log 中显示。 

   E.推荐的大码流编码宽高为:2048×1536(3M 像素) 、1280×1024(1.3M 像素) 、1920×1080(1080P) 、1280×720(720P)、704×576、704×480、352×288、352×240。 

   F.对于 H.264主码流,编码图像大小不为 D1 时,其编码方式推荐使用帧编码。

   G.当参数 pstWm为空时,表示该编码通道不需要使用水印,否则认为需要使用数字水印。如果创建成功,数字水印默认使能。目前只有 H.264 编码的大码流可以设置数字水印,其他的情况设置数字水印时均返回错误码HI_ERR_VENC_NOT_SUPPORT。

   H.销毁并不存在的通道,返回错误码 HI_ERR_VENC_UNEXIST。

   I.销毁前必须保证通道已经从通道组反注册,否则返回错误码HI_ERR_VENC_NOT_PERM。

4.注册/反注册编码通道到通道组

HI_S32 HI_MPI_VENC_RegisterChn(VENC_GRP VeGroup,VENC_CHN VeChn); 

HI_S32 HI_MPI_VENC_UnRegisterChn(VENC_CHN VeChn); 

   A.注册并不存在的通道,返回错误码 HI_ERR_VENC_UNEXIST。

   B.注册通道到不存在的通道组,返回错误码 HI_ERR_VENC_UNEXIST。 

   C.同一个编码通道只能注册到一个通道组,如果该通道已经注册到某个通道组,则返回 HI_ERR_VENC_NOT_PERM。 

   D.主次码流注册的时候需要判定以下约束关系: 

      −  主码流要先于次码流注册,否则返回 HI_ERR_VENC_NOT_PERM。 

      −  如果编码通道已经注册,则在反注册前不能再进行注册,否则返回HI_ERR_VENC_NOT_PERM。

   E.MD通道注册必须在编码通道注册成功之后进行,否则返回HI_ERR_VENC_NOT_PERM。 

   F.同组的主次码流若为 1:1的关系,则编码方式必须同为帧编码,否则返回HI_ERR_VENC_NOT_PERM。 

   G.同组的主次码流宽高必须符合如下约束:D/s – d = R(主次码流宽或高分别为 D和 d,s 为1、2 或者4,R 为0 到 16)。 

   H.如果通道未注册,则返回错误码 HI_ERR_VENC_NOT_PERM。

   I.如果编码通道未停止接收图像编码(HI_MPI_VENC_StopRecvPic 可停止接收) ,则返回错误码 HI_ERR_VENC_NOT_PERM。 

   J.反注册后会将编码通道复位,如果用户还在使用未及时释放的码流 buffer,将不能保证此 buffer 数据的正确性。用户可以使用 HI_MPI_VENC_Query接口来查询状态,确认自己所有的操作都完成之后再反注册通道。

5.开启/停止编码通道接收输入图像

HI_S32 HI_MPI_VENC_StartRecvPic(VENC_CHN VeChn); 

HI_S32 HI_MPI_VENC_StopRecvPic(VENC_CHN VeChn); 

   A.如果通道未创建,则返回 HI_ERR_VENC_UNEXIST。 

   B.如果通道没有注册到通道组,则返回 HI_ERR_VENC_NOT_PERM。

   C.此接口不判断当前是否已经开启接收,直接将状态设置为开启接收。 

   D.此接口用于开启编码通道接收图像来编码,请注意它和绑定通道组的区别。

   E.开始接收输入是针对通道的,只有开启接收之后编码器才开始接收图像编码。

   F.此接口并不判断当前是否停止接收,直接将状态设置为停止接收。 

   G.此接口用于编码通道停止接收图像来编码,在编码通道反注册前必须停止接收图像。 

   H.调用此接口仅停止接收原始数据编码,码流 buffer并不会被清除。

6.获取编码通道对应的设备文件句柄

HI_S32 HI_MPI_VENC_GetFd(VENC_CHN VeChn);

         A. 用户可以获取文件句柄实现多通道 select 获取视频帧数据

7.查询编码通道状态

HI_S32 HI_MPI_VENC_Query(VENC_CHN VeChn, VENC_CHN_STAT_S *pstStat);

   A.如果通道未创建,则返回 HI_ERR_VENC_UNEXIST。 

   B.此接口用于查询此函数调用时刻的编码器状态,pstStat 包含三个主要的信息: 

      −  在编码通道状态结构体中,u32LeftPics表示待编码的帧个数。 

    在反注册通道前,可以通过查询是否还有图像待编码来决定反注册时机,防反注册时将可能需要编码的帧清理出去。 

      −  在编码通道状态结构体中,u32LeftStreamBytes表示码流 buffer 中剩余的 by数目。 

    在反注册通道前,可以通过查询是否还有码流没有被处理来决定反注册时机防止反注册时将可能需要的码流清理出去。 

      −  在编码通道状态结构体中,u32CurPacks 表示当前帧的码流包个数。 

    在按包获取时当前帧可能不是一个完整帧(被取走一部分) ,按帧获取时表示当前一个完整帧的包个数(如果没有一帧数据则为 0)。用户在需要按帧获取码流时,需要查询一个完整帧的包个数,在这种情况下,通常可以在 select 成功之后执行 query操作,此时 u32CurPacks是当前完整帧中包的个数。

8.获取/释放编码的码流

HI_S32 HI_MPI_VENC_GetStream(VENC_CHN VeChn, VENC_STREAM_S *pstStream, HI_U32 u32BlockFlag);

HI_S32 HI_MPI_VENC_ReleaseStream(VENC_CHN VeChn, VENC_STREAM_S *pstStream); 

   A.如果通道未创建,返回错误码 HI_ERR_VENC_UNEXIST。

   B.如果 pstStream为空,返回错误码 HI_ERR_VENC_NULL_PTR。

   C.支持阻塞或非阻塞两种方式获取。同时可支持 select/poll 系统调用,u32BlockFlag:HI_IO_BLOCK(阻塞)  HI_IO_NOBLOCK(非阻塞)。

      −  非阻塞获取时,如果缓冲无数据,则返回 HI_ERR_VENC_BUF_EMPTY。 

      −  阻塞时,如果缓冲无数据,则会等待有数据时才返回 HI_SUCCESS。

   D.支持按包或按帧方式获取码流。如果按包获取,则: 

      −  对于 H.264编码协议,每次获取的是一个 NAL 单元。 

      −  对于 JPEG编码协议(包括 JPEG抓拍和 MJPEG) ,每次获取的是一个 ECS或图像参数码流包。 

      −  对于 MPEG4编码协议,每次获取的是一帧一个包,因此按帧获取或者按包获取,结果相同。

   E.码流结构体 VENC_STREAM_S包含3 个部分: 

      −  码流包信息指针 

          pstPack 指向一组 VENC_PACK_S 的内存空间,该空间由调用者分配。如果是按包获取,则此空间不小于 sizeof(VENC_PACK_S)的大小;如果按帧获取,则此空间不小于 N × sizeof(VENC_PACK_S)的大小,其中 N代表当前帧之中的包的个数,可以在 select之后通过查询接口获得。 

      −  码流包个数 u32PackCount  

          在输入时,此值指定 pstPack 中 VENC_PACK_S 的个数。按包获取时,u32PackCount 必须不小于1;按帧获取时,包个数。在函数调用成功后,u32PackCount 返回实际填充 pstPack 的包的个数。 

      −  序列号 u32Seq 

          按帧获取时是帧序列号;按包获取时为包序列号。 

   F.如果用户长时间不获取码流,那么码流缓冲区就会满。一个编码通道如果发生码流缓冲区满,就会停止该编码通道编码,等有足够的码流缓冲可以用来编码时,才开始继续编码,这种情况对于主次码流编码通道来说,相互不受影响。

   G.用户应该及时获取码流,防止由于码流 buffer阻塞导致编码器停止工作。 

   H.如果通道未创建,则返回错误码 HI_ERR_VENC_UNEXIST。 

   I.如果 pstStream为空,则返回错误码 HI_ERR_VENC_NULL_PTR。 

   J.此接口应当和 HI_MPI_VENC_GetStream配对起来使用,用户获取码流后必须及时释放已经获取的码流缓存,否则可能会导致码流 buffer满,影响编码器编码,并且用户必须按先获取先释放的顺序释放已经获取的码流缓存。 

   K.在编码通道反注册以后,所有未释放的码流包均无效,不能再使用或者释放这部分无效的码流缓存。

   L.释放无效的码流会返回 HI_ERR_VENC_ILLEGAL_PARAM。

附:编码过程为1-2-3-4-5-6-7-8

嵌入式 视频编码(H264)