您的位置:首页 > 其它

ffmpeg源码分析之vfwcap

2013-02-26 15:59 218 查看
很多人想做基于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函数:

看注释吧,很全哦。

[cpp] view
plaincopy

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 thread

cparms.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);

}

再看一下读取一帧的函数

[cpp] view
plaincopy

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回调函数:

[cpp] view
plaincopy

//此函数在窗口线程中执行,VFW抓到一帧调用一次

static LRESULT CALLBACK videostream_cb(HWND hwnd, LPVIDEOHDR vdhdr)

{

AVFormatContext *s;

struct vfw_ctx *ctx;

AVPacketList **ppktl, *pktl_next;

//取得窗口中保存的AVFormatContext

s = (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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: