基于mini2440的USB视频采集

来源:互联网 发布:asp自动发卡源码 编辑:程序博客网 时间:2024/05/17 20:25
基于mini2440的USB视频采集
        在嵌入式系统中,视频采集主要采用两种接口:一种是标准摄像头接口,一种是USB接口(USB1.1)。标准的摄像头接口,接口复杂,但传输速度快,适合高质量视频采集,而USB接口,接口简单,但有性能瓶颈,只能用于低质量的视频采集。mini2440开发板采用的是S3C2440芯片,S3C2440自带了一个OHCI的USB1.1主机接口和一个CMOS摄像头标准接口。所以mini2440开发板的两种视频采集方式都可以,这里主要介绍基于USB接口的视频采集。因为前一段时间编写了主机上基于GTK的USB视频采集程序,现在需要将其移植到开发板上。
       根据主机与开发板环境的不同,需要移植的部分主要就是视频显示部分。在主机上视频显示程序是调用GTK的库函数,而在开发板上有众多的UI可以选择,可以基于QT或者基于minigui 做显示界面,但是最简单最直接的方式就是操作frambuffer设备显示,因为这样可以避免GUI函数带来的性能损失,直接看到采集的实际效果,但这种方式只适用于实验程序没有太大的实用价值,我的采集程序程序就是采用了这种方式。USB摄像头采用和主机程序测试一样的摄像头(十几块钱的山寨摄像头),视频输出格式为YUYV,接口为USB2.0接口兼容USB1.1。为了避免线程切换带来的性能损失,在程序中我去掉了显示以及采集线程,主程序采用大循环的结构。下面简单的介绍一下程序:
1 主函数
int main(int argc, const char* argv[]){int fp = 0;unsigned int i;/** init struct camera */struct camera *cam;//这个是我自定义的结构,代表一个摄像头,定义在v4l2.h中struct timeval tpstart,tpend;        float timeuse;//以上变量是为了统计每帧采集的时间unsigned short *pbuf;//帧缓存地址 cam = malloc(sizeof(struct camera)); if (!cam) { printf("malloc camera failure!\n");exit(1);}        cam->device_name = "/dev/video0";cam->buffers = NULL;cam->width = IMAGE_WIDTH;cam->height = IMAGE_HEIGHT;cam->display_depth = 3;  /* RGB24 */cam->rgbbuf = malloc(cam->width * cam->height * cam->display_depth);if (!cam->rgbbuf) { printf("malloc rgbbuf failure!\n");exit(1);}open_camera(cam);get_cam_cap(cam);get_cam_pic(cam);get_cam_win(cam);cam->video_win.width = cam->width;cam->video_win.height = cam->height;set_cam_win(cam);close_camera(cam);open_camera(cam);get_cam_win(cam);        init_camera(cam);        start_capturing (cam);//以上初始化摄像头,设置采集图像格式为YUYV,采集图像大小为IMAGE_WIDTH×IMAGE_HEIGHT,mmap方式读取数据 fp = fb_init ("/dev/fb0");//打开初始化frambuffer设备,用mmap映射帧缓存地址为fbbufif (fp < 0){printf("Error : Can not open framebuffer device\n");exit(1);}pbuf = (unsigned short *)fbbuf;for (i = 0; i < 320 *240; i++) {pbuf[i] = make_pixel(0, 0x0, 0x0, 0xff);}//清屏成蓝色       #ifdef DEBUG_GTIMEgettimeofday(&tpstart,NULL);#endif        for (;;) {if (read_frame (cam)) {draw_image(pbuf, cam->rgbbuf);//将采集的数据显示到屏幕上#ifdef DEBUG_GTIMEgettimeofday(&tpend,NULL);               timeuse = 1000000 * (tpend.tv_sec - tpstart.tv_sec) + (tpend.tv_usec - tpstart.tv_usec);                timeuse /= 1000000;                printf("Used Time:%10f s\n",timeuse);               gettimeofday(&tpstart,NULL);#endif//以上用gettimeofday函数统计采集一帧的时间}        }       return 0;}
        主函数主要初始化摄像头,分配数据结构,为视频采集做准备。然后初始化frambuffer设备,映射帧缓存,为视频显示做准备。主函数调用的v4l2接口函数与主机测试程序几乎一样。与主机测试程序不同的是显示程序draw_image,它用来显示一帧图像。
2 draw_image 函数
static void draw_image(unsigned short *pbuf, unsigned char *buf){unsigned int x,y;unsigned int pixel_num;if (WINDOW_W <= 240) {for (y = WINDOW_Y; y < WINDOW_Y + WINDOW_H; y++) {for (x = WINDOW_X; x < WINDOW_X + WINDOW_W; x++) {pixel_num = ((y - WINDOW_Y) * IMAGE_WIDTH + x - WINDOW_X) * 3;pbuf[y * 240 + x] = make_pixel(0, (unsigned int)buf[pixel_num], (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);}}} else { /* reverse */for (x = 0; x < WINDOW_W; x++) {for (y = 0; y < WINDOW_H; y++) {pixel_num = (y * IMAGE_WIDTH + x) * 3;pbuf[x * 240 + y] = make_pixel(0, (unsigned int)buf[pixel_num], (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);}}}}
        这个函数作用就是显示一帧图像,因为在程序初始化阶段已经映射了帧缓存fbbuf,所以只需要将一帧图像的数据capy到帧缓存处就可以显示到lcd上了。在程序的开始处定义了一组宏。
#define IMAGE_WIDTH 320    //采集视频的宽度#define IMAGE_HEIGHT 240    //采集视频的高度#define WINDOW_W 176    //显示视频的宽度#define WINDOW_H 144    //显示视频的高度#define WINDOW_X 40     //显示起始横坐标#define WINDOW_Y 60     //显示起始纵坐标
        这样通过这一组宏就可以调整显示图像的大小与位置,因为mini2440的lcd为240 × 320 的竖屏,而摄像头采集回来的最大图像大小为320 × 240 ,所以这里用了一个技巧。如果显示的图像宽度大于240,那么就将屏幕翻转,这样可以更好的显示。因为在yuv422转rgb的函数中,转换出的RGB格式为RGB24,而mini2440的屏幕为RGB16的,所以需要做一个颜色转换。
static inline int  make_pixel(unsigned int a, unsigned int r, unsigned int g, unsigned int b){    return (unsigned int)(((r >> 3) << 11) | ((g >> 2) << 5 | (b >> 3)));}
        这个函数就是将RGB24格式转换成RGB16的格式。
3 性能分析
        以上程序可以正确的进行摄像头的视频采集与显示,但是最大只能采集到176 × 144 的低质量图像,如果采集分辨里达到320 * 240 图像会非常卡,有明显的延迟与丢帧现象,这种原因是USB1.1每秒所传的帧数有限造成的。USB1.1最大每秒可传的帧数由图像大小和USB速度共同决定。下面以320×240 YUYV格式的图像为例,计算USB1.1最大每秒可传的帧数。
    (1)每帧需要传输的数据量为 320 × 240 × 2 × 8 = 1228800bit = 1.2288Mbit
    (2)USB协议规定:USB1.1的最大传输比特率为12M也就是每秒传12Mbit。这只是理论上的数据,实际传输也就10M左右。我们以10Mbps为例
    (3)USB协议规定:同步传输不得超过总线的带宽的90%,所以传播速度还得乘以0.9,为9Mbps。
    (4)USB传输速度包括了协议相关的位,USB协议规定USB同步传输包,每个包的协议信息为9个字节。每秒帧数还与每个USB帧(1mS)传输的包的数量有关系,这与同步端点的最大数据有关系,我的摄像头同步端点最大数据为8字节。所以每个包的数据与协议数据比就是 8 : 9, 这样一来带宽还得乘以一个 8/17,为4.2353Mbps
    (5) 最后算出每秒帧数据就是 4.2353 / 1.2288 = 3.4
    以上计算没有考虑SOF包,以及USB位填充,以及其他的因素,粗略的算出,对于320×240的一幅YUYV格式的图像,USB1.1最大每秒传输3.4帧。可谓是非常小了,但这只是理论的值,实际我用gettimeofday测出的只有每秒一帧多,这样的速度不卡才怪。所以最后得出的结论就是:由于USB1.1的速度限制,采用USB1.1做图像采集,在USB摄像头输出格式为未压缩的原始数据(如:RGB,YUV)的前提下只能采集到低分辨率,低质量的画面。基本上不能用于产品,我查了一些资料,USB1.1数据采集的系统,USB摄像头采集出来的数据格式大多是已经压缩过的,如JPEG格式,这样可大大减轻USB传输的负担,提高视频采集的采集质量。但是这样也有弊端,采集回的数据不是原始数据,不方便对数据的二次处理。所以这种压缩输出格式的USB摄像头基本上都是USB1.1的,USB2.0的摄像头输出格式基本上都是未经处理的原始数据,因为2.0的速度已经足够快,理论480Mbps的速度绝对满足需要。
        实验代码在我的资源里:http://download.csdn.net/detail/yaozhenguo2006/3925480