RV1126——多线程获取高分辨率和低分辨率的H264码流
前面两章我们已经搞定了单路 H264 码流获取、H264/H265 双编码码流获取。 都是固定分辨率取流虽然能满足基础录像需求但在真实的 IPC 摄像头项目中远远不够。实际项目里几乎全部是双分辨率方案 一路高清 1080P做本地精细录像存档一路低清 720P做网页预览、手机实时查看、低带宽推流。所以这一章我们继续基于 RV1126 RKMedia实战实现一帧摄像头画面同时输出 1080P 高分辨率、720P 低分辨率两路 H264 码流多线程独立获取并保存。一、为什么要做双分辨率码流简单说就是各司其职高分辨率1080P画质清晰、细节完整用于本地录像回放、取证存档低分辨率720P体积小、码率低、占用带宽少专门用于网络预览、实时直播。如果只存 1080P带宽小的设备会卡、存储空间消耗巨大 如果只用 720P录像画质太差达不到监控要求。所以高低双分辨率并行是嵌入式摄像头的标配方案。二、RKMedia 双分辨率实现原理很多新手会踩坑以为 VI 可以直接输出两路不同分辨率数据。这里纠正一个关键知识点 VI 采集出来的原始 NV12 画面分辨率是固定的无法直接编码出两种尺寸。正确的硬件链路流程VI 采集原图 → 一路直通高清 VENC 编码 → 一路经过 RGA 缩放 → 低清 VENC 编码完整数据流VI 模块采集 1920×1080 原始画面原始画面分两路分发第一路直接送入 VENC0编码 1080P 高清 H264第二路送入 RGA 硬件缩放为 1280×720缩放后的画面送入 VENC1编码 720P 低清 H264开启两个独立线程分别获取两路码流同时开启 RGA 转发线程处理缩放数据三、为什么需要多线程和之前章节逻辑一致再次强调工程规范RKMedia 的GetMediaBuffer是阻塞接口。 如果所有逻辑放主线程会导致编码阻塞卡死数据无法流转严重丢帧、链路异常所以本章我们开启3 个独立子线程分工处理高清取流线程专门读取 1080P VENC 码流、保存文件低清取流线程专门读取 720P VENC 码流、保存文件RGA 转发线程循环读取缩放后图像转发给低清编码器线程隔离互不干扰保证两路码流稳定、不丢帧、不卡顿。四、核心开发步骤1、VI 摄像头初始化配置摄像头分辨率 1080P、像素格式 NV12、缓冲数量开启正常采集模式。 保证输出原始画面完整、稳定。2、RGA 缩放模块初始化配置 RGA 输入尺寸为 1080P输出尺寸缩放为 720P。 利用瑞芯微硬件 RGA 完成图像缩放全程无 CPU 消耗。3、两路 VENC 编码通道初始化分别创建两个 VENC 通道VENC0高分辨率编码通道1920×1080、H264、CBR 码率模式VENC1低分辨率编码通道1280×720、H264、CBR 码率模式两路编码参数独立配置互不影响。4、模块绑定链路搭建完整数据通路VI 绑定 高清 VENC原图直接编码VI 绑定 RGA原图进入缩放处理通过软件链路绑定让硬件自动流转数据。5、创建多线程取流分别启动高清取流线程、低清取流线程、RGA 数据转发线程。 循环阻塞取帧、写入本地裸流文件。五、代码#include assert.h #include fcntl.h #include getopt.h #include pthread.h #include signal.h #include stdbool.h #include stdio.h #include stdlib.h #include time.h #include unistd.h // RKMedia 核心API头文件提供VI、RGA、VENC、Bind等驱动接口 #include rkmedia_api.h // 宏定义各个模块通道编号 #define PIPE_ID 0 // VI管道ID #define VI_CHN_ID 0 // 摄像头通道号 #define RGA_CHN_ID 0 // RGA硬件缩放通道号 #define HIGH_VENC_CHN 0 // 高清编码通道1080P #define LOW_VENC_CHN 1 // 低清编码通道720P // 高清码流获取线程 // 功能从VENC高清通道获取1080P H264码流保存到文件 void * get_high_venc_thread(void * args) { // 设置线程为分离模式自动释放资源不需要主线程join pthread_detach(pthread_self()); // 打开文件保存高清H264码流 FILE * high_venc_file fopen(test_high_venc.h264, w); // 媒体缓冲区用于接收VENC输出的一帧数据 MEDIA_BUFFER mb; // 循环持续取流 while(1) { // 阻塞获取高清VENC通道的码流数据 mb RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, HIGH_VENC_CHN, -1); // 获取失败退出线程 if(!mb) { printf(Get High_Venc Break...\n); return NULL; } printf(Get High_Venc Succeed...\n); // 将H264码流写入文件 fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, high_venc_file); // 必须释放缓冲区否则编码器会卡死 RK_MPI_MB_ReleaseBuffer(mb); } return NULL; } // RGA图像数据转发线程 // 功能从RGA获取缩放后的720P图像转发给低清VENC进行编码 void * rga_handle_thread(void * args) { pthread_detach(pthread_self()); MEDIA_BUFFER mb; while(1) { // 阻塞获取RGA输出的缩放图像 mb RK_MPI_SYS_GetMediaBuffer(RK_ID_RGA, RGA_CHN_ID, -1); // 获取失败退出循环 if(!mb) { printf(Get RGA Break...\n); break; } printf(Get RGA Succeed...\n); // 将缩放后的图像数据发送给低清VENC编码通道 RK_MPI_SYS_SendMediaBuffer(RK_ID_VENC, LOW_VENC_CHN, mb); // 释放图像缓冲区 RK_MPI_MB_ReleaseBuffer(mb); } return NULL; } // 低清码流获取线程 // 功能从VENC低清通道获取720P H264码流保存到文件 void * get_low_venc_thread(void * args) { pthread_detach(pthread_self()); MEDIA_BUFFER mb; // 打开文件保存低清H264码流 FILE * low_venc_file fopen(test_low_venc.h264, w); while(1) { // 阻塞获取低清VENC通道的码流数据 mb RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, LOW_VENC_CHN, -1); if(!mb) { printf(Get Low_Venc Break...\n); } printf(Get Low_Venc Succeed...\n); // 将H264码流写入文件 fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, low_venc_file); // 释放缓冲区 RK_MPI_MB_ReleaseBuffer(mb); } return NULL; } // 主函数模块初始化 绑定 启动线程 int main(int argc, char *argv[]) { int ret; // 1. 初始化VI摄像头采集模块 VI_CHN_ATTR_S vi_chn_attr; // 摄像头设备节点 vi_chn_attr.pcVideoNode rkispp_scale0; // 输出分辨率 1920x1080 vi_chn_attr.u32Width 1920; vi_chn_attr.u32Height 1080; // 图像格式 NV12 vi_chn_attr.enPixFmt IMAGE_TYPE_NV12; // 内存映射模式 vi_chn_attr.enBufType VI_CHN_BUF_TYPE_MMAP; // 缓冲区数量 vi_chn_attr.u32BufCnt 3; // 正常工作模式 vi_chn_attr.enWorkMode VI_WORK_MODE_NORMAL; // 设置VI通道属性 ret RK_MPI_VI_SetChnAttr(PIPE_ID, VI_CHN_ID, vi_chn_attr); if(ret) { printf(VI_CHN_ATTR Set Failed...\n); return 0; } else { printf(VI_CHN_ATTR Set Succeed...\n); } // 启用VI通道开始采集图像 ret | RK_MPI_VI_EnableChn(PIPE_ID, VI_CHN_ID); if(ret) { printf(VI_CHN_ATTR Enable Attr Failed...\n); return 0; } else { printf(VI_CHN_ATTR Enable Succeed...\n); } // 2. 初始化RGA硬件缩放模块 RGA_ATTR_S rga_info; // RGA输入配置与VI输出一致 1920x1080 NV12 rga_info.stImgIn.u32Width 1920; rga_info.stImgIn.u32Height 1080; rga_info.stImgIn.u32HorStride 1920; rga_info.stImgIn.u32VirStride 1080; rga_info.stImgIn.imgType IMAGE_TYPE_NV12; rga_info.stImgIn.u32X 0; rga_info.stImgIn.u32Y 0; // RGA输出配置缩放到 1280x720 NV12 rga_info.stImgOut.u32Width 1280; rga_info.stImgOut.u32Height 720; rga_info.stImgOut.u32HorStride 1280; rga_info.stImgOut.u32VirStride 720; rga_info.stImgOut.imgType IMAGE_TYPE_NV12; rga_info.stImgOut.u32X 0; rga_info.stImgOut.u32Y 0; // RGA缓冲池数量 rga_info.u16BufPoolCnt 3; // 旋转0度 rga_info.u16Rotaion 0; // 不翻转 rga_info.enFlip RGA_FLIP_NULL; // 使能缓冲池 rga_info.bEnBufPool RK_TRUE; // 创建RGA通道 ret RK_MPI_RGA_CreateChn(RGA_CHN_ID, rga_info); if (ret) { printf(RGA Create Failed...\n); return 0; } else { printf(RGA Create Succeed...\n); } // 3. 初始化高清VENC编码通道1080P H264 VENC_CHN_ATTR_S high_venc_attr; // 编码类型 H264 high_venc_attr.stVencAttr.enType RK_CODEC_TYPE_H264; // 输入图像格式 NV12 high_venc_attr.stVencAttr.imageType IMAGE_TYPE_NV12; // 编码分辨率 1920x1080 high_venc_attr.stVencAttr.u32PicWidth 1920; high_venc_attr.stVencAttr.u32PicHeight 1080; high_venc_attr.stVencAttr.u32VirWidth 1920; high_venc_attr.stVencAttr.u32VirHeight 1080; // H264 profile Baseline high_venc_attr.stVencAttr.u32Profile 66; // 不旋转 high_venc_attr.stVencAttr.enRotation VENC_ROTATION_0; // 码率控制模式CBR恒定码率 high_venc_attr.stRcAttr.enRcMode VENC_RC_MODE_H264CBR; // I帧间隔 high_venc_attr.stRcAttr.stH264Cbr.u32Gop 25; // 源帧率 25fps high_venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum 25; high_venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen 1; // 目标帧率 25fps high_venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum 25; high_venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen 1; // 码率 8Mbps high_venc_attr.stRcAttr.stH264Cbr.u32BitRate 8388608; // 创建高清VENC通道 ret RK_MPI_VENC_CreateChn(HIGH_VENC_CHN, high_venc_attr); if (ret) { printf(Set High_Venc_Attr Failed...\n); return 0; } else { printf(Set High_Venc_Attr Succeed...\n); } // 4. 初始化低清VENC编码通道720P H264 VENC_CHN_ATTR_S low_venc_attr; // 编码类型 H264 low_venc_attr.stVencAttr.enType RK_CODEC_TYPE_H264; low_venc_attr.stVencAttr.imageType IMAGE_TYPE_NV12; // 编码分辨率 1280x720 low_venc_attr.stVencAttr.u32PicWidth 1280; low_venc_attr.stVencAttr.u32PicHeight 720; low_venc_attr.stVencAttr.u32VirWidth 1280; low_venc_attr.stVencAttr.u32VirHeight 720; low_venc_attr.stVencAttr.u32Profile 66; low_venc_attr.stVencAttr.enRotation VENC_ROTATION_0; // CBR恒定码率 low_venc_attr.stRcAttr.enRcMode VENC_RC_MODE_H264CBR; low_venc_attr.stRcAttr.stH264Cbr.u32Gop 25; low_venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum 25; low_venc_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen 1; low_venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum 25; low_venc_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen 1; // 码率 8Mbps低清可以适当改小 low_venc_attr.stRcAttr.stH264Cbr.u32BitRate 8388608; // 创建低清VENC通道 ret RK_MPI_VENC_CreateChn(LOW_VENC_CHN, low_venc_attr); if (ret) { printf(Set Low_Venc_Attr Failed...\n); return 0; } else { printf(Set Low_Venc_Attr Succeed...\n); } // 5. 模块绑定建立数据链路 MPP_CHN_S vi_chn_s; vi_chn_s.enModId RK_ID_VI; vi_chn_s.s32ChnId VI_CHN_ID; MPP_CHN_S high_chn_s; high_chn_s.enModId RK_ID_VENC; high_chn_s.s32ChnId HIGH_VENC_CHN; // VI 绑定 高清VENC → 原图直接编码 ret RK_MPI_SYS_Bind(vi_chn_s, high_chn_s); if(ret) { printf(Vi Bind High_Venc Failed...\n); return -1; } else { printf(Vi Bind High_Venc Succeed...\n); } MPP_CHN_S rga_chn_s; rga_chn_s.enModId RK_ID_RGA; rga_chn_s.s32ChnId RGA_CHN_ID; // VI 绑定 RGA → 图像进入缩放流程 ret RK_MPI_SYS_Bind(vi_chn_s, rga_chn_s); if(ret) { printf(Vi Bind Rga Failed...\n); return -1; } else { printf(Vi Bind Rga Succeed...\n); } // 6. 创建三个工作线程 pthread_t high_venc_pid; pthread_t rga_pid; pthread_t low_venc_pid; // 高清取流线程 pthread_create(high_venc_pid, NULL, get_high_venc_thread, NULL); // RGA数据转发线程 pthread_create(rga_pid, NULL, rga_handle_thread, NULL); // 低清取流线程 pthread_create(low_venc_pid, NULL, get_low_venc_thread, NULL); // 主线程保持运行 while(1) { sleep(1); } // 7. 程序退出时资源释放 RK_MPI_SYS_UnBind(vi_chn_s, high_chn_s); RK_MPI_SYS_UnBind(vi_chn_s, rga_chn_s); RK_MPI_RGA_DestroyChn(RGA_CHN_ID); RK_MPI_VENC_DestroyChn(HIGH_VENC_CHN); RK_MPI_VENC_DestroyChn(LOW_VENC_CHN); RK_MPI_VI_DisableChn(PIPE_ID, VI_CHN_ID); return 0; }将代码编译移植进板子进行运行这部分流程参考vi模块的博客六、效果验证程序正常运行后会自动生成两个裸流文件高清test_high_venc.h2641080P低清test_low_venc.h264720P继续使用 ffplay 播放验证# 播放1080P高清码流 ffplay -x 600 test_high_venc.h264 # 播放720P低清码流 ffplay -x 500 test_low_venc.h264两路画面均可正常播放、无花屏、无卡顿代表双分辨率取流成功。七、关键注意点1、低清码流不能直接取 RGA 数据RGA 只做图像缩放不编码必须把缩放后的 NV12 数据转发给 VEN1 编码我们取流只能从 VENC 通道取。2、Buffer 必须成对释放无论是 RGA 取出的缓存还是 VENC 编码后的缓存必须 Release。 不释放会直接导致媒体池耗尽、编码器卡死、程序僵死。3、单 VI 支持多绑定RKMedia 支持一个 VI 同时绑定多个下游模块这也是双分辨率实现的核心基础。八、本章总结掌握了嵌入式摄像头高低双分辨率的标准工程架构理解 RGA 硬件缩放 双 VENC 并行编码的核心原理通过多线程隔离实现两路码流同时稳定获取、同时保存这套架构是后续双路 RTSP 推流、双路 MP4 录像、预览 录像分离的底层基础。