原:http://blog.csdn.net/flyfight88/article/details/8541068

ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料。可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快,有些API已经完全换掉了,导致dranger教程中的 代码已经无法编译,正好最近需要使用ffmpeg,于是就利用dranger的教程和代码,自己边学边记录,于是也就有了这个所谓的 New FFmpeg Tutorial,希望对学习ffmpeg的人有所帮助。

source code:videoframe.c

视频播放过程

首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器(Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

FFmpeg的API就是根据这个过程设计的,因此使用FFmpeg来处理视频文件的方法非常直观简单。下面就一步一步介绍从视频文件中解码出图片的过程。

声明变量

首先定义整个过程中需要使用到的变量:

01 int main(int argc, const char *argv[])
02 {
03   AVFormatContext *pFormatCtx = NULL;
04   int             i, videoStream;
05   AVCodecContext  *pCodecCtx;
06   AVCodec         *pCodec;
07   AVFrame         *pFrame;
08   AVFrame         *pFrameRGB;
09   AVPacket        packet;
10   int             frameFinished;
11   int             numBytes;
12   uint8_t         *buffer;
  • AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等
  • AVCodecCotext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。
  • pCodec:真正的编解码器,其中有编解码需要调用的函数
  • AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像
  • AVPacket:解析文件时会将音/视频帧读入到packet中

打开文件

接下来我们打开一个视频文件。

1 av_register_all();

av_register_all 定义在 libavformat 里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。

1 if( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 )
2     return -1;

使用新的API avformat_open_input来打开一个文件,第一个参数是一个AVFormatContext指针变量的地址,它会根据打开的文件信息填充AVFormatContext,需要注意的是,此处的pFormatContext必须为NULL或由avformat_alloc_context分配得到,这也是上一节中将其初始化为NULL的原因,否则此函数调用会出问题。第二个参数是打开的文件名,通过argv[1]指定,也就是命令行的第一个参数。后两个参数分别用于指定特定的输入格式(AVInputFormat)以及指定文件打开额外参数的AVDictionary结构,这里均留作NULL。

1 if( avformat_find_stream_info(pFormatCtx, NULL ) < 0 )
2     return -1;
3   
4   av_dump_format(pFormatCtx, -1, argv[1], 0);

avformat_open_input函数只是读文件头,并不会填充流信息,因此我们需要接下来调用avformat_find_stream_info获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。
最后调用一个帮助函数av_dump_format,输出文件的信息,也就是我们在使用ffmpeg时能看到的文件详细信息。第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。

现在 pFormatCtx->streams 中已经有所有流了,因此现在我们遍历它找到第一条视频流:

1 videoStream = -1;
2 for( i = 0; i < pFormatCtx->nb_streams; i++ )
3   if( pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
4     videoStream = i;
5     break;
6   }
7  
8 if( videoStream == -1 )
9   return -1;

codec_type 的宏定义已经由以前的 CODEC_TYPE_VIDEO 改为 AVMEDIA_TYPE_VIDEO 了。接下来我们通过这条 video stream 的编解码信息打开相应的解码器:

1 pCodecCtx = pFormatCtx->streams[videoStream]->codec;
2  
3 pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
4 if( pCodec == NULL )
5   return -1;
6  
7 if( avcodec_open2(pCodecCtx, pCodec, NULL) < 0 )
8   return -1;

分配图像缓存

接下来我们准备给即将解码的图片分配内存空间。

1 pFrame = avcodec_alloc_frame();
2   if( pFrame == NULL )
3     return -1;
4   
5   pFrameRGB = avcodec_alloc_frame();
6   if( pFrameRGB == NULL )
7     return -1;

调用 avcodec_alloc_frame 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据:

1 numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
2               pCodecCtx->height);

这里调用 avpicture_get_size,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间:

1 buffer = av_malloc(numBytes);
2   
3   avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
4           pCodecCtx->width, pCodecCtx->height);

接着上面的,首先是用 av_malloc 分配上面计算大小的内存空间,然后调用 avpicture_fill 将 pFrameRGB 跟 buffer 指向的内存关联起来。

获取图像

OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。

01 i = 0;
02 while( av_read_frame(pFormatCtx, &packet) >= 0 ) {
03   if( packet.stream_index == videoStream ) {
04     avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
05  
06     if( frameFinished ) {
07   struct SwsContext *img_convert_ctx = NULL;
08   img_convert_ctx =
09     sws_getCachedContext(img_convert_ctx, pCodecCtx->width,
10                  pCodecCtx->height, pCodecCtx->pix_fmt,
11                  pCodecCtx->width, pCodecCtx->height,
12                  PIX_FMT_RGB24, SWS_BICUBIC,
13                  NULL, NULL, NULL);
14   if( !img_convert_ctx ) {
15     fprintf(stderr, "Cannot initialize sws conversion context\n");
16     exit(1);
17   }
18   sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,
19         pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
20         pFrameRGB->linesize);
21   if( i++ < 50 )
22     SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
23     }
24   }
25   av_free_packet(&packet);
26 }

av_read_frame 从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用 avcodec_decode_video2 对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置 frameFinished,如果已经得到下一帧图像则设置 frameFinished 非零,否则为零。所以这里我们判断 frameFinished 是否为零来确定 pFrame 中是否已经得到解码的图像。注意在每次处理完后需要调用 av_free_packet 释放读取的packet。

解码得到图像后,很有可能不是我们想要的 RGB24 格式,因此需要使用 swscale 来做转换,调用 sws_getCachedContext 得到转换上下文,使用sws_scale 将图形从解码后的格式转换为 RGB24,最后将前50帧写人 ppm 文件。最后释放图像以及关闭文件:

01 av_free(buffer);
02   av_free(pFrameRGB);
03   av_free(pFrame);
04   avcodec_close(pCodecCtx);
05   avformat_close_input(&pFormatCtx);
06   
07   return 0;
08 }
09   
10 static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)
11 {
12   FILE *pFile;
13   char szFilename[32];
14   int y;
15   
16   sprintf(szFilename, "frame%d.ppm", iFrame);
17   pFile = fopen(szFilename, "wb");
18   if( !pFile )
19     return;
20   fprintf(pFile, "P6\n%d %d\n255\n", width, height);
21   
22   for( y = 0; y < height; y++ )
23     fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
24   
25   fclose(pFile);
26

}

[cpp] view plaincopy

print?

  1. /*    gcc -o videoframe videoframe.c -lavformat -lavcodec -lavutil -lz -lm -lpthread -lswscale  */
  2. #include <libavformat/avformat.h>
  3. #include <libavcodec/avcodec.h>
  4. #include <libavutil/avutil.h>
  5. #include <libswscale/swscale.h>
  6. #include <stdio.h>
  7. staticvoidSaveFrame(AVFrame *pFrame,intwidth,intheight,intiFrame)
  8. {
  9. FILE*pFile;
  10. charszFilename[32];
  11. inty;
  12. sprintf(szFilename, "frame%d.ppm", iFrame);
  13. pFile = fopen(szFilename, "wb");
  14. if(!pFile)
  15. return;
  16. fprintf(pFile, "P6\n%d %d\n255\n", width, height);//ppm 文件头
  17. for(y=0; y<height; y++)
  18. fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
  19. fclose(pFile);
  20. }
  21. intmain(intargc,constchar*argv[])
  22. {
  23. #if 1
  24. AVFormatContext *pFormatCtx = NULL;
  25. inti, videoStream;
  26. AVCodecContext *pCodecCtx;
  27. AVCodec *pCodec;
  28. AVFrame *pFrame;
  29. AVFrame *pFrameRGB;
  30. AVPacket packet;
  31. intframeFinished;
  32. intnumBytes;
  33. uint8_t *buffer;
  34. #endif
  35. av_register_all();
  36. if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0) {
  37. return-1;
  38. }
  39. if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
  40. return-1;
  41. }
  42. av_dump_format(pFormatCtx, -1, argv[1], 0);
  43. videoStream = -1;
  44. for(i=0; i<pFormatCtx->nb_streams; i++)
  45. if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
  46. videoStream = i;
  47. break;
  48. }
  49. if(videoStream == -1) {
  50. return-1;
  51. }
  52. pCodecCtx = pFormatCtx->streams[videoStream]->codec;
  53. pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
  54. if(pCodec == NULL) {
  55. return-1;
  56. }
  57. if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
  58. return-1;
  59. }
  60. pFrame = avcodec_alloc_frame();
  61. if(pFrame == NULL) {
  62. return-1;
  63. }
  64. pFrameRGB = avcodec_alloc_frame();
  65. if(pFrameRGB == NULL) {
  66. return-1;
  67. }
  68. numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
  69. buffer = av_malloc(numBytes);
  70. avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
  71. i = 0;
  72. while(av_read_frame(pFormatCtx, &packet) >=0) {
  73. if(packet.stream_index == videoStream) {
  74. avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
  75. if(frameFinished) {
  76. structSwsContext *img_convert_ctx = NULL;
  77. img_convert_ctx = sws_getCachedContext(img_convert_ctx, pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
  78. if(!img_convert_ctx) {
  79. fprintf(stderr, "Cannot initialize sws conversion context\n");
  80. exit(1);
  81. }
  82. sws_scale(img_convert_ctx,(constuint8_t *const*)pFrame->data, pFrame->linesize, 0 , pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
  83. if(i++ < 50) {
  84. SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
  85. }
  86. }
  87. }
  88. av_free_packet(&packet);
  89. }
  90. av_free(buffer);
  91. av_free(pFrameRGB);
  92. av_free(pFrame);
  93. avcodec_close(pCodecCtx);
  94. avformat_close_input(&pFormatCtx);
  95. return0;
  96. }

ffmpeg 解码视频小例子相关推荐

  1. ffmpeg解码视频文件并播放

    最近学习了一下如何使用ffmpeg解码音视频,网上的教程挺多但是也挺杂的,搞了好几天,明白了ffmpeg解码音视频的大体流程,这里记录一下ffmpeg解码视频并播放音视频的例子,但并没有做音频.视频播 ...

  2. ffmpeg解码视频存为BMP文件

    ffmpeg解码视频存为BMP文件 分类: ffmpeg2011-07-28 12:13 8人阅读 评论(0) 收藏 举报 view plain #include <windows.h> ...

  3. ffmpeg 解码视频(h264、mpeg2)输出yuv420p文件

    ffmpeg 解码视频(h264.mpeg2)输出yuv420p文件 播放yuv可以参考:ffplay -pixel_format yuv420p -video_size 768x320 -frame ...

  4. 从零实现简易播放器:4.ffmpeg 解码视频为yuv数据-使用avcodec_send_packet与avcodec_receive_frame

    ffmpeg 解码视频为yuv数据 作者:史正 邮箱:shizheng163@126.com 如有错误还请及时指正 如果有错误的描述给您带来不便还请见谅 如需交流请发送邮件,欢迎联系 csdn : h ...

  5. 使用 FFmpeg 开发播放器基础--使用 ffmpeg 解码视频文件

    原:http://blog.chinaunix.net/uid-11344913-id-4282729.html 使用 ffmpeg 解码多媒体文件之前,首先需要了解一些基本的概念: 容器:多媒体文件 ...

  6. FFmpeg解码视频帧为jpg图片保存到本地

    之前遇到一个需求是将视频一秒一秒解码成一帧一帧的图片,用户滑动选择时间节点(微信朋友圈发10秒视频的编辑界面).开始我是用的MediaMetadataRetriever类来获取图片,但是对于分辨率比较 ...

  7. ffmpeg解码视频

    目录 一.前言 二.ffmpeg解码API介绍 三.ffmpeg解码示例 四.ffmpeg解码框架设计 <ffmpeg解码H264/H265为yuv代码实现>链接: https://edu ...

  8. FFmpeg解码视频并保存为图片

    1.多媒体文件的读取 一个多媒体文件包含有多个流(视频流 video stream,音频流 audio stream,字幕等):流是一种抽象的概念,表示一连串的数据元素:     流中的数据元素称为帧 ...

  9. FFmpeg -- 解码视频

    ffmpeg是编解码的利器,用了很久,以前看过dranger 的教程,非常精彩,受益颇多,是学习ffmpeg api很好的材料.可惜的是其针对的ffmpeg版本已经比较老了,而ffmpeg的更新又很快 ...

最新文章

  1. 盘点当下大热的 7 大 Github 机器学习『创新』项目
  2. 阿里前员工跳槽后曝光薪资截图:新公司月入五万多,很满足!
  3. Qt学习一门:直接使用QT具
  4. ignite windows无法访问linux ignite集群_Linux常见命令
  5. 别做喷子,多去钻研!
  6. python函数图像加标签_tkinter(py3)更改图像标签,函数内部,实时
  7. HTTP-FLV直播初探-flv.js播放器使用实例
  8. _beginthreadex与CreateThread区别与联系
  9. oracle rac实例切换,RAC+单实例DG的切换
  10. MSN Messenger
  11. 速看,PMP备考通关宝典来袭
  12. 电子名片帮助实体服装产业快速融入互联网
  13. Linux查看opencv版本
  14. TC软件概要设计文档(手机群控)
  15. 服务器:RAID、AHCI、IDE
  16. 2020 mse 清华_家长们看过来!2020年下半年剑桥MSE考试备考全攻略!
  17. 编写属于自己的Python第三方库
  18. 数据结构—C语言:校园导航系统(最短路径两种算法:深度搜素以及Dijkstra)
  19. 经济与金融大数据挖掘——知识点总结回顾
  20. wkhtmltox 中文显示一半_免费!联合国官员孩子上的中文课,这次我get到了~

热门文章

  1. 百度工程师带你探秘C++内存管理(理论篇)
  2. 企业信用修复服务器,信用修复
  3. c语言课程设计的题目有哪些,C语言课程设计题目
  4. 寄售转拍系统/拍卖系统/竞拍系统/转拍系统/字画拍卖转拍/委托转售系统
  5. 盘点2009十佳新商业模式
  6. 优秀的UI设计所具有的13个原则,天瑞地安小编总结
  7. 【Bug】steam双方都是国区 礼物无法入库问题
  8. 联想电脑预装系统的激活工具
  9. ABAQUS切削模拟
  10. WordPress批量添加、修改、删除自定义字段的sql命令