Rockchip MPP(Media Process Platform)解码H264

# Rockchip MPP(Media Process Platform)解码H264

1. 简介

本例完成H264格式文件解码,并保存为yuv格式文件。参考mpp_dec_test重写,进行了一部分精简。

瑞芯微提供的媒体处理软件平台(Media Process Platform,简称 MPP)是适用于瑞芯微芯片系列的
通用媒体处理软件平台。该平台对应用软件屏蔽了芯片相关的复杂底层处理,其目的是为了屏蔽不
同芯片的差异,为使用者提供统一的视频媒体处理接口(Media Process Interface,缩写 MPI)。

2. 源码mpp-dec-h264-to-yuv-file.c

/**
 * 1. make
 * 2. ./mpp-dec-h264-to-yuv-file
 * 3. gst-launch-1.0 filesrc location=Tennis1080p.yuv ! videoparse width=1920 height=1080 format=nv12 ! videoconvert ! xvimagesink
 * 4. gst-launch-1.0 filesrc location=Tennis1080p.h264 ! h264parse ! mppvideodec ! xvimagesink
 */
#include <unistd.h>
#include <stdio.h>
#include <rockchip/rk_mpi.h>

#define __IN_FILE__ ("Tennis1080p.h264")
#define __OUT_FILE__ ("Tennis1080p.yuv")

void dump_frame(MppFrame frame, FILE *out_fp)
{
    printf("dump_frame_to_file\n");

    RK_U32 width    = 0;
    RK_U32 height   = 0;
    RK_U32 h_stride = 0;
    RK_U32 v_stride = 0;
    MppFrameFormat fmt  = MPP_FMT_YUV420SP;
    MppBuffer buffer    = NULL;
    RK_U8 *base = NULL;

    width    = mpp_frame_get_width(frame);
    height   = mpp_frame_get_height(frame);
    h_stride = mpp_frame_get_hor_stride(frame);
    v_stride = mpp_frame_get_ver_stride(frame);
    fmt      = mpp_frame_get_fmt(frame);
    buffer   = mpp_frame_get_buffer(frame);

    RK_U32 buf_size = mpp_frame_get_buf_size(frame);
    printf("w x h: %dx%d hor_stride:%d ver_stride:%d buf_size:%d\n",
           width, height, h_stride, v_stride, buf_size);
           
    if (NULL == buffer) {
        printf("buffer is null\n");
        return ;
    }

    base = (RK_U8 *)mpp_buffer_get_ptr(buffer);

    // MPP_FMT_YUV420SP
    if (fmt != MPP_FMT_YUV420SP) {
        printf("fmt %d not supported\n", fmt);
        return;
    }

    RK_U32 i;
    RK_U8 *base_y = base;
    RK_U8 *base_c = base + h_stride * v_stride;

    for (i = 0; i < height; i++, base_y += h_stride) {
        fwrite(base_y, 1, width, out_fp);
    }
    for (i = 0; i < height / 2; i++, base_c += h_stride) {
        fwrite(base_c, 1, width, out_fp);
    }
}

void dump_frame_to_file(MppCtx ctx, MppApi *mpi, MppFrame frame, FILE *out_fp)
{
    printf("decode_and_dump_to_file\n");

    MPP_RET ret;

    if (mpp_frame_get_info_change(frame)) {
        printf("mpp_frame_get_info_change\n");
        /**
         * 第一次解码会到这个分支,需要为解码器设置缓冲区.
         * 解码器缓冲区支持3种模式。参考【图像内存分配以及交互模式】Rockchip_Developer_Guide_MPP_CN.pdf
         * 这里使用纯内部模式。
         */
        ret = mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
        if (ret) {
            printf("mpp_frame_get_info_change mpi->control error"
                    "MPP_DEC_SET_INFO_CHANGE_READY %d\n", ret);
        }
        return;
    }

    RK_U32 err_info = mpp_frame_get_errinfo(frame);
    RK_U32 discard = mpp_frame_get_discard(frame);    
    printf("err_info: %u discard: %u\n", err_info, discard);

    if (err_info) {
        return;
    }
        
    // save
    dump_frame(frame, out_fp);
    return;
}

