基于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 主函数
[cpp]  view plain copy
  1. int main(int argc, const char* argv[])  
  2. {  
  3.     int fp = 0;  
  4.     unsigned int i;  
  5.     /* 
  6.     * init struct camera  
  7.     */  
  8.     struct camera *cam;  
  9. //这个是我自定义的结构,代表一个摄像头,定义在v4l2.h中  
  10.     struct timeval tpstart,tpend;  
  11.         float timeuse;  
  12. //以上变量是为了统计每帧采集的时间  
  13.     unsigned short *pbuf;  
  14. //帧缓存地址  
  15.   
  16.     cam = malloc(sizeof(struct camera));  
  17.     if (!cam) {   
  18.         printf("malloc camera failure!\n");  
  19.         exit(1);  
  20.     }  
  21.         cam->device_name = "/dev/video0";  
  22.     cam->buffers = NULL;  
  23.     cam->width = IMAGE_WIDTH;  
  24.     cam->height = IMAGE_HEIGHT;  
  25.     cam->display_depth = 3;  /* RGB24 */  
  26.     cam->rgbbuf = malloc(cam->width * cam->height * cam->display_depth);  
  27.   
  28.     if (!cam->rgbbuf) {   
  29.         printf("malloc rgbbuf failure!\n");  
  30.         exit(1);  
  31.     }  
  32.     open_camera(cam);  
  33.     get_cam_cap(cam);  
  34.     get_cam_pic(cam);  
  35.     get_cam_win(cam);  
  36.     cam->video_win.width = cam->width;  
  37.     cam->video_win.height = cam->height;  
  38.     set_cam_win(cam);  
  39.     close_camera(cam);  
  40.     open_camera(cam);  
  41.     get_cam_win(cam);     
  42.         init_camera(cam);  
  43.         start_capturing (cam);  
  44. //以上初始化摄像头,设置采集图像格式为YUYV,采集图像大小为IMAGE_WIDTH×IMAGE_HEIGHT,mmap方式读取数据  
  45.     fp = fb_init ("/dev/fb0");  
  46. //打开初始化frambuffer设备,用mmap映射帧缓存地址为fbbuf  
  47.     if (fp < 0){  
  48.         printf("Error : Can not open framebuffer device\n");  
  49.         exit(1);  
  50.     }  
  51.   
  52.     pbuf = (unsigned short *)fbbuf;  
  53.     for (i = 0; i < 320 *240; i++) {  
  54.         pbuf[i] = make_pixel(0, 0x0, 0x0, 0xff);  
  55.     }  
  56. //清屏成蓝色         
  57. #ifdef DEBUG_GTIME  
  58.     gettimeofday(&tpstart,NULL);  
  59. #endif  
  60.         for (;;) {  
  61.         if (read_frame (cam)) {  
  62.             draw_image(pbuf, cam->rgbbuf);  
  63. //将采集的数据显示到屏幕上  
  64. #ifdef DEBUG_GTIME  
  65.             gettimeofday(&tpend,NULL);  
  66.                     timeuse = 1000000 * (tpend.tv_sec - tpstart.tv_sec) + (tpend.tv_usec - tpstart.tv_usec);  
  67.                     timeuse /= 1000000;  
  68.                     printf("Used Time:%10f s\n",timeuse);  
  69.                     gettimeofday(&tpstart,NULL);  
  70. #endif  
  71. //以上用gettimeofday函数统计采集一帧的时间  
  72.         }  
  73.         }  
  74.        return 0;  
  75. }  
        主函数主要初始化摄像头,分配数据结构,为视频采集做准备。然后初始化frambuffer设备,映射帧缓存,为视频显示做准备。主函数调用的v4l2接口函数与主机测试程序几乎一样。与主机测试程序不同的是显示程序draw_image,它用来显示一帧图像。
2 draw_image 函数
[cpp]  view plain copy
  1. static void draw_image(unsigned short *pbuf, unsigned char *buf)  
  2. {  
  3.     unsigned int x,y;  
  4.     unsigned int pixel_num;  
  5.     if (WINDOW_W <= 240) {  
  6.         for (y = WINDOW_Y; y < WINDOW_Y + WINDOW_H; y++) {  
  7.             for (x = WINDOW_X; x < WINDOW_X + WINDOW_W; x++) {  
  8.                 pixel_num = ((y - WINDOW_Y) * IMAGE_WIDTH + x - WINDOW_X) * 3;  
  9.                 pbuf[y * 240 + x] = make_pixel(0, (unsigned int)buf[pixel_num],   
  10.                         (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);  
  11.             }  
  12.         }  
  13.     } else { /* reverse */  
  14.         for (x = 0; x < WINDOW_W; x++) {  
  15.             for (y = 0; y < WINDOW_H; y++) {  
  16.                 pixel_num = (y * IMAGE_WIDTH + x) * 3;  
  17.                 pbuf[x * 240 + y] = make_pixel(0, (unsigned int)buf[pixel_num],   
  18.                         (unsigned int)buf[pixel_num + 1], (unsigned int)buf[pixel_num + 2]);  
  19.             }  
  20.         }  
  21.     }  
  22. }  
        这个函数作用就是显示一帧图像,因为在程序初始化阶段已经映射了帧缓存fbbuf,所以只需要将一帧图像的数据capy到帧缓存处就可以显示到lcd上了。在程序的开始处定义了一组宏。
[cpp]  view plain copy
  1. #define IMAGE_WIDTH 320    //采集视频的宽度  
  2. #define IMAGE_HEIGHT 240    //采集视频的高度  
  3. #define WINDOW_W 176    //显示视频的宽度  
  4. #define WINDOW_H 144    //显示视频的高度  
  5. #define WINDOW_X 40     //显示起始横坐标  
  6. #define WINDOW_Y 60     //显示起始纵坐标  
        这样通过这一组宏就可以调整显示图像的大小与位置,因为mini2440的lcd为240 × 320 的竖屏,而摄像头采集回来的最大图像大小为320 × 240 ,所以这里用了一个技巧。如果显示的图像宽度大于240,那么就将屏幕翻转,这样可以更好的显示。因为在yuv422转rgb的函数中,转换出的RGB格式为RGB24,而mini2440的屏幕为RGB16的,所以需要做一个颜色转换。
[cpp]  view plain copy
  1. static inline int  make_pixel(unsigned int a, unsigned int r, unsigned int g, unsigned int b)  
  2. {  
  3.     return (unsigned int)(((r >> 3) << 11) | ((g >> 2) << 5 | (b >> 3)));  
  4. }  
        这个函数就是将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

源出处: http://blog.csdn.net/yaozhenguo2006/article/details/7074706

你可能感兴趣的:(基于mini2440的USB视频采集)