FEATURED · 精选文章

RK3568 C语言实现camera图像采集应用实例(三)

发布时间 / 2026/9/8 20:59:18
来源 / 创域科博编辑部
栏目 / 资讯中心
RK3568 C语言实现camera图像采集应用实例(三) 本文使用纯C语言实现Camera图像采集、NV12格式转换和LCD显示功能 去除冗余抽象深入理解Camera Preview的基本运行机制继续之前的基础上通过v4l2 接口 采集图像 实现camera preview 基础上并添加使用MPP编码功能的保存成视频文件。一 硬件环境1. RK3568 正点原子开发板一套10.1寸LCD MIPI 屏幕2. camera sensor MIPI ov135803 虚拟机开发环境 Ubuntu20.04 编译环境二 MPP介绍MMP是瑞芯微提供的媒体处理软件平台Media Process Platform 简称 MPP是适用于瑞芯微芯片系列的通用媒体处理软件平台。 该平台对应用软件屏蔽了芯片相关的复杂底层处理其目的是为了屏蔽不同芯片的差异为使用者提供统一的视频媒体处理接口Media Process Interface缩写 MPI。 MPP提供的功能包括视频解码H.265 / H.264 / H.263 / VP9 / VP8 / MPEG-4 / MPEG-2 / MPEG-1 / VC1 / MJPEG视频编码H.264 / VP8 / MJPEG视频处理视频拷贝缩放色彩空间转换场视频解交织Deinterlace系统架构MPP 平台在系统架构的层次图如下图硬件层硬件层是瑞芯微系列芯片平台的视频编解码硬件加速模块包括 VPU rkvdec rkvenc 等不同类型不同功能的硬件加速器内核驱动层Linux 内核的编码器硬件设备驱动以及相关的 mmu内存时钟电源管理模块等。 支持的平台主要是 Linux kernel 3.10 和 4.4 两个版本。 MPP 库对于内核驱动有依赖MPP 层用户态的 MPP 层屏蔽了不同操作系统和不同芯片平台的差异为上层使用者提供统一的 MPI 接口。MPP 层包括 MPI 模块 OSAL 模块 HAL 模块以及视频编解码器Video Decoder / Video Encoder和视频处理功能模块Video Process MPP 层通过 MPI 对接各种中间件软件如 OpenMax ffmpeg 和 gstreamer或者直接对接客户的上层应用二 设计框图目录代码.├── camera├── camera.c├── display.c├── display.h├── fb.c├── list.h├── Makefile├──mpi_enc.c //添加mpp 编码的功能函数└──mpi_enc.h三 MPP接口说明MppMem 为 C 库 malloc 内存的封装。MppBuffer 为硬件用的 dmabuf 内存的封装。 MppPacket 为一维缓存封装可以从 MppMem 和 MppBuffer 生成主要用于表示码流数据。MppFrame 为二维帧数据封装可以从 MppMem 和 MppBuffer 生成主要用于表示图像数据。使用 MppPacket 和 MppFrame 就可以简单有效的完成一般的视频编解码工作。MppPacket 和 MppFrame 就可以简单有效的完成一般的视频编解码工作。以视频解码为例码流输入端把地址和大小赋值给 MppPacket通过 put_packet 接口输入在输出端通过 get_frame 接口得到输入图像 MppFrame即可完成最简单的视频解码过程。MppBufferMppBuffer 主要用于描述供硬件使用的内存块提供了内存块的分配释放加减引用等功能。目前支持 ion/drm 两种分配器 几个重要的参数成员如下成员名称成员类型描述说明ptrvoid *表示内存块的起始虚拟地址。sizesize_t表示内存块的大小。fdint表示内存块的用户态空间文件句柄这种方式可以实现解码器在解码过程中零拷贝输出MppBufferGroup做为一个缓存的管理器可以管理外部导入的缓存MppPacketMppPacket 主要用于描述一维码流的相关信息 特别是有效数据的位置与长度。 MppPacket 几个重要的参数成员如下成员名称成员类型描述说明datavoid *表示缓存空间的起始地址。sizesize_t表示缓存空间的大小。posvoid *表示缓存空间内有效数据的起始地址。lengthsize_t表示缓存空间内有效数据的长度。如果在 decode_put_packet 调用之后length 变为 0说明此包码流己消耗完成。成员名称成员类型描述说明ptsRK_U64表示显示时间戳Present Time Stamp。dtsRK_U64表示解码时间戳Decoding Time Stamp。eosRK_U32表示码流结束标志End Of Stream。bufferMppBuffer表示 MppPacket 对应的 MppBuffer。flagRK_U32表示 MPP 内部使用的标志位包含如下标志#define MPP_PACKET_FLAG_EOS (0x00000001)#define MPP_PACKET_FLAG_EXTRA_DATA (0x00000002)#define MPP_PACKET_FLAG_INTERNAL (0x00000004)#define MPP_PACKET_FLAG_INTRA (0x00000008)MppFrame图像封装成员名称成员类型描述说明modeRK_U32表示图像数据帧场属性ptsRK_U64表示图像的显示时间戳Present Time Stamp。dtsRK_U64表示图像的解码时间戳Decoding Time Stamp。eosRK_U32表示图像的结束标志End Of Stream。errinfoRK_U32表示图像的错误标志是否图像内有解码错误。discardRK_U32表示图像的丢弃标志如果图像解码时的参考关系不满足要求则这帧图像会被标记为需要丢弃不被显示。buf_sizesize_t表示图像需要分配的缓存大小与图像的格式相关也与解码数据的格式相关。info_changeRK_U32如果为真表示当前 MppFrame 是一个用于标记码流信息变化的描述结构说明了新的宽高 stride以及图像格式。可能的 info_change 原因有1. 图像序列宽高变化。2. 图像序列格式变化如 8bit 变为 10bit。一旦 info_change 产生需要重新分析解码器使用的内存池。fmtMppFrame Format表示图像色彩空间格式以及内存排布方式color_rangeMppFrame ColorRange表示图像数据彩色空间范围 YUV full range 0 ~ 2558bitMppFrame 是其输出的信息结构体码流解码后的信息包括像素数据与 pts错误信息等相关信息都需要带在 MppFrame 结构体给调用者。 MppFrame 中的 pts/dts以及 eos 标志就是继承自对应的输入 MppPacket。一旦发现码流分辨率改变 MppFrame 中的 info_change 标志就会对应置位向用户通知发生了 info_change 事件需要用户进行缓存池修改处理。MppApiMppApi 结构体封装了 MPP 的对外 API 接口 用户通过使用 MppApi 结构中提供的函数指针实现视频编解码功能 其结构如成员名称成员类型描述sizeRK_U32MppApi 结构体大小。versionRK_U32MppApi 结构体版本。decode函数指针MPP_RET (*decode)(MppCtx ctx, MppPacket packet, MppFrame *frame)视频解码接口同时进行输入与输出单独使用。ctx MPP 实例上下文。packet 输入码流。frame 输出图像。返回值 0 为正常非零为错误码。decode_put_packet函数指针MPP_RET (*decode_put_packet)(MppCtx ctx, MppPacket packet)视频解码输入接口与 decode_get_frame 配合使用。ctx MPP 实例上下文。packet 输入码流。ecode_get_frame函数指针MPP_RET (*decode_get_frame)(MppCtx ctx, MppFrame *frame)视频解码输出接口与 decode_put_packet 配合使用。ctx MPP 实例上下文。frame 输出图像。返回值 0 为正常表示获取输出过程正常需要判断是否有得到有值的 frame 指针非零为出现错误。encode函数指针MPP_RET (*encode)(MppCtx ctx, MppFrame frame, MppPacket *packet)视频编码接口同时进行输入与输出单独使用。ctx MPP 实例上下文。frame 输入图像。packet 输出码流。返回值 0 为正常非零为错误码。encode_put_frame接口定义MPP_RET encode_put_frame(MppCtx ctx, MppFrame frame)输入参数ctx MPP 解码器实例frame 待输入的图像数据返回参数运行错误码功能输入 frame 图像数据给 ctx 指定的 MPP 编码器实例 encode_put_frame 是阻塞式函数会把调用者阻塞住直到输入图像使用完成会一定程度上导致软硬件运行无法并行效率下降。encode_get_packet接口定义MPP_RET encode_get_packet(MppCtx ctx, MppPacket *packet)输入参数ctx MPP 解码器实例packet 用于获取 MppPacket 实例的指针。返回参数运行错误码功能从 ctx 指定的 MPP 编码器实例里获取完成编码的 packet 描述信息以 H.264 编码器为例编码器的输出数据分为头信息sps/pps和图像数据I/P slice两部分头信息需要通过 control 接口的 MPP_ENC_GET_EXTRA_INFO 命令获取图像数据则是通过encode_get_packet 接口来获取。获取的时机是在 control 接口的SET_RC_CFG/SET_PREP_CFG/SET_CODEC_CFG 参数配置命令完成之后。在参数配置命令调用时编码器会进行各个参数的更新在更新全部完成之后调用 MPP_ENC_GET_EXTRA_INFO 获取到的头信息才是最新的。四 代码及API介绍参考瑞芯微mpp 代码的例程编码器 demo 为 mpi_enc_test 系列程序包括单线程的 mpi_enc_test及多实例的 mpi_enc_multi_test。test_ctx_initstatic MPP_RET test_ctx_init(MpiEncTestData **data, MpiEncTestCmd *cmd) { MpiEncTestData *p NULL; MPP_RET ret MPP_OK; if (!data || !cmd) { printf(invalid input data %p cmd %p\n, data, cmd); return MPP_ERR_NULL_PTR; } p calloc(sizeof(MpiEncTestData), 1); if (!p) { printf(create MpiEncTestData failed\n); ret MPP_ERR_MALLOC; goto RET; } // get paramter from cmd p-width cmd-width; p-height cmd-height; p-hor_stride cmd-width;//MPP_ALIGN(cmd-width, 16); p-ver_stride cmd-height;//MPP_ALIGN(cmd-height, 16); p-fmt cmd-format; p-type cmd-type; if (cmd-type MPP_VIDEO_CodingMJPEG) cmd-num_frames 1; p-num_frames cmd-num_frames; if (cmd-have_input) { p-fp_input fopen(cmd-file_input, rb); if (NULL p-fp_input) { printf(failed to open input file %s\n, cmd-file_input); printf(create default yuv image for test\n); } } if (cmd-have_output) { p-fp_output fopen(cmd-file_output, wb); if (NULL p-fp_output) { printf(failed to open output file %s\n, cmd-file_output); ret MPP_ERR_OPEN_FILE; } } // update resource parameter if (p-fmt MPP_FMT_YUV420SP_VU) p-frame_size MPP_ALIGN(cmd-width, 16) * MPP_ALIGN(cmd-height, 16) * 2; else if (p-fmt MPP_FMT_YUV422_UYVY) { // NOTE: yuyv and uyvy need to double stride p-hor_stride * 2; p-frame_size p-hor_stride * p-ver_stride; } else p-frame_size p-hor_stride * p-ver_stride * 4; p-packet_size p-frame_size;//p-width * p-height; RET: *data p; return ret; }test_mpp_setupstatic MPP_RET test_mpp_setup(MpiEncTestData *p) { MPP_RET ret; MppApi *mpi; MppCtx ctx; MppEncCodecCfg *codec_cfg; MppEncPrepCfg *prep_cfg; MppEncRcCfg *rc_cfg; MppPollType block MPP_POLL_BLOCK; if (NULL p) return MPP_ERR_NULL_PTR; mpi p-mpi; ctx p-ctx; codec_cfg p-codec_cfg; prep_cfg p-prep_cfg; rc_cfg p-rc_cfg; /* setup default parameter */ p-fps 30; p-gop 60; p-bps p-width * p-height / 8 * p-fps; prep_cfg-change MPP_ENC_PREP_CFG_CHANGE_INPUT | MPP_ENC_PREP_CFG_CHANGE_ROTATION | MPP_ENC_PREP_CFG_CHANGE_FORMAT; prep_cfg-width p-width; prep_cfg-height p-height; prep_cfg-hor_stride p-hor_stride; prep_cfg-ver_stride p-ver_stride; prep_cfg-format p-fmt; prep_cfg-rotation MPP_ENC_ROT_0; ret mpi-control(ctx, MPP_ENC_SET_PREP_CFG, prep_cfg); if (ret) { printf(mpi control enc set prep cfg failed ret %d\n, ret); goto RET; } ret mpi-control(ctx, MPP_SET_OUTPUT_TIMEOUT, (MppParam)block); if (MPP_OK ! ret) { printf(mpi-control MPP_SET_OUTPUT_TIMEOUT failed\n); goto RET; } rc_cfg-change MPP_ENC_RC_CFG_CHANGE_ALL; rc_cfg-rc_mode MPP_ENC_RC_MODE_CBR; rc_cfg-quality MPP_ENC_RC_QUALITY_MEDIUM; if (rc_cfg-rc_mode MPP_ENC_RC_MODE_CBR) { /* constant bitrate has very small bps range of 1/16 bps */ rc_cfg-bps_target p-bps; rc_cfg-bps_max p-bps * 17 / 16; rc_cfg-bps_min p-bps * 15 / 16; } else if (rc_cfg-rc_mode MPP_ENC_RC_MODE_VBR) { if (rc_cfg-quality MPP_ENC_RC_QUALITY_CQP) { /* constant QP does not have bps */ rc_cfg-bps_target -1; rc_cfg-bps_max -1; rc_cfg-bps_min -1; } else { /* variable bitrate has large bps range */ rc_cfg-bps_target p-bps; rc_cfg-bps_max p-bps * 17 / 16; rc_cfg-bps_min p-bps * 1 / 16; } } /* fix input / output frame rate */ rc_cfg-fps_in_flex 0; rc_cfg-fps_in_num p-fps; rc_cfg-fps_in_denorm 1; rc_cfg-fps_out_flex 0; rc_cfg-fps_out_num p-fps; rc_cfg-fps_out_denorm 1; rc_cfg-gop p-gop; rc_cfg-skip_cnt 0; printf(mpi_enc_test bps %d fps %d gop %d\n, rc_cfg-bps_target, rc_cfg-fps_out_num, rc_cfg-gop); ret mpi-control(ctx, MPP_ENC_SET_RC_CFG, rc_cfg); if (ret) { printf(mpi control enc set rc cfg failed ret %d\n, ret); goto RET; } codec_cfg-coding p-type; switch (codec_cfg-coding) { case MPP_VIDEO_CodingAVC : { codec_cfg-h264.change MPP_ENC_H264_CFG_CHANGE_PROFILE | MPP_ENC_H264_CFG_CHANGE_ENTROPY | MPP_ENC_H264_CFG_CHANGE_TRANS_8x8; /* * H.264 profile_idc parameter * 66 - Baseline profile * 77 - Main profile * 100 - High profile */ codec_cfg-h264.profile 100; /* * H.264 level_idc parameter * 10 / 11 / 12 / 13 - qcif15fps / cif7.5fps / cif15fps / cif30fps * 20 / 21 / 22 - cif30fps / half-D125fps / D112.5fps * 30 / 31 / 32 - D125fps / 720p30fps / 720p60fps * 40 / 41 / 42 - 1080p30fps / 1080p30fps / 1080p60fps * 50 / 51 / 52 - 4K30fps */ codec_cfg-h264.level 40; codec_cfg-h264.entropy_coding_mode 1; codec_cfg-h264.cabac_init_idc 0; codec_cfg-h264.transform8x8_mode 1; } break; case MPP_VIDEO_CodingMJPEG : { codec_cfg-jpeg.change MPP_ENC_JPEG_CFG_CHANGE_QP; codec_cfg-jpeg.quant 7; } break; case MPP_VIDEO_CodingVP8 : { } break; case MPP_VIDEO_CodingHEVC : { codec_cfg-h265.change MPP_ENC_H265_CFG_INTRA_QP_CHANGE; codec_cfg-h265.intra_qp 26; } break; default : { printf(support encoder coding type %d\n, codec_cfg-coding); } break; } ret mpi-control(ctx, MPP_ENC_SET_CODEC_CFG, codec_cfg); if (ret) { printf(mpi control enc set codec cfg failed ret %d\n, ret); goto RET; } /* optional */ p-sei_mode MPP_ENC_SEI_MODE_ONE_FRAME; ret mpi-control(ctx, MPP_ENC_SET_SEI_CFG, p-sei_mode); if (ret) { printf(mpi control enc set sei cfg failed ret %d\n, ret); goto RET; } RET: return ret; }test_mpp_runstatic MPP_RET test_mpp_run(MpiEncTestData *p, int fd, size_t size) { MPP_RET ret; MppApi *mpi; MppCtx ctx; MppBuffer buf NULL; if (NULL p) return MPP_ERR_NULL_PTR; mpi p-mpi; ctx p-ctx; if (p-type MPP_VIDEO_CodingAVC) { MppPacket packet NULL; ret mpi-control(ctx, MPP_ENC_GET_EXTRA_INFO, packet); if (ret) { printf(mpi control enc get extra info failed\n); goto RET; } /* get and write sps/pps for H.264 */ if (packet) { void *ptr mpp_packet_get_pos(packet); size_t len mpp_packet_get_length(packet); if (p-fp_output) fwrite(ptr, 1, len, p-fp_output); packet NULL; } } do { MppFrame frame NULL; if (p-packet) mpp_packet_deinit(p-packet); p-packet NULL; ret mpp_frame_init(frame); if (ret) { printf(mpp_frame_init failed\n); goto RET; } mpp_frame_set_width(frame, p-width); mpp_frame_set_height(frame, p-height); mpp_frame_set_hor_stride(frame, p-hor_stride); mpp_frame_set_ver_stride(frame, p-ver_stride); mpp_frame_set_fmt(frame, p-fmt); #if 0 mpp_frame_set_buffer(frame, p-frm_buf); #else MppBufferInfo inputCommit; memset(inputCommit, 0, sizeof(inputCommit)); inputCommit.type MPP_BUFFER_TYPE_ION; inputCommit.size size; inputCommit.fd fd; ret mpp_buffer_import(buf, inputCommit); if (ret) { printf(import input picture buffer failed\n); goto RET; } mpp_frame_set_buffer(frame, buf); #endif mpp_frame_set_eos(frame, p-frm_eos); ret mpi-encode_put_frame(ctx, frame); mpp_frame_deinit(frame); if (ret) { printf(mpp encode put frame failed\n); goto RET; } ret mpi-encode_get_packet(ctx, p-packet); if (ret) { printf(mpp encode get packet failed\n); goto RET; } if (p-packet) { // write packet to file here void *ptr mpp_packet_get_pos(p-packet); size_t len mpp_packet_get_length(p-packet); p-pkt_eos mpp_packet_get_eos(p-packet); if (p-fp_output) fwrite(ptr, 1, len, p-fp_output); p-enc_data ptr; p-enc_len len; } } while (0); RET: if (buf) mpp_buffer_put(buf); return ret; }mpi_enc_test_runMPP_RET mpi_enc_test_run(MpiEncTestData **data, int fd, size_t size) { MPP_RET ret MPP_OK; MpiEncTestData *p *data; ret test_mpp_run(p, fd, size); if (ret) printf(test mpp run failed ret %d\n, ret); return ret; }五 编码器数据流接口在显示线程中同时做复制显示帧用于编码运行结果将生成的nv12帧图像进行进行编码保存文件h264格式存放将h264转化为mp4 格式ffmpeg -i output.h264 -c copy output.mp4录制完成文件打开效果如图
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