4. 视频编码H.264
任务目标
本案例基于嵌入式Linux平台(以Rockchip RK3588为例),调用硬件编码器(MPP编码模块)将采集到的原始NV12视频帧编码为H.264码流,并输出可写入文件的编码数据。通过本案例,你将掌握硬件编码器的初始化、帧输入、码流输出及资源释放的完整流程。
环境依赖
- 硬件平台:RK3588(或其他Rockchip系列芯片)
- 系统依赖:Linux内核启用V4L2及Rockchip MPP驱动
- 用户态库:
librga(可选,用于格式转换)、rockchip_mpp(核心编码库) - 编译工具:交叉编译器(如aarch64-linux-gnu-gcc)
核心实现
以下代码实现从NV12原始帧到H.264码流的完整编码流程。代码通过MPP的编码通道API完成硬件编码,并将编码后的数据包写入输出文件。输入帧数据由程序内部生成(模拟采集),实际使用时替换为摄像头或HDMI采集的帧数据即可。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "rk_mpi.h"
#include "mpp_buffer.h"
#include "mpp_frame.h"
#include "mpp_packet.h"
#define FRAME_WIDTH 1280
#define FRAME_HEIGHT 720
#define FRAME_SIZE (FRAME_WIDTH * FRAME_HEIGHT * 3 / 2) // NV12格式
static int create_test_frame(void *buf, int width, int height) {
// 生成测试图像:上半部分为灰色,下半部分为白色
uint8_t *y = (uint8_t *)buf;
uint8_t *uv = y + width * height;
for (int i = 0; i < height; i++) {
memset(y + i * width, (i < height / 2) ? 128 : 235, width);
}
memset(uv, 128, width * height / 2); // UV分量设为中性值
return 0;
}
int main(int argc, char **argv) {
MppCtx ctx = NULL;
MppApi *mpi = NULL;
MppEncCfg cfg = NULL;
MppBuffer frm_buf = NULL;
MppPacket pkt = NULL;
FILE *out_fp = NULL;
int ret = 0;
int frame_count = 30; // 编码30帧
// 1. 初始化编码器上下文
ret = mpp_create(&ctx, &mpi);
if (ret != MPP_OK) {
printf("mpp_create failed\n");
return -1;
}
// 2. 配置编码参数
mpp_enc_cfg_init(&cfg);
mpp_enc_cfg_set_s32(cfg, "prep:width", FRAME_WIDTH);
mpp_enc_cfg_set_s32(cfg, "prep:height", FRAME_HEIGHT);
mpp_enc_cfg_set_s32(cfg, "prep:format", MPP_FMT_NV12);
mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingAVC);
mpp_enc_cfg_set_s32(cfg, "rc:mode", MPP_ENC_RC_MODE_CBR);
mpp_enc_cfg_set_s32(cfg, "rc:bps", 2000000); // 2Mbps
mpp_enc_cfg_set_s32(cfg, "rc:bps_max", 2000000);
mpp_enc_cfg_set_s32(cfg, "rc:bps_min", 2000000);
mpp_enc_cfg_set_s32(cfg, "codec:gop", 30); // I帧间隔
mpp_enc_cfg_set_s32(cfg, "codec:qp_init", 26);
ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
if (ret != MPP_OK) {
printf("set cfg failed\n");
goto exit;
}
// 3. 准备输入帧缓冲区
ret = mpp_buffer_get(NULL, &frm_buf, FRAME_SIZE);
if (ret != MPP_OK) {
printf("get buffer failed\n");
goto exit;
}
// 4. 打开输出文件
out_fp = fopen("output.h264", "wb");
if (!out_fp) {
printf("open output file failed\n");
goto exit;
}
// 5. 编码循环
for (int i = 0; i < frame_count; i++) {
// 生成或获取一帧原始数据
create_test_frame(mpp_buffer_get_ptr(frm_buf), FRAME_WIDTH, FRAME_HEIGHT);
// 发送帧到编码器
MppFrame frame = NULL;
mpp_frame_init(&frame);
mpp_frame_set_buffer(frame, frm_buf);
mpp_frame_set_width(frame, FRAME_WIDTH);
mpp_frame_set_height(frame, FRAME_HEIGHT);
mpp_frame_set_fmt(frame, MPP_FMT_NV12);
mpp_frame_set_pts(frame, i * 33333); // 33.3ms间隔
ret = mpi->encode_put_frame(ctx, frame);
mpp_frame_deinit(&frame);
if (ret != MPP_OK) {
printf("encode_put_frame failed at frame %d\n", i);
break;
}
// 获取编码后的码流包
ret = mpi->encode_get_packet(ctx, &pkt);
if (ret == MPP_OK && pkt) {
void *data = mpp_packet_get_pos(pkt);
size_t len = mpp_packet_get_length(pkt);
fwrite(data, 1, len, out_fp);
printf("Frame %d encoded, size: %zu bytes\n", i, len);
mpp_packet_deinit(&pkt);
} else {
printf("encode_get_packet failed at frame %d\n", i);
}
}
// 6. 发送EOS并获取剩余码流
mpi->encode_put_frame(ctx, NULL);
while (1) {
ret = mpi->encode_get_packet(ctx, &pkt);
if (ret != MPP_OK || !pkt) break;
void *data = mpp_packet_get_pos(pkt);
size_t len = mpp_packet_get_length(pkt);
fwrite(data, 1, len, out_fp);
mpp_packet_deinit(&pkt);
}
exit:
// 7. 资源清理
if (out_fp) fclose(out_fp);
if (frm_buf) mpp_buffer_put(frm_buf);
if (cfg) mpp_enc_cfg_deinit(cfg);
if (ctx) {
mpi->reset(ctx);
mpp_destroy(ctx);
}
printf("Encoding complete. Output: output.h264\n");
return ret;
}
编译与运行
# 交叉编译(假设使用RK提供的工具链)
aarch64-linux-gnu-gcc -o h264_encoder h264_encoder.c -I/usr/include/rockchip -lmpp
# 将可执行文件拷贝到目标板并运行
./h264_encoder
验证方法
- 运行输出检查:程序执行后应打印每帧编码后的字节数(通常I帧较大,P帧较小),最终生成
output.h264文件。 - 码流完整性验证:在PC上使用FFmpeg检查输出文件:
ffprobe output.h264
# 预期输出:编码格式为h264,分辨率1280x720,像素格式yuv420p
- 解码回放验证:使用FFmpeg解码并统计帧数:
ffmpeg -i output.h264 -f null -
# 预期输出:解码30帧无错误,无丢帧警告
通过条件:ffprobe能正确识别H.264编码格式与分辨率,ffmpeg解码无报错且帧数为30,即证明硬件编码器工作正常。
常见问题
- 编码器初始化失败:确认内核已加载
rkvdec/rkvenc驱动,且/dev/mpp_service节点存在。 - 输出文件为空:检查编码参数中
rc:mode是否设置为CBR或VBR,部分固件默认VBR可能导致码率波动。 - 帧率不匹配:实际采集帧率与
pts设置不一致时,播放可能出现快慢不均,需根据实际采集时间戳设置pts。