rv1126 获取图像数据,实现图像裁剪、缩放、旋转【RK_MPI API接口】

发布时间 2023-04-14 09:06:02作者: 阿风小子

前言
刚接触RK平台,目前正在学习探索阶段,欢迎朋友们一起讨论,指出文章错误和可以优化的地方;
如果想参照文中描述进行编译、执行程序,请先参考阅读rv1126 SDK编译和rv1126 数据流;
版本说明,测试使用SDK版本是2020-0912版本,文中记录的问题,可能在新版本已经解决;文中使用的接口函数,可能老版本没有实现。
备注:后续重新购买了2020-1212版本SDK,旋转问题已经解决,不需要再修改源码。另外,编译时,需要多带上如下几个库:-lrknn_runtime -lod_share -lrockx -lOpenVX -lVSC -lGAL -lArchModelSw -lNNArchPerf

代码
直接上代码,代码将同一帧图像数据,利用RK平台的RGA功能,分别对图像进行了格式转换、缩放+旋转+格式转换、裁剪+格式转换,得到3路图像输出,并存储成文件。
代码是参考SDK包rv1126_1109/external/rkmedia/test/c_api/rkmedia_vi_rga_test.c修改而成。
说明:

编译后,执行可能遇到旋转效果无法实现,可以参考后续说明的方法修改;
RK平台提供的接口,缩放图的长宽应该是要16的倍数,如果参照rv1126_1109/external/rkmedia/test/c_api/rkmedia_vi_rga_test.c里缩放输出960x540,会发现存储得到的图像数据有异常。修改输出尺寸为960x544后正常;
// Copyright 2020 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include <pthread.h>

//#include "common/sample_common.h"
#include "rkmedia_api.h"
#include "rkmedia_venc.h"

static bool quit = false;

// 信号处理程序
static void sigterm_handler(int sig)
{
fprintf(stderr, "signal %d\n", sig);
quit = true;
}

// 图像数据处理线程
static void *GetMediaBuffer(void *arg)
{
RGA_CHN rga_chn = *(RGA_CHN *)arg;
char save_path[512];

//
printf("#Start %s thread, rga_chn:%d\n", __func__, rga_chn);
sprintf(save_path, "/userdata/output_%d.nv12", rga_chn);
//
FILE *save_file = fopen(save_path, "w");
if (!save_file)
printf("ERROR: Open %s failed!\n", save_path);

MEDIA_BUFFER mb = NULL;
int save_cnt = 0;
int recv_len;
while (!quit)
{
mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, rga_chn, 50);
if (!mb)
{
if (!quit)
{
continue;
}
printf("chn-%d:RK_MPI_SYS_GetMediaBuffer get null buffer!\n", rga_chn);
break;
}

recv_len = RK_MPI_MB_GetSize(mb);
printf("Get Frame-chn-%d:ptr:%p, fd:%d, size:%zu, mode:%d, channel:%d, "
"timestamp:%lld\n",
rga_chn,
RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetFD(mb), recv_len,
RK_MPI_MB_GetModeID(mb), RK_MPI_MB_GetChannelID(mb),
RK_MPI_MB_GetTimestamp(mb));

if (save_file && (save_cnt < 1))
{
int rtn = fwrite(RK_MPI_MB_GetPtr(mb), 1, recv_len, save_file);
fsync(fileno(save_file));
printf("#Save frame-chn-%d:%d to %s, rtn = %d\n", rga_chn, save_cnt, save_path, rtn);
save_cnt++;
}

RK_MPI_MB_ReleaseBuffer(mb);
}

if (save_file)
fclose(save_file);
printf("%s-chn-%d - exit\r\n", __func__, rga_chn);

return NULL;
}

//
int main()
{
int ret = -1;
//
VI_PIPE vi_pipe_0 = 0;
VI_CHN vi_chn_1 = 1;
//
RGA_CHN rga_chn_0 = 0;
RGA_CHN rga_chn_1 = 1;
RGA_CHN rga_chn_2 = 2;

// 初始化mpi sys
RK_MPI_SYS_Init();

//
// 数据源,ISP20的输出:
// rkispp_m_bypass, 不支持设置分辨率,不支持缩放, NV12/NV16/YUYV/FBC0/FBC2
// rkispp_scale0, max width: 3264,最大支持 8 倍缩放, NV12/NV16/YUYV
// rkispp_scale1, max width: 1280,最大支持 8 倍缩放, NV12/NV16/YUYV
// rkispp_scale2, max width: 1280,最大支持 8 倍缩放, NV12/NV16/YUYV
//
VI_CHN_ATTR_S vi_chn_attr;
vi_chn_attr.pcVideoNode = "rkispp_scale0";
vi_chn_attr.u32BufCnt = 4;
vi_chn_attr.u32Width = 1920;
vi_chn_attr.u32Height = 1080;
vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;
vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;
//
ret = RK_MPI_VI_SetChnAttr(vi_pipe_0, vi_chn_1, &vi_chn_attr);
ret |= RK_MPI_VI_EnableChn(vi_pipe_0, vi_chn_1);
if (ret)
{
printf("Create vi[%d] failed! ret=%d\n", vi_chn_1, ret);
return -1;
}

//
// 源数据配置
//
MPP_CHN_S stSrcChn;
stSrcChn.enModId = RK_ID_VI;
stSrcChn.s32DevId = vi_pipe_0;
stSrcChn.s32ChnId = vi_chn_1;

//
// 输出-0
// 格式转换 IMAGE_TYPE_NV12 -> IMAGE_TYPE_YUV420P
//
RGA_ATTR_S stRgaAttr_0;
stRgaAttr_0.bEnBufPool = RK_TRUE;
stRgaAttr_0.u16BufPoolCnt = 4;
stRgaAttr_0.u16Rotaion = 0;
stRgaAttr_0.stImgIn.u32X = 0;
stRgaAttr_0.stImgIn.u32Y = 0;
stRgaAttr_0.stImgIn.imgType = IMAGE_TYPE_NV12;
stRgaAttr_0.stImgIn.u32Width = 1920;
stRgaAttr_0.stImgIn.u32Height = 1080;
stRgaAttr_0.stImgIn.u32HorStride = 1920;
stRgaAttr_0.stImgIn.u32VirStride = 1080;
stRgaAttr_0.stImgOut.u32X = 0;
stRgaAttr_0.stImgOut.u32Y = 0;
stRgaAttr_0.stImgOut.imgType = IMAGE_TYPE_YUV420P;
stRgaAttr_0.stImgOut.u32Width = 1920;
stRgaAttr_0.stImgOut.u32Height = 1080;
stRgaAttr_0.stImgOut.u32HorStride = 1920;
stRgaAttr_0.stImgOut.u32VirStride = 1080;
//
ret = RK_MPI_RGA_CreateChn(rga_chn_0, &stRgaAttr_0);
if (ret)
{
printf("Create rga[%d] falied! ret=%d\n", rga_chn_0, ret);
goto EXIT_0;
}

pthread_t read_thread_0;
pthread_create(&read_thread_0, NULL, GetMediaBuffer, &rga_chn_0);

//
MPP_CHN_S stDestChn_0;
stDestChn_0.enModId = RK_ID_RGA;
stDestChn_0.s32DevId = 0;
stDestChn_0.s32ChnId = rga_chn_0;
//
ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn_0);
if (ret)
{
printf("Bind vi[1] and rga[%d] failed! ret=%d\n", rga_chn_0, ret);
goto EXIT_1;
}

