RV1136——获取VENC的H264码流

上一章我们把视频编码原理讲透了:原始 YUV 数据太大,必须通过 H.264 编码压缩,才能真正用于存储、直播、网络传输。我们也认识了 SPS、PPS、I 帧、码率这些核心概念。

理论落地才有价值,这一章我们直接上实战代码 : 在 RV1126 平台上,通过RKMedia + 多线程 ,从 VENC 硬件编码模块中,实时获取标准 H.264 码流数据 ,并保存成可直接播放的 .h264 文件。

这是做摄像头录像、RTSP 推流、网络传输的最基础、最核心一步。

前言

在 RKMedia 架构里,VENC 是负责硬件编码的模块。它会把 VI 采集、RGA 缩放后的图像,自动压缩成 H.264 码流。

但有一个关键点: 编码出来的 H.264 数据,不能直接在主线程里读,必须开一个独立线程去取。

原因很简单:

  • 主线程要负责模块初始化、链路管理;
  • 取码流是一个阻塞等待的过程;
  • 如果不放线程里,会导致整个程序卡住,甚至丢帧。

所以这一章的核心就是: 初始化 VENC → 绑定 RGA 与 VENC → 开线程循环取 H.264 数据 → 保存文件

一、整体流程梳理(和前面的章节 VI+RGA 衔接)

我们的整体数据流是这样的:

复制代码
摄像头(VI) → RGA硬件缩放 → VENC硬件编码 → 多线程获取H.264码流 → 保存文件

对应代码执行步骤:

  1. 初始化系统(RK_MPI_SYS_Init)
  2. 初始化 VI(摄像头采集)
  3. 初始化 VENC(H.264 编码配置)
  4. 绑定模块:VI → VENC
  5. 创建线程,专门获取 VENC 输出的 H.264 数据
  6. 启动所有模块数据流
  7. 线程中循环取码流、写文件
  8. 程序退出时释放资源

二、VI模块初始化

2.1 初始化 VI 模块:

在取码流之前,必须先告诉 VENC 怎么编码:分辨率多大、编码格式 H.264、码率多少、I 帧间隔多大、是否开启 SPS/PPS 等。

这些参数都在 VENC_ATTR_S 结构体里配置。

关键配置项

  • 编码类型:H.264
  • 输入分辨率:和 RGA 输出保持一致(比如 1280×720)
  • 码率设置:CBR/VBR,比如设置 2Mbps
  • GOP(I 帧间隔):一般设 30~60 帧一个 I 帧
  • 帧率:25fps
  • 开启编码缓冲池

这些参数直接决定画质、流畅度、文件大小

VI模块的初始化实际上就是对VI_CHN_ATTR_S的参数进行设置、然后调用RK_MPI_VI_SetChnAttr 设置VI模块并使能RK_MPI_VI_EnableChn **,**代码如下:

2.2 初始化 VENC 模块:

VENC模块的初始化实际上就是对VENC_CHN_ATTR_S的参数进行设置、然后调用RK_MPI_VENC_CreateChn 创建编码器**,**代码如下:

复制代码
VENC_CHN_ATTR_S venc_chn_attr;

venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;

。。。。。。。。。。。。。。。。(这里是设置VENC的属性)

ret = RK_MPI_VENC_CreateChn(0, &venc_chn_attr);

2.3 绑定 VI 节点和 VENC 节点:

绑定VI节点和VENC节点,使其两个模块能够关联起来,使用的API是RK_MPI_SYS_Bind 关于这个API的定义,之前说过了,这里不在阐述。代码如下:

复制代码
MPP_CHN_S vi_chn_s;

vi_chn_s.enModId = RK_ID_VI;

vi_chn_s.s32ChnId = 0;

MPP_CHN_S venc_chn_s;

venc_chn_s.enModId = RK_ID_VENC;

venc_chn_s.s32ChnId = 0;

ret = RK_MPI_SYS_Bind(&vi_chn_s, &venc_chn_s);

2.4 开启多线程采集 VENC 的数据

为什么必须用多线程取码流?

很多新手直接把取码流的逻辑写在主线程,结果一运行就出现:

  • 画面卡顿
  • 码流丢失
  • 程序卡死
  • 编码器报 timeout

原因: RK_MPI_SYS_GetMediaBuffer 这个接口是阻塞式的 ------ 没有数据时,它会一直等。

如果放在主线程,整个程序都会被卡住,其他模块无法正常运行。

标准做法:开一个独立线程,死循环取码流。 线程只做三件事:

  1. 从 VENC 模块取一帧 H.264 数据
  2. 把数据写入文件
  3. 释放码流 buffer(必须释放!)

