=====================================================
最簡單的基于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 plaincopy
???AVDictionary*?options?=?NULL;??????????????????????????????AVInputFormat?*ifmt=av_find_input_format("gdigrab");???if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){????printf("Couldn't?open?input?stream.(無法打開輸入流)\n");????return?-1;????}??
2.?dshow
使用dshow抓屏需要安裝抓屏軟件:screen-capture-recorder
軟件地址:
http://sourceforge.net/projects/screencapturer/
下載軟件安裝完成后,可以指定dshow的輸入設備為“screen-capture-recorder”即可。有關dshow設備的使用方法在上一篇文章中已經有詳細敘述,這里不再重復。參考的代碼如下:
[cpp]?view plaincopy
AVInputFormat?*ifmt=av_find_input_format("dshow");???if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){????printf("Couldn't?open?input?stream.(無法打開輸入流)\n");????return?-1;???}??
注:上述兩種抓屏方法也可以直接使用ffmpeg.exe的命令行完成,可以參考文章:
FFmpeg獲取DirectShow設備數據(攝像頭,錄屏)
在Linux下可以使用x11grab抓屏,在MacOS下可以使用avfoundation抓屏,在這里不再詳細敘述。
代碼
下面直接貼上程序代碼:
[cpp]?view plaincopy
???????????????????????????????????#include?<stdio.h>????#define?__STDC_CONSTANT_MACROS????#ifdef?_WIN32????extern?"C"??{??#include?"libavcodec/avcodec.h"??#include?"libavformat/avformat.h"??#include?"libswscale/swscale.h"??#include?"libavdevice/avdevice.h"??#include?"SDL/SDL.h"??};??#else????#ifdef?__cplusplus??extern?"C"??{??#endif??#include?<libavcodec/avcodec.h>??#include?<libavformat/avformat.h>??#include?<libswscale/swscale.h>??#include?<libavdevice/avdevice.h>??#include?<SDL/SDL.h>??#ifdef?__cplusplus??};??#endif??#endif??????#define?OUTPUT_YUV420P?0??????#define?USE_DSHOW?0??????#define?SFM_REFRESH_EVENT??(SDL_USEREVENT?+?1)????#define?SFM_BREAK_EVENT??(SDL_USEREVENT?+?2)????int?thread_exit=0;????int?sfp_refresh_thread(void?*opaque)??{??????thread_exit=0;??????while?(!thread_exit)?{??????????SDL_Event?event;??????????event.type?=?SFM_REFRESH_EVENT;??????????SDL_PushEvent(&event);??????????SDL_Delay(40);??????}??????thread_exit=0;????????????SDL_Event?event;??????event.type?=?SFM_BREAK_EVENT;??????SDL_PushEvent(&event);????????return?0;??}??????void?show_dshow_device(){??????AVFormatContext?*pFormatCtx?=?avformat_alloc_context();??????AVDictionary*?options?=?NULL;??????av_dict_set(&options,"list_devices","true",0);??????AVInputFormat?*iformat?=?av_find_input_format("dshow");??????printf("========Device?Info=============\n");??????avformat_open_input(&pFormatCtx,"video=dummy",iformat,&options);??????printf("================================\n");??}??????void?show_avfoundation_device(){??????AVFormatContext?*pFormatCtx?=?avformat_alloc_context();??????AVDictionary*?options?=?NULL;??????av_dict_set(&options,"list_devices","true",0);??????AVInputFormat?*iformat?=?av_find_input_format("avfoundation");??????printf("==AVFoundation?Device?Info===\n");??????avformat_open_input(&pFormatCtx,"",iformat,&options);??????printf("=============================\n");??}????????int?main(int?argc,?char*?argv[])??{????????AVFormatContext?*pFormatCtx;??????int?????????????i,?videoindex;??????AVCodecContext??*pCodecCtx;??????AVCodec?????????*pCodec;????????????av_register_all();??????avformat_network_init();??????pFormatCtx?=?avformat_alloc_context();??????????????????????????????????????avdevice_register_all();????????#ifdef?_WIN32??#if?USE_DSHOW??????????????????????????????????????????AVInputFormat?*ifmt=av_find_input_format("dshow");??????if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){??????????printf("Couldn't?open?input?stream.\n");??????????return?-1;??????}??#else????????????AVDictionary*?options?=?NULL;????????????????????????????????????????????????????????????AVInputFormat?*ifmt=av_find_input_format("gdigrab");??????if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){??????????printf("Couldn't?open?input?stream.\n");??????????return?-1;??????}????#endif??#elif?defined?linux????????????AVDictionary*?options?=?NULL;????????????????????????????????????????????????AVInputFormat?*ifmt=av_find_input_format("x11grab");????????????if(avformat_open_input(&pFormatCtx,":0.0+10,20",ifmt,&options)!=0){??????????printf("Couldn't?open?input?stream.\n");??????????return?-1;??????}??#else??????show_avfoundation_device();????????????AVInputFormat?*ifmt=av_find_input_format("avfoundation");??????????????????if(avformat_open_input(&pFormatCtx,"1",ifmt,NULL)!=0){??????????printf("Couldn't?open?input?stream.\n");??????????return?-1;??????}??#endif????????if(avformat_find_stream_info(pFormatCtx,NULL)<0)??????{??????????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;??????pCodec=avcodec_find_decoder(pCodecCtx->codec_id);??????if(pCodec==NULL)??????{??????????printf("Codec?not?found.\n");??????????return?-1;??????}??????if(avcodec_open2(pCodecCtx,?pCodec,NULL)<0)??????{??????????printf("Could?not?open?codec.\n");??????????return?-1;??????}??????AVFrame?*pFrame,*pFrameYUV;??????pFrame=av_frame_alloc();??????pFrameYUV=av_frame_alloc();????????????????????????if(SDL_Init(SDL_INIT_VIDEO?|?SDL_INIT_AUDIO?|?SDL_INIT_TIMER))?{????????????printf(?"Could?not?initialize?SDL?-?%s\n",?SDL_GetError());???????????return?-1;??????}???????int?screen_w=640,screen_h=360;??????const?SDL_VideoInfo?*vi?=?SDL_GetVideoInfo();????????????screen_w?=?vi->current_w/2;??????screen_h?=?vi->current_h/2;??????SDL_Surface?*screen;???????screen?=?SDL_SetVideoMode(screen_w,?screen_h,?0,0);????????if(!screen)?{????????????printf("SDL:?could?not?set?video?mode?-?exiting:%s\n",SDL_GetError());????????????return?-1;??????}??????SDL_Overlay?*bmp;???????bmp?=?SDL_CreateYUVOverlay(pCodecCtx->width,?pCodecCtx->height,SDL_YV12_OVERLAY,?screen);???????SDL_Rect?rect;??????rect.x?=?0;??????????rect.y?=?0;??????????rect.w?=?screen_w;??????????rect.h?=?screen_h;??????????????int?ret,?got_picture;????????AVPacket?*packet=(AVPacket?*)av_malloc(sizeof(AVPacket));????#if?OUTPUT_YUV420P???????FILE?*fp_yuv=fopen("output.yuv","wb+");????#endif??????????struct?SwsContext?*img_convert_ctx;??????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);?????????????SDL_Thread?*video_tid?=?SDL_CreateThread(sfp_refresh_thread,NULL);????????????SDL_WM_SetCaption("Simplest?FFmpeg?Grab?Desktop",NULL);????????????SDL_Event?event;????????for?(;;)?{????????????????????SDL_WaitEvent(&event);??????????if(event.type==SFM_REFRESH_EVENT){????????????????????????????if(av_read_frame(pFormatCtx,?packet)>=0){??????????????????if(packet->stream_index==videoindex){??????????????????????ret?=?avcodec_decode_video2(pCodecCtx,?pFrame,?&got_picture,?packet);??????????????????????if(ret?<?0){??????????????????????????printf("Decode?Error.\n");??????????????????????????return?-1;??????????????????????}??????????????????????if(got_picture){??????????????????????????SDL_LockYUVOverlay(bmp);??????????????????????????pFrameYUV->data[0]=bmp->pixels[0];??????????????????????????pFrameYUV->data[1]=bmp->pixels[2];??????????????????????????pFrameYUV->data[2]=bmp->pixels[1];???????????????????????????????pFrameYUV->linesize[0]=bmp->pitches[0];??????????????????????????pFrameYUV->linesize[1]=bmp->pitches[2];?????????????????????????????pFrameYUV->linesize[2]=bmp->pitches[1];??????????????????????????sws_scale(img_convert_ctx,?(const?unsigned?char*?const*)pFrame->data,?pFrame->linesize,?0,?pCodecCtx->height,?pFrameYUV->data,?pFrameYUV->linesize);????#if?OUTPUT_YUV420P????????????????????????????int?y_size=pCodecCtx->width*pCodecCtx->height;??????????????????????????????fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);??????????????????????????????fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);????????????????????????????fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);????#endif????????????????????????????SDL_UnlockYUVOverlay(bmp);?????????????????????????????????????????????????????SDL_DisplayYUVOverlay(bmp,?&rect);?????????????????????????}??????????????????}??????????????????av_free_packet(packet);??????????????}else{????????????????????????????????????thread_exit=1;??????????????}??????????}else?if(event.type==SDL_QUIT){??????????????thread_exit=1;??????????}else?if(event.type==SFM_BREAK_EVENT){??????????????break;??????????}????????}??????????sws_freeContext(img_convert_ctx);????#if?OUTPUT_YUV420P???????fclose(fp_yuv);??#endif?????????SDL_Quit();??????????????av_free(pFrameYUV);??????avcodec_close(pCodecCtx);??????avformat_close_input(&pFormatCtx);????????return?0;??}??
結果
程序的運行效果如下。這個運行結果還是十分有趣的,會出現一個屏幕“嵌套”在另一個屏幕里面的現象,環環相套。
可以通過代碼定義的宏來確定是否將解碼后的YUV420P數據輸出成文件:
[cpp]?view plaincopy
#define?OUTPUT_YUV420P?0??
可以通過下面的宏定義來確定使用GDIGrab或者是Dshow打開攝像頭:
[cpp]?view plaincopy
????#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 plaincopy
::VS2010?Environment??call?"D:\Program?Files\Microsoft?Visual?Studio?10.0\VC\vcvarsall.bat"??::include??@set?INCLUDE=include;%INCLUDE%??::lib??@set?LIB=lib;%LIB%??::compile?and?link??cl?simplest_ffmpeg_grabdesktop.cpp?/MD?/link?SDL.lib?SDLmain.lib?avcodec.lib?^??avformat.lib?avutil.lib?avdevice.lib?avfilter.lib?postproc.lib?swresample.lib?swscale.lib?^??/SUBSYSTEM:WINDOWS?/OPT:NOREF??
MinGW:MinGW命令行下運行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
[plain]?view plaincopy
g++?simplest_ffmpeg_grabdesktop.cpp?-g?-o?simplest_ffmpeg_grabdesktop.exe?\??-I?/usr/local/include?-L?/usr/local/lib?\??-lmingw32?-lSDLmain?-lSDL?-lavformat?-lavcodec?-lavutil?-lavdevice?-lswscale??
GCC(Linux):Linux命令行下運行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
[plain]?view plaincopy
gcc?simplest_ffmpeg_grabdesktop.cpp?-g?-o?simplest_ffmpeg_grabdesktop.out?\??-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 plaincopy
gcc?simplest_ffmpeg_grabdesktop.cpp?-g?-o?simplest_ffmpeg_grabdesktop.out?\??-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上已經更新。
總結
以上是生活随笔為你收集整理的最简单的基于FFmpeg的AVDevice例子(屏幕录制)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。