视频采集

更新时间:2023-08-09 09:25:02下载pdf

本文介绍 IPC(IP Camera)设备的视频采集全流程,包括视频采集(VI,Video Input)、视频编码(VENC,Video Encoding)初始化、视频流的获取。

功能说明

视频采集,是指采集编码后的视频数据。IPC 设备常用视频规格如下:

  • 2 路编码视频
    • 高分辨率的 H265 流
    • 低分辨率的 H264 流
  • 1 路图片
    用于告警上报或二次识别
  • 2 路 YUV
    • 二维码识别(640*360)
    • 动检检测(160 *90)

如下图,FH8636 平台数据流信息:

视频采集

T31 平台,传感器竖装(门铃场景),手动旋转 YUV,平台数据流信息如下:

视频采集

实现方案

驱动加载及内存分配

  • 部分平台有传感器驱动。如安凯平台,在文件系统的初始化脚本中,加载传感器驱动。

    insmod /drv/ak_ion.ko
    insmod /drv/ak_uio.ko
    insmod /drv/ak_pcm.ko
    insmod /drv/ak_i2c.ko
    if [ -e /drv/sensor_sc2336.ko ]; then
        insmod /drv/ak_isp.ko
        insmod /drv/sensor_sc2336.ko
    fi
    if [ -e /drv/sensor_sc3336.ko ]; then
        insmod /drv/ak_isp.ko internal_pclk=100000000
        insmod /drv/sensor_sc3336.ko
    fi
    

    安凯的多媒体内存分配,在内核中,由 BSP 分配。

  • 有些平台没有传感器驱动,如富瀚平台。传感器的寄存器序列放置在代码中。

    #内存分配
    insmod /drv/vmm.ko mmz=anonymous,0,0xA2600000,26624K anony=1
    #0xA2600000 = 0xA0000000+0x2600000,其中 0x2600000 = 38M,代表系统内存。26624K=26M,代表 mmz 的长度
    #加载驱动
    insmod /drv/media_process.ko
    insmod /drv/isp.ko
    insmod /drv/enc.ko
    insmod /drv/jpeg.ko
    insmod /drv/bgm.ko
    insmod /drv/nna.ko
    

    不同的平台有不同驱动加载方式。只需完成传感器的寄存器序列初始化即可。

配置视频属性

配置传感器的基础属性。包括传感器的长宽,传感器名,传感器默认的镜像翻转,ISP 图像风格等。

编码采集

配置编码器的属性。具体配置,可参考本文的 使用示例

功能说明图 中所示,不同流有一定的耦合关系。例如子码流和 QR 用同一路 VPU,因此,两者的分辨率需要求均为 640*360。

视频采集分为两种方式,主动采集和被动采集。

  • 主动采集,应用层调用取流 API,获取码流。
  • 被动采集,注册回调函数,底层调用回调函数,推送视频流。

使用方法

一个芯片平台仅支持一种采集方式。不做特殊说明时,默认采用主动采集。

主动采集

视频采集

被动采集

被动采集,调用逻辑基本和上图一致。差异点是,tal_venc_init 前,注册回调参数。上层不需要调用 tal_venc_get_frame 取流接口。底层默认开启线程,调用 put_cb 发送视频。

数据结构

初始化参数配置

// vi 和 isp
typedef struct{
    CHAR_T conf[128];                                             // isp conf path
    CHAR_T conf1[128];                                            // isp conf1 path
    CHAR_T conf2[128];                                            // isp conf2 path
    CHAR_T conf3[128];                                            // isp conf3 path
    CHAR_T version[32];                                           // version num
    CHAR_T name[16];                                              // isp sensor num
    TKL_ISP_DN_SWITCH_CONFIG_T isp_dn_switch_config;              // ADN switch config
    TKL_VI_SENSOR_CBUS_TYPE_E sensor_type;                       // sensor control bus type
    INT32_T              addr;                                    // sensor address
    INT32_T              width;
    INT32_T              height;
    TKL_ISP_FIG_STYLE_CONFIG_T isp_fig_style_day;                 // isp fig style in day
    TKL_ISP_FIG_STYLE_CONFIG_T isp_fig_style_night;               // isp fig style in night
    INT32_T              fps;                                     // sensor fps
}TKL_VI_ISP_CONFIG_T;