使用的API是RK_MPI_SYS_GetMediaBuffer 模块ID是RK_ID_VENC,通道号ID是VENC创建的ID号**。** 这个API的具体作用已经在之前的获取VI数据的博客已经讲解过,我们直接上代码

复制代码
while(1)

{

  .........................

  mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, s32_chn_id, -1);

  fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, h264_file);

.......................

}

三、代码

复制代码
#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、VENC、Bind等所有接口
#include "rkmedia_api.h"

// ===================== 宏定义 =====================
#define CAMERA_PATH "rkispp_scale0"  // 摄像头V4L2设备节点
#define CAMERA_ID    0               // 摄像头管道ID,默认0
#define CAMERA_CHN   0               // 摄像头通道号,默认0
#define VENC_CHN     0               // 硬件编码通道号,默认0

// ===================== 线程函数 =====================
/**
 * @brief 从VENC获取H264码流的线程函数
 * @param args 线程参数,未使用
 * @return 线程退出指针
 */
void * get_h264_venc_thread(void *args)
{
    // 将线程设置为分离模式,自动释放资源,无需主线程pthread_join回收
    pthread_detach(pthread_self());

    // 以读写方式打开文件,保存H264裸码流
    FILE * h264_file = fopen("test_camre.h264","w+");
    // 定义媒体缓存结构体,用于接收编码后的数据
    MEDIA_BUFFER mb;

    // 无限循环,持续从VENC取码流
    while(1)
    {
        // 从VENC模块阻塞获取一帧H264数据
        // RK_ID_VENC:指定从编码模块取流
        // VENC_CHN:编码通道号
        // -1:永久阻塞等待,直到有数据
        mb = RK_MPI_SYS_GetMediaBuffer(RK_ID_VENC, VENC_CHN, -1);
        
        // 没有获取到缓存,退出循环
        if (!mb)
        {
            printf("Get Venc Mb Break...\n");
            break;
        }
        printf("Get H264 One Frame...\n");

        // 将获取到的H264数据写入文件
        // RK_MPI_MB_GetPtr(mb):获取数据缓冲区指针
        // RK_MPI_MB_GetSize(mb):获取数据长度
        fwrite(RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), 1, h264_file);

        // 必须释放缓存,归还给VENC缓冲池,否则会导致缓存耗尽
        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通道属性配置结构体
    vi_chn_attr.pcVideoNode = CAMERA_PATH;    // 绑定摄像头设备节点
    vi_chn_attr.u32Width = 1920;              // 摄像头输出宽度 1920
    vi_chn_attr.u32Height = 1080;             // 摄像头输出高度 1080
    vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12;   // 图像格式 NV12
    vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;  // 内存映射模式
    vi_chn_attr.u32BufCnt = 3;                // VI缓存帧数
    vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;  // 正常工作模式

    // 设置VI通道属性
    ret = RK_MPI_VI_SetChnAttr(CAMERA_ID, CAMERA_CHN, &vi_chn_attr);
    if(ret)
    {
        printf("Vi Set Attr Failed...\n");
        return 0;
    }
    else
    {
        printf("Vi Set Attr Succeed...\n");
    }

    // 使能VI通道,开启摄像头硬件
    ret = RK_MPI_VI_EnableChn(CAMERA_ID, CAMERA_CHN);
    if(ret)
    {
        printf("Vi Enable Attr Failed...\n");
        return 0;
    }
    else
    {
        printf("Vi Enable Attr Succeed...\n");
    }

    // ===================== 2. 初始化VENC硬件编码模块 =====================
    VENC_CHN_ATTR_S venc_chn_attr;  // VENC通道配置结构体

    // 基础编码配置
    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;  // 编码类型 H264
    venc_chn_attr.stVencAttr.u32PicWidth = 1920;          // 编码图像宽度
    venc_chn_attr.stVencAttr.u32PicHeight = 1080;         // 编码图像高度
    venc_chn_attr.stVencAttr.u32VirWidth = 1920;          // 虚拟宽度(与输入一致)
    venc_chn_attr.stVencAttr.u32VirHeight = 1080;        // 虚拟高度(与输入一致)
    venc_chn_attr.stVencAttr.u32Profile = 66;            // H264 profile 66=Baseline
    venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12; // 输入图像格式NV12
    venc_chn_attr.stVencAttr.enRotation = VENC_ROTATION_0; // 不旋转

    // 码率控制配置:H264 CBR 恒定码率模式
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;  // I帧间隔,25帧一个I帧
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;  // 源帧率 分子
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;  // 源帧率 分母
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;// 目标帧率 分子
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1; // 目标帧率 分母
    venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = 8388608;     // 码率 8Mbps

    // 创建VENC编码通道
    ret = RK_MPI_VENC_CreateChn(VENC_CHN, &venc_chn_attr);
    if(ret)
    {
        printf("Venc Create Failed...\n");
        return 0;
    }
    else
    {
        printf("Venc create Succeed...\n");
    }

    // ===================== 3. 绑定VI和VENC模块 =====================
    // 定义源模块:VI
    MPP_CHN_S vi_chn_s;
    vi_chn_s.enModId = RK_ID_VI;
    vi_chn_s.s32ChnId = CAMERA_CHN;

    // 定义目标模块:VENC
    MPP_CHN_S venc_chn_s;
    venc_chn_s.enModId = RK_ID_VENC;
    venc_chn_s.s32ChnId = VENC_CHN;

    // 执行绑定:VI输出 → VENC输入,自动流转数据
    ret = RK_MPI_SYS_Bind(&vi_chn_s, &venc_chn_s);
     if(ret)
    {
        printf("RK_MPI_SYS_Bind Failed...\n");
        return 0;
    }
    else
    {
        printf("RK_MPI_SYS_Bind Succeed...\n");
    }

    // ===================== 4. 创建取流线程 =====================
    pthread_t pid;
    // 创建线程,执行get_h264_venc_thread函数,从VENC取码流
    pthread_create(&pid, NULL, get_h264_venc_thread, NULL);

    // 主线程休眠,保持程序运行
    while(1)
    {
        sleep(1);
    }

    // ===================== 5. 资源释放(正常退出时执行) =====================
    RK_MPI_VI_DisableChn(CAMERA_ID, CAMERA_CHN);   // 关闭VI通道
    RK_MPI_VENC_DestroyChn(VENC_CHN);             // 销毁VENC通道
    RK_MPI_SYS_UnBind(&vi_chn_s, &venc_chn_s);    // 解绑模块

    return 0;
}

四、运行效果与验证

程序移植进板子并运行,具体流程前面的章节讲的很清楚了,这里不再赘述,程序运行成功后,ssh会出现这样一个画面

这就意味着采集成功了。

会在当前目录生成一个 test_camre.h264 文件。

这个文件可以直接用 ffmpeg播放(播放的具体流程也在前面的博客):

复制代码
ffplay.exe test_canre.h264

出现这个画面:

说明能正常播放、画面清晰不花屏,说明码流获取成功。

五、本章小结

这一章我们完成了从 "理论编码" 到 "实战取流" 的关键一步:

  1. 学会了 VENC 初始化配置
  2. 理解了 为什么必须用多线程取码流
  3. 掌握了 RKMedia 取 H.264 标准码流 的标准写法
  4. 实现了 保存可播放 H.264 文件
相关推荐
音视频牛哥1 小时前
SmartMediaKit 还是云服务厂商?企业级音视频系统选型背后的技术逻辑
音视频·大牛直播sdk·低延迟rtsp播放器·轻量级rtsp服务器·rtmp同屏推流·smartmediakit·低延迟rtmp播放器
李二。11 小时前
鸿蒙原生ArkTS-鸿蒙6.0新特性-动态模糊视频背景登录页
华为·音视频·harmonyos
菊风 Juphoon11 小时前
认证合规刚需落地|菊风智能双录,助力CA机构全流程合规留痕
音视频·双录·认证双录
大蚂蚁2号12 小时前
本地视频转文字,音频转文字免费工具
音视频
换个昵称都难14 小时前
webrtc voice engine 介绍(新版webrtc)
ffmpeg·音视频·webrtc
searchforAI14 小时前
2026年AI笔记工具对比实测:NotebookLM、通义听悟、Ai好记怎么选?
人工智能·笔记·gpt·ai·whisper·音视频·语音识别
音视频牛哥17 小时前
基于 SmartMediaKit 的座舱远程遥控系统技术方案——面向多摄像头、多角度、低延迟传输控制与内网/5G广域网融合场景
音视频·低延迟rtsp播放器·低延迟视频传输·座舱远程遥控·5g远程控制·远程驾驶系统·低延迟rtmp播放
EasyGBS18 小时前
从“后厨黑箱”到“透明厨房”:国标GB28181视频平台EasyGBS平台AI视频分析如何守护舌尖上的安全
人工智能·安全·音视频
昨日之日200618 小时前
LongCat-Video-Avatar-1.5 - 一句话生成口型同步、动作稳定的数字人 说话/唱歌 视频 一键整合包下载
音视频