Hi3518EV200进行H264编码时对VENC的基本设置

〇、文档说明

本文性质为个人学习笔记。
由于项目需要,基于Hi3518EV200平台进行H264编码,在此过程中涉及到对VENC的控制和理解,记录以备忘。
另外,如果您在学习hi3518编码h264的过程中有疑惑,希望我的博文可以帮到您:
1.Hi3518EV200实现H264视频采集的源码及流程详解(不依赖SAMPLE库)
2.从零开始进行Hi3518_SDK安装、环境搭建和Linux内核编译,并生成可供烧写的uImage
期待如今的我和未来的你,一起进步!

一、VENC(Video Encoder,视频编码模块)

VENC的相关内容,无论是网上还是手册中都已经介绍的非常详细了,但是对于一个学习的人而言,应当考虑的是如何将这些东西转化为自己的理解,而理解的最好过程就是用自己的语言讲出来。
VENC的学习,先从了解它的作用开始。下文描述、举例、现象等均在Hi3518EV200平台进行,MPP[Hi3518EV200_MPP_V1.0.4.0 B050 Release]
视频流从sensor到输出是这样的:
VI(sensor-ISP-ViDev0-Chn[0]/Ext_Chn[1-16])–VPSS(Group-Chn[(在线)0/(离线)0-31]/Ext_Chn[4-11])–VENC(Chn[0-15],Payload[H264/H265/JPEG],RC[CBR/VBR/AVBR/FIXQP])–输出
上面这个流程涵盖了几乎所有基本配置内容,其中通道数的限制都是基于手册查找。VENC在整个编码过程中可以干的事情有:关于通道有效荷载的编码协议选择、编码码率控制。我们所有针对VENC的设置都是围绕这两方面进行。

二、VENC设置步骤

VENC部分需要完成的设置一共有三项:
1.1选择要编码的编码方式,照着该编码方式配置相应结构体;
1.2按照所选择的编码方式的不同,配置与其对应的码流控制参数结构体;
2.按照上述的配置创建VENC通道;
3.启动通道的图像接收并绑定前级模块。

    VENC_CHN VencChn=0;                 //选择VENC通道号——0
    PAYLOAD_TYPE_E enPayLoad=PT_H264;   //选择编码方式——H264,更换编码要同时更换后面的设置参数
    VENC_CHN_ATTR_S stVencChnAttr;      //VENC通道属性参数结构体
    VENC_ATTR_H264_S stH264Attr;        //H264属性参数结构体
    VENC_ATTR_H264_CBR_S stH264Cbr;     //H264-CBR码率控制参数结构体
    SIZE_S stPicSize;                   //图像尺寸信息变量
    stPicSize.u32Width=1920;            //设置图像宽度
    stPicSize.u32Height=1080;           //设置图像高度
    HI_U32 u32Profile=0;                //0: baseline; 1:MP; 2:HP;3:Svc_t

    //--1--创建VENC通道
    stVencChnAttr.stVeAttr.enType=enPayLoad;
    if(enPayLoad==PT_H264)//在编码为H264的时候执行,换编码方式在此处
    {
        stH264Attr.u32MaxPicWidth=stPicSize.u32Width;  //设置最大图像宽度
        stH264Attr.u32MaxPicHeight=stPicSize.u32Height;//设置最大图像高度
        stH264Attr.u32PicWidth=stPicSize.u32Width;     //设置图像宽度
        stH264Attr.u32PicHeight=stPicSize.u32Height;   //设置图像高度
        stH264Attr.u32BufSize=stPicSize.u32Width * stPicSize.u32Height;//流缓冲器大小
        stH264Attr.u32Profile=u32Profile;              /*0: baseline; 1:MP; 2:HP;  3:svc_t */
        stH264Attr.bByFrame=HI_TRUE;                   //获取流模式是切片模式还是帧模式?
        stH264Attr.u32BFrameNum=0;                     //0: 不支持B帧; >=1:B帧的数量
        stH264Attr.u32RefNum=1;                        //0: 默认; >=0:参考帧数量
        memcpy(&stVencChnAttr.stVeAttr.stAttrH264e, &stH264Attr, sizeof(VENC_ATTR_H264_S));
        //选择编码码率控制方式——CBR
        {
            stVencChnAttr.stRcAttr.enRcMode=VENC_RC_MODE_H264CBR;
            stH264Cbr.u32Gop         =30;//图像组:I帧间间隔
            stH264Cbr.u32StatTime    =1; //码率统计时间
            stH264Cbr.u32SrcFrmRate  =30;//输入(VI)帧率
            stH264Cbr.fr32DstFrmRate =30;//输出帧率
            //下面的定义需要根据传感器分辨率修改
            stH264Cbr.u32BitRate=1024*2;
            stH264Cbr.u32FluctuateLevel=0;//平均比特率
            memcpy(&stVencChnAttr.stRcAttr.stAttrH264Cbr, &stH264Cbr, sizeof(VENC_ATTR_H264_CBR_S));
        }
    }
    s32Ret=HI_MPI_VENC_CreateChn(VencChn, &stVencChnAttr);//创建VENC通道
    
    //--2--启动接收VENC图像
    s32Ret=HI_MPI_VENC_StartRecvPic(VencChn);
    
    //--3--绑定VPSS和VENC
    MPP_CHN_S stSrcChn;  //源通道设备参数结构体
    MPP_CHN_S stDestChn; //目的通道设备参数结构体
    stSrcChn.enModId=HI_ID_VPSS;
    stSrcChn.s32DevId=0; //选择VPSS组号——0
    stSrcChn.s32ChnId=0; //选择VPSS通道号——0
    stDestChn.enModId=HI_ID_VENC;
    stDestChn.s32DevId=0;
    stDestChn.s32ChnId=0;//选择VENC通道号——0
    s32Ret=HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);//绑定源通道和目的通道

