1. VENC 模块简介

在音视频开发中,VENC(Video Encoder,视频编码器)是将摄像头、屏幕录制等产生的原始视频帧(YUV/RGB)压缩为符合标准的视频码流(如 H.264、H.265)的核心模块。它直接影响最终视频的画质、延迟和文件大小,是实时通信、直播推流、安防监控等场景的基础设施。

VENC 模块通常位于采集(VI)、预处理(VPSS)之后,负责接收原始帧、根据配置参数编码、输出码流包。在不同的平台和 SDK 中,VENC 的实现和 API 可能不同(如海思的 HiMPP VENC、Android 的 MediaCodec、iOS 的 VideoToolbox、FFmpeg 的 libx264/libx265 等),但整体架构和工作流程高度相似。本文将从通用设计角度出发,介绍视频编码的关键概念、参数配置、码率控制、软硬编码选择及典型实践。

2. 视频编码基础

视频编码的核心目标是 消除冗余。原始视频数据量巨大(1080p@30fps 的 YUV420 每秒约 93 MB),不压缩几乎无法存储和传输。编码器通过以下三种技术实现高压缩比:

  • 帧内预测(Intra Prediction):利用同一帧内相邻像素的相关性进行预测,通常用于 I 帧(关键帧)。
  • 帧间预测(Inter Prediction):利用前后帧的时间相关性,通过运动估计和补偿生成 P 帧、B 帧。
  • 变换与量化:将空间域的残差数据变换到频域(如 DCT、DST),然后对高频系数进行粗量化,用视觉冗余换取压缩率。
  • 熵编码:对量化后的系数和语法元素进行无损压缩(如 CABAC、CAVLC),进一步减小码流。

编码器输出的码流由一系列的 NAL 单元组成,包含序列参数集(SPS)、图像参数集(PPS)、切片数据等。解码器根据这些信息重建视频帧。

3. 主流视频编码标准

  • H.264/AVC:目前应用最广泛的编码标准,兼容性好,硬编解码支持度高。Baseline/Main/High 三档 Profile 覆盖从低功耗到高清场景。
  • H.265/HEVC:在同等画质下码率比 H.264 降低约 50%,但编码复杂度更高,专利费用问题促使了 AV1 的兴起。
  • VP9:Google 主导的开源标准,广泛应用于 WebRTC 和 YouTube,开源友好且无专利风险。
  • AV1:由 AOMedia 联盟推出的新一代开源标准,压缩效率优于 H.265,但编码计算量极高,适合点播而非低延迟实时场景。
  • H.266/VVC:最新标准,进一步提升压缩率,但编码复杂度极高,目前普及度还较低。

实际项目中,通常根据设备能力、延迟要求和协议支持来选择编码格式。移动端优先考虑 H.264/H.265 硬编;Web 端可能使用 VP9/AV1;安防行业仍以 H.264/H.265 为主。

4. VENC 模块工作流程

典型的 VENC 模块处理流程如下:

是

否

接收原始帧(YUV/RGB)

前处理(缩放、格式转换)

图像缓冲区管理

是否关键帧?

帧内预测编码

帧间运动估计 + 补偿

变换/量化

熵编码

输出码流包(NAL)

回调/写入文件/推流

  • 输入:通常是 VI(视频输入)或 VPSS(视频处理子系统)处理后的物理地址块。
  • 通道(Channel):一个 VENC 模块可创建多个编码通道,每个通道独立配置编码参数。
  • 帧率控制:编码器可配置帧率(FPS),通过设置编码间隔控制输出帧数。
  • GOP 结构:决定 I/P/B 帧的编排,直接影响压缩率和随机访问性能。

5. 关键参数配置

不同 SDK 的参数可能命名不同,但核心配置项相似。以下以通用参数为例:

参数含义典型值
编码格式视频压缩标准H.264, H.265, VP9 等
分辨率输出视频宽高1920x1080, 1280x720
比特率 / 码率每秒输出的比特数2 Mbps (1080p), 1 Mbps (720p)
帧率每秒帧数25, 30
GOP 长度 / I 帧间隔两个 I 帧之间的帧数通常为帧率的 1~2 倍(如 50)
Profile编码工具集Baseline, Main, High
码率控制模式CBR / VBR / CVBR / AVBR见下一节
输入格式原始帧格式NV12, YUV420, RGBA
缓冲区大小码流输出缓冲区的大小根据最大码率估算

下面是一个瑞芯微rv1126平台 VENC 属性配置的简化示例(C 语言):

VENC_CHN_ATTR_S venc_chn_attr;
    //******     VENC Attr Set   ************************//
    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_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;
    venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
    venc_chn_attr.stVencAttr.enRotation = VENC_ROTATION_0;

    //********* VENC RCMODE Set  *******************//
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;
    //25/1 NUM/DEN == FrameRate
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1; //
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = 8388608; //1M
    ret = RK_MPI_VENC_CreateChn(VENC_CHN, &venc_chn_attr);


6. 码率控制策略

