# 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
,初始化mpi
,mpi
是一个指向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
格式。
- Git
gitee