第六章 mpi_enc_test.c
/*
* Copyright 2015 Rockchip Electronics Co. LTD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if defined(_WIN32)
#include "vld.h"
#endif
#define MODULE_TAG "mpi_enc_test"
#include
#include "rk_mpi.h"
#include "mpp_env.h"
#include "mpp_mem.h"
#include "mpp_time.h"
#include "mpp_debug.h"
#include "mpp_common.h"
#include "utils.h"
#include "mpi_enc_utils.h"
#include "camera_source.h"
#include "mpp_enc_roi_utils.h"
typedef struct {
// base flow context
MppCtx ctx;
MppApi *mpi;
RK_S32 chn;
// global flow control flag
RK_U32 frm_eos;
RK_U32 pkt_eos;
RK_U32 frm_pkt_cnt;
RK_S32 frame_num;
RK_S32 frame_count;
RK_U64 stream_size;
/* end of encoding flag when set quit the loop */
volatile RK_U32 loop_end;
// src and dst
FILE *fp_input;
FILE *fp_output;
FILE *fp_verify;
/* encoder config set */
MppEncCfg cfg;
MppEncPrepCfg prep_cfg;
MppEncRcCfg rc_cfg;
MppEncCodecCfg codec_cfg;
MppEncSliceSplit split_cfg;
MppEncOSDPltCfg osd_plt_cfg;
MppEncOSDPlt osd_plt;
MppEncOSDData osd_data;
RoiRegionCfg roi_region;
MppEncROICfg roi_cfg;
// input / output
MppBufferGroup buf_grp;
MppBuffer frm_buf;
MppBuffer pkt_buf;
MppBuffer md_info;
MppEncSeiMode sei_mode;
MppEncHeaderMode header_mode;
// paramter for resource malloc
RK_U32 width;
RK_U32 height;
RK_U32 hor_stride;
RK_U32 ver_stride;
MppFrameFormat fmt;
MppCodingType type;
RK_S32 loop_times;
CamSource *cam_ctx;
MppEncRoiCtx roi_ctx;
// resources
size_t header_size;
size_t frame_size;
size_t mdinfo_size;
/* NOTE: packet buffer may overflow */
size_t packet_size;
RK_U32 osd_enable;
RK_U32 osd_mode;
RK_U32 split_mode;
RK_U32 split_arg;
RK_U32 split_out;
RK_U32 user_data_enable;
RK_U32 roi_enable;
// rate control runtime parameter
RK_S32 fps_in_flex;
RK_S32 fps_in_den;
RK_S32 fps_in_num;
RK_S32 fps_out_flex;
RK_S32 fps_out_den;
RK_S32 fps_out_num;
RK_S32 bps;
RK_S32 bps_max;
RK_S32 bps_min;
RK_S32 rc_mode;
RK_S32 gop_mode;
RK_S32 gop_len;
RK_S32 vi_len;
RK_S64 first_frm;
RK_S64 first_pkt;
} MpiEncTestData;
/* For each instance thread return value */
typedef struct {
float frame_rate;
RK_U64 bit_rate;
RK_S64 elapsed_time;
RK_S32 frame_count;
RK_S64 stream_size;
RK_S64 delay;
} MpiEncMultiCtxRet;
typedef struct {
MpiEncTestArgs *cmd; // pointer to global command line info
const char *name;
RK_S32 chn;
pthread_t thd; // thread for for each instance
MpiEncTestData ctx; // context of encoder
MpiEncMultiCtxRet ret; // return of encoder
} MpiEncMultiCtxInfo;
MPP_RET test_ctx_init(MpiEncMultiCtxInfo *info)
{
MpiEncTestArgs *cmd = info->cmd;
MpiEncTestData *p = &info->ctx;
MPP_RET ret = MPP_OK;
// get paramter from cmd
p->width = cmd->width;
p->height = cmd->height;
p->hor_stride = (cmd->hor_stride) ? (cmd->hor_stride) :
(MPP_ALIGN(cmd->width, 16));
p->ver_stride = (cmd->ver_stride) ? (cmd->ver_stride) :
(MPP_ALIGN(cmd->height, 16));
p->fmt = cmd->format;
p->type = cmd->type;
p->bps = cmd->bps_target;
p->bps_min = cmd->bps_min;
p->bps_max = cmd->bps_max;
p->rc_mode = cmd->rc_mode;
p->frame_num = cmd->frame_num;
if (cmd->type == MPP_VIDEO_CodingMJPEG && p->frame_num == 0) {
mpp_log("jpege default encode only one frame. Use -n [num] for rc case\n");
p->frame_num = 1;
}
p->gop_mode = cmd->gop_mode;
p->gop_len = cmd->gop_len;
p->vi_len = cmd->vi_len;
p->fps_in_flex = cmd->fps_in_flex;
p->fps_in_den = cmd->fps_in_den;
p->fps_in_num = cmd->fps_in_num;
p->fps_out_flex = cmd->fps_out_flex;
p->fps_out_den = cmd->fps_out_den;
p->fps_out_num = cmd->fps_out_num;
p->mdinfo_size = (MPP_VIDEO_CodingHEVC == cmd->type) ?
(MPP_ALIGN(p->hor_stride, 64) >> 6) *
(MPP_ALIGN(p->ver_stride, 64) >> 6) * 32 :
(MPP_ALIGN(p->hor_stride, 64) >> 6) *
(MPP_ALIGN(p->ver_stride, 16) >> 4) * 8;
if (cmd->file_input) {
if (!strncmp(cmd->file_input, "/dev/video", 10)) {
mpp_log("open camera device");
p->cam_ctx = camera_source_init(cmd->file_input, 4, p->width, p->height, p->fmt);
mpp_log("new framecap ok");
if (p->cam_ctx == NULL)
mpp_err("open %s fail", cmd->file_input);
} else {
p->fp_input = fopen(cmd->file_input, "rb");
if (NULL == p->fp_input) {
mpp_err("failed to open input file %s\n", cmd->file_input);
mpp_err("create default yuv image for test\n");
}
}
}
if (cmd->file_output) {
p->fp_output = fopen(cmd->file_output, "w+b");
if (NULL == p->fp_output) {
mpp_err("failed to open output file %s\n", cmd->file_output);
ret = MPP_ERR_OPEN_FILE;
}
}
if (cmd->file_slt) {
p->fp_verify = fopen(cmd->file_slt, "wt");
if (!p->fp_verify)
mpp_err("failed to open verify file %s\n", cmd->file_slt);
}
// update resource parameter
switch (p->fmt & MPP_FRAME_FMT_MASK) {
case MPP_FMT_YUV420SP:
case MPP_FMT_YUV420P: {
p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64) * 3 / 2;
} break;
case MPP_FMT_YUV422_YUYV :
case MPP_FMT_YUV422_YVYU :
case MPP_FMT_YUV422_UYVY :
case MPP_FMT_YUV422_VYUY :
case MPP_FMT_YUV422P :
case MPP_FMT_YUV422SP : {
p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64) * 2;
} break;
case MPP_FMT_RGB444 :
case MPP_FMT_BGR444 :
case MPP_FMT_RGB555 :
case MPP_FMT_BGR555 :
case MPP_FMT_RGB565 :
case MPP_FMT_BGR565 :
case MPP_FMT_RGB888 :
case MPP_FMT_BGR888 :
case MPP_FMT_RGB101010 :
case MPP_FMT_BGR101010 :
case MPP_FMT_ARGB8888 :
case MPP_FMT_ABGR8888 :
case MPP_FMT_BGRA8888 :
case MPP_FMT_RGBA8888 : {
p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64);
} break;
default: {
p->frame_size = MPP_ALIGN(p->hor_stride, 64) * MPP_ALIGN(p->ver_stride, 64) * 4;
} break;
}
if (MPP_FRAME_FMT_IS_FBC(p->fmt)) {
if ((p->fmt & MPP_FRAME_FBC_MASK) == MPP_FRAME_FBC_AFBC_V1)
p->header_size = MPP_ALIGN(MPP_ALIGN(p->width, 16) * MPP_ALIGN(p->height, 16) / 16, SZ_4K);
else
p->header_size = MPP_ALIGN(p->width, 16) * MPP_ALIGN(p->height, 16) / 16;
} else {
p->header_size = 0;
}
return ret;
}
MPP_RET test_ctx_deinit(MpiEncTestData *p)
{
if (p) {
if (p->cam_ctx) {
camera_source_deinit(p->cam_ctx);
p->cam_ctx = NULL;
}
if (p->fp_input) {
fclose(p->fp_input);
p->fp_input = NULL;
}
if (p->fp_output) {
fclose(p->fp_output);
p->fp_output = NULL;
}
if (p->fp_verify) {
fclose(p->fp_verify);
p->fp_verify = NULL;
}
}
return MPP_OK;
}
/*
该函数主要是设置编码器参数,并将编码器参数保存到 MpiEncMultiCtxInfo 结构体中。
具体地,该函数首先使用默认参数对 MpiEncTestData 结构体中的 fps_in_den、fps_in_num、fps_out_den、fps_out_num 和 bps 进行设置。
接下来,该函数通过 mpp_enc_cfg_set_xxx 函数设置编码器参数,例如设置编码器输入图像的宽、高、水平步长、垂直步长、格式等;
设置码率控制模式、输入帧率、输出帧率、GOP 长度等;设置是否允许码率溢出时丢帧、最大码率、最小码率等。最后,该函数设置不同编码器和码率控制模式下的量化参数。
注:MPP_RET 是 typedef int32_t,代表函数返回值为 32 位有符号整数,表示函数的执行结果。具体地,函数执行成功时返回 MPP_OK(0),否则返回其他错误码。
*/
MPP_RET test_mpp_enc_cfg_setup(MpiEncMultiCtxInfo *info)
{
MpiEncTestArgs *cmd = info->cmd;
MpiEncTestData *p = &info->ctx;
MppApi *mpi = p->mpi;
MppCtx ctx = p->ctx;
MppEncCfg cfg = p->cfg;
RK_U32 quiet = cmd->quiet;
MPP_RET ret;
/* setup default parameter */
if (p->fps_in_den == 0)
p->fps_in_den = 1;
if (p->fps_in_num == 0)
p->fps_in_num = 30;
if (p->fps_out_den == 0)
p->fps_out_den = 1;
if (p->fps_out_num == 0)
p->fps_out_num = 30;
if (!p->bps)
p->bps = p->width * p->height / 8 * (p->fps_out_num / p->fps_out_den);
mpp_enc_cfg_set_s32(cfg, "prep:width", p->width);
mpp_enc_cfg_set_s32(cfg, "prep:height", p->height);
mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", p->hor_stride);
mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", p->ver_stride);
mpp_enc_cfg_set_s32(cfg, "prep:format", p->fmt);
mpp_enc_cfg_set_s32(cfg, "rc:mode", p->rc_mode);
/* fix input / output frame rate */
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_flex", p->fps_in_flex);
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", p->fps_in_num);
mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denorm", p->fps_in_den);
mpp_enc_cfg_set_s32(cfg, "rc:fps_out_flex", p->fps_out_flex);
mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", p->fps_out_num);
mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denorm", p->fps_out_den);
mpp_enc_cfg_set_s32(cfg, "rc:gop", p->gop_len ? p->gop_len : p->fps_out_num * 2);
/* drop frame or not when bitrate overflow */
mpp_enc_cfg_set_u32(cfg, "rc:drop_mode", MPP_ENC_RC_DROP_FRM_DISABLED);
mpp_enc_cfg_set_u32(cfg, "rc:drop_thd", 20); /* 20% of max bps */
mpp_enc_cfg_set_u32(cfg, "rc:drop_gap", 1); /* Do not continuous drop frame */
/* setup bitrate for different rc_mode */
mpp_enc_cfg_set_s32(cfg, "rc:bps_target", p->bps);
switch (p->rc_mode) {
case MPP_ENC_RC_MODE_FIXQP : {
/* do not setup bitrate on FIXQP mode */
} break;
case MPP_ENC_RC_MODE_CBR : {
/* CBR mode has narrow bound */
mpp_enc_cfg_set_s32(cfg, "rc:bps_max", p->bps_max ? p->bps_max : p->bps * 17 / 16);
mpp_enc_cfg_set_s32(cfg, "rc:bps_min", p->bps_min ? p->bps_min : p->bps * 15 / 16);
} break;
case MPP_ENC_RC_MODE_VBR :
case MPP_ENC_RC_MODE_AVBR : {
/* VBR mode has wide bound */
mpp_enc_cfg_set_s32(cfg, "rc:bps_max", p->bps_max ? p->bps_max : p->bps * 17 / 16);
mpp_enc_cfg_set_s32(cfg, "rc:bps_min", p->bps_min ? p->bps_min : p->bps * 1 / 16);
} break;
default : {
/* default use CBR mode */
mpp_enc_cfg_set_s32(cfg, "rc:bps_max", p->bps_max ? p->bps_max : p->bps * 17 / 16);
mpp_enc_cfg_set_s32(cfg, "rc:bps_min", p->bps_min ? p->bps_min : p->bps * 15 / 16);
} break;
}
/* setup qp for different codec and rc_mode */
switch (p->type) {
case MPP_VIDEO_CodingAVC :
case MPP_VIDEO_CodingHEVC : {
switch (p->rc_mode) {
case MPP_ENC_RC_MODE_FIXQP : {
RK_S32 fix_qp = cmd->qp_init;
mpp_enc_cfg_set_s32(cfg, "rc:qp_init", fix_qp);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max", fix_qp);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min", fix_qp);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max_i", fix_qp);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min_i", fix_qp);
mpp_enc_cfg_set_s32(cfg, "rc:qp_ip", 0);
} break;
case MPP_ENC_RC_MODE_CBR :
case MPP_ENC_RC_MODE_VBR :
case MPP_ENC_RC_MODE_AVBR : {
mpp_enc_cfg_set_s32(cfg, "rc:qp_init", -1);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max", 51);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min", 10);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max_i", 51);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min_i", 10);
mpp_enc_cfg_set_s32(cfg, "rc:qp_ip", 2);
} break;
default : {
mpp_err_f("unsupport encoder rc mode %d\n", p->rc_mode);
} break;
}
} break;
case MPP_VIDEO_CodingVP8 : {
/* vp8 only setup base qp range */
mpp_enc_cfg_set_s32(cfg, "rc:qp_init", 40);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max", 127);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min", 0);
mpp_enc_cfg_set_s32(cfg, "rc:qp_max_i", 127);
mpp_enc_cfg_set_s32(cfg, "rc:qp_min_i", 0);
mpp_enc_cfg_set_s32(cfg, "rc:qp_ip", 6);
} break;
case MPP_VIDEO_CodingMJPEG : {
/* jpeg use special codec config to control qtable */
mpp_enc_cfg_set_s32(cfg, "jpeg:q_factor", 80);
mpp_enc_cfg_set_s32(cfg, "jpeg:qf_max", 99);
mpp_enc_cfg_set_s32(cfg, "jpeg:qf_min", 1);
} break;
default : {
} break;
}
/* setup codec */
mpp_enc_cfg_set_s32(cfg, "codec:type", p->type);
switch (p->type) {
case MPP_VIDEO_CodingAVC : {
/*
* H.264 profile_idc parameter
* 66 - Baseline profile
* 77 - Main profile
* 100 - High profile
*/
mpp_enc_cfg_set_s32(cfg, "h264:profile", 100);
/*
* H.264 level_idc parameter
* 10 / 11 / 12 / 13 - qcif@15fps / [email protected] / cif@15fps / cif@30fps
* 20 / 21 / 22 - cif@30fps / half-D1@@25fps / [email protected]
* 30 / 31 / 32 - D1@25fps / 720p@30fps / 720p@60fps
* 40 / 41 / 42 - 1080p@30fps / 1080p@30fps / 1080p@60fps
* 50 / 51 / 52 - 4K@30fps
*/
mpp_enc_cfg_set_s32(cfg, "h264:level", 40);
mpp_enc_cfg_set_s32(cfg, "h264:cabac_en", 1);
mpp_enc_cfg_set_s32(cfg, "h264:cabac_idc", 0);
mpp_enc_cfg_set_s32(cfg, "h264:trans8x8", 1);
} break;
case MPP_VIDEO_CodingHEVC :
case MPP_VIDEO_CodingMJPEG :
case MPP_VIDEO_CodingVP8 : {
} break;
default : {
mpp_err_f("unsupport encoder coding type %d\n", p->type);
} break;
}
p->split_mode = 0;
p->split_arg = 0;
p->split_out = 0;
mpp_env_get_u32("split_mode", &p->split_mode, MPP_ENC_SPLIT_NONE);
mpp_env_get_u32("split_arg", &p->split_arg, 0);
mpp_env_get_u32("split_out", &p->split_out, 0);
if (p->split_mode) {
mpp_log_q(quiet, "%p split mode %d arg %d out %d\n", ctx,
p->split_mode, p->split_arg, p->split_out);
mpp_enc_cfg_set_s32(cfg, "split:mode", p->split_mode);
mpp_enc_cfg_set_s32(cfg, "split:arg", p->split_arg);
mpp_enc_cfg_set_s32(cfg, "split:out", p->split_out);
}
ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
if (ret) {
mpp_err("mpi control enc set cfg failed ret %d\n", ret);
goto RET;
}
/* optional */
p->sei_mode = MPP_ENC_SEI_MODE_ONE_FRAME;
ret = mpi->control(ctx, MPP_ENC_SET_SEI_CFG, &p->sei_mode);
if (ret) {
mpp_err("mpi control enc set sei cfg failed ret %d\n", ret);
goto RET;
}
if (p->type == MPP_VIDEO_CodingAVC || p->type == MPP_VIDEO_CodingHEVC) {
p->header_mode = MPP_ENC_HEADER_MODE_EACH_IDR;
ret = mpi->control(ctx, MPP_ENC_SET_HEADER_MODE, &p->header_mode);
if (ret) {
mpp_err("mpi control enc set header mode failed ret %d\n", ret);
goto RET;
}
}
RK_U32 gop_mode = p->gop_mode;
mpp_env_get_u32("gop_mode", &gop_mode, gop_mode);
if (gop_mode) {
MppEncRefCfg ref;
mpp_enc_ref_cfg_init(&ref);
if (p->gop_mode < 4)
mpi_enc_gen_ref_cfg(ref, gop_mode);
else
mpi_enc_gen_smart_gop_ref_cfg(ref, p->gop_len, p->vi_len);
ret = mpi->control(ctx, MPP_ENC_SET_REF_CFG, ref);
if (ret) {
mpp_err("mpi control enc set ref cfg failed ret %d\n", ret);
goto RET;
}
mpp_enc_ref_cfg_deinit(&ref);
}
/* setup test mode by env */
mpp_env_get_u32("osd_enable", &p->osd_enable, 0);
mpp_env_get_u32("osd_mode", &p->osd_mode, MPP_ENC_OSD_PLT_TYPE_DEFAULT);
mpp_env_get_u32("roi_enable", &p->roi_enable, 0);
mpp_env_get_u32("user_data_enable", &p->user_data_enable, 0);
if (p->roi_enable) {
mpp_enc_roi_init(&p->roi_ctx, p->width, p->height, p->type, 4);
mpp_assert(p->roi_ctx);
}
RET:
return ret;
}
/*
功能:运行 MPP 编码器测试
输入参数:
info:包含测试参数的多个编码器上下文信息结构体 MpiEncMultiCtxInfo
返回值:
ret:MPP_RET 类型的返回值
函数流程:
从输入参数 info 中获取编码器测试参数 cmd 和编码器上下文信息结构体 p,以及其中的 MppApi 和 MppCtx。
初始化变量 quiet、chn、cap_num、checkcrc 和 ret。
如果编码器类型为 AVC 或 HEVC,执行以下操作:
初始化变量 packet,将 p->pkt_buf 分配给它
用 MPP_ENC_GET_HDR_SYNC 命令获取头部信息,如果失败,打印错误信息并跳转到 RET 标签;否则,获取信息的指针和长度,如果文件指针 p->fp_output 不为空,将信息写入文件。
释放 packet 占用的资源。
当 p->pkt_eos 不为真时,执行以下操作:
初始化变量 meta、frame 和 packet,将 p->frm_buf 分配给 buf。
如果 p->fp_input 不为空,从输入文件 p->fp_input 中读取图像数据,如果返回值为 MPP_NOK 或已经到达文件结尾,将 p->frm_eos 设置为真;否则如果返回值为 MPP_ERR_VALUE,跳转到 RET 标签。
如果 p->fp_input 为空,执行以下操作:
如果 p->cam_ctx 为空,用 fill_image 生成一帧图像;否则从摄像头获取一帧图像。
如果捕获的帧数不足 50 帧,将获取到的帧数据重新放回队列并继续下一轮循环。
初始化 frame 并设置其中的宽度、高度、水平和垂直步长、格式、帧结束标志和缓冲区。
获取 frame 的 meta,将 packet 分配给它。
如果 p->osd_enable、p->user_data_enable 或 p->roi_enable 中有一个为真,执行以下操作:
如果 p->user_data_enable 为真,生成一帧用户数据,将其放入 meta 中。
如果 frame_count 能被 10 整除,将 meta 中 KEY_USER_DATA 的值设为用户数据。
使用 mpi 对编码器进行编码,如果编码失败,跳转到 RET 标签。
如果编码结束,将 p->pkt_eos 设置为真。
释放 frame 和 packet 占用的资源。
如果文件指针 p->fp_output 不为空,将编码后的数据写入文件。
如果 ret 不等于 MPP_OK,跳转到 RET 标签。
返回 MPP_OK。
其中,RET 标签用于释放资源并返回错误码。
*/
MPP_RET test_mpp_run(MpiEncMultiCtxInfo *info)
{
MpiEncTestArgs *cmd = info->cmd;
MpiEncTestData *p = &info->ctx;
MppApi *mpi = p->mpi;
MppCtx ctx = p->ctx;
RK_U32 quiet = cmd->quiet;
RK_S32 chn = info->chn;
RK_U32 cap_num = 0;
DataCrc checkcrc;
MPP_RET ret = MPP_OK;
memset(&checkcrc, 0, sizeof(checkcrc));
checkcrc.sum = mpp_malloc(RK_ULONG, 512);
if (p->type == MPP_VIDEO_CodingAVC || p->type == MPP_VIDEO_CodingHEVC) {
MppPacket packet = NULL;
/*
* Can use packet with normal malloc buffer as input not pkt_buf.
* Please refer to vpu_api_legacy.cpp for normal buffer case.
* Using pkt_buf buffer here is just for simplifing demo.
*/
mpp_packet_init_with_buffer(&packet, p->pkt_buf);
/* NOTE: It is important to clear output packet length!! */
mpp_packet_set_length(packet, 0);
ret = mpi->control(ctx, MPP_ENC_GET_HDR_SYNC, packet);
if (ret) {
mpp_err("mpi control enc get extra info failed\n");
goto RET;
} else {
/* get and write sps/pps for H.264 */
void *ptr = mpp_packet_get_pos(packet);
size_t len = mpp_packet_get_length(packet);
if (p->fp_output)
fwrite(ptr, 1, len, p->fp_output);
}
mpp_packet_deinit(&packet);
}
while (!p->pkt_eos) {
MppMeta meta = NULL;
MppFrame frame = NULL;
MppPacket packet = NULL;
void *buf = mpp_buffer_get_ptr(p->frm_buf);
RK_S32 cam_frm_idx = -1;
MppBuffer cam_buf = NULL;
RK_U32 eoi = 1;
if (p->fp_input) {
ret = read_image(buf, p->fp_input, p->width, p->height,
p->hor_stride, p->ver_stride, p->fmt);
if (ret == MPP_NOK || feof(p->fp_input)) {
p->frm_eos = 1;
if (p->frame_num < 0 || p->frame_count < p->frame_num) {
clearerr(p->fp_input);
rewind(p->fp_input);
p->frm_eos = 0;
mpp_log_q(quiet, "chn %d loop times %d\n", chn, ++p->loop_times);
continue;
}
mpp_log_q(quiet, "chn %d found last frame. feof %d\n", chn, feof(p->fp_input));
} else if (ret == MPP_ERR_VALUE)
goto RET;
} else {
if (p->cam_ctx == NULL) {
ret = fill_image(buf, p->width, p->height, p->hor_stride,
p->ver_stride, p->fmt, p->frame_count);
if (ret)
goto RET;
} else {
cam_frm_idx = camera_source_get_frame(p->cam_ctx);
mpp_assert(cam_frm_idx >= 0);
/* skip unstable frames */
if (cap_num++ < 50) {
camera_source_put_frame(p->cam_ctx, cam_frm_idx);
continue;
}
cam_buf = camera_frame_to_buf(p->cam_ctx, cam_frm_idx);
mpp_assert(cam_buf);
}
}
ret = mpp_frame_init(&frame);
if (ret) {
mpp_err_f("mpp_frame_init failed\n");
goto RET;
}
mpp_frame_set_width(frame, p->width);
mpp_frame_set_height(frame, p->height);
mpp_frame_set_hor_stride(frame, p->hor_stride);
mpp_frame_set_ver_stride(frame, p->ver_stride);
mpp_frame_set_fmt(frame, p->fmt);
mpp_frame_set_eos(frame, p->frm_eos);
if (p->fp_input && feof(p->fp_input))
mpp_frame_set_buffer(frame, NULL);
else if (cam_buf)
mpp_frame_set_buffer(frame, cam_buf);
else
mpp_frame_set_buffer(frame, p->frm_buf);
meta = mpp_frame_get_meta(frame);
mpp_packet_init_with_buffer(&packet, p->pkt_buf);
/* NOTE: It is important to clear output packet length!! */
mpp_packet_set_length(packet, 0);
mpp_meta_set_packet(meta, KEY_OUTPUT_PACKET, packet);
mpp_meta_set_buffer(meta, KEY_MOTION_INFO, p->md_info);
if (p->osd_enable || p->user_data_enable || p->roi_enable) {
if (p->user_data_enable) {
MppEncUserData user_data;
char *str = "this is user data\n";
if ((p->frame_count & 10) == 0) {
user_data.pdata = str;
user_data.len = strlen(str) + 1;
mpp_meta_set_ptr(meta, KEY_USER_DATA, &user_data);
}
static RK_U8 uuid_debug_info[16] = {
0x57, 0x68, 0x97, 0x80, 0xe7, 0x0c, 0x4b, 0x65,
0xa9, 0x06, 0xae, 0x29, 0x94, 0x11, 0xcd, 0x9a
};
MppEncUserDataSet data_group;
MppEncUserDataFull datas[2];
char *str1 = "this is user data 1\n";
char *str2 = "this is user data 2\n";
data_group.count = 2;
datas[0].len = strlen(str1) + 1;
datas[0].pdata = str1;
datas[0].uuid = uuid_debug_info;
datas[1].len = strlen(str2) + 1;
datas[1].pdata = str2;
datas[1].uuid = uuid_debug_info;
data_group.datas = datas;
mpp_meta_set_ptr(meta, KEY_USER_DATAS, &data_group);
}
if (p->osd_enable) {
/* gen and cfg osd plt */
mpi_enc_gen_osd_plt(&p->osd_plt, p->frame_count);
p->osd_plt_cfg.change = MPP_ENC_OSD_PLT_CFG_CHANGE_ALL;
p->osd_plt_cfg.type = MPP_ENC_OSD_PLT_TYPE_USERDEF;
p->osd_plt_cfg.plt = &p->osd_plt;
ret = mpi->control(ctx, MPP_ENC_SET_OSD_PLT_CFG, &p->osd_plt_cfg);
if (ret) {
mpp_err("mpi control enc set osd plt failed ret %d\n", ret);
goto RET;
}
/* gen and cfg osd plt */
mpi_enc_gen_osd_data(&p->osd_data, p->buf_grp, p->width,
p->height, p->frame_count);
mpp_meta_set_ptr(meta, KEY_OSD_DATA, (void*)&p->osd_data);
}
if (p->roi_enable) {
RoiRegionCfg *region = &p->roi_region;
/* calculated in pixels */
region->x = MPP_ALIGN(p->width / 8, 16);
region->y = MPP_ALIGN(p->height / 8, 16);
region->w = 128;
region->h = 256;
region->force_intra = 0;
region->qp_mode = 1;
region->qp_val = 24;
mpp_enc_roi_add_region(p->roi_ctx, region);
region->x = MPP_ALIGN(p->width / 2, 16);
region->y = MPP_ALIGN(p->height / 4, 16);
region->w = 256;
region->h = 128;
region->force_intra = 1;
region->qp_mode = 1;
region->qp_val = 10;
mpp_enc_roi_add_region(p->roi_ctx, region);
/* send roi info by metadata */
mpp_enc_roi_setup_meta(p->roi_ctx, meta);
}
}
if (!p->first_frm)
p->first_frm = mpp_time();
/*
* NOTE: in non-block mode the frame can be resent.
* The default input timeout mode is block.
*
* User should release the input frame to meet the requirements of
* resource creator must be the resource destroyer.
*/
ret = mpi->encode_put_frame(ctx, frame);
if (ret) {
mpp_err("chn %d encode put frame failed\n", chn);
mpp_frame_deinit(&frame);
goto RET;
}
mpp_frame_deinit(&frame);
do {
ret = mpi->encode_get_packet(ctx, &packet);
if (ret) {
mpp_err("chn %d encode get packet failed\n", chn);
goto RET;
}
mpp_assert(packet);
if (packet) {
// write packet to file here
void *ptr = mpp_packet_get_pos(packet);
size_t len = mpp_packet_get_length(packet);
char log_buf[256];
RK_S32 log_size = sizeof(log_buf) - 1;
RK_S32 log_len = 0;
if (!p->first_pkt)
p->first_pkt = mpp_time();
p->pkt_eos = mpp_packet_get_eos(packet);
if (p->fp_output)
fwrite(ptr, 1, len, p->fp_output);
if (p->fp_verify && !p->pkt_eos) {
calc_data_crc((RK_U8 *)ptr, (RK_U32)len, &checkcrc);
mpp_log("p->frame_count=%d, len=%d\n", p->frame_count, len);
write_data_crc(p->fp_verify, &checkcrc);
}
log_len += snprintf(log_buf + log_len, log_size - log_len,
"encoded frame %-4d", p->frame_count);
/* for low delay partition encoding */
if (mpp_packet_is_partition(packet)) {
eoi = mpp_packet_is_eoi(packet);
log_len += snprintf(log_buf + log_len, log_size - log_len,
" pkt %d", p->frm_pkt_cnt);
p->frm_pkt_cnt = (eoi) ? (0) : (p->frm_pkt_cnt + 1);
}
log_len += snprintf(log_buf + log_len, log_size - log_len,
" size %-7zu", len);
if (mpp_packet_has_meta(packet)) {
meta = mpp_packet_get_meta(packet);
RK_S32 temporal_id = 0;
RK_S32 lt_idx = -1;
RK_S32 avg_qp = -1;
if (MPP_OK == mpp_meta_get_s32(meta, KEY_TEMPORAL_ID, &temporal_id))
log_len += snprintf(log_buf + log_len, log_size - log_len,
" tid %d", temporal_id);
if (MPP_OK == mpp_meta_get_s32(meta, KEY_LONG_REF_IDX, <_idx))
log_len += snprintf(log_buf + log_len, log_size - log_len,
" lt %d", lt_idx);
if (MPP_OK == mpp_meta_get_s32(meta, KEY_ENC_AVERAGE_QP, &avg_qp))
log_len += snprintf(log_buf + log_len, log_size - log_len,
" qp %d", avg_qp);
}
mpp_log_q(quiet, "chn %d %s\n", chn, log_buf);
mpp_packet_deinit(&packet);
fps_calc_inc(cmd->fps);
p->stream_size += len;
p->frame_count += eoi;
if (p->pkt_eos) {
mpp_log_q(quiet, "chn %d found last packet\n", chn);
mpp_assert(p->frm_eos);
}
}
} while (!eoi);
if (cam_frm_idx >= 0)
camera_source_put_frame(p->cam_ctx, cam_frm_idx);
if (p->frame_num > 0 && p->frame_count >= p->frame_num)
break;
if (p->loop_end)
break;
if (p->frm_eos && p->pkt_eos)
break;
}
RET:
MPP_FREE(checkcrc.sum);
return ret;
}
void *enc_test(void *arg)
{
// 将 void* 类型的参数转换为 MpiEncMultiCtxInfo 结构体指针类型
MpiEncMultiCtxInfo *info = (MpiEncMultiCtxInfo *)arg;
// 获取编码参数结构体
MpiEncTestArgs *cmd = info->cmd;
// 获取上下文信息 MpiEncTestData 结构体指针
MpiEncTestData *p = &info->ctx;
// 获取编码器测试结果 存储结构体 MpiEncMultiCtxRet 指针
MpiEncMultiCtxRet *enc_ret = &info->ret;
// 设置阻塞超时时间
MppPollType timeout = MPP_POLL_BLOCK;
// 获取安静模式标志位
RK_U32 quiet = cmd->quiet;
// 定义返回值变量ret
MPP_RET ret = MPP_OK;
// 定义开始时间t_s,结束时间t_e
RK_S64 t_s = 0;
RK_S64 t_e = 0;
// 打印开始信息
mpp_log_q(quiet, "%s start\n", info->name);
// 初始化测试用数据,参数为外部传入数据结构体
ret = test_ctx_init(info);
if (ret) {
mpp_err_f("test data init failed ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 获取 DRAM 缓冲组
ret = mpp_buffer_group_get_internal(&p->buf_grp, MPP_BUFFER_TYPE_DRM);
if (ret) {
mpp_err_f("failed to get mpp buffer group ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 从缓冲组分配输入帧存储空间
ret = mpp_buffer_get(p->buf_grp, &p->frm_buf, p->frame_size + p->header_size);
if (ret) {
mpp_err_f("failed to get buffer for input frame ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 从缓冲组分配输出包存储空间
ret = mpp_buffer_get(p->buf_grp, &p->pkt_buf, p->frame_size);
if (ret) {
mpp_err_f("failed to get buffer for output packet ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 从缓冲组分配运动信息输出存储空间
ret = mpp_buffer_get(p->buf_grp, &p->md_info, p->mdinfo_size);
if (ret) {
mpp_err_f("failed to get buffer for motion info output packet ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 创建编码器,返回句柄和mpi 实例
ret = mpp_create(&p->ctx, &p->mpi);
if (ret) {
mpp_err("mpp_create failed ret %d\n", ret);
goto MPP_TEST_OUT;
}
// 打印编码器相关信息
mpp_log_q(quiet, "%p encoder test start w %d h %d type %d\n",
p->ctx, p->width, p->height, p->type);
// 设置最大等待时长(超时时间)
ret = p->mpi->control(p->ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
if (MPP_OK != ret) {
mpp_err("mpi control set output timeout %d ret %d\n", timeout, ret);
goto MPP_TEST_OUT;
}
ret = mpp_init(p->ctx, MPP_CTX_ENC, p->type); // 初始化p -> mpi的上下文, 将context设置为MPP_CTX_ENC和输入和输出视频类型为p->type
if (ret) {
mpp_err("mpp_init failed ret %d\n", ret);
goto MPP_TEST_OUT; // 如果失败则退出并释放资源
}
ret = mpp_enc_cfg_init(&p->cfg); // 配置编码器功能
if (ret) {
mpp_err_f("mpp_enc_cfg_init failed ret %d\n", ret);
goto MPP_TEST_OUT;
}
ret = test_mpp_enc_cfg_setup(info); // 设置内部缓冲区
if (ret) {
mpp_err_f("test mpp setup failed ret %d\n", ret);
goto MPP_TEST_OUT;
}
t_s = mpp_time(); // 获取当前时间
ret = test_mpp_run(info); // 执行MPIEncMultiCtxInfo
t_e = mpp_time(); // 获取当前时间
if (ret) {
mpp_err_f("test mpp run failed ret %d\n", ret);
goto MPP_TEST_OUT;
}
ret = p->mpi->reset(p->ctx);
if (ret) {
mpp_err("mpi->reset failed\n");
goto MPP_TEST_OUT;
}
enc_ret->elapsed_time = t_e - t_s; // 计算结果
enc_ret->frame_count = p->frame_count; // 帧数
enc_ret->stream_size = p->stream_size; // 比特流大小
enc_ret->frame_rate = (float)p->frame_count * 1000000 / enc_ret->elapsed_time; // 帧速率
enc_ret->bit_rate = (p->stream_size * 8 * (p->fps_out_num / p->fps_out_den)) / p->frame_count; // 比特率
enc_ret->delay = p->first_pkt - p->first_frm; // 错误时间, 单位: ms
MPP_TEST_OUT: // 释放分配的资源
if (p->ctx) {
mpp_destroy(p->ctx);
p->ctx = NULL;
}
if (p->cfg) {
mpp_enc_cfg_deinit(p->cfg);
p->cfg = NULL;
}
if (p->frm_buf) {
mpp_buffer_put(p->frm_buf);
p->frm_buf = NULL;
}
if (p->pkt_buf) {
mpp_buffer_put(p->pkt_buf);
p->pkt_buf = NULL;
}
if (p->md_info) {
mpp_buffer_put(p->md_info);
p->md_info = NULL;
}
if (p->osd_data.buf) {
mpp_buffer_put(p->osd_data.buf);
p->osd_data.buf = NULL;
}
if (p->buf_grp) {
mpp_buffer_group_put(p->buf_grp);
p->buf_grp = NULL;
}
if (p->roi_ctx) {
mpp_enc_roi_deinit(p->roi_ctx);
p->roi_ctx = NULL;
}
test_ctx_deinit(p);
return NULL;
}
int enc_test_multi(MpiEncTestArgs* cmd, const char *name)
{
MpiEncMultiCtxInfo *ctxs = NULL; //定义多线程结构体数组,每个元素存储当前线程的信息和状态
float total_rate = 0.0; //总帧率
RK_S32 ret = MPP_NOK; //返回值错误码,默认为MPP_NOK(-1)
RK_S32 i = 0; //循环计数变量
ctxs = mpp_calloc(MpiEncMultiCtxInfo, cmd->nthreads); //动态分配多线程结构体数组内存空间
if (NULL == ctxs) {
mpp_err("failed to alloc context for instances\n"); //分配内存空间失败则打印一条错误日志
return -1; //强制退出函数
}
for (i = 0; i < cmd->nthreads; i++) { //给所有线程分配信息和状态,调用 enc_test 函数进行编码
ctxs[i].cmd = cmd;
ctxs[i].name = name;0
ctxs[i].chn = i;
ret = pthread_create(&ctxs[i].thd, NULL, enc_test, &ctxs[i]); //创建新线程并调用 enc_test 来执行编解码任务
if (ret) {
mpp_err("failed to create thread %d\n", i); //线程创建失败,则打印一条错误日志
return ret; //退出函数
}
}
if (cmd->frame_num < 0) { //如果需要循环编码,则等待用户输入停止编码并结束循环编码
mpp_log("*******************************************\n");
mpp_log("**** Press Enter to stop loop encoding ****\n");
mpp_log("*******************************************\n");
getc(stdin); //等待用户输入
for (i = 0; i < cmd->nthreads; i++)
ctxs[i].ctx.loop_end = 1; //将所有线程标记为停止
}
for (i = 0; i < cmd->nthreads; i++)
pthread_join(ctxs[i].thd, NULL); //等待线程结束
for (i = 0; i < cmd->nthreads; i++) {
MpiEncMultiCtxRet *enc_ret = &ctxs[i].ret;
mpp_log("chn %d encode %d frames time %lld ms delay %3d ms fps %3.2f bps %lld\n",
i, enc_ret->frame_count, (RK_S64)(enc_ret->elapsed_time / 1000), (RK_S32)(enc_ret->delay / 1000),
enc_ret->frame_rate, enc_ret->bit_rate); //打印第 i 个线程的编码结果信息
total_rate += enc_ret->frame_rate; //累加总帧率
}
MPP_FREE(ctxs); //释放线程结构体数组内存
total_rate /= cmd->nthreads; //计算平均帧率
mpp_log("%s average frame rate %.2f\n", name, total_rate); //打印编码器名字和平均帧率
return ret; //返回编码结果
}
/*
* 主函数,程序从这里开始执行。返回值为 int 类型,表示程序运行成功与否。
* argc 表示传递给程序的命令行参数数量;argv 以字符串数组的形式存储了这些参数内容。
*/
int main(int argc, char **argv)
{
// ret 为整型变量,用于保存函数调用结果
RK_S32 ret = MPP_NOK;
// 定义 cmd 变量并分配内存空间,用于保存解析后的命令行参数
MpiEncTestArgs* cmd = mpi_enc_test_cmd_get();
// 解析命令行参数并更新 cmd 对象
ret = mpi_enc_test_cmd_update_by_args(cmd, argc, argv);
// 如果解析出现错误,跳转到 LABEL DONE,进行释放资源等操作
if (ret)
goto DONE;
// 显示解析后的参数
mpi_enc_test_cmd_show_opt(cmd);
// 调用 enc_test_multi 函数,实现 H.265/H.264 视频编码的多路并发处理
ret = enc_test_multi(cmd, argv[0]);
DONE:
// 释放 cmd 对象所占内存
mpi_enc_test_cmd_put(cmd);
// 返回程序执行结果
return ret;
}