度申相机使用rga加速MONO8转换NV12

两个问题:

Q1:如果修改了rga的库,能否解决mono8转nv12的问题?

A:根本原因在于 RGA 硬件单元本身不支持 YUV400 → NV12 的颜色空间转换(Color Space Conversion, CSC)。

即使修改了库,底层驱动和硬件仍然会拒绝该操作。

Q2:为什么一定要NV12格式才能使用呢?

A:不是“一定要 NV12”,而是:NV12 是 Rockchip VPU(视频编码器)和 GStreamer 硬件编码插件(如 mpph264encrkvenc)最广泛支持、效率最高的输入格式。

根据这两个问题,就有了下面的这篇文章:

纯硬件加速方式:

首先查看目前板端能支持的格式:

grep -r "RK_FORMAT_" /usr/include/rga/
forlinx@ok3588:~$ grep -r "RK_FORMAT_" /usr/include/rga/
/usr/include/rga/rga.h:/* In order to be compatible with RK_FORMAT_XX and HAL_PIXEL_FORMAT_XX,
/usr/include/rga/rga.h: * RK_FORMAT_XX is shifted to the left by 8 bits to distinguish.  */
/usr/include/rga/rga.h:    RK_FORMAT_RGBA_8888    = 0x0 << 8,  /* [0:31] R:G:B:A 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGBX_8888    = 0x1 << 8,  /* [0:31] R:G:B:X 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGB_888      = 0x2 << 8,  /* [0:23] R:G:B 8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BGRA_8888    = 0x3 << 8,  /* [0:31] B:G:R:A 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGB_565      = 0x4 << 8,  /* [0:15] R:G:B 5:6:5 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGBA_5551    = 0x5 << 8,  /* [0:15] R:G:B:A 5:5:5:1 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGBA_4444    = 0x6 << 8,  /* [0:15] R:G:B:A 4:4:4:4 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BGR_888      = 0x7 << 8,  /* [0:23] B:G:R 8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_422_SP = 0x8 << 8,  /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_422_P  = 0x9 << 8,  /* 3 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_420_SP = 0xa << 8,  /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_420_P  = 0xb << 8,  /* 3 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_422_SP = 0xc << 8,  /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_422_P  = 0xd << 8,  /* 3 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_420_SP = 0xe << 8,  /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_420_P  = 0xf << 8,  /* 3 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_BPP1         = 0x10 << 8, /* [0] little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BPP2         = 0x11 << 8, /* [0:1] little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BPP4         = 0x12 << 8, /* [0:3] little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BPP8         = 0x13 << 8, /* [0:7] little endian */
/usr/include/rga/rga.h:    RK_FORMAT_Y4           = 0x14 << 8, /* [0:3] Y little endian */
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_400    = 0x15 << 8, /* [0:7] Y little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BGRX_8888    = 0x16 << 8, /* [0:31] B:G:R:X 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_YVYU_422     = 0x18 << 8, /* [0:31] Y0:Cr0:Y1:cb0 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_YVYU_420     = 0x19 << 8, /* ODD : [0:31] Y0:Cr0:Y1:cb0 8:8:8:8 little endian
/usr/include/rga/rga.h:    RK_FORMAT_VYUY_422     = 0x1a << 8, /* [0:31] Cr0:Y0:Cb0:Y1 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_VYUY_420     = 0x1b << 8, /* ODD : [0:31] Cr0:Y0:Cb0:Y1 8:8:8:8 little endian
/usr/include/rga/rga.h:    RK_FORMAT_YUYV_422     = 0x1c << 8, /* [0:31] Y0:Cb0:Y1:cr0 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_YUYV_420     = 0x1d << 8, /* ODD : [0:31] Y0:Cb0:Y1:cr0 8:8:8:8 little endian
/usr/include/rga/rga.h:    RK_FORMAT_UYVY_422     = 0x1e << 8, /* [0:31] Cb0:Y0:Cr0:Y1 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_UYVY_420     = 0x1f << 8, /* ODD : [0:31] Cb0:Y0:Cr0:Y1 8:8:8:8 little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_420_SP_10B = 0x20 << 8, /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_420_SP_10B = 0x21 << 8, /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_422_SP_10B = 0x22 << 8, /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_422_SP_10B = 0x23 << 8, /* 2 plane YCbCr little endian
/usr/include/rga/rga.h:    RK_FORMAT_YCbCr_422_10b_SP = RK_FORMAT_YCbCr_422_SP_10B << 8,
/usr/include/rga/rga.h:    RK_FORMAT_YCrCb_422_10b_SP = RK_FORMAT_YCrCb_422_SP_10B << 8,
/usr/include/rga/rga.h:    RK_FORMAT_BGR_565      = 0x24 << 8, /* [0:16] B:G:R 5:6:5 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BGRA_5551    = 0x25 << 8, /* [0:16] B:G:R:A 5:5:5:1 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_BGRA_4444    = 0x26 << 8, /* [0:16] B:G:R:A 4:4:4:4 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ARGB_8888    = 0x28 << 8, /* [0:31] A:R:G:B 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_XRGB_8888    = 0x29 << 8, /* [0:31] X:R:G:B 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ARGB_5551    = 0x2a << 8, /* [0:16] A:R:G:B 5:5:5:1 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ARGB_4444    = 0x2b << 8, /* [0:16] A:R:G:B 4:4:4:4 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ABGR_8888    = 0x2c << 8, /* [0:31] A:B:G:R 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_XBGR_8888    = 0x2d << 8, /* [0:31] X:B:G:R 8:8:8:8 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ABGR_5551    = 0x2e << 8, /* [0:16] A:B:G:R 5:5:5:1 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_ABGR_4444    = 0x2f << 8, /* [0:16] A:B:G:R 4:4:4:4 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_RGBA2BPP     = 0x30 << 8, /* [0:1] Color:Alpha 1:1 little endian */
/usr/include/rga/rga.h:    RK_FORMAT_UNKNOWN      = 0x100 << 8,
forlinx@ok3588:~$

转换成,选择同为单通道的格式,即8-bit 灰度图格式

RK_FORMAT_Y8 、RK_FORMAT_YCbCr_400

度申相机mono格式转换与rk硬件转换

编译没有问题,但是运行过程中出现下面的错误

问题根本原因: 不支持单通道灰度图(YUV400)直接转 NV12

Rockchip 的 RGA 模块在RK3588上不支持 YUV400 作为 imcvtcolor 的源格式。 它只支持:

  • RGB 系列(RGB888、BGR888、RGBA 等)

  • YUV420/YUV422 半平面或打包格式(如 NV12、YUYV)

根据编译信息得知支持的格式:

RGBA_8888

RGB_888

RGB_565

YUV420_sp_8bit

YUV420_sp_10bit

YUV422_sp_8bit

YUV422_sp_10bit YUYV422

软件扩展+硬件加速方式

MONO8先转换为RGBA8888,然后再用RGA转换为NV12

MONO8(软件扩展)-> RGBA8888(硬件加速转换)-> NV12

初始化函数:

/****************************  RGA 相关  ****************************/
static bool rga_init(CameraConfig *cam) {
    size_t align = 128;
    cam->rga.src_size = VIDEO_WIDTH * VIDEO_HEIGHT * 4;  // RGBA8888格式
    cam->rga.dst_size = VIDEO_WIDTH * VIDEO_HEIGHT * 3 / 2;  // NV12大小

    cam->rga.src_buf = (unsigned char *)aligned_alloc(align, cam->rga.src_size);
    cam->rga.dst_buf = (unsigned char *)aligned_alloc(align, cam->rga.dst_size);
    if (!cam->rga.src_buf || !cam->rga.dst_buf) {
        fprintf(stderr, "RGA 内存分配失败\n");
        return false;
    }
    memset(cam->rga.src_buf, 0, cam->rga.src_size);
    memset(cam->rga.dst_buf, 0, cam->rga.dst_size);

    cam->rga.src_img = {0};
    cam->rga.src_img.vir_addr = cam->rga.src_buf;
    cam->rga.src_img.width    = VIDEO_WIDTH;
    cam->rga.src_img.height   = VIDEO_HEIGHT;
    cam->rga.src_img.wstride  = VIDEO_WIDTH;
    cam->rga.src_img.hstride  = VIDEO_HEIGHT;
    cam->rga.src_img.format   = RK_FORMAT_RGBA_8888;  // 使用RGBA格式

    cam->rga.dst_img = {0};
    cam->rga.dst_img.vir_addr = cam->rga.dst_buf;
    cam->rga.dst_img.width    = VIDEO_WIDTH;
    cam->rga.dst_img.height   = VIDEO_HEIGHT;
    cam->rga.dst_img.wstride  = VIDEO_WIDTH;
    cam->rga.dst_img.hstride  = VIDEO_HEIGHT;
    cam->rga.dst_img.format   = RK_FORMAT_YCbCr_420_SP;

    printf("RGA 初始化成功(MONO8→NV12)\n");
    return true;
}

转变函数:

static bool rga_convert(CameraConfig *cam, const unsigned char *mono) {
    if (!mono || !cam->rga.src_buf || !cam->rga.dst_buf) return false;
    
    // 将MONO8转换为RGBA8888
    unsigned char *rgba_buf = (unsigned char *)malloc(VIDEO_WIDTH * VIDEO_HEIGHT * 4);
    if (!rgba_buf) return false;
    
    for (int i = 0; i < VIDEO_WIDTH * VIDEO_HEIGHT; i++) {
        rgba_buf[i*4] = mono[i];     // R
        rgba_buf[i*4+1] = mono[i];   // G
        rgba_buf[i*4+2] = mono[i];   // B
        rgba_buf[i*4+3] = 255;       // A
    }
    
    memcpy(cam->rga.src_buf, rgba_buf, VIDEO_WIDTH * VIDEO_HEIGHT * 4);
    free(rgba_buf);

    pthread_mutex_lock(&g_rga_mutex);
    IM_STATUS ret = imcvtcolor(cam->rga.src_img, cam->rga.dst_img,
                               RK_FORMAT_RGBA_8888, RK_FORMAT_YCbCr_420_SP);
    pthread_mutex_unlock(&g_rga_mutex);

    if (ret != IM_STATUS_SUCCESS) {
        fprintf(stderr, "RGA 转换失败: %s\n", imStrError(ret));
        return false;
    }
    return true;
}

测试结果:

1、单通道测试显示正常:

2、测试多路推流时遇到问题:

问题的主要原因:使用dvpSetTargetFormatSel不属于linux系统的api

主要是因为linux版本,与windown版本的api不一样,使用dvpSetTargetFormatSel函数在linux端不起作用

更换成dvpSetTargetFormat是可以正常的。

dvpSetTargetFormat(cam->dvp_handle, TARGET_FORMAT);

终于找到bug,修复成功

开关机后重启测试完成。

1 个赞