码率控制直接决定画面质量与带宽占用的平衡。常见策略如下:

  • CBR(Constant Bit Rate) – 恒定码率。无论画面复杂度如何,都输出固定比特,优点是带宽稳定、可预测;缺点是复杂画面可能质量下降(块效应)。适用于实时通信、直播。
  • VBR(Variable Bit Rate) – 可变码率。允许码率在设定范围内波动,简单画面码率低,复杂画面码率高。优点是画质更稳定,但带宽波动大。适用于点播、本地录制。
  • CVBR(Constrained VBR) – 受限 VBR,限制了最高/最低码率,兼顾画质和带宽。
  • AVBR(Average VBR) – 长期平均码率接近设定值,短期可波动。
  • CQP(Constant QP) – 固定量化参数,不控制码率,适合测试或对码率无要求的场景。

选择策略时需考虑场景:安防摄像机通常使用 CBR 或 CVBR,以保证网络传输稳定;而手机本地录像可能采用 VBR 以获得更高画质。

7. 硬编码与软编码对比

维度硬编码(Hardware Encoder)软编码(Software Encoder)
实现方式专用 ASIC 或 GPU 编码单元CPU 软件算法(如 x264, x265)
性能低 CPU 占用,适合高帧率/多路占用 CPU,路数受限
画质同码率下通常弱于软编码可通过算法优化取得更好质量
灵活性参数调整受限,由硬件决定可精细调节,支持更多特性
延迟低延迟,适合实时场景可配置低延迟,但资源消耗大
典型 APIMediaCodec (Android), VideoToolbox (iOS), VAAPI (Linux)libx264, libx265, FFmpeg 软件编码

实际开发中,移动端通常优先使用硬编码以降低功耗和发热;安防嵌入式 SoC(如海思、瑞芯微)内部集成硬编码器,配合专有 SDK 实现高性能编码;而在需要极致画质或参数可控性时,可能回退到软编码。

8. 实践:编码一个视频流

#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>

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

#define CAMERA_PATH "rkispp_scale0"
#define CAMERA_ID 0
#define CAMERA_CHN 0
#define VENC_CHN 0

void * get_h264_venc_thread(void * args)
{
    pthread_detach(pthread_self());
    FILE * h264_file = fopen("test_camera.h264", "w+");
    MEDIA_BUFFER mb;

    while (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");
        fwrite(RK_MPI_MB_GetPtr(mb),RK_MPI_MB_GetSize(mb),1, h264_file);
        RK_MPI_MB_ReleaseBuffer(mb);
    }

    return NULL;
}




int main(int argc, char *argv[])
{
    int ret;
    VI_CHN_ATTR_S vi_chn_attr;
    vi_chn_attr.pcVideoNode = CAMERA_PATH; //Path
    vi_chn_attr.u32Width = 1920; //Width
    vi_chn_attr.u32Height = 1080; //Height
    vi_chn_attr.enPixFmt = IMAGE_TYPE_NV12; //ImageType
    vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;//BufType
    vi_chn_attr.u32BufCnt = 3; //Cnt
    vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL; //Mode
    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 Success.....\n");
    }

    ret = RK_MPI_VI_EnableChn(CAMERA_ID, CAMERA_CHN);
    if(ret)
    {
        printf("Vi Enable Attr Failed.....\n");
        return 0;
    }
    else
    {
        printf("Vi Enable Attr Success.....\n");
    }

    VENC_CHN_ATTR_S venc_chn_attr;
    //******     VENC Attr Set   ************************//
    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_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;
    venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
    venc_chn_attr.stVencAttr.enRotation = VENC_ROTATION_0;

    //********* VENC RCMODE Set  *******************//
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
    venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;
    //25/1 NUM/DEN == FrameRate
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1; //
    venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = 8388608; //1M
    ret = RK_MPI_VENC_CreateChn(VENC_CHN, &venc_chn_attr);
    if(ret)
    {
        printf("Create  Venc Failed .....\n");
        return 0;
    }
    else
    {
        printf("Create  Venc Success .....\n");
    }

    MPP_CHN_S vi_chn_s;
    vi_chn_s.enModId = RK_ID_VI;
    vi_chn_s.s32ChnId = CAMERA_CHN;

    MPP_CHN_S venc_chn_s;
    venc_chn_s.enModId = RK_ID_VENC;
    venc_chn_s.s32ChnId = VENC_CHN;
    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  Success .....\n");
    }

    pthread_t pid;
    pthread_create(&pid, NULL, get_h264_venc_thread, NULL);

    while (1)
    {
        sleep(1);
    }

    RK_MPI_VI_DisableChn(CAMERA_ID, CAMERA_CHN);
    RK_MPI_VENC_DestroyChn(VENC_CHN);
    RK_MPI_SYS_UnBind(&vi_chn_s, &venc_chn_s);

    return 0;
}

9. 总结

VENC 模块是音视频系统中的关键环节,理解其内部原理、编码标准、码率控制和软硬差异,有助于开发者在不同平台上做出最优选择。无论是使用 Android 的 MediaCodec,还是 Linux 嵌入式平台的私有 VENC,甚至基于 FFmpeg 的软件方案,背后的设计思路都是相通的。希望本文能为你的音视频开发之路提供一份清晰的 VENC 模块参考。

Logo

邀请您加入社区

更多推荐