音视频开发 - VENC 模块深入解析
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 模块处理流程如下:
- 输入:通常是 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,路数受限 |
| 画质 | 同码率下通常弱于软编码 | 可通过算法优化取得更好质量 |
| 灵活性 | 参数调整受限,由硬件决定 | 可精细调节,支持更多特性 |
| 延迟 | 低延迟,适合实时场景 | 可配置低延迟,但资源消耗大 |
| 典型 API | MediaCodec (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 模块参考。
更多推荐
所有评论(0)