ffmpeg源碼分析之vfwcap

來源:互聯網
上載者:User

很多人想做基於live555的webcam即時視頻傳輸.在linux下操作webcam有V4L架構.在windows下有兩種選擇:vfw和directshow.但directshow的架構很獨立,你如果想用它,就得做一個能打包rtp發送的render filter,想利用live555架構不是不可以,但是麻煩.所以vfw是最好的選擇.

本文分析ffmpeg的vfwcap的實現,同時也協助瞭解ffmpeg的架構和inputdevice的寫法.

要實現一個input device,首先要實作一個結構:
AVInputFormat ff_vfwcap_demuxer = {
.name = "vfwcap",
.long_name = NULL_IF_CONFIG_SMALL("VfW video capture"),
.priv_data_size = sizeof(struct vfw_ctx),
.read_header = vfw_read_header,
.read_packet = vfw_read_packet,
.read_close = vfw_read_close,
.flags = AVFMT_NOFILE,
.priv_class = &vfw_class,
};
可以看到,input device實際上是一個Input format.這裡實現了read header和,read packet和read close函數,分別在input device初始化,讀和關閉時被調用.

那麼先看一下read header函數:

看注釋吧,很全哦。

static int vfw_read_header(AVFormatContext *s, AVFormatParameters *ap){//vfw_ctx是使用VFW API時需要的一些對象和參數,是專用資料,儲存在priv_data中struct vfw_ctx *ctx = s->priv_data;AVCodecContext *codec;AVStream *st;int devnum;int bisize;BITMAPINFO *bi = NULL;CAPTUREPARMS cparms;DWORD biCompression;WORD biBitCount;int ret;AVRational framerate_q;if (!strcmp(s->filename, "list")) {for (devnum = 0; devnum <= 9; devnum++) {char driver_name[256];char driver_ver[256];//擷取VFW驅動資訊ret = capGetDriverDescription(devnum, driver_name,sizeof(driver_name), driver_ver, sizeof(driver_ver));if (ret) {av_log(s, AV_LOG_INFO, "Driver %d\n", devnum);av_log(s, AV_LOG_INFO, " %s\n", driver_name);av_log(s, AV_LOG_INFO, " %s\n", driver_ver);}}return AVERROR(EIO);}//調用Win API函數,建立與VFW Capture裝置相關的視窗//HWND_MESSAGE是自訂視窗訊息,用於響應VFW裝置發來的訊息。ctx->hwnd = capCreateCaptureWindow(NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0);if (!ctx->hwnd) {av_log(s, AV_LOG_ERROR, "Could not create capture window.\n");return AVERROR(EIO);}//s->filename是從命令列接收的,代表vfw裝置的序號,預設是0./* If atoi fails, devnum==0 and the default device is used */devnum = atoi(s->filename);//串連裝置ret = SendMessage(ctx->hwnd, WM_CAP_DRIVER_CONNECT, devnum, 0);if (!ret) {av_log(s, AV_LOG_ERROR, "Could not connect to device.\n");DestroyWindow(ctx->hwnd);return AVERROR(ENODEV);}//設定參數:是不是不要overlay,不要preview?我猜的,沒查msdn。SendMessage(ctx->hwnd, WM_CAP_SET_OVERLAY, 0, 0);SendMessage(ctx->hwnd, WM_CAP_SET_PREVIEW, 0, 0);//設定視頻資料接收函數。VFW擷取視頻是在它的視窗線程中,videostream_cb//在每次擷取一幀資料時被調用,所以它也在那個線程中執行ret = SendMessage(ctx->hwnd, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0,(LPARAM) videostream_cb);if (!ret) {av_log(s, AV_LOG_ERROR, "Could not set video stream callback.\n");goto fail;}//把AVFormatContext儲存在視窗的USERDATA區,為的是以後響應//VFW的視窗訊息時可以擷取到AVFormatContext對象SetWindowLongPtr(ctx->hwnd, GWLP_USERDATA, (LONG_PTR) s);//建立一個流對象並加入AVFormatContext對象中。st = av_new_stream(s, 0);if (!st) {vfw_read_close(s);return AVERROR(ENOMEM);}/* Set video format *///先取得video format結構的大小bisize = SendMessage(ctx->hwnd, WM_CAP_GET_VIDEOFORMAT, 0, 0);if (!bisize)goto fail;//分配存放video format結構的記憶體bi = av_malloc(bisize);if (!bi) {vfw_read_close(s);return AVERROR(ENOMEM);}//這次才是真正的擷取video format結構ret = SendMessage(ctx->hwnd, WM_CAP_GET_VIDEOFORMAT, bisize, (LPARAM) bi);if (!ret)goto fail;//顯示給使用者看看dump_bih(s, &bi->bmiHeader);//分析幀率,ctx->framerate是從命令列得到的ret = av_parse_video_rate(&framerate_q, ctx->framerate);if (ret < 0) {av_log(s, AV_LOG_ERROR, "Could not parse framerate '%s'.\n",ctx->framerate);goto fail;}//分析視頻的size,也是從命令列得到的,是使用者要求的if (ctx->video_size) {ret = av_parse_video_size(&bi->bmiHeader.biWidth,&bi->bmiHeader.biHeight, ctx->video_size);if (ret < 0) {av_log(s, AV_LOG_ERROR, "Couldn't parse video size.\n");goto fail;}}//video foramt中的參數被使用者輸入所替代,設定給VFW裝置ret = SendMessage(ctx->hwnd, WM_CAP_SET_VIDEOFORMAT, bisize, (LPARAM) bi);if (!ret) {av_log(s, AV_LOG_ERROR, "Could not set Video Format.\n");goto fail;}//是否是壓縮資料,vfw支援mjpeg編碼。biCompression = bi->bmiHeader.biCompression;biBitCount = bi->bmiHeader.biBitCount;/* Set sequence setup *///擷取流參數ret = SendMessage(ctx->hwnd, WM_CAP_GET_SEQUENCE_SETUP, sizeof(cparms),(LPARAM) &cparms);if (!ret)goto fail;//顯示了使用者看看dump_captureparms(s, &cparms);//設定一些我們想設定的流數cparms.fYield = 1; // Spawn a background threadcparms.dwRequestMicroSecPerFrame = (framerate_q.den * 1000000)/ framerate_q.num;//一幀持續的時間?是吧?cparms.fAbortLeftMouse = 0;cparms.fAbortRightMouse = 0;cparms.fCaptureAudio = 0;cparms.vKeyAbort = 0;//設定回去ret = SendMessage(ctx->hwnd, WM_CAP_SET_SEQUENCE_SETUP, sizeof(cparms),(LPARAM) &cparms);if (!ret)goto fail;//設定libav中相應的參數,要與VFW中的設定一致哦。codec = st->codec;codec->time_base = (AVRational) {framerate_q.den, framerate_q.num};codec->codec_type = AVMEDIA_TYPE_VIDEO;codec->width = bi->bmiHeader.biWidth;codec->height = bi->bmiHeader.biHeight;codec->pix_fmt = vfw_pixfmt(biCompression, biBitCount);if (codec->pix_fmt == PIX_FMT_NONE) {//如果沒有找到pix fmt,說明是種壓縮格式,分析出編碼器的id。codec->codec_id = vfw_codecid(biCompression);if (codec->codec_id == CODEC_ID_NONE) {//擷取不到這種編碼格式的編碼器,搞不定它,只能退出了。av_log(s, AV_LOG_ERROR, "Unknown compression type. ""Please report verbose (-v 9) debug information.\n");vfw_read_close(s);return AVERROR_PATCHWELCOME;}codec->bits_per_coded_sample = biBitCount;} else {//如果找到了pix fmt,說明是一種未編碼格式,那就是RAWVIDEO。codec->codec_id = CODEC_ID_RAWVIDEO;if (biCompression == BI_RGB) {codec->bits_per_coded_sample = biBitCount;codec->extradata = av_malloc(9 + FF_INPUT_BUFFER_PADDING_SIZE);if (codec->extradata) {codec->extradata_size = 9;memcpy(codec->extradata, "BottomUp", 9);}}}av_freep(&bi);//設定為每要個幀打時間戳記時的參數,32表示時間戳記是32位的,1和1000表示計時//單位為1/1000秒。av_set_pts_info(st, 32, 1, 1000);//建立互斥量,videostream_cb運行在VFW的窗線程中,而read_packet運行於ffmpeg的線程中,//因而需要對一些東西進行同步保護。ctx->mutex = CreateMutex(NULL, 0, NULL);if (!ctx->mutex) {av_log(s, AV_LOG_ERROR, "Could not create Mutex.\n");goto fail;}//建立事件,用於寫線程通知讀線程,資料到了,沒有它讀線程會空轉。ctx->event = CreateEvent(NULL, 1, 0, NULL);if (!ctx->event) {av_log(s, AV_LOG_ERROR, "Could not create Event.\n");goto fail;}//開始抓取視頻,NOFILE設定不儲存檔案?是吧?ret = SendMessage(ctx->hwnd, WM_CAP_SEQUENCE_NOFILE, 0, 0);if (!ret) {av_log(s, AV_LOG_ERROR, "Could not start capture sequence.\n");goto fail;}return 0;fail: av_freep(&bi);vfw_read_close(s);return AVERROR(EIO);}

再看一下讀取一幀的函數

static int vfw_read_packet(AVFormatContext *s, AVPacket *pkt){struct vfw_ctx *ctx = s->priv_data;AVPacketList *pktl = NULL;while (!pktl) {//等待VFW線程中向pkt1這個隊例中插入pkg,這兒的pkt是一幀。WaitForSingleObject(ctx->mutex, INFINITE);//該我們了,從隊列中取出一幀pktl = ctx->pktl;if (ctx->pktl) {*pkt = ctx->pktl->pkt;ctx->pktl = ctx->pktl->next;av_free(pktl);}ResetEvent(ctx->event);ReleaseMutex(ctx->mutex);if (!pktl) {if (s->flags & AVFMT_FLAG_NONBLOCK) {return AVERROR(EAGAIN);} else {//如果幀隊列為空白說明沒資料了,等待VFW線程(videostream_cb)向隊列中插入新的幀WaitForSingleObject(ctx->event, INFINITE);}}}//取得了一幀,返回給調用者。ctx->curbufsize -= pkt->size;return pkt->size;}

VFW回呼函數:

//此函數在視窗線程中執行,VFW抓到一幀調用一次static LRESULT CALLBACK videostream_cb(HWND hwnd, LPVIDEOHDR vdhdr){AVFormatContext *s;struct vfw_ctx *ctx;AVPacketList **ppktl, *pktl_next;//取得視窗中儲存的AVFormatContexts = (AVFormatContext *) GetWindowLongPtr(hwnd, GWLP_USERDATA);//從而取得私人資料ctx = s->priv_data;dump_videohdr(s, vdhdr);//我們可以丟掉這一幀嗎?if (shall_we_drop(s))return FALSE;//等待ffmpeg線程讀取幀隊列WaitForSingleObject(ctx->mutex, INFINITE);//分配一個pkg,用於存放這一幀pktl_next = av_mallocz(sizeof(AVPacketList));if (!pktl_next)goto fail;if (av_new_packet(&pktl_next->pkt, vdhdr->dwBytesUsed) < 0) {av_free(pktl_next);goto fail;}//時間戳記pktl_next->pkt.pts = vdhdr->dwTimeCaptured;memcpy(pktl_next->pkt.data, vdhdr->lpData, vdhdr->dwBytesUsed);//將包插入隊列for (ppktl = &ctx->pktl; *ppktl; ppktl = &(*ppktl)->next);*ppktl = pktl_next;ctx->curbufsize += vdhdr->dwBytesUsed;SetEvent(ctx->event);//設定事件,使read_packet可以操作隊列。因為隊列不為空白了ReleaseMutex(ctx->mutex);return TRUE;fail: ReleaseMutex(ctx->mutex);return FALSE;}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.