int main(void)
{
    printf("---------- mpp start ----------\n");

    // 1. 打开输入文件
    FILE *in_fp = fopen(__IN_FILE__, "rb");
    if (!in_fp) {
        printf("fopen error\n");
        return -1;
    }

    // 2. 打开输出文件
    FILE *out_fp = fopen(__OUT_FILE__, "wb+");
    if (!out_fp) {
        printf("fopen error\n");
        return -1;
    }

    // 3. 初始化解码器上下文,MppCtx MppApi
    MppCtx ctx = NULL;
    MppApi *mpi = NULL;
    MPP_RET ret = mpp_create(&ctx, &mpi);
    if (MPP_OK != ret) {
        printf("mpp_create error\n");
        return -1;
    }

    /**
     * 4. 配置解器
     *      - 解码文件需要 split 模式
     *      - 设置非阻塞模式,0非阻塞(默认),-1阻塞,+val 超时(ms)
     */
    RK_U32 need_split = -1;
    ret = mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, (MppParam*)&need_split);
    if (MPP_OK != ret) {
        printf("mpi->control error MPP_DEC_SET_PARSER_SPLIT_MODE\n");
        return -1;
    }
    
    ret = mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);  // 固定为H264
    if (MPP_OK != ret) {
        printf("mpp_init error\n");
        return -1;
    }

    // 5. 初始化包,MppPacket
    int buf_size = 5 * 1024 * 1024;
    char *buf = (char*)malloc(buf_size);
    if (!buf) {
        printf("malloc error\n");
        return -1;
    }
    MppPacket pkt = NULL;
    ret = mpp_packet_init(&pkt, buf, buf_size);
    if (MPP_OK != ret) {
        printf("mpp_packet_init error\n");
        return -1;
    }

    // 6. 循环读取文件,输入解码器,解码,保存结果
    int over = 0;
    while (!over) {
        printf("decode...\n");
        int len = fread(buf, 1, buf_size, in_fp);
        printf("read file length:%d\n", len);

        if (0 < len) {
            mpp_packet_write(pkt, 0, buf, len);
            mpp_packet_set_pos(pkt, buf);
            mpp_packet_set_length(pkt, len);
            if (feof(in_fp) || len < buf_size) {  // 文件读完,设置结束标志位
                mpp_packet_set_eos(pkt);
                printf("mpp_packet_set_eos\n");
            }
        }

        /**
         * decode_put_packet返回失败,意味着内部缓冲区已满。
         * 非阻塞模式,使用pkt_is_send判断当前读取的数据包(buf)是否成功发送。
         */
        int pkt_is_send = 0;
        while (!pkt_is_send && !over) {
            if (0 < len) {
                printf("pkt remain:%d\n", mpp_packet_get_length(pkt));
                ret = mpi->decode_put_packet(ctx, pkt);
                if (MPP_OK == ret) {
                    printf("pkt send success remain:%d\n", mpp_packet_get_length(pkt));
                    pkt_is_send = 1;
                }
            }

            MppFrame frame;
            MPP_RET ret;
            ret = mpi->decode_get_frame(ctx, &frame);
            if (MPP_OK != ret || !frame) {
                printf("decode_get_frame falied ret:%d\n", ret);
                usleep(2000);  // 等待一下2ms,通常1080p解码时间2ms
                continue;
            }

            printf("decode_get_frame success\n");
            dump_frame_to_file(ctx, mpi, frame, out_fp);

            if (mpp_frame_get_eos(frame)) {
                printf("mpp_frame_get_eos\n");
                mpp_frame_deinit(&frame);
                over = 1;
                continue;
            }
            mpp_frame_deinit(&frame);
        }
    }

    // 7. 释放资源
    fclose(in_fp);
    fclose(out_fp);
    mpi->reset(ctx);
    mpp_packet_deinit(&pkt);
    mpp_destroy(ctx);
    free(buf);
    
    printf("---------- mpp over ----------\n");
    return 0;
}

3. 编译运行

# 编译运行测试
$ make
$ ./mpp-dec-h264-to-yuv-file
$ gst-launch-1.0 filesrc location=Tennis1080p.yuv ! videoparse width=1920 height=1080 format=nv12 ! videoconvert ! xvimagesink

# 播放原视频
$ gst-launch-1.0 filesrc location=Tennis1080p.h264 ! h264parse ! mppvideodec ! xvimagesink

4. 走读

	// 1. 打开输入文件
    FILE *in_fp = fopen(__IN_FILE__, "rb");
    if (!in_fp) {
        printf("fopen error\n");
        return -1;
    }

    // 2. 打开输出文件
    FILE *out_fp = fopen(__OUT_FILE__, "wb+");
    if (!out_fp) {
        printf("fopen error\n");
        return -1;
    }

打开两个文件,输入是H264格式视频压缩文件,输出是YUV(YUV420SP)格式文件。

	// 3. 初始化解码器上下文,MppCtx MppApi
    MppCtx ctx = NULL;
    MppApi *mpi = NULL;
    MPP_RET ret = mpp_create(&ctx, &mpi);
    if (MPP_OK != ret) {
        printf("mpp_create error\n");
        return -1;
    }

    /**
     * 4. 配置解器
     *      - 解码文件需要 split 模式
     *      - 设置非阻塞模式,0非阻塞(默认),-1阻塞,+val 超时(ms)
     */
    RK_U32 need_split = -1;
    ret = mpi->control(ctx, MPP_DEC_SET_PARSER_SPLIT_MODE, (MppParam*)&need_split);
    if (MPP_OK != ret) {
        printf("mpi->control error MPP_DEC_SET_PARSER_SPLIT_MODE\n");
        return -1;
    }
    
    ret = mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);  // 固定为H264
    if (MPP_OK != ret) {
        printf("mpp_init error\n");
        return -1;
    }

mpp_create创建MPP解码器上下文ctx,初始化mpimpi是一个指向MppApi数据结构的指针,MppApi通过成员,提供了操作解码器的回调函数函数。通过mpi->control控制解码器行为,MPP_DEC_SET_PARSER_SPLIT_MODE设置了分帧解码模式。在这种模式下解码器内部对输入解码器数据进行分割。mpp_init(ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);设置ctx作为解码器H264使用。

	// 5. 初始化包,MppPacket
    int buf_size = 5 * 1024 * 1024;
    char *buf = (char*)malloc(buf_size);
    if (!buf) {
        printf("malloc error\n");
        return -1;
    }
    MppPacket pkt = NULL;
    ret = mpp_packet_init(&pkt, buf, buf_size);
    if (MPP_OK != ret) {
        printf("mpp_packet_init error\n");
        return -1;
    }

初始化解码器的数据包,通过malloc分配内存,用于保存实际的二进制数据。这种模式下,对pkt调用mpp_packet_deinit()时不会释放buf

	int over = 0;
    while (!over) {
        printf("decode...\n");
        int len = fread(buf, 1, buf_size, in_fp);
        printf("read file length:%d\n", len);

        if (0 < len) {
            mpp_packet_write(pkt, 0, buf, len);
            mpp_packet_set_pos(pkt, buf);
            mpp_packet_set_length(pkt, len);
            if (feof(in_fp) || len < buf_size) {  // 文件读完,设置结束标志位
                mpp_packet_set_eos(pkt);
                printf("mpp_packet_set_eos\n");
            }
        }

        /**
         * decode_put_packet返回失败,意味着内部缓冲区已满。
         * 非阻塞模式,使用pkt_is_send判断当前读取的数据包(buf)是否成功发送。
         */
        int pkt_is_send = 0;
        while (!pkt_is_send && !over) {
            if (0 < len) {
                printf("pkt remain:%d\n", mpp_packet_get_length(pkt));
                ret = mpi->decode_put_packet(ctx, pkt);
                if (MPP_OK == ret) {
                    printf("pkt send success remain:%d\n", mpp_packet_get_length(pkt));
                    pkt_is_send = 1;
                }
            }

            MppFrame frame;
            MPP_RET ret;
            ret = mpi->decode_get_frame(ctx, &frame);
            if (MPP_OK != ret || !frame) {
                printf("decode_get_frame falied ret:%d\n", ret);
                usleep(2000);  // 等待一下2ms,通常1080p解码时间2ms
                continue;
            }

            printf("decode_get_frame success\n");
            dump_frame_to_file(ctx, mpi, frame, out_fp);

            if (mpp_frame_get_eos(frame)) {
                printf("mpp_frame_get_eos\n");
                mpp_frame_deinit(&frame);
                over = 1;
                continue;
            }
            mpp_frame_deinit(&frame);
        }
    }

over变量用于标记解码是否完成。从文件读取到数据buf后使用mpp_packet_write(), mpp_packet_set_pos(), mpp_packet_set_length()组装MppPacket,当文件读取结束时使用mpp_packet_set_eos()设置结束标志。
pkt_is_send标记了当前数据包pkt是否通过decode_put_packet()被解码器消耗掉。如果没有被消耗,则说明解码器缓冲可能满了,需要读取一部分数据出来。
decode_get_frame用于获取解码器后的二进制数据(yuv),这里使用dump_frame_to_file将正确获取的YUV保存到文件。
当解码器中数据读取到结束标志位时进入mpp_frame_get_eos()分支,设置over变量,最终循环终止,解码结束。

    // 7. 释放资源
    fclose(in_fp);
    fclose(out_fp);
    mpi->reset(ctx);
    mpp_packet_deinit(&pkt);
    mpp_destroy(ctx);
    free(buf);

