news 2026/6/10 8:09:33

RV1126——多线程获取高分辨率和低分辨率的H264码流

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RV1126——多线程获取高分辨率和低分辨率的H264码流

前面两章我们已经搞定了单路 H264 码流获取H264/H265 双编码码流获取。 都是固定分辨率取流,虽然能满足基础录像需求,但在真实的 IPC 摄像头项目中远远不够。

实际项目里几乎全部是双分辨率方案: 一路高清 1080P做本地精细录像存档,一路低清 720P做网页预览、手机实时查看、低带宽推流。

所以这一章我们继续基于 RV1126 + RKMedia,实战实现:一帧摄像头画面,同时输出 1080P 高分辨率、720P 低分辨率两路 H264 码流,多线程独立获取并保存。

一、为什么要做双分辨率码流?

简单说就是各司其职

  1. 高分辨率(1080P):画质清晰、细节完整,用于本地录像回放、取证存档;
  2. 低分辨率(720P):体积小、码率低、占用带宽少,专门用于网络预览、实时直播。

如果只存 1080P,带宽小的设备会卡、存储空间消耗巨大; 如果只用 720P,录像画质太差,达不到监控要求。

所以高低双分辨率并行是嵌入式摄像头的标配方案。

二、RKMedia 双分辨率实现原理

很多新手会踩坑:以为 VI 可以直接输出两路不同分辨率数据

这里纠正一个关键知识点: VI 采集出来的原始 NV12 画面分辨率是固定的,无法直接编码出两种尺寸

正确的硬件链路流程:VI 采集原图 → 一路直通高清 VENC 编码 → 一路经过 RGA 缩放 → 低清 VENC 编码

完整数据流:

  1. VI 模块采集 1920×1080 原始画面
  2. 原始画面分两路分发
  3. 第一路直接送入 VENC0,编码 1080P 高清 H264
  4. 第二路送入 RGA 硬件缩放为 1280×720
  5. 缩放后的画面送入 VENC1,编码 720P 低清 H264
  6. 开启两个独立线程分别获取两路码流,同时开启 RGA 转发线程处理缩放数据

三、为什么需要多线程?

和之前章节逻辑一致,再次强调工程规范:

RKMedia 的GetMediaBuffer阻塞接口。 如果所有逻辑放主线程,会导致:

  • 编码阻塞卡死
  • 数据无法流转
  • 严重丢帧、链路异常

所以本章我们开启3 个独立子线程分工处理:

  1. 高清取流线程:专门读取 1080P VENC 码流、保存文件
  2. 低清取流线程:专门读取 720P VENC 码流、保存文件
  3. 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.h264(1080P)
  • 低清:test_low_venc.h264(720P)

继续使用 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 同时绑定多个下游模块,这也是双分辨率实现的核心基础。

八、本章总结

  1. 掌握了嵌入式摄像头高低双分辨率的标准工程架构;
  2. 理解 RGA 硬件缩放 + 双 VENC 并行编码的核心原理;
  3. 通过多线程隔离,实现两路码流同时稳定获取、同时保存
  4. 这套架构是后续双路 RTSP 推流、双路 MP4 录像、预览 + 录像分离的底层基础。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 8:07:04

新乡高考志愿填报指导

每年高考结束后&#xff0c;新乡的考生和家长们都会面临一个重要的选择——志愿填报。这个过程不仅关系到未来几年的学习环境&#xff0c;更影响着长远的职业发展。然而&#xff0c;信息不对称、专业选择迷茫等问题常常困扰着大家。与那些只提供表面化建议的服务不同&#xff0…

作者头像 李华
网站建设 2026/6/10 8:02:12

HCS12单片机模糊控制指令集深度解析与实战应用

1. 项目概述&#xff1a;在HCS12单片机上实现高效模糊控制在嵌入式控制领域&#xff0c;我们常常会遇到一些“说不清、道不明”的控制难题。比如&#xff0c;你怎么用精确的数学公式去描述“水温有点凉&#xff0c;需要稍微加热一点”这种人类直觉&#xff1f;传统的PID控制器在…

作者头像 李华
网站建设 2026/6/10 7:59:17

TikTok评论数据采集难题:浏览器控制台自动化解决方案

TikTok评论数据采集难题&#xff1a;浏览器控制台自动化解决方案 【免费下载链接】TikTokCommentScraper 项目地址: https://gitcode.com/gh_mirrors/ti/TikTokCommentScraper 在社交媒体数据分析领域&#xff0c;TikTok评论数据采集一直是个技术挑战。传统的API方式受…

作者头像 李华
网站建设 2026/6/10 7:58:13

VC++ MFC轻量图表库:折线图、饼图、柱状图三合一绘图源码包

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;一套开箱即用的VC MFC图表绘制源码&#xff0c;专注在原生Windows桌面应用中实现数据可视化。内置折线图、饼图、柱状图三种基础图表类型&#xff0c;全部基于MFC GDI接口开发&#xff0c;不依赖任何第三方图形…

作者头像 李华
网站建设 2026/6/10 7:54:11

2026年京东云OpenClaw/Hermes Agent配置Token Plan超全安装步骤

2026年京东云OpenClaw/Hermes Agent配置Token Plan超全安装步骤。OpenClaw是开源的个人AI助手&#xff0c;Hermes Agent则是一个能自我进化的AI智能体框架。阿里云提供计算巢、轻量服务器及无影云电脑三种部署OpenClaw 与 Hermes Agent的方案、百炼Token Plan兼容主流 AI 工具&…

作者头像 李华