第三章 toybrick mpp mpi_dec_nt_test.c 讲解

6 篇文章 4 订阅 ¥9.90 ¥99.00

第三章 toybrick mpp mpi_dec_nt_test.c 讲解


前言

该示例程序主要演示了如何使用MPP库中的解码器进行视频解码,并且提供了三种不同的缓冲模式供选择。这三种模式分别是:

纯内部模式:解码器会在内部创建缓冲区,用户不需要调用MPP_DEC_SET_EXT_BUF_GROUP函数来设置解码器的外部缓冲区,只需要调用MPP_DEC_SET_INFO_CHANGE_READY函数通知解码器可以开始解码即可。但是该模式存在一些缺点,例如解码器创建的缓冲区可能无法在解码器关闭前释放,可能导致内存泄漏或崩溃;解码器的内存使用量也无法控制,可能会占用所有可用内存;另外该模式不易实现零拷贝显示路径。
半内部模式:用户需要根据解码器返回的信息创建MppBufferGroup,并为每个MppFrame分配缓冲区。用户可以使用mpp_buffer_group_limit_config函数来限制解码器的内存使用量。该模式相对容易使用,并且用户可以在解码器关闭后释放MppBufferGroup,从而使内存安全的保存更长时间。但是该模式仍然存在一些缺点,例如缓冲区限制仍不准确,内存使用量仍然是100%固定的;另外该模式仍不易实现零拷贝显示路径。
纯外部模式:用户需要创建一个空的MppBufferGroup,并通过文件句柄从外部分配器中导入内存。在Android平台上,SurfaceFlinger会创建缓冲区,然后将文件句柄从SurfaceFlinger传递给MediaServer,最后提交到解码器的MppBufferGroup中。该模式最适合实现零拷贝显示,但是不易学习和使用,同时可能需要外部解析器来获取外部分配器正确的缓冲区大小。
示例程序还提供了缓冲区大小的计算方法,并根据不同的编解码器推荐了不同数量的缓冲区。

一、dec_nt_decode

使用rockchip平台的mpp库进行视频解码的代码。代码使用MPP库来创建上下文、控制和解码视频流。MPP库的创建完成后,代码调用了mpp_dec_cfg_init(&cfg)来初始化解码器的配置。然后使用MPP_DEC_GET_CFG命令从解码器上下文中获取默认配置,并设置了MPP_DEC_SET_CFG命令以应用该配置。代码还创建了一个数据结构,其中包括输入和输出数据,以及指向解码器上下文和MPP API的指针。接下来,代码使用线程来执行解码操作。

int dec_nt_decode(MpiDecTestCmd *cmd)
{
    // base flow context
    MppCtx ctx          = NULL;         //解码库的上下文
    MppApi *mpi         = NULL;         //API结构体

    // input / output                   //解码视频流
    MppPacket packet    = NULL;         //输入的压缩数据包
    MppFrame  frame     = NULL;         //输出的解码数据帧

    // paramter for resource malloc
    RK_U32 width        = cmd->width;
    RK_U32 height       = cmd->height;
    MppCodingType type  = cmd->type;    //编码格式

    // config for runtime mode
    MppDecCfg cfg       = NULL;         //cfg是MPP解码器的配置结构体
    RK_U32 need_split   = 1;            //need_split表示是否需要分割压缩数据包

    // resources
    MppBuffer frm_buf   = NULL;         //frm_buf是解码数据帧的缓冲区
    pthread_t thd;                      //解码的线程
    pthread_attr_t attr;                //线程属性                        
    MpiDecLoopData data;                //data是解码器的参数结构体
    MPP_RET ret = MPP_OK;               //ret是MPP解码库返回值。      

    //对一些变量进行初始化和设置:
    mpp_log("mpi_dec_test start\n");
    memset(&data, 0, sizeof(data));
    pthread_attr_init(&attr);           //attr结构体初始化

    cmd->simple = (cmd->type != MPP_VIDEO_CodingMJPEG) ? (1) : (0); //cmd->simple用于判断是否为MJPEG编码格式

    //如果需要输出解码结果,则打开输出文件和验证文件。
    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;
        }
    }

    if (cmd->file_slt) {
        data.fp_verify = fopen(cmd->file_slt, "wt");
        if (!data.fp_verify)
            mpp_err("failed to open verify file %s\n", cmd->file_slt);
    }

    //根据视频编码格式的不同,分别对packet和frame进行初始化
    if (cmd->simple) {
        ret = mpp_packet_init(&packet, NULL, 0);
        mpp_err_f("mpp_packet_init get %p\n", packet);
        if (ret) {
            mpp_err("mpp_packet_init failed\n");
            goto MPP_TEST_OUT;
        }
    } else {
        RK_U32 hor_stride = MPP_ALIGN(width, 16);
        RK_U32 ver_stride = MPP_ALIGN(height, 16);

        ret = mpp_buffer_group_get_internal(&data.frm_grp, MPP_BUFFER_TYPE_ION);
        if (ret) {
            mpp_err("failed to get buffer group for input frame ret %d\n", ret);
            goto MPP_TEST_OUT;
        }

        ret = mpp_frame_init(&frame); /* output frame */
        if (ret) {
            mpp_err("mpp_frame_init failed\n");
            goto MPP_TEST_OUT;
        }

        /*
         * NOTE: For jpeg could have YUV420 and YUV422 the buffer should be
         * larger for output. And the buffer dimension should align to 16.
         * YUV420 buffer is 3/2 times of w*h.
         * YUV422 buffer is 2 times of w*h.
         * So create larger buffer with 2 times w*h.
         * 
         */
        //该注释解释了对于JPEG图像格式,输出缓冲区的大小需要考虑到可能出现的YUV420和YUV422两种格式,
        //且缓冲区的尺寸需要对齐到16个像素。对于YUV420格式,缓冲区大小应该是图像宽度和高度的1.5倍,
        //对于YUV422格式,缓冲区大小应该是图像宽度和高度的2倍。因此,在这种情况下,创建的缓冲区应该
        //比所需的尺寸大,大小应该是图像宽度和高度的2倍。
        ret = mpp_buffer_get(data.frm_grp, &frm_buf, hor_stride * ver_stride * 4);
        if (ret) {
            mpp_err("failed to get buffer for input frame ret %d\n", ret);
            goto MPP_TEST_OUT;
        }

        mpp_frame_set_buffer(frame, frm_buf);
    }

    // decoder demo
    ret = mpp_create(&ctx, &mpi);
    if (ret) {
        mpp_err("mpp_create failed\n");
        goto MPP_TEST_OUT;
    }

    mpp_log("%p mpi_dec_test decoder test start w %d h %d type %d\n",
            ctx, width, height, type);

    ret = mpi->control(ctx, MPP_SET_DISABLE_THREAD, NULL);

    ret = mpp_init(ctx, MPP_CTX_DEC, type);
    if (ret) {
        mpp_err("%p mpp_init failed\n", ctx);
        goto MPP_TEST_OUT;
    }

    //mpp_dec_cfg_init(&cfg)来初始化解码器的配置。
    mpp_dec_cfg_init(&cfg);

    /* get default config from decoder context */ //使用MPP_DEC_GET_CFG命令从解码器上下文中获取默认配置
    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;
    }

    //应用该配置
    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;
    data.packet         = packet;
    data.frame          = frame;
    data.frame_count    = 0;
    data.frame_num      = cmd->frame_num;
    data.quiet          = cmd->quiet;

    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    ret = pthread_create(&thd, &attr, thread_decode, &data);
    if (ret) {
        mpp_err("failed to create thread for input ret %d\n", ret);
        goto MPP_TEST_OUT;
    }

    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;
    }

    pthread_join(thd, NULL);

    cmd->max_usage = data.max_usage;

    ret = mpi->reset(ctx);
    if (ret) {
        mpp_err("%p mpi->reset failed\n", ctx);
        goto MPP_TEST_OUT;
    }

MPP_TEST_OUT:
    if (data.packet) {
        mpp_packet_deinit(&data.packet);
        data.packet = NULL;
    }

    if (frame) {
        mpp_frame_deinit(&frame);
        frame = NULL;
    }

    if (ctx) {
        mpp_destroy(ctx);
        ctx = NULL;
    }

    if (!cmd->simple) {
        if (frm_buf) {
            mpp_buffer_put(frm_buf);
            frm_buf = 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 (data.fp_verify) {
        fclose(data.fp_verify);
        data.fp_verify = NULL;
    }

    if (cfg) {
        mpp_dec_cfg_deinit(cfg);
        cfg = NULL;
    }

    pthread_attr_destroy(&attr);

    return ret;
}

二、dec_loop

这是一个视频解码器的函数,主要作用是从输入二进制文件读取数据包并进行解码,直到数据包全部处理完毕。
具体实现流程如下:

读取输入二进制文件并获取数据包。
对于每个数据包,调用解码函数进行解码,然后获取可用的帧并释放。
如果帧信息发生了变化,则根据变化信息创建MppBufferGroup缓冲区组,并限制解码器内存使用。
如果数据包处理完毕,且当前帧数小于0或者大于总帧数,则重复读取数据包;否则,将循环结束标志设置为1。
另外,该函数中还包含了一些注释,介绍了解码器的三种缓冲模式及其优缺点。

介绍解码器的缓冲模式,有三种模式可供选择:
纯内部模式(Mode 1):用户不需要调用 MPP_DEC_SET_EXT_BUF_GROUP 控制解码器,只需调用 MPP_DEC_SET_INFO_CHANGE_READY 让解码器继续运行。解码器会在内部创建缓冲区,用户需要释放他们获取的每个帧。该模式的优点是易于使用且快速实现,缺点是解码器返回的缓冲区可能在解码器关闭前没有返回,导致内存泄漏或崩溃;解码器的内存使用无法控制,随意消耗系统内存;难以实现零拷贝显示路径。

半内部模式(Mode 2):用户需要根据返回的信息更改 MppFrame 创建 MppBufferGroup。用户可以使用 mpp_buffer_group_limit_config 函数限制解码器的内存使用。该模式的优点是易于使用;用户可以在解码器关闭后释放 MppBufferGroup,使内存更安全地保留;可以通过 mpp_buffer_group_limit_config 限制内存使用。缺点是缓冲区限制仍然不精确,内存使用是100%固定的;难以实现零拷贝显示路径。

纯外部模式(Mode 3):在此模式下,用户需要创建一个空的 MppBufferGroup,并通过文件句柄从外部分配器导入内存。在Android上,surfaceflinger将创建缓冲区。然后mediaserver从surfaceflinger获取文件句柄并提交到解码器的MppBufferGroup。该模式的优点是最高效的零拷贝显示方式。缺点是难以学习和使用;播放器的工作流程可能限制这种用法;可能需要一个外部解析器来获取外部分配器的正确缓冲区大小。

对于缓冲区大小的计算,对于像素数据,需要计算 hor_stride * ver_stride * 3 / 2 的大小;对于额外信息,需要计算 hor_stride * ver_stride / 2 的大小;总的缓冲区大小需要 hor_stride * ver_stride * 2。

对于H.264/H.265,20个以上的缓冲区就足够了;对于其他编解码器,10个缓冲区就足够了。

 * NOTE: We can choose decoder's buffer mode here.
                 * There are three mode that decoder can support:
                 *
                 * Mode 1: Pure internal mode
                 * In the mode user will NOT call MPP_DEC_SET_EXT_BUF_GROUP
                 * control to decoder. Only call MPP_DEC_SET_INFO_CHANGE_READY
                 * to let decoder go on. Then decoder will use create buffer
                 * internally and user need to release each frame they get.
                 *
                 * Advantage:
                 * Easy to use and get a demo quickly
                 * Disadvantage:
                 * 1. The buffer from decoder may not be return before
                 * decoder is close. So memroy leak or crash may happen.
                 * 2. The decoder memory usage can not be control. Decoder
                 * is on a free-to-run status and consume all memory it can
                 * get.
                 * 3. Difficult to implement zero-copy display path.
                 *
                 * Mode 2: Half internal mode
                 * This is the mode current test code using. User need to
                 * create MppBufferGroup according to the returned info
                 * change MppFrame. User can use mpp_buffer_group_limit_config
                 * function to limit decoder memory usage.
                 *
                 * Advantage:
                 * 1. Easy to use
                 * 2. User can release MppBufferGroup after decoder is closed.
                 *    So memory can stay longer safely.
                 * 3. Can limit the memory usage by mpp_buffer_group_limit_config
                 * Disadvantage:
                 * 1. The buffer limitation is still not accurate. Memory usage
                 * is 100% fixed.
                 * 2. Also difficult to implement zero-copy display path.
                 *
                 * Mode 3: Pure external mode
                 * In this mode use need to create empty MppBufferGroup and
                 * import memory from external allocator by file handle.
                 * On Android surfaceflinger will create buffer. Then
                 * mediaserver get the file handle from surfaceflinger and
                 * commit to decoder's MppBufferGroup.
                 *
                 * Advantage:
                 * 1. Most efficient way for zero-copy display
                 * Disadvantage:
                 * 1. Difficult to learn and use.
                 * 2. Player work flow may limit this usage.
                 * 3. May need a external parser to get the correct buffer
                 * size for the external allocator.
                 *
                 * The required buffer size caculation:
                 * hor_stride * ver_stride * 3 / 2 for pixel data
                 * hor_stride * ver_stride / 2 for extra info
                 * Total hor_stride * ver_stride * 2 will be enough.
                 *
                 * For H.264/H.265 20+ buffers will be enough.
                 * For other codec 10 buffers will be enough.
                 */

三 thread_decode

首先初始化了data中的checkcrc结构体,然后分配了luma和chroma的sum空间。接着,记录了开始时间t_s,并且在while循环内调用dec_loop函数进行解码操作。当data->loop_end为真时,跳出循环。记录结束时间t_e,并计算了elapsed_time、frame_count、frame_rate和delay,其中elapsed_time是解码用时,frame_count是解码的总帧数,frame_rate是解码帧率,delay是第一帧解码前的延迟。最后输出解码的帧数、用时、延迟和帧率等信息,并释放luma和chroma的sum空间。函数最终返回NULL。

void *thread_decode(void *arg)
{
    MpiDecLoopData *data = (MpiDecLoopData *)arg;
    RK_S64 t_s, t_e;

    memset(&data->checkcrc, 0, sizeof(data->checkcrc));
    data->checkcrc.luma.sum = mpp_malloc(RK_ULONG, 512);
    data->checkcrc.chroma.sum = mpp_malloc(RK_ULONG, 512);

    t_s = mpp_time();

    while (!data->loop_end)
        dec_loop(data);

    t_e = mpp_time();
    data->elapsed_time = t_e - t_s;
    data->frame_count = data->frame_count;
    data->frame_rate = (float)data->frame_count * 1000000 / data->elapsed_time;
    data->delay = data->first_frm - data->first_pkt;

    mpp_log("decode %d frames time %lld ms delay %3d ms fps %3.2f\n",
            data->frame_count, (RK_S64)(data->elapsed_time / 1000),
            (RK_S32)(data->delay / 1000), data->frame_rate);

    MPP_FREE(data->checkcrc.luma.sum);
    MPP_FREE(data->checkcrc.chroma.sum);

    return NULL;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MPP_DEC_SET_OUTPUT_FORMAT是MPP库中的一个接口宏,用于设置解码器的输出格式。具体来说,它可以设置解码器输出视频帧的颜色空间格式、宽度、高度等参数。 MPP_DEC_SET_OUTPUT_FORMAT的具体用法如下: ```c MPP_RET mpp_dec_set_output_format(MppDecCtx ctx, MppFrameFormat fmt) ``` 其中,ctx是解码器的上下文环境,fmt是要设置的输出格式,它是一个枚举类型,包括以下几种: - MPP\_FMT\_YUYV422:YUV422格式,每个像素占用16位,Y、U、V分量交错存储。 - MPP\_FMT\_YVYU422:YUV422格式,每个像素占用16位,Y、V、U分量交错存储。 - MPP\_FMT\_UYVY422:YUV422格式,每个像素占用16位,U、Y、V分量交错存储。 - MPP\_FMT\_VYUY422:YUV422格式,每个像素占用16位,V、Y、U分量交错存储。 - MPP\_FMT\_RGB888:RGB格式,每个像素占用24位,依次存储R、G、B分量。 - MPP\_FMT\_BGR888:BGR格式,每个像素占用24位,依次存储B、G、R分量。 - MPP\_FMT\_RGBA8888:RGBA格式,每个像素占用32位,依次存储R、G、B、A分量。 - MPP\_FMT\_BGRA8888:BGRA格式,每个像素占用32位,依次存储B、G、R、A分量。 - MPP\_FMT\_ARGB8888:ARGB格式,每个像素占用32位,依次存储A、R、G、B分量。 - MPP\_FMT\_ABGR8888:ABGR格式,每个像素占用32位,依次存储A、B、G、R分量。 - MPP\_FMT\_Y8:YUV420格式,每个像素占用8位,只包含亮度分量。 - MPP\_FMT\_Y10P:10位YUV420格式,每个像素占用10位,只包含亮度分量。 - MPP\_FMT\_Y16:YUV420格式,每个像素占用16位,只包含亮度分量。 需要注意的是,MPP_DEC_SET_OUTPUT_FORMAT只是一个宏定义,具体的设置参数会根据不同的处理器架构和版本而有所不同,因此具体的设置方法需要参考MPP库的开发文档和示例代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值