typedef struct {
    INT32_T enable;          // 1,enable,0,disable
    TKL_VI_CHN_E chn;        // video input channel
    INT32_T mirror;          // mirror defaults
    INT32_T filp;            // filp defaults
    TKL_VI_ISP_CONFIG_T isp; // isp config
    VOID *pdata;             // reserver data
} TKL_VI_CONFIG_T;

// venc
typedef enum
{
    TKL_VIDEO_PB_FRAME = 0,
    TKL_VIDEO_I_FRAME,
    TKL_VIDEO_TS_FRAME,
    TKL_AUDIO_FRAME,
    TKL_CMD_FRAME,
    TKL_MEDIA_FRAME_MAX,
}TKL_MEDIA_FRAME_TYPE_E;

typedef enum
{
    TKL_CODEC_VIDEO_MPEG4 = 0,
    TKL_CODEC_VIDEO_H263,
    TKL_CODEC_VIDEO_H264,
    TKL_CODEC_VIDEO_MJPEG,
    TKL_CODEC_VIDEO_H265,
    TKL_CODEC_VIDEO_YUV420,
    TKL_CODEC_VIDEO_YUV422,
    TKL_CODEC_VIDEO_MAX = 99,

    TKL_CODEC_AUDIO_ADPCM,
    TKL_CODEC_AUDIO_PCM,
    TKL_CODEC_AUDIO_AAC_RAW,
    TKL_CODEC_AUDIO_AAC_ADTS,
    TKL_CODEC_AUDIO_AAC_LATM,
    TKL_CODEC_AUDIO_G711U,
    TKL_CODEC_AUDIO_G711A,
    TKL_CODEC_AUDIO_G726,
    TKL_CODEC_AUDIO_SPEEX,
    TKL_CODEC_AUDIO_MP3,
    TKL_CODEC_AUDIO_MAX = 199,
    TKL_CODEC_TYPE_MAX
}TKL_MEDIA_CODEC_TYPE_E;

typedef struct
{
    UINT_T left;                                         // osd 左上角的位置 x 坐标
    UINT_T top;                                          // osd 左上角的位置 y 坐标
    UINT_T font_w;                                       // osd 字体大小,字体的宽
    UINT_T font_h;                                       // osd 字体大小,字体的高
}TKL_VENC_OSD_CONFIG_T;                                  // osd 属性配置

typedef struct {
    UINT_T enable;                    // 1,enable,0,disable
    TKL_VENC_CHN_E chn;               // video encode channel
    TKL_VENC_TYPE_E type;             // stream work type
    TKL_MEDIA_CODEC_TYPE_E codectype; // codec type
    UINT_T fps;                       // fps
    UINT_T gop;                       // I Frame interval
    UINT_T bitrate;                   // bitrate,kbps
    UINT_T width;
    UINT_T height;
    UINT_T min_qp;
    UINT_T max_qp;
    TKL_VENC_DATA_TRANS_MODE_E trans_mode;
    TKL_VENC_PUT_CB put_cb;
    TKL_VENC_OSD_CONFIG_T osd;
} TKL_VENC_CONFIG_T;

#define TAL_VI_CONFIG_T            TKL_VI_CONFIG_T
#define TAL_VI_HARDWARE_SOURCE_T   TKL_VI_HARDWARE_SOURCE_T

设置参数命令和结构体

typedef enum {
    TAL_VI_CMD_MIRROR_FLIP,
    TAL_VI_CMD_ANTIFLICKER,
    TAL_VI_CMD_ILLUMIN,
    TAL_VI_CMD_DAYMODE,
    TAL_VI_CMD_SENSOR_REG,
} TAL_VI_CMD_E;

