ARM9+Linux下USB攝像頭影象採集
終於實現了在Micro2440+Linux2.6.29環境下通過NFS檔案系統實現USB攝像頭影象採集。在此,做一個總結,以備後續參考。
實驗條件:RedHat9.0(核心版本2.4.18),Micro2440開發板,Linux2.6.29(arm9),ANC (Vimicro Corp.) USB PC攝像頭,GCC,SDL-1.2.14.tar,servfox-R1_1_3[1].tar,spcaview-20061208[1].tar。
一、升級Redhat9.0核心
由於Readhat9.0核心版本是2.4,很多核心驅動未包含其中,尤其是USB萬能攝像頭GSPCA和UVC。
二、配置編譯Micro2440上執行核心
重新編譯Micro2440上執行的核心,配置V4L和USB驅動。選擇linux-2.6.29中的config-mini2440為參考,執行:cp config-mini2440 .config;然後make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-;出現相應的圖形化配置視窗。
如果需要在Linux作業系統中使用USB攝像頭進行視訊資料採集,則必須在進行核心配置時,應檢查Linux 核心中是否已經添加了對Video4Linux驅動和對USB攝像頭驅動模組的支援。
利用Linux核心配置make menuconfig命令對Video4Linux支援驅動進行配置,選中多媒體裝置選項“Multimedia device->”(如圖15-1所示)按回車,進入多媒體裝置配置介面。在多媒體配置介面中,選中“Video For Linux”,就可以使核心實現對Video4Linux驅動的支援,為視訊採集裝置提供程式設計介面。
配置好核心對Video4Linux驅動後,返回核心配置主介面,選中USB支援選項(如圖15-3 所示)“USB support—>” 按回車,進入USB支援配置介面
配置完成後,執行make uImage ARCH=arm CROSS_COMPILE=arm-linux-。完成後,uImage就生成在arch/arm/boot/中。通過uboot下載uImage到記憶體中,啟動核心,實現NFS起檔案系統。
三、安裝SDL-1.2.14
因為在redhat9.0中顯示視訊影象,呼叫spcaview(後面介紹)需要有SDL庫的支援,否則會報錯。所以,將SDL-1.2.14.tar下載傳送到系統中,解壓後執行.configure;make;make install。完成SDL庫的安裝。
四、servfox-R1_1_3工具
下載Servfox-R1_1_3解壓進入其目錄,將其中的Makefile.arm修改為Makefile,並修改Makefile裡面的CC為arm -linux -gcc,因為預設的是載入的動態庫,因此還需要修改Makefile中的SERVFLAGS為SERVFLAGS= -O2 –DLINUX $(WARNINGS) –static,其中-static是我們新增的內容,make之後生成servfox。
五、 spcaview,redhat9.0下的視訊顯示工具
下載spcaview原始碼包解壓,進入解壓後的目錄,make後會報錯,一般需要安裝SDL庫,去網上下載後安裝SDL庫,在PC 機上安裝SDL庫:
解壓:tar xvf SDL-1.2.13.tar.tar
配置:./configure
編譯:make
安裝:make install
進入spcaview解壓目錄修改Makefile中的修改Makefile中的SDLLIBS =$(shell sdl-config --libs) 和 SDLFLAGS = $(shell sdl-config --cflags),這兩句主要是指定了SDL庫的庫路徑和編譯選項。Make之後生成spcaview,在客戶端執行spcaview –w 伺服器ip:伺服器埠號。就可以採集影象了,
[[email protected] spcaview-20061208]# ./spcaview -g -w 10.103.4.211:7070
Spcaview version: 1.1.7 date: 06:11:2006 (C) [email protected]
using Server 10.103.4.211 Port 7070
六、測試
將新編譯的核心載入到ARM上,通過NFS檔案系統執行servfox :
# ./servfox -d /dev/video0 -g -s 640x480 -w 7070
servfox version: 1.1.3 date: 11:12:2005 (C) [email protected]
wrong spca5xx device
Waiting .... for connection. CTrl_c to stop !!!!
Got connection from 10.103.4.216
在redhat9.0中執行spcaview:
[[email protected] spcaview-20061208]# ./spcaview -g -w 10.103.4.211:7070
Spcaview version: 1.1.7 date: 06:11:2006 (C) [email protected]
using Server 10.103.4.211 Port 7070
此時,視訊能在redhat9.0視窗中顯示,如圖所示。
七、總結
在實現影象採集的過程中,碰到很多問題,嘗試過很多方法,走了很多彎路,雖然很多碰壁,但也有不少收穫。
編譯核心:嘗試編譯高版本的核心,或者編譯新核心的時候,最好選擇和現有的配置檔案相近的系統的配置檔案作為參考。在原有基礎上增加一些模組,編譯,然後試驗。如果一次性增加模組過多,容易造成編譯錯誤等問題。
編譯工具:在編譯某些外掛的時候,如:servfox、spcaview等,經常會報一些檔案未定義、庫不存在等錯誤,一般是由於系統缺少相應的庫檔案,需要下載安裝,如SDL,或者由於系統本身版本不匹配,庫檔案安裝位置不符造成。熟練運用goole、百度進行搜尋,找到問題解決方法。同時,很多工具、庫、外掛等在開源社群網站都有穩定版本下載,使用這些網站提供的版本可靠性更好。
重點難點:makefile、kconfig等檔案的書寫,kit工具使用等。
附錄:視訊採集原始碼分析(看程式碼採用Lucida Console或者Consolas等寬字型較好,0和o區別明顯,不易混淆)
/*
* V4L2 video capture example
*
* This program can be used and distributed without restrictions.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> /* getopt_long() */
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#define CLEAR(x) memset (&(x), 0, sizeof (x))
typedef enum {
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR,
} io_method; //一共有三種視訊採集方式:使用read、write方式;記憶體對映方式和使用者指標模式
struct buffer {
void * start;
size_t length;
}; // 定義緩衝區
static char * dev_name = NULL; // 裝置名稱,可根據裝置名開啟裝置
static io_method io = IO_METHOD_MMAP;// 視訊採集方式,記憶體對映方式
static int fd = -1;
struct buffer * buffers = NULL;
static unsigned int n_buffers = 0;
static void errno_exit (const char *s)// 錯誤退出,並列印錯誤資訊
{
fprintf (stderr, "%s error %d, %s\n",s, errno, strerror (errno));
exit (EXIT_FAILURE);
}
static intxioctl (int fd,int request,void *arg)// 根據引數對裝置進行控制
{
int r;
do r = ioctl (fd, request, arg);
while (-1 == r && EINTR == errno);
return r;
}
static void process_image (const void * p)// 影象處理
{
fputc ('.', stdout);
fflush (stdout);// 重新整理輸出緩衝
}
static int read_frame (void)// 對影象幀進行讀取
{
struct v4l2_buffer buf;//這是視訊介面V4L2中相關檔案定義的,在videodev2.h中
unsigned int i;
switch (io)
case IO_METHOD_READ:
if (-1 == read (fd, buffers[0].start, buffers[0].length)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("read");
}
}
process_image (buffers[0].start);
break;
case IO_METHOD_MMAP://採用記憶體對映的方式
CLEAR (buf);//對緩衝區清零
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl (fd, VIDIOC_DQBUF, &buf)) {
//VIDIOC_DQBUF控制引數作用是申請視訊緩衝區
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("VIDIOC_DQBUF");
}
}
assert (buf.index < n_buffers);
process_image (buffers[buf.index].start);
if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
break;
case IO_METHOD_USERPTR:
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
if (-1 == ioctl (fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("VIDIOC_DQBUF");
}
}
for (i = 0; i < n_buffers; ++i)
if (buf.m.userptr == (unsigned long) buffers[i].start
&& buf.length == buffers[i].length)
break;
assert (i < n_buffers);
process_image ((void *) buf.m.userptr);
if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
break;
}
return 1;
}
static void mainloop (void)//迴圈採集
{
unsigned int count;
count = 100;
while (count-- > 0) {
for (;;) {
fd_set fds;
struct timeval tv;//時間結構體
int r;
FD_ZERO (&fds);//清零
FD_SET (fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select (fd + 1, &fds, NULL, NULL, &tv);//等待一幀影象採集完成,超時定為2秒
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit ("select");
}
if (0 == r) {
fprintf (stderr, "select timeout\n");
exit (EXIT_FAILURE);
}
if (read_frame ())//讀取一幀影象
break;
/* EAGAIN - continue select loop. */
}
}
}
static void stop_capturing (void)//終止採集
{
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl (fd, VIDIOC_STREAMOFF, &type))
errno_exit ("VIDIOC_STREAMOFF");
break;
}
}
static void start_capturing (void)//開始採集
{
unsigned int i;
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl (fd, VIDIOC_STREAMON, &type))
errno_exit ("VIDIOC_STREAMON");
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long) buffers[i].start;
buf.length = buffers[i].length;
if (-1 == ioctl (fd, VIDIOC_QBUF, &buf))
errno_exit ("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl (fd, VIDIOC_STREAMON, &type))
errno_exit ("VIDIOC_STREAMON");
break;
}
}
static void uninit_device (void)//解除安裝裝置
{
unsigned int i;
switch (io) {
case IO_METHOD_READ:
free (buffers[0].start);
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers[i].start, buffers[i].length))
errno_exit ("munmap");
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i)
free (buffers[i].start);
break;
}
free (buffers);
}
static void init_read (unsigned int buffer_size)//初始化讀取
{
buffers = calloc (1, sizeof (*buffers));
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
buffers[0].length = buffer_size;
buffers[0].start = malloc (buffer_size);
if (!buffers[0].start) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
}
static void init_mmap (void)//初始化記憶體對映
{
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno) {
fprintf (stderr, "%s does not support "
"memory mapping\n", dev_name);
exit (EXIT_FAILURE);
} else {
errno_exit ("VIDIOC_REQBUFS");
}
}
if (req.count < 2) {
fprintf (stderr, "Insufficient buffer memory on %s\n",
dev_name);
exit (EXIT_FAILURE);
}
buffers = calloc (req.count, sizeof (*buffers));
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
struct v4l2_buffer buf; //代表驅動中的一幀
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf))
errno_exit ("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap (NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit ("mmap");
}
}
static void init_userp (unsigned int buffer_size)
{
struct v4l2_requestbuffers req;
unsigned int page_size;
page_size = getpagesize ();