释放响应资源,本例默认前面的初始化,内存分配等操作成功,没有做错误处理。

void dump_frame_to_file(MppCtx ctx, MppApi *mpi, MppFrame frame, FILE *out_fp)
{
    printf("decode_and_dump_to_file\n");

    MPP_RET ret;

    if (mpp_frame_get_info_change(frame)) {
        printf("mpp_frame_get_info_change\n");
        /**
         * 第一次解码会到这个分支,需要为解码器设置缓冲区.
         * 解码器缓冲区支持3种模式。参考【图像内存分配以及交互模式】Rockchip_Developer_Guide_MPP_CN.pdf
         * 这里使用纯内部模式。
         */
        ret = mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);
        if (ret) {
            printf("mpp_frame_get_info_change mpi->control error"
                    "MPP_DEC_SET_INFO_CHANGE_READY %d\n", ret);
        }
        return;
    }

    RK_U32 err_info = mpp_frame_get_errinfo(frame);
    RK_U32 discard = mpp_frame_get_discard(frame);    
    printf("err_info: %u discard: %u\n", err_info, discard);

    if (err_info) {
        return;
    }
        
    // save
    dump_frame(frame, out_fp);
    return;
}

这个函数将从解码器获取的MppFrame保存到指定文件,当解码器第一次返回时通常会走进mpp_frame_get_info_change()分支,在这个分支里可以设置图像内存分配以及交互模式模式,如果使用纯内部模式,则直接调用ret = mpi->control(ctx, MPP_DEC_SET_INFO_CHANGE_READY, NULL);即可。
获取到有效数据时,err_info为0,discard表示丢弃的帧。

void dump_frame(MppFrame frame, FILE *out_fp)
{
    printf("dump_frame_to_file\n");

    RK_U32 width    = 0;
    RK_U32 height   = 0;
    RK_U32 h_stride = 0;
    RK_U32 v_stride = 0;
    MppFrameFormat fmt  = MPP_FMT_YUV420SP;
    MppBuffer buffer    = NULL;
    RK_U8 *base = NULL;

    width    = mpp_frame_get_width(frame);
    height   = mpp_frame_get_height(frame);
    h_stride = mpp_frame_get_hor_stride(frame);
    v_stride = mpp_frame_get_ver_stride(frame);
    fmt      = mpp_frame_get_fmt(frame);
    buffer   = mpp_frame_get_buffer(frame);

    RK_U32 buf_size = mpp_frame_get_buf_size(frame);
    printf("w x h: %dx%d hor_stride:%d ver_stride:%d buf_size:%d\n",
           width, height, h_stride, v_stride, buf_size);
           
    if (NULL == buffer) {
        printf("buffer is null\n");
        return ;
    }

    base = (RK_U8 *)mpp_buffer_get_ptr(buffer);

    // MPP_FMT_YUV420SP
    if (fmt != MPP_FMT_YUV420SP) {
        printf("fmt %d not supported\n", fmt);
        return;
    }

    RK_U32 i;
    RK_U8 *base_y = base;
    RK_U8 *base_c = base + h_stride * v_stride;

    for (i = 0; i < height; i++, base_y += h_stride) {
        fwrite(base_y, 1, width, out_fp);
    }
    for (i = 0; i < height / 2; i++, base_c += h_stride) {
        fwrite(base_c, 1, width, out_fp);
    }
}

这个函数帮助dump_frame_to_file()实现MppFrame解析和保存,通过

	width    = mpp_frame_get_width(frame);
    height   = mpp_frame_get_height(frame);
    h_stride = mpp_frame_get_hor_stride(frame);
    v_stride = mpp_frame_get_ver_stride(frame);
    fmt      = mpp_frame_get_fmt(frame);
    buffer   = mpp_frame_get_buffer(frame);
    RK_U32 buf_size = mpp_frame_get_buf_size(frame);

获取到帧描述数据。h_stride表示表示水平宽度,byte为单位。v_stride为垂直宽度,1为单位。fmt为平面格式。buffer为MppBuffer,维护了实际的二进制数据。
这里只处理示例视频的MPP_FMT_YUV420SP格式。

  1. Git
    gitee
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值