概要
使用ffmpeg软件解码mp4文件,解码为yuv420p类型的数据。
整体架构流程
-
1.打开输入流文件 avformat_open_input
-
2.获取输入流信息 avformat_find_stream_info
-
3.获取视频流信息 av_find_best_stream
-
4.创建一个解码的上下文 avcodec_alloc_context3
-
5.获取原文件解码参数 avcodec_parameters_to_context
-
6.找到当前ffmpeg的解码器 avcodec_find_decoder
-
7.打开解码器 avcodec_open2
-
8.读取源文件的帧数据获得 packet av_read_frame
-
9.发送packet到解码器进行解码 avcodec_send_packet
-
10.解码器把解码后的数据发送回来 avcodec_receive_frame
代码
//软解码MP4为yuv文件
//ffplay big_buck_bunny.yuv -s 640x360 -pix_fmt yuv420p播放yuv视频
av_register_all();
//1.打开输入流文件 avformat_open_input
AVFormatContext *inpFormatCtx = NULL;
const char *inFilename = "/home/16THDD/xieyingbo/xieyingbo/big_buck_bunny.mp4";
const char *outFilename = "/home/16THDD/xieyingbo/xieyingbo/big_buck_bunny.yuv";
int ret = avformat_open_input(&inpFormatCtx, inFilename, NULL, NULL);
if(ret < 0)
{
std::cout << "open inpFormatCtx error" << std::endl;
return;
}
//2.获取输入流信息 avformat_find_stream_info
ret = avformat_find_stream_info(inpFormatCtx, NULL);
if(ret < 0)
{
std::cout << "find stream error" << std::endl;
return;
}
//3.获取视频流信息 av_find_best_stream
int videoINdex = av_find_best_stream(inpFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if(videoINdex < 0)
{
std::cout << "find best stream" << std::endl;
return;
}
else
{
std::cout << "videoIndex = " << videoINdex << std::endl;
}
//4.创建一个解码的上下文 avcodec_alloc_context3
//AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
AVCodecContext * codecCtx = avcodec_alloc_context3(NULL);
if(codecCtx == NULL)
{
avcodec_free_context(&codecCtx);
std::cout << "alloc avcodec codecCtx error" << std::endl;
return;
}
//5.获取原文件解码参数放到新的avcodec里面来 avcodec_parameters_to_context
//int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *par);
ret = avcodec_parameters_to_context(codecCtx, inpFormatCtx->streams[videoINdex]->codecpar);
if(ret < 0)
{
std::cout << "avcodec parameters to context error" << std::endl;
return;
}
//6.找到当前ffmpeg支持这个流(h264)的解码器 avcodec_find_decoder
//AVCodec *avcodec_find_decoder(enum AVCodecID id);
AVCodec * avcodec = avcodec_find_decoder(codecCtx->codec_id);
if(avcodec == NULL)
{
std::cout << "avcodec find decoder error, codec_id = " << codecCtx->codec_id << std::endl;
return;
}
//7.打开解码器 avcodec_open2
//int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
ret = avcodec_open2(codecCtx, avcodec, NULL);
if(ret != 0)
{
std::cout << "avcodec_open2 error" << std::endl;
return;
}
FILE *dst_fp = fopen(outFilename, "wb+");
if(dst_fp == NULL)
{
std::cout << "open file outFilename error" << std::endl;
return;
}
//8.读取源文件的帧数据获得packet av_read_frame
//int av_read_frame(AVFormatContext *s, AVPacket *pkt);
AVPacket packet;
av_init_packet(&packet);
AVFrame *frame = av_frame_alloc();
while(av_read_frame(inpFormatCtx, &packet) == 0)
{
if(packet.stream_index == videoINdex)
{
//9.发送packet到解码器进行解码 avcodec_send_packet
//int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
ret = avcodec_send_packet(codecCtx, &packet);
if(ret != 0)
{
std::cout << "avcodec_send_packet error" << std::endl;
av_packet_unref(&packet);
return;
}
//10.解码器把解码后的数据发送回来 avcodec_receive_frame
//int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
while(avcodec_receive_frame(codecCtx, frame) == 0)
{
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if(ret < 0)
{
return;
}
//11.存储数据(YUV420类型数据)
fwrite(frame->data[0], 1, codecCtx->width *codecCtx->height, dst_fp); //存储Y
fwrite(frame->data[1], 1, codecCtx->width *codecCtx->height / 4, dst_fp); //存储U
fwrite(frame->data[2], 1, codecCtx->width *codecCtx->height / 4, dst_fp); //存储V
}
}
av_packet_unref(&packet);
}
std::cout << "解码成功" << std::endl;
小结
使用ffplay播放生成的yuv数据: