[学习笔记]基于ffmpeg的视频解码,输出YUV图像到文件。
2015-11-06 14:49
896 查看
虽然在音视频领域工作了一段时间,但是对于ffmpeg还是比较陌生,从这周开始入手学习。
拜读了雷霄骅的《最简单的基于FFMPEG+SDL的视频播放器
ver2》,受益匪浅,链接如下
/article/1379090.html
采用文中的方法,利用ffmpeg的库对视频进行解码,在这里把步骤描述的更加详细一些。
采用的是VS2010的编译环境。
1.首先新建一个空的工程。然后从ffmpeg网站下载ffmpeg工程的库文件。Dev版本中包含h文件和Lib文件,shared版本包含dll文件。将这些文件拷贝到工程目录下。
我的ffmpeg是10月29日下载的版本:20151028-git-dd36749
将mingw安装目录下的include的inttypes.h,stdint.h,_mingw.h三个文件拷到你ffmpeg库的目录下的include在_mingw.h文件的结尾处(在#endif 一行之前)添加了一行: #define __restrict__
2.在工程的属性-->C/C++-->常规-->附加包含目录,设置h文件的目录到工程下的/include目录:
工程的属性-->链接器-->常规-->附加库目录,设置库目录到工程下的lib目录。
3流程:
以下是代码
"https://code.csdn.net/snippets/1311673.js"
代码和雷大师的代码主要由以下区别:
1.宏PIX_FMT_YUV420P用AV_PIX_FMT_YUV420P代替,好像在新的ffmpeg版本中并没有宏PIX_FMT_YUV420P
2.分采用SWS和不采用SWS两种方式输出YUV图像
SWS的作用是转化图像的格式和尺寸,视频解码输出的图像(存在pFrame中)本身就是YUV格式的,其实是不需要做SWS转换的。
但是,我发现pFrame中的图像数据横向的对齐方式是按64(4个宏块)对齐的。
举个栗子
:图像尺寸为672X272,直接按这个尺寸输出的pFrame中的图像,打开后会发现是花屏的
而经过了SWS转换,存在pFrameYUV的图像是这样的:
672按64对齐后为704,我们按704X272输出pFrame中的图像,并按704X272显示,则如下图:
可以清楚的看到数据对齐造成的“绿边”,所以代码中的SWS转化只是剪裁了图像的尺寸。
当然你不调用SWS,也可以修改代码不输出绿边。我输出绿边是为了更好的显示图像数据在pFrame中的存储方式。
3.加入宏FRAMES_NEED,可以按照你需要的帧数解码视频
4.添加了较为详尽的注释。
拜读了雷霄骅的《最简单的基于FFMPEG+SDL的视频播放器
ver2》,受益匪浅,链接如下
/article/1379090.html
采用文中的方法,利用ffmpeg的库对视频进行解码,在这里把步骤描述的更加详细一些。
采用的是VS2010的编译环境。
1.首先新建一个空的工程。然后从ffmpeg网站下载ffmpeg工程的库文件。Dev版本中包含h文件和Lib文件,shared版本包含dll文件。将这些文件拷贝到工程目录下。
我的ffmpeg是10月29日下载的版本:20151028-git-dd36749
将mingw安装目录下的include的inttypes.h,stdint.h,_mingw.h三个文件拷到你ffmpeg库的目录下的include在_mingw.h文件的结尾处(在#endif 一行之前)添加了一行: #define __restrict__
2.在工程的属性-->C/C++-->常规-->附加包含目录,设置h文件的目录到工程下的/include目录:
工程的属性-->链接器-->常规-->附加库目录,设置库目录到工程下的lib目录。
3流程:
以下是代码
/** * output_yuv.cpp * date:2015/11/02 * 基于FFmpeg的视频解码 * 输出YUV图像到文件 * * */ #include <stdio.h> #define __STDC_CONSTANT_MACROS //为了使用C99的宏 #ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #ifdef __cplusplus }; #endif #endif #pragma comment(lib,"avutil.lib") #pragma comment(lib,"avcodec.lib") #pragma comment(lib,"avformat.lib") #pragma comment(lib,"swscale.lib") #define FRAMES_NEED 30 int main(int argc, char* argv[]) { AVFormatContext *pFormatCtx; //格式上下文结构体 int i, videoindex; AVCodecContext *pCodecCtx; //codec上下文 AVCodec *pCodec; //codec AVFrame *pFrame; //Frame结构体 AVFrame *pFrameYUV; //Frame结构体 uint8_t *out_buffer; AVPacket *packet; //packet结构体 int y_size,y_size_align; int ret, got_picture; unsigned int frame_num = 0; struct SwsContext *img_convert_ctx; //图像格式转化上下文 char filepath[]="MP4_test.es"; //input FILE *fp_frame = fopen("output.yuv","wb+"); //output FILE *fp_yuv=fopen("output_sws.yuv","wb+"); av_register_all(); //ffmpeg flow 0,注册codec //avformat_network_init(); //如要打开网络流,必须运行此函数 //暂时不调用 pFormatCtx = avformat_alloc_context(); //格式上下文结构体指针开空间 if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){ //打开多媒体文件 printf("Couldn't open input stream.\n"); return -1; } if(avformat_find_stream_info(pFormatCtx,NULL)<0){ //读取音视频数据相关信息,参数0:上下文结构体指针,参数1:option printf("Couldn't find stream information.\n"); return -1; } videoindex=-1; for(i=0; i<pFormatCtx->nb_streams; i++) //遍历多媒体文件中的每一个流,判断是否为视频。 if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){ videoindex=i; break; } if(videoindex==-1){ printf("Didn't find a video stream.\n"); return -1; } pCodecCtx=pFormatCtx->streams[videoindex]->codec; //codec上下文指定到格式上下文中的codec pCodec=avcodec_find_decoder(pCodecCtx->codec_id); //找到一个codec,必须先调用av_register_all() if(pCodec==NULL){ printf("Codec not found.\n"); return -1; } if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){ //初始化一个视音频编解码器的AVCodecContext printf("Could not open codec.\n"); return -1; } pFrame=av_frame_alloc(); //原始帧 pFrameYUV=av_frame_alloc();//YUV帧 out_buffer=(uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); //宏AV_PIX_FMT_YUV420P 代替宏PIX_FMT_YUV420P avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); //将pFrameYUV和out_buffer联系起来(pFrame指向一段内存);宏AV_PIX_FMT_YUV420P 代替宏PIX_FMT_YUV420P packet=(AVPacket *)av_malloc(sizeof(AVPacket)); //开空间 //Output Info----------------------------- printf("--------------- File Information ----------------\n"); av_dump_format(pFormatCtx,0,filepath,0);//调试函数,输出文件的音、视频流的基本信息 printf("-------------------------------------------------\n"); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); //初始化SWS,图片格式装换上下文//经过验证,输出YUV不需要格式转换,但需要调整尺寸 while(av_read_frame(pFormatCtx, packet)>=0){ //读取码流中的音频若干帧或者视频一帧,作为packet if(packet->stream_index==videoindex){ //如果是视频 ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); //解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame if(ret < 0){ printf("Decode Error.\n"); return -1; } if(got_picture){ sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); //将输出结果转化成YUV,输出YUV不需要格式转换,但是需要调整尺寸,pFrame中的图像数据的对齐方式可能是按64对齐的。 y_size=pCodecCtx->width*pCodecCtx->height; //y_size_align=pCodecCtx->width*pCodecCtx->height; y_size_align=((pCodecCtx->width+63)/64*64)*pCodecCtx->height; //fwrite(pFrame->data[0],1,y_size,fp_frame); //Y //fwrite(pFrame->data[1],1,y_size/4,fp_frame); //U //fwrite(pFrame->data[2],1,y_size/4,fp_frame); //V fwrite(pFrame->data[0],1,y_size_align,fp_frame); //Y fwrite(pFrame->data[1],1,y_size_align/4,fp_frame); //U fwrite(pFrame->data[2],1,y_size_align/4,fp_frame); //V fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V //printf("Succeed to decode 1 frame!\n"); frame_num++; #ifdef FRAMES_NEED if(frame_num == FRAMES_NEED){ printf("%d frames done!\n",frame_num); break;} #endif if(frame_num%100 == 0) printf("%d frames done!\n",frame_num); } } av_free_packet(packet); } //flush decoder //FIX: Flush Frames remained in Codec #ifndef FRAMES_NEED while (1) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) { printf("%d frames in all\n",frame_num); break; } if (!got_picture) { printf("%d frames in all\n",frame_num); break; } sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); y_size=pCodecCtx->width*pCodecCtx->height; y_size_align=((pCodecCtx->width+63)/64*64)*pCodecCtx->height; fwrite(pFrame->data[0],1,y_size_align,fp_frame); //Y fwrite(pFrame->data[1],1,y_size_align/4,fp_frame); //U fwrite(pFrame->data[2],1,y_size_align/4,fp_frame); //V fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V frame_num++; if(frame_num%100 == 0) printf("%d frames done!\n",frame_num); } #endif sws_freeContext(img_convert_ctx); fclose(fp_yuv); fclose(fp_frame); av_frame_free(&pFrameYUV); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); printf("press anything to continue!\n"); getchar(); return 0; }
"https://code.csdn.net/snippets/1311673.js"
代码和雷大师的代码主要由以下区别:
1.宏PIX_FMT_YUV420P用AV_PIX_FMT_YUV420P代替,好像在新的ffmpeg版本中并没有宏PIX_FMT_YUV420P
2.分采用SWS和不采用SWS两种方式输出YUV图像
SWS的作用是转化图像的格式和尺寸,视频解码输出的图像(存在pFrame中)本身就是YUV格式的,其实是不需要做SWS转换的。
但是,我发现pFrame中的图像数据横向的对齐方式是按64(4个宏块)对齐的。
举个栗子
:图像尺寸为672X272,直接按这个尺寸输出的pFrame中的图像,打开后会发现是花屏的
而经过了SWS转换,存在pFrameYUV的图像是这样的:
672按64对齐后为704,我们按704X272输出pFrame中的图像,并按704X272显示,则如下图:
可以清楚的看到数据对齐造成的“绿边”,所以代码中的SWS转化只是剪裁了图像的尺寸。
当然你不调用SWS,也可以修改代码不输出绿边。我输出绿边是为了更好的显示图像数据在pFrame中的存储方式。
3.加入宏FRAMES_NEED,可以按照你需要的帧数解码视频
4.添加了较为详尽的注释。
相关文章推荐
- Linux服务器相互传文件
- python-字典
- jquery easyui 添加按钮逻辑,未渲染
- fullCalendar应用
- Uva 11488 Hyper Prefix Sets 字典树
- 多维转一维的方法
- Ismael Bojang领跑WSOPE豪客赛
- YII框架路由和URL生成
- python-元组
- python-list列表
- Form1调用Unit2类中函数
- poi读取excel
- Struts2-paramsPrepareParamsStack 拦截器栈
- iOS 开发博客汇总
- java应用技术 3(6)
- 第 10 章 菜单和其他资源
- leetcode45 Jump Game II
- 模拟器快捷键操作
- ORACLE简单的存储过程
- ajax原理和XmlHttpRequest对象