因为Windows开发相对方便一些,所以将VFW作为入门篇进行分享。
VFW(Video for Windows)是Microsoft推出的关于数字视频的一个软件开发包。
我们在这里主要分享:核心API调用、采集的视频的颜色空间类型、颜色空间类型转换、添加水印OSD。
目录
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视频采集与数据分析就先告一段落。祝愿你在音视频的道路上不再孤单!