三、相关设置项说明

1.VENC的图像缩放

参考《HiMPP IPC V2.0 媒体处理软件开发参考》(下文称“手册”)521页,通道接收到图像之后,比较图像尺寸和编码通道尺寸:如果输入图像比编码通道尺寸大,VENC将按照编码通道尺寸大小,调用VGS对源图像进行缩小,然后对缩小之后的图像进行编码。如果输入图像比编码通道尺寸小,VENC丢弃源图像。VENC不支持放大输入图像编码。如果输入图像与编码通道尺寸相当,VENC直接接受源图像,进行编码。
也就是说,VENC模块只支持原比例编码或者缩小,但不支持放大,如果设置的编码宽高大于输入图像的宽高,则会产生错误
VENC输出的图像宽高在stH264Attr.u32PicWidth和stH264Attr.u32PicHeight两个变量进行设置。
对于编码器宽高取值的限制在手册535页:
编码器属性最大宽高,通道宽高必须满足如下约束:
− MaxPicWidth∈[MIN_WIDTH, MAX_WIDTH]
− MaxPicHeight∈[MIN_HEIGHT, MAX_HEIGHT]
− PicWidth∈[MIN_WIDTH, MAX_WIDTH]
− PicHeight∈[MIN_HEIGHT, MAX_HEIGHT]
− 最大宽高,通道宽高必须是MIN_ALIGN的整数倍。
其中MIN_WIDTH,MAX_WIDTH,MIN_HEIGHT,MAX_HEIGHT,MIN_ALIGN分别表示编码通道支持的最小宽度,最大宽度,最小高度,最大高度,最小对齐单元(像素)。
芯片相关编码通道差异
另外,在手册774也中对宽高的属性也做了说明:
编码器属性中除通道宽高(u32PicWidth和u32PicHeight)外都是静态属性,一旦创建编码通道成功,静态属性不支持被修改,除非该通道被销毁,重新创建。
这说明在通道建立成功后,可以在程序运行过程中动态地修改编码宽、高

2.VENC帧率控制

