基於mini2440的USB視頻採集

基於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 主函數
  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. }  
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映射幀緩存地址爲fbbuf
	if (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_GTIME
	gettimeofday(&tpstart,NULL);
#endif
        for (;;) {
		if (read_frame (cam)) {
			draw_image(pbuf, cam->rgbbuf);
//將採集的數據顯示到屏幕上
#ifdef DEBUG_GTIME
			gettimeofday(&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 函數
  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. }  
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上了。在程序的開始處定義了一組宏。
  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     //顯示起始縱座標  
#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的,所以需要做一個顏色轉換。
  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. }  
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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章