1. 程式人生 > >最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)

最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)

malloc projects == 格式 mac 跨平臺 buffer 版本 span

=====================================================
最簡單的基於FFmpeg的AVDevice樣例文章列表:

最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)

最簡單的基於FFmpeg的AVDevice樣例(屏幕錄制)
=====================================================


FFmpeg中有一個和多媒體設備交互的類庫:Libavdevice。

使用這個庫能夠讀取電腦(或者其它設備上)的多媒體設備的數據,或者輸出數據到指定的多媒體設備上。

Libavdevice支持以下設備作為輸入端:
alsa
avfoundation
bktr
dshow
dv1394
fbdev
gdigrab
iec61883
jack
lavfi
libcdio
libdc1394
openal
oss
pulse
qtkit
sndio
video4linux2, v4l2
vfwcap
x11grab
decklink
Libavdevice支持以下設備作為輸出端:
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選擇“圖像->插入濾鏡”

技術分享然後就能夠通過查看Audio Capture Sources來查看音頻輸入設備的中文簡體名稱了。從圖中能夠看出是“內裝麥克風 (Conexant 20672 SmartAudi”。
技術分享

在Linux平臺上能夠使用video4linux2打開視頻設備。在MacOS上,能夠使用avfoundation打開視頻設備,這裏不再詳述。


代碼

以下直接貼上程序代碼:

/**
 * 最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)
 * Simplest FFmpeg Device (Read Camera)
 *
 * 雷霄驊 Lei Xiaohua
 * [email protected]
 * 中國傳媒大學/數字電視技術
 * 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; }



結果

程序的執行效果例如以下。輸出了攝像頭的數據。

技術分享

能夠通過以下的宏定義來確定是否將解碼後的YUV420P數據輸出成文件:
#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上已經更新。

最簡單的基於FFmpeg的AVDevice樣例(讀取攝像頭)