VENC作为编码的模块,自然有能力决定编码产生的帧率大小,对帧率的设置在码率参数结构体中进行:
stH264Cbr.u32SrcFrmRate表示从前级模块(VI或VPSS)传来的视频图像的帧率,一般我们在设置VPSS模块的时候不开启它的帧率控制,例如芯片SAMPLE例程库中进行的那样,此时从VPSS模块传来的图像帧率就是VI模块输出的图像帧率。
VI模块的帧率设置分为两部分,一是sensor向ISP注册时传入的帧率参数,这个帧率是sensor支持的帧率,是不一定能任意修改的;二是VI物理通道设置的帧率控制,这个在SAMPLE库中也没有进行设置。所以我们不妨也按照这种方法进行,即对帧率的修改放在VENC模块上。
此时传给VENC模块的源帧率就是sensor支持得到的最大值,假设是30fps,我们只需要设置目的帧率stH264Cbr.fr32DstFrmRate变量即可完成帧率控制。
但是实际的情况要是都这么简单就好了。
按照手册951页的说法,我给翻译翻译,就是说VENC的帧率控制分为两种情况,分别为“增帧”或者“减帧/不控制”。
在增帧模式下,要设置的是u32SrcFrmRate成员,假设前级传来的帧率是30fps,我想要60fps,那就应该设置u32SrcFrmRate=60,fr32DstFrmRate=60;
在减帧或者不控制的情况下,源帧率要设置成产生时间参考的实际帧率,此时要区分在线模式或者离线模式,关于离线模式和在线模式的说明可以参考手册32页。这里如果我们在VI/VPSS前级模块没有进行任何帧率控制,那么u32SrcFrmRate就是VI的帧率(sensor帧率)。目的帧率fr32DstFrmRate的设置分为两种,一是目标帧率为整数,此时fr32DstFrmRate设置为整数即可,例如25帧(每秒25帧);二是目标帧率为分数,例如每两秒25帧(每秒25/2帧),则fr32DstFrmRate=(2<<16)+25。

3.GOP设置

声明: 本节参考了https://blog.csdn.net/xiaoyida11/article/details/52852398
GOP设置由u32Gop成员确定,其目的是定义视频编码帧中的I帧间间隔,如果设置编码码流帧配置模式为多包模式(参考手册527页),那么可以发现,I帧会分为4个小包,其中第四包为数据包。下图为设置GOP为30时的编码流情况:
gop=30rc(gop=30)
u32Gop成员即决定了I帧和I帧之间间隔包计数。顺便提一嘴,B帧的编码有前文程序中的stH264Attr.u32BFrameNum变量确定,参考例程,已经给关了。
如果希望I帧出现在每秒编码帧的首包,则应该将u32Gop设置为目的帧率。下图为设置u32Gop=fr32DstFrmRate=10的编码输出情况。
gop=10
暂时从项目中总结的就是这么多,以上。

四、阅读注意

1.在本记录中为了简便起见,删除了所有对函数返回值s32Ret的判断,在实际程序中应该对相应执行失败的函数做适当处理。
2.本文仅为个人学习记录,如直接套用或者言语不明造成经济损失等后果作者不承担责任,如需转载,请注明原作者及出处。
————2020-1-2 @燕卫博————

很抱歉,我无法提供完整的程序代码。但我可以给出一些参考: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <fcntl.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> #include <linux/videodev2.h> #include "rk_mpi.h" #include "rk_venc.h" #define TAG "mpi_h264enc_test" #define VIDEO_WIDTH 1920 #define VIDEO_HEIGHT 1080 #define VIDEO_FPS 30 #define FRAME_NUM 1000 typedef struct { int fd; void *start; size_t length; } camera_t; static camera_t *camera_open(int id) { camera_t *camera = (camera_t *)malloc(sizeof(camera_t)); if (camera == NULL) { printf("[%s] malloc camera_t failed\n", TAG); return NULL; } char dev_name[16]; sprintf(dev_name, "/dev/video%d", id); camera->fd = open(dev_name, O_RDWR); if (camera->fd < 0) { printf("[%s] open %s failed\n", TAG, dev_name); free(camera); return NULL; } struct v4l2_capability cap; if (ioctl(camera->fd, VIDIOC_QUERYCAP, &cap) < 0) { printf("[%s] VIDIOC_QUERYCAP failed\n", TAG); close(camera->fd); free(camera); return NULL; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { printf("[%s] %s is not a video capture device\n", TAG, dev_name); close(camera->fd); free(camera); return NULL; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { printf("[%s] %s does not support streaming i/o\n", TAG, dev_name); close(camera->fd); free(camera); return NULL; } struct v4l2_format fmt; memset(&fmt, 0, sizeof(fmt)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = VIDEO_WIDTH; fmt.fmt.pix.height = VIDEO_HEIGHT; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field = V4L2_FIELD_NONE; if (ioctl(camera->fd, VIDIOC_S_FMT, &fmt) < 0) { printf("[%s] VIDIOC_S_FMT failed\n", TAG); close(camera->fd); free(camera); return NULL; } struct v4l2_requestbuffers req; memset(&req, 0, sizeof(req)); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(camera->fd, VIDIOC_REQBUFS, &req) < 0) { printf("[%s] VIDIOC_REQBUFS failed\n", TAG); close(camera->fd); free(camera); return NULL; } struct v4l2_buffer buf; for (int i = 0; i < req.count; i++) { memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(camera->fd, VIDIOC_QUERYBUF, &buf) < 0) { printf("[%s] VIDIOC_QUERYBUF failed\n", TAG); close(camera->fd); free(camera); return NULL; } camera->start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, camera->fd, buf.m.offset); if (camera->start == MAP_FAILED) { printf("[%s] mmap failed\n", TAG); close(camera->fd); free(camera); return NULL; } camera->length = buf.length; if (ioctl(camera->fd, VIDIOC_QBUF, &buf) < 0) { printf("[%s] VIDIOC_QBUF failed\n", TAG); munmap(camera->start, camera->length); close(camera->fd); free(camera); return NULL; } } enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(camera->fd, VIDIOC_STREAMON, &type) < 0) { printf("[%s] VIDIOC_STREAMON failed\n", TAG); close(camera->fd); free(camera); return NULL; } return camera; } static void camera_close(camera_t *camera) { if (camera != NULL) { enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(camera->fd, VIDIOC_STREAMOFF, &type); for (int i = 0; i < 4; i++) { munmap(camera->start, camera->length); } close(camera->fd); free(camera); } } static int camera_capture(camera_t *camera, unsigned char *buffer, int *length) { struct v4l2_buffer buf; memset(&buf, 0, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(camera->fd, VIDIOC_DQBUF, &buf) < 0) { return -1; } memcpy(buffer, camera->start, buf.bytesused); *length = buf.bytesused; if (ioctl(camera->fd, VIDIOC_QBUF, &buf) < 0) { return -1; } return 0; } static void *camera_thread(void *arg) { camera_t *camera = (camera_t *)arg; unsigned char *buffer = (unsigned char *)malloc(camera->length); if (buffer == NULL) { printf("[%s] malloc buffer failed\n", TAG); return NULL; } int length = 0; while (1) { if (camera_capture(camera, buffer, &length) == 0) { // do something with captured frame } usleep(1000); } free(buffer); return NULL; } int main(int argc, char **argv) { RK_MPI_SYS_Init(); RK_U32 u32Width = VIDEO_WIDTH; RK_U32 u32Height = VIDEO_HEIGHT; RK_U32 u32Fps = VIDEO_FPS; RK_U32 u32BitRate = u32Width * u32Height * 3 / 2 * u32Fps; RK_U32 u32KeyFrameInterval = u32Fps; RK_U32 u32Profile = 66; // H264 PROFILE_HIGH MPP_CHN_S stChnAttr; stChnAttr.mChnId = 0; stChnAttr.mModId = RK_ID_VENC; stChnAttr.mDevId = 0; stChnAttr.mWidth = u32Width; stChnAttr.mHeight = u32Height; stChnAttr.mFps = u32Fps; stChnAttr.mBitRate = u32BitRate; stChnAttr.mProfile = u32Profile; stChnAttr.mLevel = 41; // H264 LEVEL4_1 stChnAttr.mPixelFormat = RK_FMT_YUV420SP; stChnAttr.mRotation = 0; stChnAttr.mMirror = 0; stChnAttr.mFlip = 0; stChnAttr.mDrmMode = 0; stChnAttr.mDrmFd = -1; if (RK_MPI_VENC_CreateChn(0, &stChnAttr) != RK_SUCCESS) { printf("[%s] create venc chn failed\n", TAG); return -1; } if (RK_MPI_VENC_RegisterChn(0, 0, 0) != RK_SUCCESS) { printf("[%s] register venc chn failed\n", TAG); return -1; } MPP_CHN_S stSrcChn; stSrcChn.mModId = RK_ID_VI; stSrcChn.mDevId = 0; stSrcChn.mChnId = 0; MPP_CHN_S stDestChn; stDestChn.mModId = RK_ID_VENC; stDestChn.mDevId = 0; stDestChn.mChnId = 0; RK_MPI_SYS_Bind(&stSrcChn, &stDestChn); camera_t *camera = camera_open(0); if (camera != NULL) { pthread_t tid; pthread_create(&tid, NULL, camera_thread, camera); } RK_S32 s32Ret = RK_SUCCESS; MPP_FRAME_S stFrame; memset(&stFrame, 0, sizeof(stFrame)); stFrame.mModId = RK_ID_VENC; stFrame.mChannelId = 0; stFrame.mWidth = u32Width; stFrame.mHeight = u32Height; stFrame.mField = RK_FIELD_NONE; stFrame.mFrameType = RK_CODEC_FRAME_SPS_PPS_I; stFrame.mCompressMode = COMPRESS_MODE_NONE; stFrame.mBitWidth = 10; stFrame.mColor = MPP_FMT_YUV420SP; for (int i = 0; i < FRAME_NUM; i++) { s32Ret = RK_MPI_VENC_GetFrm(0, &stFrame, RK_TRUE); if (s32Ret != RK_SUCCESS) { printf("[%s] venc get frame failed\n", TAG); goto done; } unsigned char *y = (unsigned char *)stFrame.mVirAddr[0]; unsigned char *uv = (unsigned char *)stFrame.mVirAddr[1]; int y_len = stFrame.mWidth * stFrame.mHeight; int uv_len = stFrame.mWidth * stFrame.mHeight / 2; RK_MPI_VENC_RcCfg rc_cfg; memset(&rc_cfg, 0, sizeof(rc_cfg)); rc_cfg.mRcMode = VENC_RC_MODE_H264CBR; rc_cfg.mBitRate = u32BitRate; rc_cfg.mFrmRate = u32Fps; rc_cfg.mGop = u32KeyFrameInterval; rc_cfg.mQpMin = 30; rc_cfg.mQpMax = 51; rc_cfg.mQpInit = 35; rc_cfg.mMaxReEncodeTimes = 5; rc_cfg.mMaxQPDelta = 10; rc_cfg.mMaxBitRateTolerance = 1000; RK_MPI_VENC_SetRcCfg(0, &rc_cfg); RK_MPI_VENC_H264Cfg h264_cfg; memset(&h264_cfg, 0, sizeof(h264_cfg)); h264_cfg.mProfile = u32Profile; h264_cfg.mLevel = 41; h264_cfg.mEntropyMode = VENC_ENTROPY_MODE_CABAC; h264_cfg.mCabacInitIdc = 0; h264_cfg.mSliceNum = 2; h264_cfg.mSliceMode = VENC_H264_SLICEMODE_SINGLE; RK_MPI_VENC_SetH264Cfg(0, &h264_cfg); RK_MPI_VENC_H264Vui h264_vui; memset(&h264_vui, 0, sizeof(h264_vui)); h264_vui.mAspectRatioIdc = 0; h264_vui.mOverScanInfo = 0; h264_vui.mBitstreamRestriction = 0; RK_MPI_VENC_SetH264Vui(0, &h264_vui); RK_MPI_VENC_H264Sei h264_sei; memset(&h264_sei, 0, sizeof(h264_sei)); h264_sei.mRecoveryPoint = 0; h264_sei.mRecoveryPointInfoPresent = 0; h264_sei.mBufferingPeriod = 0; h264_sei.mPictureTiming = 0; RK_MPI_VENC_SetH264Sei(0, &h264_sei); MPP_ENC_CFG_S stMppEncCfg; memset(&stMppEncCfg, 0, sizeof(stMppEncCfg)); stMppEncCfg.mRcCfg = &rc_cfg; stMppEncCfg.mGopCfg = NULL; stMppEncCfg.mH264Cfg = &h264_cfg; stMppEncCfg.mH264VuiCfg = &h264_vui; stMppEncCfg.mH264SeiCfg = &h264_sei; RK_MPI_VENC_SetMppCfg(0, &stMppEncCfg); MPP_ENC_SEI_S stEncSei; memset(&stEncSei, 0, sizeof(stEncSei)); stEncSei.mEncSeiMode = MPP_ENC_SEI_MODE_ALL; RK_MPI_VENC_SetExtCfg(0, ENC_CFG_SEI, &stEncSei); RK_MPI_VENC_SendFrame(0, y, uv, y_len, uv_len); RK_MPI_VENC_ReleaseFrm(0, &stFrame); usleep(1000 * 1000 / u32Fps); } done: if (RK_MPI_VENC_UnRegisterChn(0, 0, 0) != RK_SUCCESS) { printf("[%s] unregister venc chn failed\n", TAG); } if (RK_MPI_VENC_DestroyChn(0) != RK_SUCCESS) { printf("[%s] destroy venc chn failed\n", TAG); } RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn); if (camera != NULL) { camera_close(camera); } RK_MPI_SYS_Exit(); return 0; } ``` 这是一个基于 Rockchip RK3399 平台的 H.264 编码程序,程序中包含了使用 MPP 接口对 H.264 编码进行配置的相关代码,可以根据需要进行修改。注意,本程序只是一个示例程序,不保证可以直接编译通过并运行。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值