音视频采集—Windows平台VFW

因为Windows开发相对方便一些,所以将VFW作为入门篇进行分享。

VFW(Video for Windows)是Microsoft推出的关于数字视频的一个软件开发包。

我们在这里主要分享:核心API调用、采集的视频的颜色空间类型、颜色空间类型转换、添加水印OSD。

目录

1、主要的API调用过程:

2、采集到的音频数据格式:

3、采集到的视频数据格式:

4、将YUY2转换为YUV420P(YU12/I420):

5、添加水印:


1、主要的API调用过程:

capSetCallbackOnStatus,设置capture status的callback。status:IDS_CAP_BEGIN/IDS_CAP_END。
capSetCallbackOnVideoStream,设置capture video stream的callback。
capSetCallbackOnWaveStream,设置capture audio stream的callback。
videoStreamCallback和audioStreamCallback在同一个线程。

/*开始捕捉(即便可以预览,但只有capCaptureSequenceNoFile才是真正开始采集数据)*/
capCaptureSequenceNoFile(m_hWndCapture);

capSetCallbackOnFrame  和  capSetCallbackOnVideoStream  区别:
capSetCallbackOnFrame  只要驱动连接上,就开始调用了。
capSetCallbackOnVideoStream  只有当开始采集时,才调用。

使用 capSetCallbackOnFrame 的时候,可以不用 capCaptureSequenceNoFile ,但是视频帧里面没有时间信息
使用 capSetCallbackOnVideoStream 的时候,要用 capCaptureSequenceNoFile

2、采集到的音频数据格式:

capGetAudioFormat(m_hWndVideo, &wave_format, sizeof(wave_format));

格式:PCM
采样率:11025HZ
通道数:1
编码格式:unsigned 8-bit PCM
字节序:Little endian

PCM转为WAVE格式:在原始的PCM数据前,添加42字节的WAVE头。

3、采集到的视频数据格式:

capGetVideoFormat(m_hWndVideo , &m_BitmapInfo , sizeof(BITMAPINFO));
其中参数m_BitmapInfo.bmiHeader.biCompression表示视频数据格式。

我笔记本电脑自带的摄像头,biCompression值为:844715353(0x32595559),
经查阅,此值对应的视频数据格式为:YUY2
其设计艺术如下:0x32595559,小端方式内存存储,故由低向高:0x 59 55 59 32,对应的内容为:YUY2

BITMAPINFO.bmiHeader.biCompression的可能取值如下:
BI_RGB  
BI_RLE8
BI_RLE4
BI_BITFIELDS
……
根据此值确定视频数据格式,以便进一步对其进行处理。

4、将YUY2转换为YUV420P(YU12/I420):


/*
 * 每种采样格式的数据排列形式,包括打包模式和平面模式。基于此,才能在它们之间做格式转换
 * 从yuy2转为yuv420p(YUV422打包格式 转为 YUV420平面模式),要丢1/2的U和1/2的V
 * 丢弃:跨行丢弃
 *
 * YUY2:    y0  u0  y1  v0  y2  u1  y3  v1
 *          y4  u2  y5  v2  y6  u3  y7  v3
 *          y8  u4  y9  v4  y10 u5  y11 v5
 *          y12 u6  y13 v6  y14 u7  y15 v7
 *
 * YUV420P: y0  y1  y2  y3
 *          y4  y5  y6  y7
 *          y8  y9  y10 y11
 *          y12 y13 y14 y15
 *          u0  u1
 *          u4  u5
 *          v0  v1
 *          u4  u5
 *
 * YUY2:   y_size=W*H,u_size=1/2*W*H,v_size=1/2*W*H
 * YUV420P:y_size=W*H,u_size=1/4*W*H,v_size=1/4*W*H
 *
 * YUY2:   每行2*W个字节。
 *          1个像素对应1个Y,一个字节。2个像素对应一组UV,一组UV是2个字节。
 *          2个Y共用一组UV。2个像素共4个字节。
 */
void YUY2_YUV420P(BYTE *YUY2buff,BYTE *YUV420Pbuff, int w, int h)
{
    unsigned char * YUV420P_y = YUV420Pbuff;
    unsigned char * YUV420P_u = YUV420Pbuff + w*h;
    unsigned char * YUV420P_v = YUV420P_u + w*h/4;

    int y_size = w*h;
    for (int i = 0; i < y_size; i++) 
    {
        YUV420P_y[i] = YUY2buff[2*i];
    }

    for (int i = 0, k = 0; i < h; i++)
    {
        // deal one row, every two rows
        if (i%2 == 0)
        {
            // 2个像素4个字节,包含1个U
            // 反过来:1个U,对应2个像素,4个字节
            for (int j = 0; j < w/2; j++)
            {
                // 每行2*W个字节
                YUV420P_u[k] = YUY2buff[i*2*w + 4*j+1];
                k++;
            }
        }
    }

    for (int i = 0, k = 0; i < h; i++)
    {
        // deal one row, every two rows
        if (i%2 == 0)
        {
            // 2个像素4个字节,包含1个V
            // 反过来:1个V,对应2个像素,4个字节
            for (int j = 0; j < w/2; j++)
            {
                // 每行2*W个字节
                YUV420P_v[k] = YUY2buff[i*2*w + 4*j+3];
                k++;
            }
        }
    }
}

5、添加水印:

#define TEXT_WORD_SIZE          (1*2*16)
#define WORD_COLUMN_COUNT       (16) // 16bit==2byte
#define WORD_ROW_COUNT          (16)
#define WORDS_H_SPACE           (2) // 横向的间距

