海思mpp视频编码

海思mpp视频编码

海思MPP框架

代码仓库:https://gitee.com/kidwjb/codec_video_audio/tree/master/%E6%B5%B7%E6%80%9D

海思mpp主要分为:视频输入(VI)、视频处理 (VPSS)、视频编码(VENC)、视频解码(VDEC)、视频输出(VO)、视频侦测分析 (VDA)、视频拼接(AVS)、音频输入(AI)、音频输出(AO)、音频编码(AENC)、音 频解码(ADEC)、区域管理(REGION)等模块。

image20251130202653861
  • VI模块捕获视频图像,可对其做剪切、缩放、镜像等处理,并输出多路不同分辨率的图像数据
  • 解码模块对编码后的视频码流进行解码,并将解析后的图像数据送VPSS进行图像处理或直接送VO显示。可对H.264/H.265格式的视频码流进行解码。
  • VPSS模块接收VI和解码模块发送过来的图像,可对图像进行去噪、图像增强、锐化等处理,并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍。
  • 编码模块接收VI捕获并经VPSS处理后输出的图像数据,可叠加用户通过Region模块设置的OSD图像,然后按不同协议进行编码并输出相应码流。
  • VDA模块接收VPSS的输出图像,并进行移动侦测和遮挡侦测,最后输出侦测分析结果。
  • VO模块接收VPSS处理后的输出图像,可进行播放控制等处理,最后按用户配置的输出协议输出给外围视频设备。
  • AVS模块接收多路图像,进行拼接合成全景图像。
  • AI模块捕获音频数据,然后AENC模块支持按多种音频协议对其进行编码,最后输出音频码流。
  • 用户从网络或外围存储设备获取的音频码流可直接送给ADEC模块,ADEC支持解 码多种不同的音频格式码流,解码后数据送给AO模块即可播放声音。

MMZ

海思芯片的物理内存被划分为两个部分,一块供OS使用另一块就是MMZ(Media Memory Zone,多媒体内存区域)。操作MMZ内存需要HI_MPI提供的接口。malloc不会申请到MMZ里的内存。

媒体模块使用的图像、码流等buffer都是来自mmz,mmz buffer的分配、释放、映射等都是基于物理地址管理的。媒体API接口里关于图像、码流等的缓存buffer也是基于物理地址定义的,应用程序可通过API直接获取到图像buffer物理地址信息,也可以通过API接口把图像buffer的物理地址送入模块,在模块内存调用硬件IP对图像进行处理。

多进程共享方式

  • 分配mmz buffer的进程为这个buffer的属主进程,默认只有属主进程具备本 buffer的读写权限、映射权限、释放权限,其它进程没有。
  • 注册了共享的进程为这个buffer的共享进程,则共享进程具备本buffer的读写权限、映射权限,不具备释放权限
  • mmz buffer读写权限指的是媒体驱动所有涉及mmz buffer输入或输出的API只允许buffer的属主进程和共享进程调用,其它进程调用会返回失败。
  • mmz buffer的映射权限指的是ss_mpi_sys_mmapss_mpi_sys_mmap_cached接口只允许buffer的属主进程和共享进程调用,其它进程调用会返回失败。
  • mmz buffer的释放权限指的是ss_mpi_sys_mmz_free接口只允许buffer的属主进程调用,其它进程调用会返回失败。

mmz buffer多进程共享方式一共有三种,最常用的是注册共享给特定的进程id

  • 通过ss_mpi_sys_mem_share接口将单个mmz buffer注册共享给特定的进程 id。
  • 通过ss_mpi_vb_pool_share接口将单个vb池共享给特定的进程id。
  • 通过ss_mpi_isp_mem_share接口将单个isp pipe所有的统计信息共享给特定的进程id获取。

系统控制

视频缓存池

视频缓存池主要向媒体业务提供大块物理内存管理功能,负责内存的分配和回收,充分发挥内存缓存池的作用,让物理内存资源在各个媒体处理模块中合理使用。

一组大小相同、物理地址连续的缓存块组成一个视频缓存池。必须在系统初始化之前配置公共视频缓存池。根据业务的不同,公共缓存池的数量、缓存块的大小和数量不同。

所有的视频输入通道都可以从公共视频缓存池中获取视频缓存块用于保存采集的图像

image20251130204233421

系统绑定

MPP提供系统绑定接口(ss_mpi_sys_bind),即通过数据接收者绑定数据源来建立两者之间的关联关系(只允许数据接收者绑定数据源)。绑定后,数据源生成的数据将自动发送给接收者。

MPP支持的绑定关系如下:

![屏幕截图 20251130 204517](https://kidwjb.top/pic_store/屏幕截图 2025-11-30 204517.png)

image20251130204534447
image20251130204637778

VI和VPSS工作模式

VI和VPSS各自的工作模式分为在线,离线

image20251130205843042

在海思(HiSilicon)的 MPP(Media Process Platform)框架中,VI(Video Input)和 VPSS(Video Process Sub-System)之间的数据流可以有不同的工作模式,这些模式通过枚举类型(如你列出的宏定义)来描述。

这些模式定义了 VI 模块是否直接在线(online)将图像数据传给 VPSS,还是先写入内存再由 VPSS 读取(offline)

VI_CAPVI_PROCVI(Video Input)模块内部的两个逻辑功能单元(或工作阶段),用于描述视频采集和预处理的分工。VI_CAP 从摄像头获取原始RAW数据,然后VI_PROC将原始RAW数据预处理为YUV数据

视频处理子系统

VPSS是视频处理子系统。支持对输入图像进行统一预处理,如去噪、去隔行、裁剪 等,然后再对各通道分别进行处理,如缩放、加边框等。

基本概念

  • Group VPSS对用户提供组(Group)的概念。最大个数请参考 OT_VPSS_MAX_GRP_NUM定义,各Group分时复用VPSS硬件,硬件依次处理各个组提交的任务。
  • Pipe VPSS组的输入通道,非拼接场景使用Pipe0,拼接场景其他通道可接收多路数据进行拼接。
  • Channel VPSS组的输出通道。一个VPSS组提供多个通道,每个通道具有缩放等功能。
image20251201201121342

通过调用SYS模块的绑定接口,可与VPSS/USER/VDEC/VIVO/VENC/SVP等模块进行 绑定,其中前者为VPSS的输入源,后者为VPSS的接收者。用户可通过MPI接口对 Group进行管理。每个Group除拼接场景外仅可与一个输入源绑定。

Group的物理通道有两种工作模式:AUTOUSER,两种模式间可动态切换。AUTO模式下各通道仅可与一个接收者绑定,主要用于预览和回放场景下做播放控制。USER模式下各通道可与多个接收者绑定。

==需要特别注意:==USER模式主要用于对同一通道图像进行多路编码的场景,此模式下播放控制不生效,因此回放场景下不建议使用USER模式。。VPSS只有工作在离线模式下才 支持AUTO模式。如果绑定VENC使用FMU,在开启OT_VPSS_DIRECT_CHN通道或 WRAP通道时,OT_VPSS_CHN0也不支持AUTO模式。

视频编码

VENC模块,即视频编码模块。本模块支持多路实时编码,且每路编码独立,编码协议 和编码profile可以不同。本模块支持视频编码同时,调度Region模块对编码图像内容 进行叠加和遮挡。

image20251201201931353

典型的编码流程包括了输入图像的接收、图像内容的遮挡和覆盖、图像的编码、以及码流的输出等过程。

编码通道

编码通道作为基本容器,保存编码通道的多种用户设置和管理编码通道的多种内部资源。编码通道完成图像转化为码流的功能,具体由码率控制器和编码器协同完成。

这里的编码器指的是狭义上的编码器,只完成编码功能。码率控制器提供了对编码参数 的控制和调整,从而对输出码率进行控制。

image20251201202303535

码率控制

  • CBR:CBR(Constant Bit Rate)固定比特率。即在码率统计时间内保证编码码率平稳。码率稳定主要由两个量来评估。
    • 码率统计时间stats_time 单位为秒(s),码率统计时间越长,每帧图像的码率波动对于码率调节的影响越弱,码率的调节会更缓慢,图像质量的波动会更轻微码率统计时间越短,每帧图像的码率波动对于码率调节的影响越强,图像码率的调节会更灵敏,图像质量的波动会更剧烈
    • 行级码率控制调节幅度row_qp_delta 行级码率控制调节幅度是一帧内行级调节的最大范围,其中行级以宏块行为单位。调节幅度越大,允许行级调整的QP范围越大,码率越平稳。对于图像复杂度分布不均匀的场景,行级码率控制调节幅度设置过大会带来图像质量不均匀。
  • ABR:即恒定平均目标码率码控算法,简单场景分配较低比特数,复杂场景分配足够比特数,使得有限的bit数能够在不同场景下合理分配. ABR可以认为是CBR和VBR的折中优化方案,特别在对质量和视频带宽都有要求的情况下,可以优先选择该模式。
  • VBR: VBR(Variable Bit Rate)可变比特率,即允许在码率统计时间内编码码率波动,从而保证编码图像质量平稳。
  • AVBR:AVBR(Adaptive Variable Bit Rate)自适应可变比特率,即允许在码率统计时间内编码码率波动,从而保证编码图像质量平稳。码率控制内部会检测当前场景的运动静止状态,在运动时用较高码率编码,在静止时主动降低目标码率。

编码码流帧配置模式

编码码流帧配置支持两种模式:单包模式多包模式(在不调用slice分割接口及其插 入用户数据接口的情况下)

  • 多包模式:对于H.264,当为I帧时,调用ss_mpi_venc_get_stream接口,一个I 帧至少包含4个NAL包(NAL包分别为sps包pps包sei包Islice包,且NAL包是 独立的,包类型不同);对于JPEG,一帧图像包含2个包(1个图像参数包,1个 图像数据包,2个包是独立的,包类型不同)。
  • 单包模式:对于H.264,当为I帧时,调用ss_mpi_venc_get_stream接口,一个I 帧包含1个NAL包(该NAL包的包类型为Islice包,且包含sps、pps、sei、Islice的数据);对于JPEG,一帧图像只有1个包(该包的包类型为图像数据包,且包含图 像参数包的数据)

两种模式可通过调用**ss_mpi_venc_set_mod_param接口设置h.265e.ko/h.264e.ko/ svac3e.ko/jpege.ko模块参数one_stream_buf来选择。模块参数为1表示单包模式**;模块参数为0表示多包模式系统默认为0。

当用户调用分包接口(例如:ss_mpi_venc_set_slice_split时,一帧会被分成多个 slice,如果用户选择单包模式时,对于I帧来说,该帧第一个ISlice包会包含 sps、 pps、sei的数据,该帧的其他ISlice则没有。即**对于H.264,sps、pps、sei的数据只会出现在I帧的第一个Islice中并合为1个包,且包类型为ISlice;**对于JPEG/MJPEG来说, 图像参数包只会出现在一帧的第一个数据包中并合为1个包,且包类型为数据包。

image20251201204404054

参考帧

❌ 误解澄清:参考帧 ≠ I帧

假设一个 GOP(图像组)结构如下(H.264 常见):

I₀ → P₁ → P₂ → P₃ → I₄ → P₅ → ...
  • I₀ 是 I 帧,也是后续 P₁、P₂、P₃ 的参考帧
  • 如果编码器配置 “最多 3 个参考帧”,则:
    • P₃ 可以同时参考 I₀、P₁、P₂(共 3 个)
    • 或只参考最近的 P₁、P₂(共 2 个),取决于运动复杂度和编码策略

“H.264、H.265、SVAC3 编码目前支持的参考帧数最多为3个”

✅ 意思是:

在编码一帧 P 帧或 B 帧时,编码器最多可以从前面已编码的 3 帧中选择参考帧(即 MaxNumRefFrames = 3)。

代码实现

笔者使用的是hi3516芯片,视频编码两路获取码流,一路获取VENC编码输出码流,一路获取VPSS输出码流

编码初始化

海思mpp的视频编码初始化主要分为以下几步:

  1. 初始化系统
  2. 初始化VI
  3. 初始化VPSS
  4. 绑定VI和VPSS
  5. 初始化VENC
  6. 绑定VPSS和VENC

在进行这些步骤之前,我们可以先初始化一些编码参数,这里我的传感器类型选择SC4336P_MIPI_4M_30FPS_10BIT,然后选择一个通道编码,并且设定分辨率为1080P

并且去获取编码尺寸

 // 设置图片尺寸(针对通道0)
static hi_void sample_venc_set_pic_size_0(sample_sns_type sns_type,
                                          hi_pic_size*    pic_size)
{
    pic_size[0] = BIG_STREAM_SIZE;  // 默认大码流尺寸,1080P
    if (sns_type == SC4336P_MIPI_3M_30FPS_10BIT) {
        pic_size[0] = PIC_2304X1296;  // 如果是特定传感器,设置不同尺寸
    }
}


    hi_s32               ret;
    sample_sns_type      sns_type              = {};
    sample_venc_param    enc_param             = {0};
    hi_pic_size          pic_size[CHN_NUM_MAX] = {};

    /* *****************************************
     * step 0: related parameter ready
     ***************************************** */
    // sns_type = SENSOR0_TYPE;  // 传感器类型
    sns_type               = SC4336P_MIPI_4M_30FPS_10BIT;
    enc_param.venc_chn_num = CHN_NUM_MAX;  // 编码通道数量
    sample_venc_set_pic_size_0(sns_type, pic_size);  // 设置第一个通道的图片尺寸
    sample_venc_get_enc_size(pic_size, &enc_param);  // 获取编码尺寸
    sample_set_venc_vpss_chn(&g_venc_vpss_chn,
                             enc_param.venc_chn_num);  // 设置VENC和VPSS通道映射

初始化系统

首先设置一些系统参数:

sys_cfg->route_num    = 1;                         // 路由数量
sys_cfg->mode_type    = HI_VI_ONLINE_VPSS_ONLINE;  // VI和VPSS在线模式
sys_cfg->vpss_wrap_en = HI_FALSE;                   // VPSS环绕模式使能参数
  • 目前是一路VI到一路VPSS,所以路由数量为1
  • 配置为在线模式让数据直达
  • 不使能环绕模式,主要是为了VPSS能够支持用户调用API获取视频数据

然后进行系统的初始化

// 在线环绕模式系统初始化
static hi_s32 sample_venc_online_wrap_sys_init(sample_venc_param* enc_param,
                                               sample_sys_cfg*     sys_cfg)
{
    hi_s32    ret;
    hi_vb_cfg vb_cfg = {0};
    hi_u32    supplement_config =
        HI_VB_SUPPLEMENT_JPEG_MASK | HI_VB_SUPPLEMENT_BNR_MOT_MASK;  // 补充配置

    sample_venc_online_wrap_get_default_vb_cfg(
        &vb_cfg, enc_param, sys_cfg->vpss_wrap_size);  // 获取默认VB配置

    if (supplement_config == 0) {
        ret = sample_comm_sys_vb_init(&vb_cfg);  // 初始化VB
    }
    else {
        ret = sample_comm_sys_init_with_vb_supplement(
            &vb_cfg, supplement_config);  // 带补充配置的VB初始化
    }

    if (ret != HI_SUCCESS) {
        sample_print("sample_venc_sys_init failed!\n");
    }

    if (sample_comm_vi_set_vi_vpss_mode(sys_cfg->mode_type,
                                        HI_VI_AIISP_MODE_DEFAULT) !=
        HI_SUCCESS) {  // 设置VI和VPSS模式
        goto sys_exit;
    }

    return HI_SUCCESS;
sys_exit:
    sample_comm_sys_exit();  // 系统去初始化
    return HI_FAILURE;
}

在这里主要是要注意VB缓冲池的初始化,由于要获取两路码流,而分隔点就是在VPSS,所以要初始化两个VB池

// 获取在线模式的默认VB配置
static hi_void
sample_venc_online_wrap_get_default_vb_cfg(hi_vb_cfg*         vb_cfg,
                                           sample_venc_param* enc_param,
                                           hi_u32             wrap_size)
{
    hi_vb_calc_cfg  calc_cfg;
    hi_pic_buf_attr buf_attr;

    (hi_void) memset_s(vb_cfg, sizeof(hi_vb_cfg), 0, sizeof(hi_vb_cfg));
    vb_cfg->max_pool_cnt = 128; /* 128 blks */  // 设置最大缓冲池数量

    // Pool 0: 通道0 - 编码通道(原始分辨率)
    buf_attr.width     = enc_param->enc_size[0].width;
    buf_attr.height    = enc_param->enc_size[0].height;
    buf_attr.align     = HI_DEFAULT_ALIGN;
    buf_attr.bit_width = HI_DATA_BIT_WIDTH_8;
    buf_attr.pixel_format =
        HI_PIXEL_FORMAT_YVU_SEMIPLANAR_420;          // YUV420半平面格式
    buf_attr.compress_mode = HI_COMPRESS_MODE_NONE;  // 不压缩
    buf_attr.video_format  = HI_VIDEO_FORMAT_LINEAR;
    hi_common_get_pic_buf_cfg(&buf_attr, &calc_cfg);  // 获取图片缓冲配置

    vb_cfg->common_pool[0].blk_size = calc_cfg.vb_size;   // 设置块大小
    vb_cfg->common_pool[0].blk_cnt  = 4; /* 通道0深度3 + 1余量 */

    // Pool 1: 通道1 - 显示通道(240x320)
    buf_attr.width     = 240;
    buf_attr.height    = 320;
    buf_attr.compress_mode = HI_COMPRESS_MODE_NONE;  // 通道1不压缩
    hi_common_get_pic_buf_cfg(&buf_attr, &calc_cfg);

    vb_cfg->common_pool[1].blk_size = calc_cfg.vb_size;
    vb_cfg->common_pool[1].blk_cnt  = 8; /* 通道1深度6 + 2余量 */

    printf("[VB] Pool 0 (Channel 0): %u bytes x %u blocks\n", 
           vb_cfg->common_pool[0].blk_size, vb_cfg->common_pool[0].blk_cnt);
    printf("[VB] Pool 1 (Channel 1): %u bytes x %u blocks\n", 
           vb_cfg->common_pool[1].blk_size, vb_cfg->common_pool[1].blk_cnt);
}

初始化VI

// 视频输入 (VI) 初始化
static hi_s32 sample_venc_vi_init(sample_vi_cfg* vi_cfg)
{
    hi_s32 ret;

    ret = sample_comm_vi_start_vi(vi_cfg);  // 启动VI模块
    if (ret != HI_SUCCESS) {
        sample_print("sample_comm_vi_start_vi failed: 0x%x\n", ret);
        return ret;
    }

    return HI_SUCCESS;
}

初始化VPSS

// 在线环绕模式下启动VPSS
static hi_s32 sample_venc_online_wrap_start_vpss(hi_vpss_grp      grp,
                                                 sample_vpss_cfg* vpss_cfg)
{
    hi_s32                  ret;
    hi_frame_interrupt_attr frame_interrupt_attr = {};
    sample_vpss_chn_attr    vpss_chn_attr        = {};

    frame_interrupt_attr.interrupt_type =
        HI_FRAME_INTERRUPT_EARLY_END;  // 设置帧中断类型
    frame_interrupt_attr.early_line = vpss_cfg->grp_attr.max_height / 2;
    /* 2 half */  // 提前中断的行数
    ret = hi_mpi_vpss_set_grp_frame_interrupt_attr(
        0, &frame_interrupt_attr);  // 设置VPSS组帧中断属性
    if (ret != HI_SUCCESS) {
        printf("hi_mpi_vpss_set_grp_frame_interrupt_attr failed!\n");
        return ret;
    }

    // 复制通道属性和使能状态
    (hi_void) memcpy_s(&vpss_chn_attr.chn_attr[0],
                       sizeof(hi_vpss_chn_attr) * HI_VPSS_MAX_PHYS_CHN_NUM,
                       vpss_cfg->chn_attr,
                       sizeof(hi_vpss_chn_attr) * HI_VPSS_MAX_PHYS_CHN_NUM);
    (hi_void)
        memcpy_s(vpss_chn_attr.chn_enable, sizeof(vpss_chn_attr.chn_enable),
                 vpss_cfg->chn_en, sizeof(vpss_chn_attr.chn_enable));
    (hi_void)
        memcpy_s(&vpss_chn_attr.wrap_attr[0],
                 sizeof(hi_vpss_chn_buf_wrap_attr) * HI_VPSS_MAX_PHYS_CHN_NUM,
                 vpss_cfg->wrap_attr,
                 sizeof(hi_vpss_chn_buf_wrap_attr) * HI_VPSS_MAX_PHYS_CHN_NUM);
    vpss_chn_attr.chn_array_size = HI_VPSS_MAX_PHYS_CHN_NUM;
    ret = sample_common_vpss_start(grp, &vpss_cfg->grp_attr,
                                   &vpss_chn_attr);  // 启动VPSS
    if (ret != HI_SUCCESS) {
        printf("ERROR:sample_common_vpss_start failed\r\n");
        return ret;
    }

    return HI_SUCCESS;

stop_vpss:
    sample_common_vpss_stop(grp, vpss_cfg->chn_en,
                            HI_VPSS_MAX_PHYS_CHN_NUM);  // 停止VPSS
    return HI_FAILURE;
}

这里帧中断属性选择的是HI_FRAME_INTERRUPT_EARLY_END,是在接收到帧的接近帧结束(如最后几行前)触发中断,保证平衡延迟与数据完整性

绑定VI和VPSS

hi_s32 sample_comm_vi_bind_vpss(hi_vi_pipe vi_pipe, hi_vi_chn vi_chn, hi_vpss_grp vpss_grp, hi_vpss_chn vpss_chn)
{
    hi_mpp_chn src_chn;
    hi_mpp_chn dest_chn;

    src_chn.mod_id = HI_ID_VI;
    src_chn.dev_id = vi_pipe;
    src_chn.chn_id = vi_chn;

    dest_chn.mod_id = HI_ID_VPSS;
    dest_chn.dev_id = vpss_grp;
    dest_chn.chn_id = vpss_chn;

    check_return(hi_mpi_sys_bind(&src_chn, &dest_chn), "hi_mpi_sys_bind(VI-VPSS)");

    return HI_SUCCESS;
}

if ((ret = sample_comm_vi_bind_vpss(vi_pipe, 0, vpss_grp, 0)) !=
        HI_SUCCESS) {  // 绑定VI和VPSS
        sample_print("VI Bind VPSS err for %#x!\n", ret);
        goto EXIT_VPSS_STOP;
    }

初始化VENC

VENC的初始化主要是设置GOP,设置编码参数,通道数等

    hi_s32                     ret;
    hi_venc_gop_mode           gop_mode;
    hi_venc_gop_attr           gop_attr;
    sample_comm_venc_chn_param chn_param[CHN_NUM_MAX] = {0};  // VENC通道参数

    if (get_gop_mode(&gop_mode) != HI_SUCCESS) {  // 获取GOP模式
        return HI_FAILURE;
    }
    if ((ret = sample_comm_venc_get_gop_attr(gop_mode, &gop_attr)) !=
        HI_SUCCESS) {  // 获取GOP属性
        sample_print("Venc Get GopAttr for %#x!\n", ret);
        return ret;
    }

    if ((ret = sample_venc_set_video_param(enc_size, chn_param, CHN_NUM_MAX,
                                           gop_attr, HI_FALSE)) !=
        HI_SUCCESS) {  // 设置视频编码参数
        return ret;
    }

    if ((ret = sample_comm_venc_mini_buf_en(chn_param, CHN_NUM_MAX)) !=
        HI_SUCCESS) {  // 使能VENC的迷你缓冲
        return ret;
    }

    /* encode h.264, only channel 0 */
    if ((ret = sample_comm_venc_start(venc_vpss_chn->venc_chn[0],
                                      &(chn_param[0]))) !=
        HI_SUCCESS) {  // 启动编码
        sample_print("Venc Start failed for %#x!\n", ret);
        return ret;
    }

绑定VPSS和VENC

hi_s32 sample_comm_vpss_bind_venc(hi_vpss_grp vpss_grp, hi_vpss_chn vpss_chn, hi_venc_chn venc_chn)
{
    hi_mpp_chn src_chn;
    hi_mpp_chn dest_chn;

    src_chn.mod_id = HI_ID_VPSS;
    src_chn.dev_id = vpss_grp;
    src_chn.chn_id = vpss_chn;

    dest_chn.mod_id = HI_ID_VENC;
    dest_chn.dev_id = 0;
    dest_chn.chn_id = venc_chn;

    check_return(hi_mpi_sys_bind(&src_chn, &dest_chn), "hi_mpi_sys_bind(VPSS-VENC)");

    return HI_SUCCESS;
}

if (sample_comm_vpss_bind_venc(vpss_grp, venc_vpss_chn->vpss_chn[0],
                                   venc_vpss_chn->venc_chn[0]) !=
        HI_SUCCESS) {  // 绑定VPSS和VENC
        sample_print("sample_comm_vpss_bind_venc failed!\n");
        sample_comm_venc_stop(venc_vpss_chn->venc_chn[0]);
        return HI_FAILURE;
    }

经过上面配置,视频编码就初始化完成了

获取视频帧数据

获取VENC编码帧数据

获取VENC的视频编码帧数据使用的是select机制,并且调用hi_mpi_venc_get_stream 接口

hi_s32 AV_DEV::get_stream()
{
    hi_s32             ret;
    hi_venc_stream     stream;
    hi_venc_chn_status stat;
    td_u32 i = 0;

    if (memset_s(&stream, sizeof(stream), 0, sizeof(stream)) != EOK) {
        printf("call memset_s error\n");
    }
    //查询编码通道状态
    ret = hi_mpi_venc_query_status(0, &stat);
    if (ret != HI_SUCCESS) {
        sample_print("hi_mpi_venc_query chn[0] failed with %#x!\n",ret);
        return 2;
    }
    //确保包大于0
    if (stat.cur_packs == 0) {
        sample_print("NOTE: current frame of chn[0] is HI_NULL!\n");
        return 1;
    }
    //分配cur_packs个包信息结构体
    stream.pack = (hi_venc_pack*)malloc(sizeof(hi_venc_pack) * stat.cur_packs);
    if (stream.pack == HI_NULL) {
        sample_print("malloc stream pack failed!\n");
        return 2;
    }

    stream.pack_cnt = stat.cur_packs;
    ret = hi_mpi_venc_get_stream(0, &stream, HI_TRUE);
    if (ret != HI_SUCCESS) {
        free(stream.pack);
        stream.pack = HI_NULL;
        sample_print("hi_mpi_venc_get_stream failed with %#x!\n", ret);
        return 2;
    }

    td_u32 total_len = 0;
    for (td_u32 j = 0; j < stream.pack_cnt; j++) {
        total_len += stream.pack[j].len;
    }

    //数据处理
    //..........................

    // 必须调用 release_stream 归还 VENC ring buffer 空间,否则 ring buffer 耗尽后 VENC 停止输出
    ret = hi_mpi_venc_release_stream(0, &stream);
    if (ret != HI_SUCCESS) {
        sample_print("hi_mpi_venc_release_stream chn[0] failed with %#x!\n", ret);
    }

    free(stream.pack);
    stream.pack = HI_NULL;

    return 0;
}

void AV_DEV::get_video(hi_void)
{
    int fd = 0;
    fd_set read_fds;
    struct timeval timeout_val;
    fd = hi_mpi_venc_get_fd(0);
    printf("[VENC] get_video thread started, fd=%d\r\n", fd);

    int loop_count = 0;
    // memory_order_acquire 保证能看到 signal_handler 中的修改
    while(g_keep_running.load(std::memory_order_acquire))
    {
        loop_count++;

        FD_ZERO(&read_fds);
        FD_SET(fd, &read_fds);

        int nfds = fd + 1;
        timeout_val.tv_sec  = 2; /* 2 is a number */
        timeout_val.tv_usec = 0;

        int ret = select(nfds, &read_fds,NULL, NULL, &timeout_val);
        if(ret < 0)
        {
            sample_print("[VENC] select failed!\n");
            break;
        }
        else if (ret == 0) {
            if (loop_count <= 10) {
                sample_print("[VENC] get venc stream time out, continue...\n");
            }
            continue;
        }
        else {
            ret = get_stream();
            if(ret != 0)
            {
                sample_print("[VENC] get venc stream fail\n");
            }
        }
    }

    printf("[VENC] Thread exiting...\n");

    return;
}

这里我使用的是std::atomic<bool> g_keep_running{true};全局变量作为线程运行标志

对于hi_mpi_venc_get_stream有几个注意事项:

● 如果通道未创建,返回失败。 ● 如果stream为空,返回OT_ERR_VENC_NULL_PTR。 ● 如果milli_sec小于-1,返回OT_ERR_VENC_ILLEGAL_PARAM。 ● 支持超时方式获取。支持select/poll系统调用。

  • milli_sec = 0时,则为非阻塞获取,即如果缓冲无数据,则返回失败 OT_ERR_VENC_BUF_EMPTY
  • milli_sec = -1时,则为阻塞,即如果缓冲无数据,则会等待有数据时才返回获取成功。
  • milli_sec > 0时,则为超时,即如果缓冲无数据,则会等待用户设定的超时时间,若在设定的时间内有数据则返回获取成功,否则返回超时失败。

● 码流结构体ot_venc_stream包含以下部分(hi_venc_streamot_venc_stream没有区别,只是前者是后者的上层封装):

  • 码流包信息指针pack指向一组ot_venc_pack的内存空间,该空间由调用者分配。如果是按包获取,则此空间不小于sizeof(ot_venc_pack的大小;如果按帧获取,则此 空间不小于N x sizeof(ot_venc_pack)的大小,其中N代表当前帧之中的包的个数,可以在select之后通过查询接口获得。
  • 码流包个数pack_cnt 在输入时,此值指定packot_venc_pack的个数。按包获取时,pack_cnt必 须不小于1;按帧获取时,pack_cnt必须不小于当前帧的包个数。在函数调用 成功后,pack_cnt返回实际填充pack的包的个数。
  • 序列号seq 按帧获取时是帧序列号;按包获取时为包序列号。
  • 码流特征信息,数据类型为联合体,包含了不同编码协议对应的码流特征信息h264_info/ jpeg_info/h265_info/svac3_info,码流特征信息的输出用于支持用户的上层应用。
  • 码流高级特征信息,数据类型为联合体,包含了不同编码协议对应的码流高级特征信息h264_adv_info/h265_adv_info/svac3_adv_info,码流高级特征信息的输出用于支持用户的上层应用。

● 如果用户长时间不获取码流,码流缓冲区就会满。一个编码通道如果发生码流缓冲区满,就会不再启动编码,直到用户获取码流,从而有足够的码流缓冲可以用于编码时,才开始继续编码。 ● 建议用户获取码流接口调用与释放码流的接口调用成对出现,且尽快释放码流,防止出现由于用户态获取码流,释放不及时而导致的码流buffer满,停止编码。

步骤1 调用ss_mpi_venc_query_status函数查询编码通道状态; 步骤2 确保cur_packs和left_stream_frames同时大于0; 步骤3 调用malloc分配cur_packs个包信息结构体; 步骤4 调用ss_mpi_venc_get_stream获取编码码流; 步骤5 调用ss_mpi_venc_release_stream释放码流缓存。

获取VPSS视频流

一般在摄像头数据经过VI采集之后会经过VPSS进行处理,然后再去VENC进行编码,而我们则需要获取VPSS的输出数据以供我们进行图像显示。

获取VPSS输出数据有两种方式,一种时通过接口获取图像帧,另一种是直接将VPSS与VO绑定。这里我只讲第一种方式,因为该方式可搭配Qt使用灵活性更高

通过接口获取
ot_video_frame结构体

目前看手册729页,有一个接口ss_mpi_vpss_get_chn_frame用于用户从通道获取一帧处理完成的图像。

  • 其中第三个参数ot_video_frame_info的说明在手册274

这是一个视频图像帧信息结构体

typedef struct {
    ot_video_frame video_frame;
    td_u32     pool_id;
    ot_mod_id  mod_id;
} ot_video_frame_info;

其中有一个视频原始图像帧结构ot_video_frame:

typedef struct {
    td_u32              width;    //图像宽度
    td_u32              height;   //图像高度
    ot_video_field       field;    //帧场模式
    ot_pixel_format      pixel_format;//视频图像像素格式
    ot_video_format      video_format;//视频图像格式
    ot_compress_mode     compress_mode;//视频压缩模式
    ot_dynamic_range     dynamic_range;//动态范围
    ot_color_gamut       color_gamut; //色域范围
    td_u32              header_stride[OT_MAX_COLOR_COMPONENT];//图像压缩头跨距
    td_u32              stride[OT_MAX_COLOR_COMPONENT];//图像数据跨距
    td_phys_addr_t     header_phys_addr[OT_MAX_COLOR_COMPONENT];//压缩头物理地址
    td_phys_addr_t     phys_addr[OT_MAX_COLOR_COMPONENT];//图像数据物理地址
    td_void* ATTRIBUTE  header_virt_addr[OT_MAX_COLOR_COMPONENT];//压缩头虚拟地址,内核态虚拟地址
    td_void* ATTRIBUTE  virt_addr[OT_MAX_COLOR_COMPONENT];//图像数据虚拟地址,内核态虚拟地址
    td_u32              time_ref;//图像帧序列号
    td_u64              pts;//图像时间戳
    td_u64              user_data[OT_MAX_USER_DATA_NUM];//用户数据
    td_u32              frame_flag; /* frame_flag, can be OR operation. */
    ot_video_supplement  supplement;//图像的补充信息
} ot_video_frame;

可以根据这个结构体进行图像帧的操作

ss_mpi_vpss_get_chn_frame接口注意
td_s32 ss_mpi_vpss_get_chn_frame(ot_vpss_grp grp, ot_vpss_chn chn, ot_video_frame_info 
*frame_info, td_s32 milli_sec)

用户从通道获取一帧处理完成的图像

想要通过这个接口获取VPSS图像数据,需要注意几项:

1.只有在USER模式下,并且队列深度不为0,才能获取到图像

他们都在同一个结构体ot_vpss_chn_attr里:

typedef struct {
  td_bool             mirror_en;                /* RW; Range: [0, 1]; Mirror enable. */
  td_bool             flip_en;                  /* RW; Range: [0, 1]; Flip enable. */
  td_bool             border_en;                /* RW; Range: [0, 1]; Border enable. */
  /* RW; range: Hi3519DV500 = [64, 8192]; Width of target image. */
  td_u32              width;
  /* RW; range: Hi3519DV500 = [64, 8192]; Height of target image. */
  td_u32              height;
  td_u32              depth;                    /* RW; Range: [0, 8]; Depth of chn image list. */
  ot_vpss_chn_mode    chn_mode;                 /* RW; Work mode of vpss channel. */
  ot_video_format     video_format;             /* RW; Video format of target image. */
  ot_dynamic_range    dynamic_range;            /* RW; Dynamic range of target image. */
  ot_pixel_format     pixel_format;             /* RW; Pixel format of target image. */
  ot_compress_mode    compress_mode;            /* RW; Compression mode of the output. */
  ot_frame_rate_ctrl  frame_rate;               /* RW; Frame rate control info. */
  ot_border           border_attr;              /* RW; Border info. */
  ot_aspect_ratio     aspect_ratio;             /* RW; Aspect ratio info. */
} ot_vpss_chn_attr;
chn_attr->chn_mode                  = HI_VPSS_CHN_MODE_USER;
chn_attr->depth                     = 1;

将结构体内的成员修改成如上即可

2.Hi3516CV610开启通道卷绕时,无法获取到帧

笔者的芯片使用的是Hi3516CV610,所以需要关闭通道环绕模式

3.为通道分配VB内存池

  • 原则: 不同分辨率的通道需要不同大小的VB缓冲区
  • 计算: 使用 hi_common_get_pic_buf_cfg() 基于每个通道的实际分辨率计算VB大小
  • 分配: 每个通道需要独立的VB池(common_pool)
// Hi3516CV610: 必须禁用所有通道的wrap,否则无法get_chn_frame
  for (chn = 0; chn < HI_VPSS_MAX_PHYS_CHN_NUM; chn++) {
      vpss_cfg->wrap_attr[chn].enable = HI_FALSE;
      vpss_cfg->wrap_attr[chn].buf_line = 0;
      vpss_cfg->wrap_attr[chn].buf_size = 0;
  }

通过上述操作即可通过ss_mpi_vpss_get_chn_frame接口获取得到VPSS的一帧图像

hi_vpss_grp grp = 0;
hi_vpss_chn chn = 1;  // 使用通道1(非压缩通道)
hi_video_frame_info frame_info = {};
ret = hi_mpi_vpss_get_chn_frame(grp, chn, &frame_info, 100);
if(ret == HI_SUCCESS)
{
printf("[VPSS] frame: %dx%d, format=%d, compress=%d, stride=[%d,%d]\r\n",
                       frame_info.video_frame.width, frame_info.video_frame.height,
                       frame_info.video_frame.pixel_format, frame_info.video_frame.compress_mode,
                       frame_info.video_frame.stride[0], frame_info.video_frame.stride[1]);
}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