免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 15906 | 回复: 1

[图形界面] 基于ffmpeg截取视频帧画面 [复制链接]

论坛徽章:
0
发表于 2010-04-21 00:19 |显示全部楼层
看到有人发了嵌入式Linux通过帧缓存截图 - Embedded Linux Framebuffer Screenshot,我在前些时间也做了一些类似的工作,不过我截的是视频文件的帧画面。文章我在blog内发表了,现看到有人做了相似的工作,一起凑个热闹。文章写的有点乱,精力有限,不进行修改了。


2010/4/10

说起某科学的超电磁炮,从我漫长的动漫生涯来看,其实并不算一部顶尖之作。但是其OP动人心弦,确为绝佳。在片头动感的音乐中,首先出现的是经典的黑白场景,对炮姐从脚到头作一次扫描,接着停格在炮姐的拼发电流的手上,画面渐入彩色。这短短的几秒钟,令我兴奋难以自抑。
之后,我就一直在纠结了。因为太过喜欢那几帧画面,想取来做桌面壁纸、屏保、手机屏保。但是在网上老找不到这些图片,或者质量太次。纠结了几个月,前两天忽然想起之前曾详细分析过ffmpeg的源码的,为什么不自己写个软件从stream中抓frame呢?于是立马动手做这件事。欲望才是创作的原动力啊,对炮姐的爱令我心中满怀热情。有人问我:为啥不播放时printscreen呢?但是printscreen可以实现某个时间段的连续帧获取吗?


开发环境:Debian /Etch


准备工作:
1、  svn checkout svn://svn.mplayerhq.hu/ffmpeg/trunk ffmpeg(下载最新版本的ffmpeg)
2、  cd ffmpeg
3、  ./configure --enable-gpl --enable-shared --prefix=/usr(编译为动态库,懒得改.bash_profile,prefix路径选/usr,默认为/usr/local)
4、  make(编译,如果要用到ffplay的话,先sudo apt-get install libsdl-dev,ffplay.c用到sdl的库)
5、  sudo make install(安装lib和include file,注意include file分几个目录存放的,不像之前版本那样统一放到/usr/include/ffmpeg下)
注:在进行这些准备工作前,最好将之前安装的ffmpeg的lib和include file全部清除干净。我一开始用的20071007的版本,再换成latest ffmpeg,编译的时候可以过去,但是运行时老出错。开始时我还以为新版本的ffmpeg的接口有非常非常大的改变,在我程序上找了很长时间无果。最后清除了20071007版的ffmpeg后,编译,顺利运行。


编写代码:
代码附录如后,现不对代码作详细说明,对于ffmpeg的架构及api使用,可参考《FFMpeg框架代码阅读》、《Using libavformat and libavcodec》。之后我应该会整理更详细一点的架构文档说明。这段代码我很大程度上参考了Using libavformat and libavcodec中的avcodec_sample.cpp,其实解码过程都是一样的,这点同样可以从ffmpeg里的例子如ffplay.c、seek_test.c中找到。
由于版本的更新,我修正了一些接口,主要是img_convert换成sws_scale,av_read_packet换成av_read_frame;实现了bmp的编码,话说bmp的标准也真是简单啊。

编译运行:
1、  gcc railgun.c -lavutil -lavformat -lavcodec –lswscale(文件名我都用railgun了,炮姐可以感受到我的爱了吧)
2、  ./a.out To_Aru_Kagaku_no_Railgun.mkv(哇啦啦,产生200张1980*1080的bmp图片,片源是1080P的BDrip,从哪一帧开始到哪一帧结束,可修改代码)

后续工作:可定位某个时间点的frame。事实上我用另外一个片源(avi格式,视频编码mpeg)已经实现了,但MKV的我死活搞不定,现在还不知道这个跟format还是跟codec有关。值得进一步研究。同时还有编解码、音视频同步、字幕显示等等很多东西,以后要先挑一个具体分析才行。

最后,我在相册发了一些炮姐的图片,全是我用这个工具提取出来的。不过CU相册有上传大小限制,我转为jpeg了。


2010/4/13

1、img_convert_ctx = sws_getContext(dec->width, dec->height, dec->pix_fmt, dec->width, dec->height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);语句中PIX_FMT_RGB24应该为PIX_FMT_BGR24。否则BMP色彩变样。原来由于图片是黑白的,所以没及时发现。后来取彩色图片时,发现青春靓丽的炮姐变整一个阿凡达了,失礼失礼。

2、总算实现了取某个时间段的frame。使用方法:./a.out air.mpg 000130 000132 (取air.mpg00:01:30--00:01:32的所有帧)

总体思路是:
timestamp = nSec * AV_TIME_BASE;
timestamp = av_rescale_q(timestamp, AV_TIME_BASE_Q, ic->streams[videoStream]->time_base);
avformat_seek_file(ic, videoStream, INT64_MIN, timestamp, timestamp, 0);

看avformat_seek_file的prototype说明,这个接口是新接口,之后可能会有新的变化,要留意这一点。
另外用这种方法来seek frame,似乎时间定位不准,存在一定的偏差(偏差貌似是按相等比例的,可以微调。也不打算继续改进了,以后的重点会转向编解码或图像处理)。
再有就是运行时会提示:[mpeg1video @ 0x804c300]warning: first frame is no keyframe,前面几帧图片失真。

论坛徽章:
0
发表于 2010-04-21 00:20 |显示全部楼层
  1. // railgun.c

  2. // A small sample program that shows how to use libavformat and libavcodec to
  3. // read video from a file and write frame to a bmp file.
  4. //
  5. // Use
  6. //
  7. // gcc railgun.c -lavutil -lavformat -lavcodec -lswscale
  8. //
  9. // to build (assuming libavformat and libavcodec are correctly installed on
  10. // your system).
  11. //
  12. // Run using
  13. //
  14. // ./a.out air.mpg 000130 000132
  15. //
  16. // to write frames(00:01:30--00:01:32) from "air.mpg" to disk in BMP
  17. // format.

  18. #include <stdlib.h>
  19. #include <stdio.h>
  20. #include <string.h>
  21. #include <math.h>

  22. #include <libavformat/avformat.h>
  23. #include <libswscale/swscale.h>

  24. #undef sprintf
  25. #undef uint8_t
  26. #undef uint16_t
  27. #undef uint32_t
  28. #define uint8_t unsigned char
  29. #define uint16_t unsigned short
  30. #define uint32_t unsigned long

  31. #pragma pack(2)
  32. typedef struct BMPHeader
  33. {
  34.     uint16_t identifier;
  35.     uint32_t file_size;
  36.     uint32_t reserved;
  37.     uint32_t data_offset;
  38. } BMPHeader;

  39. typedef struct BMPMapInfo
  40. {
  41.     uint32_t header_size;
  42.     uint32_t width;
  43.     uint32_t height;
  44.     uint16_t n_planes;
  45.     uint16_t bits_per_pixel;
  46.     uint32_t compression;
  47.     uint32_t data_size;
  48.     uint32_t hresolution;
  49.     uint32_t vresolution;
  50.     uint32_t n_colors_used;
  51.     uint32_t n_important_colors;
  52. }BMPMapInfo;

  53. int CreateBmpImg(AVFrame *pFrame, int width, int height, int iFrame)
  54. {
  55.     BMPHeader bmpheader;
  56.     BMPMapInfo bmpinfo;
  57.     FILE *fp;
  58.     int y;
  59.     char filename[32];
  60.    
  61.     // Open file
  62.     memset(filename, 0x0, sizeof(filename));
  63.     sprintf(filename, "%d.bmp", iFrame);
  64.     fp = fopen(filename, "wb");
  65.     if(!fp)return -1;

  66.     bmpheader.identifier = ('M'<<8)|'B';
  67.     bmpheader.reserved = 0;
  68.     bmpheader.data_offset = sizeof(BMPHeader) + sizeof(BMPMapInfo);
  69.     bmpheader.file_size = bmpheader.data_offset + width*height*24/8;

  70.     bmpinfo.header_size = sizeof(BMPMapInfo);
  71.     bmpinfo.width = width;
  72.     bmpinfo.height = height;
  73.     bmpinfo.n_planes = 1;
  74.     bmpinfo.bits_per_pixel = 24;
  75.     bmpinfo.compression = 0;
  76.     bmpinfo.data_size = height*((width*3 + 3) & ~3);
  77.     bmpinfo.hresolution = 0;
  78.     bmpinfo.vresolution = 0;
  79.     bmpinfo.n_colors_used = 0;
  80.     bmpinfo.n_important_colors = 0;

  81.     fwrite(&bmpheader,sizeof(BMPHeader),1,fp);
  82.     fwrite(&bmpinfo,sizeof(BMPMapInfo),1,fp);
  83.     for(y=height-1; y>=0; y--)
  84.         fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, fp);
  85.     fclose(fp);

  86.     return 0;
  87. }

  88. //解码指定videostream,并保存frame数据到pFrame上
  89. //返回: 0--成功,非0--失败
  90. int DecodeVideoFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
  91.     int videoStream, int64_t endtime, AVFrame *pFrame)
  92. {
  93.     static AVPacket packet;
  94.     static uint8_t *rawData;
  95.     static int bytesRemaining = 0;
  96.     int bytesDecoded;
  97.     int frameFinished;
  98.     static int firstTimeFlag = 1;

  99.     if (firstTimeFlag)
  100.     {
  101.         firstTimeFlag = 0;
  102.         packet.data = NULL;//第一次解frame,初始化packet.data为null
  103.     }

  104.     while (1)
  105.     {
  106.         do
  107.         {
  108.             if (packet.data == NULL) av_free_packet(&packet); //释放旧的packet
  109.             if (av_read_frame(pFormatCtx, &packet) < 0)
  110.             {
  111.                 //从frame读取数据保存到packet上,<0表明到了stream end
  112.                 printf("-->av_read_frame end\n");
  113.                 goto exit_decode;
  114.             }
  115.         } while (packet.stream_index != videoStream); //判断当前frame是否为指定的video stream

  116.         //判断当前帧是否到达了endtime,是则返回false,停止取下一帧
  117.         if (packet.pts >= endtime) return -1;
  118.         
  119.         bytesRemaining = packet.size;
  120.         rawData = packet.data;

  121.         while (bytesRemaining > 0)
  122.         {
  123.             bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, rawData, bytesRemaining);
  124.             if (bytesDecoded < 0) return -1;

  125.             bytesRemaining -= bytesDecoded;
  126.             rawData += bytesDecoded;

  127.             if (frameFinished) return 0;
  128.         }
  129.     }

  130. exit_decode:
  131.     bytesDecoded = avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, rawData, bytesRemaining);
  132.     if(packet.data != NULL) av_free_packet(&packet);
  133.     if (frameFinished != 0) return 0;
  134.     return -1;
  135. }

  136. void usage(const char *function)
  137. {
  138.     printf("Usage: %s [File Name] [Start Time] [End Time]\n", function);
  139.     printf("Ex: ./railgun panda.mpg 003005 003010\n");
  140.     printf("Time Format: HrsMinsSecs. Ex 003005 means 00 hours 30 minutes 05 senconds\n");
  141.     printf("\n");
  142. }

  143. void ParseTime(char strStartTime[], int64_t *pStartSec,
  144.     char strEndTime[], int64_t *pEndSec)
  145. {
  146.     int64_t starttime = 0, endtime = 0;
  147.     if (strStartTime && pStartSec)
  148.     {
  149.         starttime = atoi(strStartTime);
  150.         *pStartSec = (3600*starttime/10000) + \
  151.                 (60*(starttime%10000)/100) + \
  152.                 (starttime%100);
  153.     }

  154.     if (strEndTime && pEndSec)
  155.     {
  156.         endtime = atoi(strEndTime);
  157.         *pEndSec = (3600*endtime/10000) + \
  158.                 (60*(endtime%10000)/100) + \
  159.                 (endtime%100);
  160.     }
  161. }

  162. int main(int argc, char *argv[])
  163. {
  164.     const char *filename;
  165.     AVFormatContext *ic = NULL;
  166.     AVCodecContext *dec = NULL;
  167.     AVCodec *codec = NULL;
  168.     AVFrame *frame = NULL;
  169.     AVFrame *frameRGB = NULL;
  170.     uint8_t *buffer = NULL;
  171.     int numBytes;
  172.     int i, videoStream;
  173.     int64_t startTime = 0;
  174.     int64_t endTime = 0;
  175.     static struct SwsContext *img_convert_ctx = NULL;

  176.     // Register all formats and codecs
  177.     av_register_all();

  178.     filename = argv[1];

  179.     // parse begin time and end time
  180.     if (argc == 3)
  181.         ParseTime(argv[2], &startTime, NULL, NULL);
  182.     else if (argc == 4)
  183.         ParseTime(argv[2], &startTime, argv[3], &endTime);
  184.     else
  185.     {
  186.         usage(argv[0]);
  187.         return -1;
  188.     }
  189.     startTime *= AV_TIME_BASE;
  190.     endTime *= AV_TIME_BASE;
  191.    
  192.     // Open video file
  193.     if(av_open_input_file(&ic, filename, NULL, 0, NULL)!=0)
  194.     {
  195.         fprintf(stderr, "Cannt open input file\n");
  196.         usage(argv[0]);
  197.         goto exit_err;
  198.     }

  199.     // Retrieve stream information
  200.     if(av_find_stream_info(ic)<0)
  201.     {
  202.         fprintf(stderr, "Cannt find stream info\n");
  203.         goto exit_err;
  204.     }

  205.     // Dump information about file onto standard error
  206.     dump_format(ic, 0, filename, 0);

  207.     // Find the first video stream
  208.     videoStream=-1;
  209.     for(i=0; i<ic->nb_streams; i++)
  210.         if(ic->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO)
  211.         {
  212.             videoStream=i;
  213.             break;
  214.         }
  215.     if(videoStream==-1)
  216.     {
  217.         fprintf(stderr, "No video stream\n");
  218.         goto exit_err;
  219.     }

  220.     // Get a pointer to the codec context for the video stream
  221.     dec=ic->streams[videoStream]->codec;
  222.     // Find the decoder for the video stream
  223.     codec=avcodec_find_decoder(dec->codec_id);
  224.     if(codec==NULL)
  225.     {
  226.         fprintf(stderr, "Found no codec\n");
  227.         goto exit_err;
  228.     }
  229.     // Open codec
  230.     if(avcodec_open(dec, codec)<0)
  231.     {
  232.         fprintf(stderr, "Cannt open avcodec\n");
  233.         goto exit_err;
  234.     }

  235.     // Allocate video frame
  236.     frame=avcodec_alloc_frame();
  237.     // Allocate an AVFrame structure
  238.     frameRGB=avcodec_alloc_frame();
  239.     if(frameRGB==NULL)
  240.     {
  241.         av_free(frame);
  242.         fprintf(stderr, "Cannt alloc frame buffer for RGB\n");
  243.         goto exit_err;
  244.     }
  245.     // Determine required buffer size and allocate buffer
  246.     numBytes=avpicture_get_size(PIX_FMT_RGB24, dec->width, dec->height);
  247.     buffer=(uint8_t *)av_malloc(numBytes);
  248.     if (!buffer)
  249.     {
  250.         av_free(frame);
  251.         av_free(frameRGB);
  252.         fprintf(stderr, "Cannt alloc picture buffer\n");
  253.         goto exit_err;
  254.     }
  255.     // Assign appropriate parts of buffer to image planes in pFrameRGB
  256.     avpicture_fill((AVPicture *)frameRGB, buffer, PIX_FMT_RGB24, dec->width, dec->height);

  257.     img_convert_ctx = sws_getContext(dec->width, dec->height, dec->pix_fmt, dec->width, dec->height, PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
  258.     if (img_convert_ctx == NULL) {
  259.         fprintf(stderr, "Cannot initialize the conversion context\n");
  260.         goto exit_err;
  261.     }

  262.     // Seek frame
  263.     startTime = av_rescale_q(startTime, AV_TIME_BASE_Q, ic->streams[videoStream]->time_base);
  264.     endTime = av_rescale_q(endTime, AV_TIME_BASE_Q, ic->streams[videoStream]->time_base);
  265.     avformat_seek_file(ic, videoStream, INT64_MIN, startTime, startTime, 0);
  266.    
  267.     // Read frames and save first five frames to dist
  268.     i=0;
  269.     while(!DecodeVideoFrame(ic, dec, videoStream, endTime, frame))
  270.     {
  271.         // Save the frame to disk
  272.         i++;
  273.         sws_scale(img_convert_ctx, (AVPicture*)frame->data, (AVPicture*)frame->linesize,
  274.             0, dec->height, (AVPicture*)frameRGB->data, (AVPicture*)frameRGB->linesize);
  275.         CreateBmpImg(frameRGB, dec->width, dec->height, i);
  276.     }
  277.    
  278. exit_err:
  279.     // Free the RGB image
  280.     if (buffer)
  281.         av_free(buffer);
  282.     if (frameRGB)
  283.         av_free(frameRGB);
  284.     // Free the YUV frame
  285.     if (frame)
  286.         av_free(frame);
  287.     // Close the codec
  288.     if (dec)
  289.         avcodec_close(dec);
  290.     // Close the video file
  291.     if (ic)
  292.         av_close_input_file(ic);
  293.     if (img_convert_ctx)
  294.         sws_freeContext(img_convert_ctx);

  295.     return 0;
  296. }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

DTCC2021中国数据库技术大会

【数造未来】2021年10月18日-20日第十二届中国数据库技术大会
ITPUB、大会的会员您们好: 因目前国内疫情严峻,为响应北京市最新疫情防控要求,保障参会人员的健康和安全,组委会经协商决定:DTCC2021第十二届中国数据库技术大会延期至10月18日-20日(周一~周三)在北京国际会议中心举行,由此给各位带来的不便,敬请谅解!

大会官网
  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP