天天看点

最简单的基于FFmpeg的AVDevice例子(屏幕录制)

感谢作者分享:https://blog.csdn.net/leixiaohua1020/article/details/39706721

=====================================================

最简单的基于FFmpeg的AVDevice例子文章列表:

最简单的基于FFmpeg的AVDevice例子(读取摄像头)

最简单的基于FFmpeg的AVDevice例子(屏幕录制)

=====================================================

 FFmpeg中有一个和多媒体设备交互的类库:Libavdevice。使用这个库可以读取电脑的多媒体设备的数据,或者输出数据到指定的多媒体设备上。

计划写2个有关FFmpeg的libavdevice类库的例子。上篇文章记录了一个基于FFmpeg的Libavdevice类库读取摄像头数据的例子。本篇文章记录一个基于FFmpeg的Libavdevice类库录制屏幕的例子。本文程序录制当前桌面内容并且解码显示出来。有关解码显示方面的代码本文不再详述,可以参考文章:

《100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)》

抓屏方法

上篇文章记录了libavdevice的使用方法,本文不再重复。在Windows系统使用libavdevice抓取屏幕数据有两种方法:gdigrab和dshow。下文分别介绍。

1. gdigrab

gdigrab是FFmpeg专门用于抓取Windows桌面的设备。非常适合用于屏幕录制。它通过不同的输入URL支持两种方式的抓取:

(1)“desktop”:抓取整张桌面。或者抓取桌面中的一个特定的区域。

(2)“title={窗口名称}”:抓取屏幕中特定的一个窗口(目前中文窗口还有乱码问题)。

gdigrab另外还支持一些参数,用于设定抓屏的位置:

offset_x:抓屏起始点横坐标。

offset_y:抓屏起始点纵坐标。

video_size:抓屏的大小。

framerate:抓屏的帧率。

参考的代码如下:

[cpp]  view plain  copy

  1. //Use gdigrab  
  2.  AVDictionary* options = NULL;  
  3.  //Set some options  
  4.  //grabbing frame rate  
  5.  //av_dict_set(&options,"framerate","5",0);  
  6.  //The distance from the left edge of the screen or desktop  
  7.  //av_dict_set(&options,"offset_x","20",0);  
  8.  //The distance from the top edge of the screen or desktop  
  9.  //av_dict_set(&options,"offset_y","40",0);  
  10.  //Video frame size. The default is to capture the full screen  
  11.  //av_dict_set(&options,"video_size","640x480",0);  
  12.  AVInputFormat *ifmt=av_find_input_format("gdigrab");  
  13.  if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){  
  14.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  15.   return -1;  
  16.   }  

2. dshow

使用dshow抓屏需要安装抓屏软件:screen-capture-recorder

软件地址: http://sourceforge.net/projects/screencapturer/

下载软件安装完成后,可以指定dshow的输入设备为“screen-capture-recorder”即可。有关dshow设备的使用方法在上一篇文章中已经有详细叙述,这里不再重复。参考的代码如下:

[cpp]  view plain  copy

  1. AVInputFormat *ifmt=av_find_input_format("dshow");  
  2.  if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){  
  3.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  4.   return -1;  
  5.  }  

注:上述两种抓屏方法也可以直接使用ffmpeg.exe的命令行完成,可以参考文章:

FFmpeg获取DirectShow设备数据(摄像头,录屏)

在Linux下可以使用x11grab抓屏,在MacOS下可以使用avfoundation抓屏,在这里不再详细叙述。

代码

下面直接贴上程序代码:

[cpp]  view plain  copy

  1. #include <stdio.h>  
  2. #define __STDC_CONSTANT_MACROS  
  3. #ifdef _WIN32  
  4. //Windows  
  5. extern "C"  
  6. {  
  7. #include "libavcodec/avcodec.h"  
  8. #include "libavformat/avformat.h"  
  9. #include "libswscale/swscale.h"  
  10. #include "libavdevice/avdevice.h"  
  11. #include "SDL/SDL.h"  
  12. };  
  13. #else  
  14. //Linux...  
  15. #ifdef __cplusplus  
  16. extern "C"  
  17. {  
  18. #endif  
  19. #include <libavcodec/avcodec.h>  
  20. #include <libavformat/avformat.h>  
  21. #include <libswscale/swscale.h>  
  22. #include <libavdevice/avdevice.h>  
  23. #include <SDL/SDL.h>  
  24. #ifdef __cplusplus  
  25. };  
  26. #endif  
  27. #endif  
  28. //Output YUV420P   
  29. #define OUTPUT_YUV420P 0  
  30. //'1' Use Dshow   
  31. //'0' Use GDIgrab  
  32. #define USE_DSHOW 0  
  33. //Refresh Event  
  34. #define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)  
  35. #define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)  
  36. int thread_exit=0;  
  37. int sfp_refresh_thread(void *opaque)  
  38. {  
  39.     thread_exit=0;  
  40.     while (!thread_exit) {  
  41.         SDL_Event event;  
  42.         event.type = SFM_REFRESH_EVENT;  
  43.         SDL_PushEvent(&event);  
  44.         SDL_Delay(40);  
  45.     }  
  46.     thread_exit=0;  
  47.     //Break  
  48.     SDL_Event event;  
  49.     event.type = SFM_BREAK_EVENT;  
  50.     SDL_PushEvent(&event);  
  51.     return 0;  
  52. }  
  53. //Show Dshow Device  
  54. void show_dshow_device(){  
  55.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  56.     AVDictionary* options = NULL;  
  57.     av_dict_set(&options,"list_devices","true",0);  
  58.     AVInputFormat *iformat = av_find_input_format("dshow");  
  59.     printf("========Device Info=============\n");  
  60.     avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);  
  61.     printf("================================\n");  
  62. }  
  63. //Show AVFoundation Device  
  64. void show_avfoundation_device(){  
  65.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  66.     AVDictionary* options = NULL;  
  67.     av_dict_set(&options,"list_devices","true",0);  
  68.     AVInputFormat *iformat = av_find_input_format("avfoundation");  
  69.     printf("==AVFoundation Device Info===\n");  
  70.     avformat_open_input(&pFormatCtx,"",iformat,&options);  
  71.     printf("=============================\n");  
  72. }  
  73. int main(int argc, char* argv[])  
  74. {  
  75.     AVFormatContext *pFormatCtx;  
  76.     int             i, videoindex;  
  77.     AVCodecContext  *pCodecCtx;  
  78.     AVCodec         *pCodec;  
  79.     av_register_all();  
  80.     avformat_network_init();  
  81.     pFormatCtx = avformat_alloc_context();  
  82.     //Open File  
  83.     //char filepath[]="src01_480x272_22.h265";  
  84.     //avformat_open_input(&pFormatCtx,filepath,NULL,NULL)  
  85.     //Register Device  
  86.     avdevice_register_all();  
  87.     //Windows  
  88. #ifdef _WIN32  
  89. #if USE_DSHOW  
  90.     //Use dshow  
  91.     //  
  92.     //Need to Install screen-capture-recorder  
  93.     //screen-capture-recorder  
  94.     //Website: http://sourceforge.net/projects/screencapturer/  
  95.     //  
  96.     AVInputFormat *ifmt=av_find_input_format("dshow");  
  97.     if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){  
  98.         printf("Couldn't open input stream.\n");  
  99.         return -1;  
  100.     }  
  101. #else  
  102.     //Use gdigrab  
  103.     AVDictionary* options = NULL;  
  104.     //Set some options  
  105.     //grabbing frame rate  
  106.     //av_dict_set(&options,"framerate","5",0);  
  107.     //The distance from the left edge of the screen or desktop  
  108.     //av_dict_set(&options,"offset_x","20",0);  
  109.     //The distance from the top edge of the screen or desktop  
  110.     //av_dict_set(&options,"offset_y","40",0);  
  111.     //Video frame size. The default is to capture the full screen  
  112.     //av_dict_set(&options,"video_size","640x480",0);  
  113.     AVInputFormat *ifmt=av_find_input_format("gdigrab");  
  114.     if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){  
  115.         printf("Couldn't open input stream.\n");  
  116.         return -1;  
  117.     }  
  118. #endif  
  119. #elif defined linux  
  120.     //Linux  
  121.     AVDictionary* options = NULL;  
  122.     //Set some options  
  123.     //grabbing frame rate  
  124.     //av_dict_set(&options,"framerate","5",0);  
  125.     //Make the grabbed area follow the mouse  
  126.     //av_dict_set(&options,"follow_mouse","centered",0);  
  127.     //Video frame size. The default is to capture the full screen  
  128.     //av_dict_set(&options,"video_size","640x480",0);  
  129.     AVInputFormat *ifmt=av_find_input_format("x11grab");  
  130.     //Grab at position 10,20  
  131.     if(avformat_open_input(&pFormatCtx,":0.0+10,20",ifmt,&options)!=0){  
  132.         printf("Couldn't open input stream.\n");  
  133.         return -1;  
  134.     }  
  135. #else  
  136.     show_avfoundation_device();  
  137.     //Mac  
  138.     AVInputFormat *ifmt=av_find_input_format("avfoundation");  
  139.     //Avfoundation  
  140.     //[video]:[audio]  
  141.     if(avformat_open_input(&pFormatCtx,"1",ifmt,NULL)!=0){  
  142.         printf("Couldn't open input stream.\n");  
  143.         return -1;  
  144.     }  
  145. #endif  
  146.     if(avformat_find_stream_info(pFormatCtx,NULL)<0)  
  147.     {  
  148.         printf("Couldn't find stream information.\n");  
  149.         return -1;  
  150.     }  
  151.     videoindex=-1;  
  152.     for(i=0; i<pFormatCtx->nb_streams; i++)   
  153.         if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)  
  154.         {  
  155.             videoindex=i;  
  156.             break;  
  157.         }  
  158.     if(videoindex==-1)  
  159.     {  
  160.         printf("Didn't find a video stream.\n");  
  161.         return -1;  
  162.     }  
  163.     pCodecCtx=pFormatCtx->streams[videoindex]->codec;  
  164.     pCodec=avcodec_find_decoder(pCodecCtx->codec_id);  
  165.     if(pCodec==NULL)  
  166.     {  
  167.         printf("Codec not found.\n");  
  168.         return -1;  
  169.     }  
  170.     if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)  
  171.     {  
  172.         printf("Could not open codec.\n");  
  173.         return -1;  
  174.     }  
  175.     AVFrame *pFrame,*pFrameYUV;  
  176.     pFrame=av_frame_alloc();  
  177.     pFrameYUV=av_frame_alloc();  
  178.     //unsigned char *out_buffer=(unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));  
  179.     //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  
  180.     //SDL----------------------------  
  181.     if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    
  182.         printf( "Could not initialize SDL - %s\n", SDL_GetError());   
  183.         return -1;  
  184.     }   
  185.     int screen_w=640,screen_h=360;  
  186.     const SDL_VideoInfo *vi = SDL_GetVideoInfo();  
  187.     //Half of the Desktop's width and height.  
  188.     screen_w = vi->current_w/2;  
  189.     screen_h = vi->current_h/2;  
  190.     SDL_Surface *screen;   
  191.     screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);  
  192.     if(!screen) {    
  193.         printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());    
  194.         return -1;  
  195.     }  
  196.     SDL_Overlay *bmp;   
  197.     bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen);   
  198.     SDL_Rect rect;  
  199.     rect.x = 0;      
  200.     rect.y = 0;      
  201.     rect.w = screen_w;      
  202.     rect.h = screen_h;    
  203.     //SDL End------------------------  
  204.     int ret, got_picture;  
  205.     AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));  
  206. #if OUTPUT_YUV420P   
  207.     FILE *fp_yuv=fopen("output.yuv","wb+");    
  208. #endif    
  209.     struct SwsContext *img_convert_ctx;  
  210.     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);   
  211.     //------------------------------  
  212.     SDL_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread,NULL);  
  213.     //  
  214.     SDL_WM_SetCaption("Simplest FFmpeg Grab Desktop",NULL);  
  215.     //Event Loop  
  216.     SDL_Event event;  
  217.     for (;;) {  
  218.         //Wait  
  219.         SDL_WaitEvent(&event);  
  220.         if(event.type==SFM_REFRESH_EVENT){  
  221.             //------------------------------  
  222.             if(av_read_frame(pFormatCtx, packet)>=0){  
  223.                 if(packet->stream_index==videoindex){  
  224.                     ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);  
  225.                     if(ret < 0){  
  226.                         printf("Decode Error.\n");  
  227.                         return -1;  
  228.                     }  
  229.                     if(got_picture){  
  230.                         SDL_LockYUVOverlay(bmp);  
  231.                         pFrameYUV->data[0]=bmp->pixels[0];  
  232.                         pFrameYUV->data[1]=bmp->pixels[2];  
  233.                         pFrameYUV->data[2]=bmp->pixels[1];       
  234.                         pFrameYUV->linesize[0]=bmp->pitches[0];  
  235.                         pFrameYUV->linesize[1]=bmp->pitches[2];     
  236.                         pFrameYUV->linesize[2]=bmp->pitches[1];  
  237.                         sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);  
  238. #if OUTPUT_YUV420P    
  239.                         int y_size=pCodecCtx->width*pCodecCtx->height;      
  240.                         fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y     
  241.                         fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U    
  242.                         fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V    
  243. #endif    
  244.                         SDL_UnlockYUVOverlay(bmp);   
  245.                         SDL_DisplayYUVOverlay(bmp, &rect);   
  246.                     }  
  247.                 }  
  248.                 av_free_packet(packet);  
  249.             }else{  
  250.                 //Exit Thread  
  251.                 thread_exit=1;  
  252.             }  
  253.         }else if(event.type==SDL_QUIT){  
  254.             thread_exit=1;  
  255.         }else if(event.type==SFM_BREAK_EVENT){  
  256.             break;  
  257.         }  
  258.     }  
  259.     sws_freeContext(img_convert_ctx);  
  260. #if OUTPUT_YUV420P   
  261.     fclose(fp_yuv);  
  262. #endif   
  263.     SDL_Quit();  
  264.     //av_free(out_buffer);  
  265.     av_free(pFrameYUV);  
  266.     avcodec_close(pCodecCtx);  
  267.     avformat_close_input(&pFormatCtx);  
  268.     return 0;  
  269. }  

