第一章 mpi_dec_multi_test.c 源码解析
第二章 mpi_dec_mt_test.c 源码解析
mpi_dec_mt_test.c是一个基于多线程的解码器测试程序,使用MPP(Media Process Platform)库进行视频解码。该程序使用多个解码线程对输入视频进行解码,并将解码后的视频帧写入输出文件。
//主要是使用了MPP(Media Processing Platform)库中的函数实现视频解码。
int mt_dec_decode(MpiDecTestCmd *cmd)//cmd 存储了解码器的相关配置,例如输入文件路径、输出文件路径、解码器参数等。
{
MPP_RET ret = MPP_OK;
FileReader reader = cmd->reader; //reader 文件读取器,用于读取输入的视频文件。
// base flow context
MppCtx ctx = NULL; //ctx: 解码器上下文,用于存储解码器的状态信息。
MppApi *mpi = NULL; //mpi: 解码器API,提供对解码器的各种控制操作。
// input / output
MppPacket packet = NULL; //packet: 存储待解码的视频数据。
MppFrame frame = NULL; //frame: 存储解码后的视频帧数据。
// config for runtime mode
MppDecCfg cfg = NULL; //cfg: 存储解码器配置信息。
RK_U32 need_split = 1; //need_split: 是否需要在输入数据中寻找视频帧分界点的标志位。
// paramter for resource malloc width/height/type: 视频的宽度、高度和编码格式。
RK_U32 width = cmd->width;
RK_U32 height = cmd->height;
MppCodingType type = cmd->type;
pthread_t thd_in; //thd_in/thd_out: 用于分别执行输入和输出的线程。
pthread_t thd_out = 0;
pthread_attr_t attr; //attr: 线程属性。
MpiDecMtLoopData data; //data: 用于线程间传递数据的结构体。
mpp_log("mpi_dec_mt_test start\n");
memset(&data, 0, sizeof(data));
if (cmd->have_output) {
data.fp_output = fopen(cmd->file_output, "w+b");
if (NULL == data.fp_output) {
mpp_err("failed to open output file %s\n", cmd->file_output);
goto MPP_TEST_OUT;
}
}
ret = mpp_packet_init(&packet, NULL, 0);
if (ret) {
mpp_err("mpp_packet_init failed\n");
goto MPP_TEST_OUT;
}
mpp_log("mpi_dec_mt_test decoder test start w %d h %d type %d\n", width, height, type);
// decoder demo 根据用户输入的参数创建了一个解码器上下文ctx
ret = mpp_create(&ctx, &mpi);
if (ret) {
mpp_err("mpp_create failed\n");
goto MPP_TEST_OUT;
}
//用了mpp_init()函数初始化该解码器
ret = mpp_init(ctx, MPP_CTX_DEC, type);
if (ret) {
mpp_err("mpp_init failed\n");
goto MPP_TEST_OUT;
}
// NOTE: timeout value please refer to MppPollType definition
// 0 - non-block call (default)
// -1 - block call
// +val - timeout value in ms
{
MppPollType timeout = MPP_POLL_BLOCK;
MppParam param = &timeout;
ret = mpi->control(ctx, MPP_SET_OUTPUT_TIMEOUT, param);
if (ret) {
mpp_err("Failed to set output timeout %d ret %d\n", timeout, ret);
goto MPP_TEST_OUT;
}
}
mpp_dec_cfg_init(&cfg);
//根据输入数据是否需要找到视频帧分界点的需要,设置了解码器的相关配置参数
/* get default config from decoder context */
ret = mpi->control(ctx, MPP_DEC_GET_CFG, cfg);
if (ret) {
mpp_err("%p failed to get decoder cfg ret %d\n", ctx, ret);
goto MPP_TEST_OUT;
}
/*
* split_parse is to enable mpp internal frame spliter when the input
* packet is not aplited into frames.
*/
ret = mpp_dec_cfg_set_u32(cfg, "base:split_parse", need_split);
if (ret) {
mpp_err("%p failed to set split_parse ret %d\n", ctx, ret);
goto MPP_TEST_OUT;
}
//通过mpi->control()函数将配置参数传递给解码器。
ret = mpi->control(ctx, MPP_DEC_SET_CFG, cfg);
if (ret) {
mpp_err("%p failed to set cfg %p ret %d\n", ctx, cfg, ret);
goto MPP_TEST_OUT;
}
data.cmd = cmd;
data.ctx = ctx;
data.mpi = mpi;
data.loop_end = 0; //loop_end: 标志位,表示是否继续解码。
data.packet = packet;
data.frame = frame;
data.frame_count = 0;
data.frame_num = cmd->frame_num;
data.reader = reader;
data.quiet = cmd->quiet;
//对线程属性进行了初始化 并设置了线程的分离状态为PTHREAD_CREATE_JOINABLE
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
//分别用于读取输入视频数据
ret = pthread_create(&thd_in, &attr, thread_input, &data);
if (ret) {
mpp_err("failed to create thread for input ret %d\n", ret);
goto THREAD_END;
}
//读取输出解码后的视频数据
ret = pthread_create(&thd_out, &attr, thread_output, &data);
if (ret) {
mpp_err("failed to create thread for output ret %d\n", ret);
goto THREAD_END;
}
if (cmd->frame_num < 0) {
// wait for input then quit decoding
mpp_log("*******************************************\n");
mpp_log("**** Press Enter to stop loop decoding ****\n");
mpp_log("*******************************************\n");
getc(stdin);
data.loop_end = 1;
}
THREAD_END:
pthread_attr_destroy(&attr);
pthread_join(thd_in, NULL);
pthread_join(thd_out, NULL);
//重置解码器状态,并释放相关资源。
ret = mpi->reset(ctx);
if (ret) {
mpp_err("mpi->reset failed\n");
goto MPP_TEST_OUT;
}
MPP_TEST_OUT:
if (packet) {
mpp_packet_deinit(&packet);
packet = NULL;
}
if (frame) {
mpp_frame_deinit(&frame);
frame = NULL;
}
if (ctx) {
mpp_destroy(ctx);
ctx = NULL;
}
if (data.frm_grp) {
mpp_buffer_group_put(data.frm_grp);
data.frm_grp = NULL;
}
if (data.fp_output) {
fclose(data.fp_output);
data.fp_output = NULL;
}
if (cfg) {
mpp_dec_cfg_deinit(cfg);
cfg = NULL;
}
return ret;
}
该函数是一个线程函数,用于将视频流分割成多个包并将其送入解码器进行解码。
该函数是一个线程函数,用于将视频流分割成多个包并将其送入解码器进行解码。
函数首先将输入参数arg强制转换为MpiDecMtLoopData类型的指针,并从中获取上下文ctx、mpi、packet、reader和quiet。
然后,函数进入一个循环,该循环用于读取输入流并将其送入解码器进行解码。循环内部首先读取输入流中的一个文件缓冲区,并将其填充到packet中。然后,如果输入流已到达结束位置,函数会检查是否需要重新循环读取流中的数据。如果需要重新循环读取,则会将文件指针设置回文件开头,否则会将packet标记为结束位置。
接下来,函数将不断地尝试将packet送入解码器进行解码,直到成功为止。如果解码失败,函数会等待一段时间后重新尝试。这个过程会一直持续,直到收到停止循环的信号或者整个视频流都被解码完。
最后,函数输出日志并返回。
void *thread_input(void *arg)
{
//函数首先将输入参数arg强制转换为MpiDecMtLoopData类型的指针,并从中获取上下文ctx、mpi、packet、reader和quiet。
MpiDecMtLoopData *data = (MpiDecMtLoopData *)arg;
MppCtx ctx = data->ctx;
MppApi *mpi = data->mpi;
MppPacket packet = data->packet;
FileReader reader = data->reader;
RK_U32 quiet = data->quiet;
mpp_log_q(quiet, "put packet thread start\n");
do {
RK_U32 pkt_eos = 0;
FileBufSlot *slot = NULL;
//读取输入流
MPP_RET ret = reader_read(reader, &slot);
if (ret)
break;
mpp_packet_set_data(packet, slot->data);
mpp_packet_set_size(packet, slot->size);
mpp_packet_set_pos(packet, slot->data);
mpp_packet_set_length(packet, slot->size);
pkt_eos = slot->eos;
// setup eos flag
if (pkt_eos) {
if (data->frame_num < 0 || data->frame_count < data->frame_num) {//如果需要重新循环读取,则会将文件指针设置回文件开头,否则会将packet标记为结束位置。
mpp_log_q(quiet, "%p loop again\n", ctx);
reader_rewind(reader);
pkt_eos = 0;
} else {
mpp_log_q(quiet, "%p found last packet\n", ctx);
mpp_packet_set_eos(packet);
}
}
// send packet until it success
//送入解码器进行解码
do {
ret = mpi->decode_put_packet(ctx, packet); //函数将不断地尝试将packet送入解码器进行解码,直到成功为止
if (MPP_OK == ret) {
mpp_assert(0 == mpp_packet_get_length(packet));
break;
}
// if failed wait a moment and retry 解码失败,函数会等待一段时间后重新尝试
msleep(1);
} while (!data->loop_end);
if (pkt_eos)
break;
} while (!data->loop_end);
mpp_log_q(quiet, "put packet thread end\n");
return NULL;
}
该函数是多线程测试MPP(Media Process Platform)解码的主要线程之一。它的作用是从MPP解码器中获取可用的帧并处理这些帧。
void *thread_output(void *arg)
{
MpiDecMtLoopData *data = (MpiDecMtLoopData *)arg; //data: 获取传递进来的线程参数arg,将其转化为MpiDecMtLoopData类型
MpiDecTestCmd *cmd = data->cmd; //cmd: 从线程参数中获取命令参数,包括文件路径、帧率等信息。
MppCtx ctx = data->ctx; //ctx: 从线程参数中获取MPP上下文句柄。
MppApi *mpi = data->mpi; //mpi: 从线程参数中获取MPP API句柄。
RK_U32 quiet = data->quiet; //quiet: 从线程参数中获取静默标志,用于控制是否打印日志。
mpp_log_q(quiet, "get frame thread start\n");
// then get all available frame and release
do {
RK_U32 frm_eos = 0; //frm_eos: 帧结束标志,用于判断是否处理完所有帧。
MppFrame frame = NULL; //frame: 存储获取到的帧。
MPP_RET ret = mpi->decode_get_frame(ctx, &frame);//获取解码器中可用的帧,并对这些帧进行处理
//如果获取帧失败,则会在日志中打印错误信息并继续尝试获取下一帧。
if (ret) {
mpp_err("decode_get_frame failed ret %d\n", ret);
continue;
}
//如果获取到的帧为空,则线程会休眠1毫秒再次尝试获取
if (NULL == frame) {
msleep(1);
continue;
}
//如果发现帧信息改变,则会重新创建缓冲区组,并限制缓冲区数量。
if (mpp_frame_get_info_change(frame)) {
// found info change and create buffer group for decoding
RK_U32 width = mpp_frame_get_width(frame); //宽度
RK_U32 height = mpp_frame_get_height(frame); //高度
RK_U32 hor_stride = mpp_frame_get_hor_stride(frame); //水平步幅
RK_U32 ver_stride = mpp_frame_get_ver_stride(frame); //垂直步幅
RK_U32 buf_size = mpp_frame_get_buf_size(frame); //帧缓冲区大小
mpp_log_q(quiet, "decode_get_frame get info changed found\n");
mpp_log_q(quiet, "decoder require buffer w:h [%d:%d] stride [%d:%d] size %d\n",
width, height, hor_stride, ver_stride, buf_size);
if (NULL == data->frm_grp) {
/* If buffer group is not set create one and limit it */
ret = mpp_buffer_group_get_internal(&data->frm_grp, MPP_BUFFER_TYPE_ION);
if (ret) {
mpp_err("get mpp buffer group failed ret %d\n", ret);
break;
}
/* Set buffer to mpp decoder */
ret = mpi->control(ctx, MPP_DEC_SET_EXT_BUF_GROUP, data->frm_grp);
if (ret) {
mpp_err("set buffer group failed ret %d\n", ret);
break;
}
} else {
/* If old buffer group exist clear it */
ret = mpp_buffer_group_clear(data->frm_grp);
if (ret) {
mpp_err("clear buffer group failed ret %d\n", ret);
break;
}
}
/* Use limit config to limit buffer count to 24 */
ret = mpp_buffer_group_limit_config(data->frm_grp, buf_size, 24);
if (ret) {
mpp_err("limit buffer group failed ret %d\n", ret);
break;
}
ret = mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
if (ret) {
mpp_err("info change ready failed ret %d\n", ret);
break;
}
} else {
//如果帧正常获取,则会计算帧率、打印日志信息并将帧保存到输出文件中(如果输出文件已打开)
char log_buf[256]; //log_buf: 存储日志信息的缓冲区
RK_S32 log_size = sizeof(log_buf) - 1; //log_size: 日志缓冲区的大小。
RK_S32 log_len = 0; //log_len: 当前已经写入日志缓冲区的长度。
RK_U32 err_info = mpp_frame_get_errinfo(frame); //err_info: 存储帧的错误信息。
RK_U32 discard = mpp_frame_get_discard(frame); //discard: 存储帧是否被丢弃的标志。
log_len += snprintf(log_buf + log_len, log_size - log_len,
"decode get frame %d", data->frame_count);
if (mpp_frame_has_meta(frame)) {
MppMeta meta = mpp_frame_get_meta(frame);
RK_S32 temporal_id = 0;
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 (err_info || discard) {
log_len += snprintf(log_buf + log_len, log_size - log_len,
" err %x discard %x", err_info, discard);
}
mpp_log_q(quiet, "%p %s\n", ctx, log_buf);
data->frame_count++;
if (data->fp_output && !err_info)
dump_mpp_frame_to_file(frame, data->fp_output);
fps_calc_inc(cmd->fps);
}
frm_eos = mpp_frame_get_eos(frame);
mpp_frame_deinit(&frame);
//如果发现帧结束标志或达到指定帧数,则设置循环结束标志。
if ((data->frame_num > 0 && (data->frame_count >= data->frame_num)) ||
((data->frame_num == 0) && frm_eos))
data->loop_end = 1;
} while (!data->loop_end);
mpp_log_q(quiet, "get frame thread end\n");
return NULL;
}