VENC 模块,即视频编码模块。本模块支持多路实时编码,且每路编码独立,编码协议和编码 profile 可以不同。
- 初始化
Rockit
//RK_MPI_SYS_Init()在`rk_mpi_sys.h`
if(RK_MPI_SYS_Init() != RK_SUCCESS){ //RK_SUCCESS 是 0
}
- 创建通道
告诉硬件编 H264、240×320、CBR 800kbps、GOP=25等。
VENC_CHN_ATTR_S chn_attr;
memset(&chn_attr, 0, sizeof(chn_attr));
/*
* 省略配置chn_attr相关参数
*/
RK_S32 ret = RK_MPI_VENC_CreateChn(0, &chn_attr);//第0通道
- 开启收帧
VENC_RECV_PIC_PARAM_S recv_param;
memset(&recv_param, 0, sizeof(recv_param));
recv_param.s32RecvPicNum = -1; /* -1 = 持续接收,不限帧数 */
RK_MPI_VENC_StartRecvFrame(0, &recv_param)
- RGA转换成NV12
rga_dma_buf_t g_src; /* CMA 源缓冲:BGRA 屏幕帧中转 */
rga_dma_buf_t g_dst; /* CMA 目的缓冲:NV12 转换结果 */
//构造 RGA 输入/输出 buffer
rga_buf_alloc(w, h, RK_FORMAT_BGRA_8888, &g_src);
rga_buf_alloc(w, h, RK_FORMAT_YCbCr_420_SP, &g_dst);
rga_buffer_t sb = rga_wrapbuffer(&g_src, g_w, g_h, RK_FORMAT_BGRA_8888,
src_stride / 4, g_h);
rga_buffer_t db = rga_wrapbuffer(&g_dst, g_w, g_h, RK_FORMAT_YCbCr_420_SP,
NV12_STRIDE, 0);
rga_lock(); //加锁
IM_STATUS st = imcvtcolor_t(sb, db, RK_FORMAT_BGRA_8888, RK_FORMAT_YCbCr_420_SP,
IM_RGB_TO_YUV_BT601_LIMIT, 1);
rga_unlock();
-
NV12 直接送 VENC
SendFrame只是把数据丢进编码器的输入队列,并不会直接返回编码后的数据。
const rga_dma_buf_t *dst = g_dst;//处理后的变成NV12的数据
/* 将 CMA DMA 缓冲包装成 VENC 能识别的 MB_BLK */
MB_EXT_CONFIG_S mb_cfg;
memset(&mb_cfg, 0, sizeof(mb_cfg));
/*
* 配置mb_cfg种种 ...
*/
// CMA NV12 缓冲包装成 MB_BLK
MB_BLK blk = NULL;
RK_S32 ret = RK_MPI_SYS_CreateMB(&blk, &mb_cfg);
/* 填充帧信息 */
VIDEO_FRAME_INFO_S frame_info;
memset(&frame_info, 0, sizeof(frame_info));
/*
* 配置frame_info种种 ...
*/
ret = RK_MPI_VENC_SendFrame(VENC_CHN_ID, &frame_info, 100);
/* 释放 MB_BLK 包装(底层 CMA 缓冲由 rga_buf 管理,不会被释放) */
RK_MPI_MB_ReleaseMB(blk);
-
取流(GetStream)
取队列中的数据
// ==============================================
// 接你上面的代码:已经成功 SendFrame 之后
// ==============================================
// 假设你要把编码后的数据存到 test.h264 文件
FILE *fp = fopen("test.h264", "wb");
if (!fp) {
printf("open file failed\n");
return -1;
}
// 循环编码,假设编码 1000 帧后退出(实际录屏是 while(1) 直到用户停止)
int frame_count = 0;
while (frame_count < 1000) {
// 1. 获取编码后的码流包(阻塞等待,直到编码器输出一帧)
VENC_STREAM_S stream;
memset(&stream, 0, sizeof(VENC_STREAM_S));
RK_S32 ret = RK_MPI_VENC_GetStream(0, &stream, 100); // 超时 100ms
if (ret == RK_SUCCESS) {
// 2. 遍历这一帧可能包含的多个 NALU(H264 分片)
for (int i = 0; i < stream.u32PackCount; i++) {
VENC_PACK_S *pkt = &stream.stPack[i];
// 把编码数据写入文件(或通过网络发送)
fwrite(pkt->pu8Addr, 1, pkt->u32Len, fp);
fflush(fp); // 录屏建议及时刷新,防止断电丢数据
// 打印调试:这一帧的大小
printf("编码帧 #%d, 大小: %d bytes\n", frame_count, pkt->u32Len);
}
// 3. 【极其重要】释放该帧占用的编码器内部 buffer,告诉硬件可以复用这块内存
RK_MPI_VENC_ReleaseStream(0, &stream);
frame_count++;
} else if (ret == RK_ERR_VENC_BUF_EMPTY) {
// 编码器还没产出数据(可能帧率太高,硬件来不及),可以 sleep(1) 或 yield
usleep(1000);
} else {
printf("GetStream 出错: %d\n", ret);
break;
}
}
fclose(fp);
整个流程
int main() {
// 1. 系统初始化
RK_MPI_SYS_Init();
// 2. 创建编码通道 & 开启收帧
VENC_CHN_ATTR_S chn_attr = {0};
// ... 配置 H264、码率、GOP ...
RK_MPI_VENC_CreateChn(0, &chn_attr);
RK_MPI_VENC_StartRecvFrame(0, NULL); // recv_param 为 NULL 默认无限收帧
// 3. 初始化 RGA 内存(分配 src 和 dst CMA 缓冲)
rga_dma_buf_t g_src, g_dst;
rga_buf_alloc(w, h, RK_FORMAT_BGRA_8888, &g_src);
rga_buf_alloc(w, h, RK_FORMAT_YCbCr_420_SP, &g_dst);
// 4. 进入主循环(录制中)
while (running) {
// 4.1 获取屏幕 RGB 数据到 g_src(通过 drm 或 fb 捕获,略)
capture_screen_to_rga_buf(&g_src);
// 4.2 RGA 色彩转换:BGRA -> NV12
rga_buffer_t sb = rga_wrapbuffer(...);
rga_buffer_t db = rga_wrapbuffer(...);
rga_lock();
imcvtcolor_t(sb, db, ...);
rga_unlock();
// 4.3 构造 MB_BLK(基于 g_dst 的 fd)并 SendFrame
MB_BLK blk = create_mb_from_dma_fd(g_dst.dma_fd);
VIDEO_FRAME_INFO_S frame = {0};
frame.pMbBlk = blk;
frame.u64PTS = get_current_time_ms();
RK_MPI_VENC_SendFrame(0, &frame, 100);
RK_MPI_MB_ReleaseMB(blk); // 立即释放包装
// 4.4 【关键】取编码后的码流
VENC_STREAM_S stream;
if (RK_MPI_VENC_GetStream(0, &stream, 50) == RK_SUCCESS) {
fwrite(stream.stPack[0].pu8Addr, 1, stream.stPack[0].u32Len, fp);
RK_MPI_VENC_ReleaseStream(0, &stream);
}
}
// 5. 销毁(收尾)
RK_MPI_VENC_StopRecvFrame(0);
RK_MPI_VENC_DestroyChn(0);
rga_buf_free(&g_src);
rga_buf_free(&g_dst);
RK_MPI_SYS_Exit();
fclose(fp);
return 0;
}