结果

程序的运行效果如下。这个运行结果还是十分有趣的,会出现一个屏幕“嵌套”在另一个屏幕里面的现象,环环相套。

最简单的基于FFmpeg的AVDevice例子(屏幕录制)

可以通过代码定义的宏来确定是否将解码后的YUV420P数据输出成文件:

[cpp]  view plain  copy

  1. #define OUTPUT_YUV420P 0  

可以通过下面的宏定义来确定使用GDIGrab或者是Dshow打开摄像头:

[cpp]  view plain  copy

  1. //'1' Use Dshow   
  2. //'0' Use GDIgrab  
  3. #define USE_DSHOW 0  

下载

Simplest FFmpeg Device 

项目主页

SourceForge:https://sourceforge.net/projects/simplestffmpegdevice/

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_device

开源中国:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_device

CSDN下载地址:

http://download.csdn.net/detail/leixiaohua1020/7994049

注:

 本工程包含两个基于FFmpeg的libavdevice的例子:

 simplest_ffmpeg_grabdesktop:屏幕录制。

 simplest_ffmpeg_readcamera:读取摄像头

更新-1.1(2015.1.9)=========================================

该版本中,修改了SDL的显示方式,弹出的窗口可以移动了。

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8344695

更新-1.2 (2015.2.13)=========================================

这次考虑到了跨平台的要求,调整了源代码。经过这次调整之后,源代码可以在以下平台编译通过:

VC++:打开sln文件即可编译,无需配置。
cl.exe:打开compile_cl.bat即可命令行下使用cl.exe进行编译,注意可能需要按照VC的安装路径调整脚本里面的参数。编译命令如下。

[plain]  view plain  copy

  1. ::VS2010 Environment  
  2. call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"  
  3. ::include  
  4. @set INCLUDE=include;%INCLUDE%  
  5. ::lib  
  6. @set LIB=lib;%LIB%  
  7. ::compile and link  
  8. cl simplest_ffmpeg_grabdesktop.cpp /MD /link SDL.lib SDLmain.lib avcodec.lib ^  
  9. avformat.lib avutil.lib avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib ^  
  10. /SUBSYSTEM:WINDOWS /OPT:NOREF  
MinGW:MinGW命令行下运行compile_mingw.sh即可使用MinGW的g++进行编译。编译命令如下。

[plain]  view plain  copy

  1. g++ simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.exe \  
  2. -I /usr/local/include -L /usr/local/lib \  
  3. -lmingw32 -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  
GCC(Linux):Linux命令行下运行compile_gcc.sh即可使用GCC进行编译。编译命令如下。

[plain]  view plain  copy

  1. gcc simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.out \  
  2. -I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  
GCC(MacOS):MacOS命令行下运行compile_gcc_mac.sh即可使用GCC进行编译。Mac的GCC和Linux的GCC差别不大,但是使用SDL1.2的时候,必须加上“-framework Cocoa”参数,否则编译无法通过。编译命令如下。

[plain]  view plain  copy

  1. gcc simplest_ffmpeg_grabdesktop.cpp -g -o simplest_ffmpeg_grabdesktop.out \  
  2. -framework Cocoa -I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale  

PS:相关的编译命令已经保存到了工程文件夹中

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8445747

SourceForge上已经更新。