=====================================================
最簡單的基於FFmpeg的AVDevice樣例文章列表:
最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)
最簡單的基於FFmpeg的AVDevice樣例(屏幕錄制)
=====================================================
FFmpeg中有一個和多媒體設備交互的類庫:Libavdevice。
使用這個庫能夠讀取電腦(或者其它設備上)的多媒體設備的數據,或者輸出數據到指定的多媒體設備上。
Libavdevice支持以下設備作為輸入端:alsaLibavdevice支持以下設備作為輸出端:
avfoundation
bktr
dshow
dv1394
fbdev
gdigrab
iec61883
jack
lavfi
libcdio
libdc1394
openal
oss
pulse
qtkit
sndio
video4linux2, v4l2
vfwcap
x11grab
decklink
alsa
caca
decklink
fbdev
opengl
oss
pulse
sdl
sndio
xv
libavdevice使用
計划記錄兩個基於FFmpeg的libavdevice類庫的樣例。分成兩篇文章寫。本文記錄一個基於FFmpeg的Libavdevice類庫讀取攝像頭數據的樣例。下一篇文章記錄一個基於FFmpeg的Libavdevice類庫錄制屏幕的樣例。本文程序讀取計算機上的攝像頭的數據而且解碼顯示出來。
有關解碼顯示方面的代碼本文不再詳述,能夠參考文章:
《100行代碼實現最簡單的基於FFMPEG+SDL的視頻播放器(SDL1.x)》
本文主要記錄使用libavdevice須要注意的步驟。
首先。使用libavdevice的時候須要包括其頭文件:#include "libavdevice/avdevice.h"然后,在程序中須要注冊libavdevice:
avdevice_register_all();
接下來就能夠使用libavdevice的功能了。
使用libavdevice讀取數據和直接打開視頻文件比較相似。
由於系統的設備也被FFmpeg覺得是一種輸入的格式(即AVInputFormat)。使用FFmpeg打開一個普通的視頻文件使用例如以下函數:
AVFormatContext *pFormatCtx = avformat_alloc_context(); avformat_open_input(&pFormatCtx, "test.h265",NULL,NULL);
使用libavdevice的時候,唯一的不同在於須要首先查找用於輸入的設備。在這里使用av_find_input_format()完畢:
AVFormatContext *pFormatCtx = avformat_alloc_context(); AVInputFormat *ifmt=av_find_input_format("vfwcap"); avformat_open_input(&pFormatCtx, 0, ifmt,NULL);
上述代碼首先指定了vfw設備作為輸入設備。然后在URL中指定打開第0個設備(在我自己計算機上即是攝像頭設備)。
在Windows平台上除了使用vfw設備作為輸入設備之外。還能夠使用DirectShow作為輸入設備:
AVFormatContext *pFormatCtx = avformat_alloc_context(); AVInputFormat *ifmt=av_find_input_format("dshow"); avformat_open_input(&pFormatCtx,"video=Integrated Camera",ifmt,NULL) ;
使用ffmpeg.exe打開vfw設備和Directshow設備的方法能夠參考文章:
FFmpeg獲取DirectShow設備數據(攝像頭,錄屏)
注意事項
1. URL的格式是"video={設備名稱}",可是設備名稱外面不能加引號。比如在上述樣例中URL是"video=Integrated Camera",而不能寫成"video=\"Integrated Camera\"",否則就無法打開設備。這與直接使用ffmpeg.exe打開dshow設備(命令為: ffmpeg -list_options true -f dshow -i video="Integrated Camera")有非常大的不同。2. Dshow的設備名稱必須要提前獲取。在這里有兩種方法:
(1) 通過FFmpeg編程實現。使用例如以下代碼:
//Show Device 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"); }
上述代碼實際上相當於輸入了以下一條命令:
ffmpeg -list_devices true -f dshow -i dummy
執行的結果例如以下圖所看到的:
這時候須要把亂碼ANSI轉換為UTF-8。比如上圖中的第一個音頻設備顯示為“鍐呰楹﹀厠椋?
(Conexant 20672 SmartAudi”。轉碼之后即為“內裝麥克風 (Conexant 20672 SmartAudi”。使用轉碼之后的名稱就可以打開該設備。
(2) 自己去系統中看。
這種方法更簡單一些。可是缺點是須要手工操作。該方法使用DirectShow的調試工具GraphEdit(或者網上下一個GraphStudioNext)就可以查看輸入名稱。
打開GraphEdit選擇“圖像->插入濾鏡”


在Linux平台上能夠使用video4linux2打開視頻設備。在MacOS上,能夠使用avfoundation打開視頻設備,這里不再詳述。
代碼
以下直接貼上程序代碼:
/** * 最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭) * Simplest FFmpeg Device (Read Camera) * * 雷霄驊 Lei Xiaohua * leixiaohua1020@126.com * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 本程序實現了本地攝像頭數據的獲取解碼和顯示。是基於FFmpeg * 的libavdevice類庫最簡單的樣例。通過該樣例。能夠學習FFmpeg中 * libavdevice類庫的用法。 * 本程序在Windows下能夠使用2種方式讀取攝像頭數據: * 1.VFW: Video for Windows 屏幕捕捉設備。注意輸入URL是設備的序號。 * 從0至9。 * 2.dshow: 使用Directshow。注意作者機器上的攝像頭設備名稱是 * “Integrated Camera”。使用的時候須要改成自己電腦上攝像頭設 * 備的名稱。 * 在Linux下能夠使用video4linux2讀取攝像頭設備。 * 在MacOS下能夠使用avfoundation讀取攝像頭設備。 * * This software read data from Computer's Camera and play it. * It's the simplest example about usage of FFmpeg's libavdevice Library. * It's suiltable for the beginner of FFmpeg. * This software support 2 methods to read camera in Microsoft Windows: * 1.gdigrab: VfW (Video for Windows) capture input device. * The filename passed as input is the capture driver number, * ranging from 0 to 9. * 2.dshow: Use Directshow. Camera's name in author's computer is * "Integrated Camera". * It use video4linux2 to read Camera in Linux. * It use avfoundation to read Camera in MacOS. * */ #include <stdio.h> #define __STDC_CONSTANT_MACROS #ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libavdevice/avdevice.h" #include "SDL/SDL.h" }; #else //Linux... #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 //Output YUV420P #define OUTPUT_YUV420P 0 //'1' Use Dshow //'0' Use VFW #define USE_DSHOW 0 //Refresh Event #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; //Break SDL_Event event; event.type = SFM_BREAK_EVENT; SDL_PushEvent(&event); return 0; } //Show Dshow Device 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"); } //Show Dshow Device Option void show_dshow_device_option(){ AVFormatContext *pFormatCtx = avformat_alloc_context(); AVDictionary* options = NULL; av_dict_set(&options,"list_options","true",0); AVInputFormat *iformat = av_find_input_format("dshow"); printf("========Device Option Info======\n"); avformat_open_input(&pFormatCtx,"video=Integrated Camera",iformat,&options); printf("================================\n"); } //Show VFW Device void show_vfw_device(){ AVFormatContext *pFormatCtx = avformat_alloc_context(); AVInputFormat *iformat = av_find_input_format("vfwcap"); printf("========VFW Device Info======\n"); avformat_open_input(&pFormatCtx,"list",iformat,NULL); printf("=============================\n"); } //Show AVFoundation Device 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(); //Open File //char filepath[]="src01_480x272_22.h265"; //avformat_open_input(&pFormatCtx,filepath,NULL,NULL) //Register Device avdevice_register_all(); //Windows #ifdef _WIN32 //Show Dshow Device show_dshow_device(); //Show Device Options show_dshow_device_option(); //Show VFW Options show_vfw_device(); #if USE_DSHOW AVInputFormat *ifmt=av_find_input_format("dshow"); //Set own video device's name if(avformat_open_input(&pFormatCtx,"video=Integrated Camera",ifmt,NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } #else AVInputFormat *ifmt=av_find_input_format("vfwcap"); if(avformat_open_input(&pFormatCtx,"0",ifmt,NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } #endif #elif defined linux //Linux AVInputFormat *ifmt=av_find_input_format("video4linux2"); if(avformat_open_input(&pFormatCtx,"/dev/video0",ifmt,NULL)!=0){ printf("Couldn't open input stream.\n"); return -1; } #else show_avfoundation_device(); //Mac AVInputFormat *ifmt=av_find_input_format("avfoundation"); //Avfoundation //[video]:[audio] if(avformat_open_input(&pFormatCtx,"0",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("Couldn'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(); //unsigned char *out_buffer=(unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); //avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); //SDL---------------------------- 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=0,screen_h=0; SDL_Surface *screen; screen_w = pCodecCtx->width; screen_h = pCodecCtx->height; 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; //SDL End------------------------ 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 Read Camera",NULL); //Event Loop SDL_Event event; for (;;) { //Wait 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); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V #endif SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); } } av_free_packet(packet); }else{ //Exit Thread 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(out_buffer); av_free(pFrameYUV); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }
結果
程序的執行效果例如以下。輸出了攝像頭的數據。
#define OUTPUT_YUV420P 0
能夠通過以下的宏定義來確定使用VFW或者是Dshow打開攝像頭:
//'1' Use Dshow //'0' Use VFW #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的安裝路徑調整腳本里面的參數。
編譯命令例如以下。
::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_readcamera.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++進行編譯。編譯命令例如以下。
g++ simplest_ffmpeg_readcamera.cpp -g -o simplest_ffmpeg_readcamera.exe \ -I /usr/local/include -L /usr/local/lib \ -lmingw32 -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale
GCC(Linux):Linux命令行下執行compile_gcc.sh就可以使用GCC進行編譯。編譯命令例如以下。
gcc simplest_ffmpeg_readcamera.cpp -g -o simplest_ffmpeg_readcamera.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”參數。否則編譯無法通過。編譯命令例如以下。
gcc simplest_ffmpeg_readcamera.cpp -g -o simplest_ffmpeg_readcamera.out \ -framework Cocoa -I /usr/local/include -L /usr/local/lib -lSDLmain -lSDL -lavformat -lavcodec -lavutil -lavdevice -lswscale
PS:相關的編譯命令已經保存到了工程目錄中
此外,添加了MacOS下使用avfoundation讀取攝像頭的代碼。
CSDN下載地址:http://download.csdn.net/detail/leixiaohua1020/8445747
SourceForge上已經更新。