当前位置:   article > 正文

ffmpeg源码分析之vfwcap

fvwcp
很多人想做基于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 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); } 再看一下读取一帧的函数

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; //取得窗口中保存的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; }




声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/950031
推荐阅读
相关标签
  

闽ICP备14008679号