typedef enum {
    TAL_VENC_CMD_OSD,         // osd
    TAL_VENC_CMD_IDR,         // set IDR
    TAL_VENC_CMD_STREAM_BUFF, // Set the circular cache area of the video memory pool. For example, set two 30K memory
                              // pools for storage
    TAL_VENC_CMD_START,       // venc start
    TAL_VENC_CMD_STOP,        // venc stop
} TAL_VENC_CMD_E;

typedef struct {
    INT32_T enable;
    INT32_T is_dls;
} TKL_VENC_OSD_T;

取流数据类型

typedef struct {
    TKL_MEDIA_FRAME_TYPE_E frametype; // bitrate,kbps
    TKL_MEDIA_CODEC_TYPE_E codectype; // codec type
    CHAR_T *pbuf;                     // frame buffer
    UINT_T buf_size;                  // buffer size
    UINT_T used_size;                 // used buffer size
    UINT_T width;                     // frame width
    UINT_T height;                    // frame height
    UINT64_T pts;                     // sdk pts
    UINT64_T timestamp;               // system utc time,unit: ms
    UINT_T seq;                       // frame sequence number
    UINT_T fragment;                  // frame sequence's fragment 第几个分包
    BYTE_T seq_error;                 // frame sequence is error 这张图片是否错误
    BYTE_T fragment_is_last;          // frame sequence's fragment is last fragment
} TKL_VENC_FRAME_T;                   // video frame

#define TAL_VENC_FRAME_T       TKL_VENC_FRAME_T

回调函数

typedef INT_T (*TKL_VENC_PUT_CB)(TKL_VENC_FRAME_T *pframe);

视频输入

初始化

IPC 设备,暂时只支持 1 路 VI,count 为 1。

/**
 * @brief vi init
 *
 * @param[in] pconfig: vi config
 * @param[in] count: count of pconfig
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_vi_init(TAL_VI_CONFIG_T *pconfig, INT32_T count);

设置参数和获取参数

/**
 * @brief vi set
 *
 * @param[in] chn: vi chn
 * @param[in] cmd: cmd
 * @param[in] parg: parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_vi_set(INT32_T chn, TAL_VI_CMD_E cmd, VOID *parg);

/**
 * @brief vi get
 *
 * @param[in] chn: vi chn
 * @param[in] cmd: cmd
 * @param[out] parg: parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_vi_get(INT32_T chn, TAL_VI_CMD_E cmd, VOID *parg);

去初始化

/**
 * @brief vi uninit
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_vi_uninit(VOID);

视频编码

初始化

IPC 设备,暂时只支持 1 路 VI,vi_chn 传 0。

/**
 * @brief video encode init
 *
 * @param[in] vi_chn: vi channel number
 * @param[in] pconfig: venc config
 * @param[in] count: count of pconfig
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_venc_init(INT32_T vi_chn, TAL_VENC_CONFIG_T *pconfig, INT32_T count);

设置参数和获取参数

/**
 * @brief video set
 *
 * @param[in] vi_chn: vi channel number
 * @param[in] venc_chn: venc chn
 * @param[in] cmd: cmd
 * @param[in] parg: parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_venc_set(INT32_T vi_chn, INT32_T venc_chn, TAL_VENC_CMD_E cmd, VOID *parg);

/**
 * @brief video get
 *
 * @param[in] vi_chn: vi channel number
 * @param[in] venc_chn: venc chn
 * @param[in] cmd: cmd
 * @param[out] parg: parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_venc_get(INT32_T vi_chn, INT32_T venc_chn, TAL_VENC_CMD_E cmd, VOID *parg);

取流

/**
 * @brief video encode get frame
 *
 * @param[in] vi_chn: vi channel number
 * @param[in] venc_chn: venc channel number
 * @param[out] pframe: output frame
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_venc_get_frame(INT32_T vi_chn, INT32_T venc_chn, TAL_VENC_FRAME_T *pframe);

去初始化

/**
 * @brief video encode uninit
 *
 * @param[in] vi_chn: vi channel number
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_venc_uninit(INT32_T vi_chn);

使用示例

// VI 参数配置及初始化:
    TAL_VI_CONFIG_T *pvi = NULL;
    // 当前 ipc 设备只有一路 vi
    pvi = &gMediaInfo.vi[0];
    pvi->enable = true;
    pvi->chn = TKL_VI_0;
    pvi->mirror = ISP_SENSOR_MIRROR;          // 初始状态的镜像翻转
    pvi->filp = ISP_SENSOR_FILP;
    memcpy(pvi->isp.conf, ISP_SENSOR_CONF, sizeof(pvi->isp.conf));       // isp 配置文件
    memcpy(pvi->isp.version, ISP_FW_VERSION, sizeof(pvi->isp.version));
    memcpy(pvi->isp.name, ISP_SENSOR_NAME, sizeof(pvi->isp.name));
    pvi->isp.width = ISP_SENSOR_WIDTH;
    pvi->isp.height = ISP_SENSOR_HEIGHT;
    memcpy(&pvi->isp.isp_dn_switch_config, &g_Tuya_DN_SWITCH_CONFIG_DAY, sizeof(pvi->isp.isp_dn_switch_config));
    ...
    ret = tal_vi_init(pinfo->vi, 1);
    ...

// Venc 参数配置
    S_DSP_MEDIA_INFO gMediaInfo = {
    /* 主码流 */
    .venc[E_DSP_MEDIA_VIDEO_MAIN].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].chn = TKL_VENC_0,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].type = TKL_VENC_MAIN,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].fps = 20,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].gop = 40,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].bitrate = 1024,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].width = 1920,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].height = 1080,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].codectype = 4,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].min_qp = 26,
    .venc[E_DSP_MEDIA_VIDEO_MAIN].max_qp = 48,

    /* 子码流 */
    .venc[E_DSP_MEDIA_VIDEO_SUB].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_SUB].chn = TKL_VENC_1,
    .venc[E_DSP_MEDIA_VIDEO_SUB].type = TKL_VENC_SUB,
    .venc[E_DSP_MEDIA_VIDEO_SUB].fps = 20,
    .venc[E_DSP_MEDIA_VIDEO_SUB].gop = 40,
    .venc[E_DSP_MEDIA_VIDEO_SUB].bitrate = 512,
    .venc[E_DSP_MEDIA_VIDEO_SUB].width = 640,
    .venc[E_DSP_MEDIA_VIDEO_SUB].height = 360,
    .venc[E_DSP_MEDIA_VIDEO_SUB].codectype = 2,
    .venc[E_DSP_MEDIA_VIDEO_SUB].min_qp = 26,
    .venc[E_DSP_MEDIA_VIDEO_SUB].max_qp = 48,

    /* 第三码流,预留 */
    .venc[E_DSP_MEDIA_VIDEO_3RD].enable = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].chn = TKL_VENC_2,
    .venc[E_DSP_MEDIA_VIDEO_3RD].type = TKL_VENC_3RD,
    .venc[E_DSP_MEDIA_VIDEO_3RD].fps = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].gop = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].bitrate = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].width = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].height = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].codectype = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].min_qp = 0,
    .venc[E_DSP_MEDIA_VIDEO_3RD].max_qp = 0,

    /* 第四码流,预留 */
    .venc[E_DSP_MEDIA_VIDEO_4TH].enable = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].chn = TKL_VENC_3,
    .venc[E_DSP_MEDIA_VIDEO_4TH].type = TKL_VENC_4TH,
    .venc[E_DSP_MEDIA_VIDEO_4TH].fps = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].gop = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].bitrate = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].width = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].height = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].codectype = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].min_qp = 0,
    .venc[E_DSP_MEDIA_VIDEO_4TH].max_qp = 0,

    /* 抓图 */
    .venc[E_DSP_MEDIA_VIDEO_SNAP].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].chn = TKL_VENC_4,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].type = TKL_VENC_SNAP,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].fps = 20,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].gop = 40,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].bitrate = 512,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].width = 640,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].height = 360,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].codectype = 3,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].min_qp = 15,
    .venc[E_DSP_MEDIA_VIDEO_SNAP].max_qp = 15,

    /* 动检 */
    .venc[E_DSP_MEDIA_VIDEO_MD].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_MD].chn = TKL_VENC_5,
    .venc[E_DSP_MEDIA_VIDEO_MD].type = TKL_VENC_MD,
    .venc[E_DSP_MEDIA_VIDEO_MD].fps = 20,
    .venc[E_DSP_MEDIA_VIDEO_MD].gop = 40,
    .venc[E_DSP_MEDIA_VIDEO_MD].bitrate = 512,
    .venc[E_DSP_MEDIA_VIDEO_MD].width = 320,
    .venc[E_DSP_MEDIA_VIDEO_MD].height = 180,
    .venc[E_DSP_MEDIA_VIDEO_MD].codectype = 5,

    /* 人形 */
    .venc[E_DSP_MEDIA_VIDEO_HD].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_HD].chn = TKL_VENC_6,
    .venc[E_DSP_MEDIA_VIDEO_HD].type = TKL_VENC_HD,
    .venc[E_DSP_MEDIA_VIDEO_HD].fps = 20,
    .venc[E_DSP_MEDIA_VIDEO_HD].gop = 40,
    .venc[E_DSP_MEDIA_VIDEO_HD].bitrate = 512,
    .venc[E_DSP_MEDIA_VIDEO_HD].width = 512,
    .venc[E_DSP_MEDIA_VIDEO_HD].height = 288,
    .venc[E_DSP_MEDIA_VIDEO_HD].codectype = 5,

    /* 二维码 */
    .venc[E_DSP_MEDIA_VIDEO_QR].enable = 1,
    .venc[E_DSP_MEDIA_VIDEO_QR].chn = TKL_VENC_7,
    .venc[E_DSP_MEDIA_VIDEO_QR].type = TKL_VENC_QR,
    .venc[E_DSP_MEDIA_VIDEO_QR].fps =25,
    .venc[E_DSP_MEDIA_VIDEO_QR].gop = 50,
    .venc[E_DSP_MEDIA_VIDEO_QR].bitrate = 512,
    .venc[E_DSP_MEDIA_VIDEO_QR].width = 640,
    .venc[E_DSP_MEDIA_VIDEO_QR].height = 360,
    .venc[E_DSP_MEDIA_VIDEO_QR].codectype = 5,

// venc 初始化:
    ret = tal_venc_init(0, pinfo->venc, 8);
    ...

// 主动获取主码流,主码流建议分配 300k:
    TAL_VENC_FRAME_T frame = {0};
    frame.pbuf = (UCHAR_T *)malloc(300*1024);
    frame.buf_size = 300*1024;
    ret = tal_venc_get_frame(0, 0, &frame)
    ...

// 获取子码流:
    frame.pbuf = (UCHAR_T *)malloc(100*1024);
    frame.buf_size = 100*1024;
    ret = tal_venc_get_frame(0, 1, &frame)

// 抓图
    frame.pbuf = (UCHAR_T *)malloc(150*1024);
    frame.buf_size = 150*1024;
    ret = tal_venc_get_frame(0, TKL_VENC_SNAP, &frame)
    ...

// 设置 osd 开关
    TKL_VENC_OSD_T osd = {0};
    osd.enable = 1; // 开关
    osd.is_dls = 0; // 是否是夏令时
    ret = tal_venc_set(0, 0, TAL_VENC_CMD_OSD, &osd);
    ...

// 设置镜像翻转
    TKL_VI_MIRROR_FLIP_E val = TKL_VI_MIRROR_FLIP_NONE;
    ret =tal_vi_set(0, TAL_VI_CMD_MIRROR_FLIP, &val);
    ...
    val = TKL_VI_MIRROR_FLIP;
    ret = tal_vi_set(0, TAL_VI_CMD_MIRROR_FLIP, &val);

常见问题

VI 或 VENC 为什么初始化失败?

  • 请检测驱动是否完整加载。
  • 检查多媒体内存是否充足。