/*
 * yuv420p添加水印OSD:
 * 处理方式:使用16*16像素的点阵来绘制文字
 * 1) 2 个字节 1组(16bit),用以表示1行(16列)
 * 2)32个字节16组,用以表示16行
 * 3) 每个文字32个字节,以数组形式表示,存储地址按下标递增
 * 4)用short表示2个字节(1行,16bit)的数据内容,第一个字节存储在低位,第二个字节存储在高位
 */

const unsigned char table[] = {

    /*--  文字:  音  --*/
    /*--  楷体_GB231212;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x02,0x00,0x01,0x10,0x3F,0xF8,0x08,0x20,0x04,0x20,0x04,0x44,0xFF,0xFE,0x00,0x10,
    0x1F,0xF8,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,

    /*--  文字:  视  --*/
    /*--  楷体_GB231212;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x20,0x08,0x13,0xFC,0x12,0x08,0x02,0x48,0xFE,0x48,0x0A,0x48,0x12,0x48,0x32,0x48,
    0x5A,0x48,0x96,0x68,0x12,0xA8,0x10,0xA0,0x11,0x22,0x11,0x22,0x12,0x1E,0x14,0x00,

    /*--  文字:  频  --*/
    /*--  楷体_GB231212;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x10,0x04,0x13,0xFE,0x54,0x20,0x5E,0x44,0x51,0xFE,0x55,0x04,0xFF,0x24,0x11,0x24,
    0x55,0x24,0x55,0x24,0x55,0x24,0x95,0x24,0x08,0x20,0x10,0x50,0x20,0x8C,0x43,0x04

};

/*
* Function:     draw_Font_Func
* Description:  实现在yuv420p图片上面画字 
* Input:        char *ptr_frame             一帧视频的首地址
*               int width                   视频的宽度(分辨率)
*               int height                  视频的高度(分辨率)
*               const unsigned char font[]  画的字模
*               int word_count              字的个数
*               int startx                  写字的起点坐标x
*               int starty                  写字的起点坐标y
*               int color                   字颜色的选择,具体颜色在程序代码
* Return:       这里会把传进来的一帧视频的地址返回,可以不调用  
*/
char *draw_Font_Func(char *ptr_frame,
	int width, int height,
	const unsigned char font[], int word_count,
	int startx, int starty,
	int color)
{

	assert( ptr_frame != NULL );

	int tagY = 0, tagU = 0, tagV = 0;
	char *offsetY = NULL, *offsetU = NULL, *offsetV = NULL;
	unsigned short p16, mask16; // for reading hzk16 dots

	/*yuv 地址的设置 */
	offsetY = ptr_frame;
	offsetU = offsetY + width * height;
	offsetV = offsetU + width * height/4;

	switch (color)
	{
	case 0:         // Yellow
		tagY = 226;tagU = 0;tagV = 149;
		break;
	case 1:         // Red
		tagY = 76;tagU = 85;tagV = 255;
		break;
	case 2:         // Green
		tagY = 150;tagU = 44;tagV = 21;
		break;
	case 3:         // Blue
		tagY = 29;tagU = 255;tagV = 107;
		break;
	default:        // White
		tagY = 128;tagU = 128;tagV = 128;
	}


	printf("%d,", color);

	int x=0,y=0,i=0,j=0,k=0;
	for(i = 0; i < word_count; i++)
	{
#if 1
		for (j = 0, y = starty; j < WORD_ROW_COUNT && y < height - 1; j++, y+=2) // line dots per char
		{
			p16 = *(unsigned short *)(font + j*1*2 + i*TEXT_WORD_SIZE);/*取字模数据*/
			mask16 = 0x0080;  /* 二进制 1000 0000 */
			for (k = 0, x = startx +i*2*(WORD_COLUMN_COUNT+WORDS_H_SPACE); k < WORD_COLUMN_COUNT && x < width - 1; k++, x+=2) // dots in a line
			{
				if (p16 & mask16)
				{
					*(offsetY + y*width + x) = *(offsetY + y*width + x+1) = tagY;
					*(offsetY + (y+1)*width + x) = *(offsetY + (y+1)*width + x+1) = tagY;   
					*(offsetU + y * width/4 + x/2) = tagU;
					*(offsetV + y * width/4 + x/2) = tagV;
				}
				mask16 = mask16 >> 1;  /* 循环移位取数据 */
				if (mask16 == 0)
					mask16 = 0x8000;
			}
		}
#else
		for (j = 0, y = starty; j < WORD_ROW_COUNT && y < height - 1; j++, y++) // line dots per char
		{
			p16 = *(unsigned short *)(font + j*1*2 + i*TEXT_WORD_SIZE);/*取字模数据*/
			mask16 = 0x0080;  /* 二进制 1000 0000 */
			for (k = 0, x = startx +i*(WORD_COLUMN_COUNT+WORDS_H_SPACE); k < WORD_COLUMN_COUNT && x < width - 1; k++, x++) // dots in a line
			{
				if (p16 & mask16)
				{
					*(offsetY + y*width + x) = tagY;
					if(y%2 == 0)
					{
						*(offsetU + y * width/4 + x/2) = tagU;
						*(offsetV + y * width/4 + x/2) = tagV;
					}
					else
					{
						*(offsetU + (y-1) * width/4 + x/2) = tagU;
						*(offsetV + (y-1) * width/4 + x/2) = tagV;
					}					
				}
				mask16 = mask16 >> 1;  /* 循环移位取数据 */
				if (mask16 == 0)
					mask16 = 0x8000;
			}
		}
#endif
	}

	return (char *)ptr_frame;
}

void main()
{
   //...
   draw_Font_Func(frame_buffer, FRAME_WIDTH, FRAME_HEIGHT, table, 
                    sizeof(table)/TEXT_WORD_SIZE, 20, 10, 1);
   //...
}

至此,Windows平台VFW视频采集与数据分析就先告一段落。祝愿你在音视频的道路上不再孤单!

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值