//
// 输出-1
// 旋转 270 度
// 缩放 1920 -> 960
// 缩放 1080 -> 544
// 格式转换 IMAGE_TYPE_NV12 -> IMAGE_TYPE_YUV420P
//
RGA_ATTR_S stRgaAttr_1;
stRgaAttr_1.bEnBufPool = RK_TRUE;
stRgaAttr_1.u16BufPoolCnt = 4;
stRgaAttr_1.u16Rotaion = 270;
stRgaAttr_1.stImgIn.u32X = 0;
stRgaAttr_1.stImgIn.u32Y = 0;
stRgaAttr_1.stImgIn.imgType = IMAGE_TYPE_NV12;
stRgaAttr_1.stImgIn.u32Width = 1920;
stRgaAttr_1.stImgIn.u32Height = 1080;
stRgaAttr_1.stImgIn.u32HorStride = 1920;
stRgaAttr_1.stImgIn.u32VirStride = 1080;
stRgaAttr_1.stImgOut.u32X = 0;
stRgaAttr_1.stImgOut.u32Y = 0;
stRgaAttr_1.stImgOut.imgType = IMAGE_TYPE_YUV420P;
stRgaAttr_1.stImgOut.u32Width = 544;
stRgaAttr_1.stImgOut.u32Height = 960;
stRgaAttr_1.stImgOut.u32HorStride = 544;
stRgaAttr_1.stImgOut.u32VirStride = 960;
//
ret = RK_MPI_RGA_CreateChn(rga_chn_1, &stRgaAttr_1);
if (ret)
{
printf("Create rga[%d] falied! ret=%d\n", rga_chn_1, ret);
goto EXIT_2;
}

pthread_t read_thread_1;
pthread_create(&read_thread_1, NULL, GetMediaBuffer, &rga_chn_1);

//
MPP_CHN_S stDestChn_1;
stDestChn_1.enModId = RK_ID_RGA;
stDestChn_1.s32DevId = 0;
stDestChn_1.s32ChnId = rga_chn_1;
//
ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn_1);
if (ret)
{
printf("Bind vi[1] and rga[%d] failed! ret=%d\n", rga_chn_1, ret);
goto EXIT_3;
}

//
// 输出-2
// 裁剪 1920 -> 1000, 取中间部分
// 裁剪 1080 -> 800, 取中间部分
// 格式转换 IMAGE_TYPE_NV12 -> IMAGE_TYPE_YUV420P
//
RGA_ATTR_S stRgaAttr_2;
stRgaAttr_2.bEnBufPool = RK_TRUE;
stRgaAttr_2.u16BufPoolCnt = 4;
stRgaAttr_2.u16Rotaion = 0;
stRgaAttr_2.stImgIn.u32X = 460;
stRgaAttr_2.stImgIn.u32Y = 140;
stRgaAttr_2.stImgIn.imgType = IMAGE_TYPE_NV12;
stRgaAttr_2.stImgIn.u32Width = 1000;
stRgaAttr_2.stImgIn.u32Height = 800;
stRgaAttr_2.stImgIn.u32HorStride = 1920;
stRgaAttr_2.stImgIn.u32VirStride = 1080;
stRgaAttr_2.stImgOut.u32X = 0;
stRgaAttr_2.stImgOut.u32Y = 0;
stRgaAttr_2.stImgOut.imgType = IMAGE_TYPE_YUV420P;
stRgaAttr_2.stImgOut.u32Width = 1000;
stRgaAttr_2.stImgOut.u32Height = 800;
stRgaAttr_2.stImgOut.u32HorStride = 1000;
stRgaAttr_2.stImgOut.u32VirStride = 800;
//
ret = RK_MPI_RGA_CreateChn(rga_chn_2, &stRgaAttr_2);
if (ret)
{
printf("Create rga[%d] falied! ret=%d\n", rga_chn_2, ret);
goto EXIT_4;
}

pthread_t read_thread_2;
pthread_create(&read_thread_2, NULL, GetMediaBuffer, &rga_chn_2);

//
MPP_CHN_S stDestChn_2;
stDestChn_2.enModId = RK_ID_RGA;
stDestChn_2.s32DevId = 0;
stDestChn_2.s32ChnId = rga_chn_2;
//
ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn_2);
if (ret)
{
printf("Bind vi[1] and rga[%d] failed! ret=%d\n", rga_chn_2, ret);
goto EXIT_5;
}

//
// 监听退出信号
//
printf("%s initial finish\n", __func__);
signal(SIGINT, sigterm_handler);

//
// 等待退出信号
while (!quit)
{
usleep(100);
}

//
// 退出
//
ret = 0;

EXIT_6:
RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn_2);
EXIT_5:
RK_MPI_RGA_DestroyChn(rga_chn_2);
EXIT_4:
RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn_1);
EXIT_3:
RK_MPI_RGA_DestroyChn(rga_chn_1);
EXIT_2:
RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn_0);
EXIT_1:
RK_MPI_RGA_DestroyChn(rga_chn_0);
EXIT_0:
RK_MPI_VI_DisableChn(vi_pipe_0, vi_chn_1);

return ret;
}

如果已经完成SDK包的编译,可以参考如下编译该源码:

arm-linux-gnueabihf-gcc -I/home/user/work/sdk/rv1126_1109/external/rkmedia/include/rkmedia -L/home/user/work/sdk/rv1126_1109/buildroot/output/rockchip_rv1126_rv1109/staging/usr/lib -o rkmedia_vi_get_frame_test rkmedia_vi_get_frame_test.c -lpthread -ldl -lm -lpcre -lz -lglib-2.0 -leasymedia -ldrm -lrockchip_mpp -lavformat -lavcodec -lswresample -lavutil -lliveMedia -lgroupsock -lBasicUsageEnvironment -lUsageEnvironment -lasound -lv4l2 -lv4lconvert -lrga -lRKAP_ANR -lRKAP_Common -lRKAP_AEC -lrknn_api -lrockface -lsqlite3 -lmd_share -lrkaiq -lssl -lcrypto
1
执行的部分输出如下。可以看到图像数据的时间戳是同一个,个人感觉应该可以说明图像输入的源为同一帧图像。

Get Frame-chn-0:ptr:0xa1d2a000, fd:13, size:3133440, mode:16, channel:0, timestamp:3698073427
Get Frame-chn-1:ptr:0xa1100000, fd:18, size:783360, mode:16, channel:1, timestamp:3698073427
Get Frame-chn-2:ptr:0xa026c000, fd:23, size:1209600, mode:16, channel:2, timestamp:3698073427

Get Frame-chn-0:ptr:0xa2612000, fd:10, size:3133440, mode:16, channel:0, timestamp:3698106734
Get Frame-chn-1:ptr:0xa1340000, fd:15, size:783360, mode:16, channel:1, timestamp:3698106734
Get Frame-chn-2:ptr:0xa05db000, fd:20, size:1209600, mode:16, channel:2, timestamp:3698106734

Get Frame-chn-0:ptr:0xa231a000, fd:11, size:3133440, mode:16, channel:0, timestamp:3698140083
Get Frame-chn-1:ptr:0xa1280000, fd:16, size:783360, mode:16, channel:1, timestamp:3698140083
Get Frame-chn-2:ptr:0xa04b6000, fd:21, size:1209600, mode:16, channel:2, timestamp:3698140083

旋转功能处理
在刚开始测试时,发现无论如何配置旋转参数,输出的图像都无法实现旋转,于是查看了一下rkmedia的源码。在rv1126_1109/external/rkmedia/src/c_api/rkmedia_api.cc文件中发现了一些蹊跷,RK_S32 RK_MPI_RGA_CreateChn(RGA_CHN RgaChn, RGA_ATTR_S *pstRgaAttr)函数里对输入的旋转参数进行校验后,就把他遗忘了,后续再也没有给他出场的机会:

RK_U16 u16Rotaion = pstRgaAttr->u16Rotaion;
if ((u16Rotaion != 0) && (u16Rotaion != 90) && (u16Rotaion != 180) &&
(u16Rotaion != 270)) {
LOG("ERROR: %s rotation only support: 0/90/180/270!\n", __func__);
return -RK_ERR_RGA_ILLEGAL_PARAM;
}

所以,对以下源码进行了修改,大概在2373行:

// org code:
// PARAM_STRING_APPEND_TO(filter_param, KEY_BUFFER_ROTATE, 0);
// modify code:
PARAM_STRING_APPEND_TO(filter_param, KEY_BUFFER_ROTATE, u16Rotaion);
修改后,参考rv1126 SDK编译,删除buildroot/output/rockchip_rv1126_rv1109/build/rkmedia目录,再重新编译,将新编译得到的buildroot/output/rockchip_rv1126_rv1109/staging/usr/lib/libeasymedia.so.1.0.1文件,替换单板上/usr/lib/libeasymedia.so.1.0.1。替换后,重新执行测试程序,旋转功能正常。
出现这个问题,可能是因为我的SDK包是某宝上买的吧。