第二章 mpi_dec_mt_test.c 源码解析

6 篇文章 4 订阅 ¥9.90 ¥99.00

第二章 mpi_dec_mt_test.c 源码解析

第一章 mpi_dec_multi_test.c 源码解析
第二章 mpi_dec_mt_test.c 源码解析



前言

mpi_dec_mt_test.c是一个基于多线程的解码器测试程序,使用MPP(Media Process Platform)库进行视频解码。该程序使用多个解码线程对输入视频进行解码,并将解码后的视频帧写入输出文件。

一、mt_dec_decode 函数

//主要是使用了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;
}

二、thread_input

该函数是一个线程函数,用于将视频流分割成多个包并将其送入解码器进行解码。
该函数是一个线程函数,用于将视频流分割成多个包并将其送入解码器进行解码。

函数首先将输入参数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;
}

三、thread_output

该函数是多线程测试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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值