ffmpeg源码分析之vfwcap

很多人想做基于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;
}





  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值