servfox分析

servfox分析

構建嵌入式Linux網絡視頻監控系統中,我們採用servfox來做服務器採集程序. servfox涉及到的內容主要有:V4L1接口、套接字和多線程編程. 這裏簡單分析一下servfox-R1_1_3.
1. servfox做了什麼?

servfox在採集圖像的過程中主要做什麼事情?
它初始化攝像頭設備後創建了線程1採集視頻圖像. 然後主程序創建一個套接字監聽,阻塞等待客戶端的請求連接. 連接成功後再創建線程2發送採集到的圖像數據給客戶端.
線程1:採集視頻圖像.
線程2:發送圖像數據給客戶端.

在採集線程和發送線程同時運行的情況下,會存在對存儲壓縮過的圖像數據的緩衝區這個臨界區競爭的情況. 爲了能把採集到的一幀圖像數據完整地發送出去,需要採用一些同步機制. servfox只是個應用程序,它初始化設備,獲取設備屬性和圖像屬性,設置圖像參數,捕捉圖像數據,都是通過Video4Linux接口標準調用驅動的相關函數完成的. 本文末尾將會列舉部分攝像頭設備驅動要實現的file_oerations結構體裏面的函數.
2. servfox運行步驟

servfox運行流程圖如下:

2.1 從命令行傳遞參數給變量

main()函數內,首先執行的是一個for循環體. 看一下里面的幾個語句:
...
if (strcmp (argv[i], "-d") == 0) {
if (i + 1 >= argc) {
if(debug)
printf ("No parameter specified with -d, aborting.\n");
exit (1);
}
videodevice = strdup (argv[i + 1]);
}
...

videodevice保存了攝像頭設備節點名稱. 用戶不指定的話,後面會將它設置爲"/dev/video0".
...
if (strcmp (argv[i], "-g") == 0) {
/* Ask for read instead default mmap */
grabmethod = 0;
}
...

通過grabmethod的設置就指定了採集圖像時使用mmap()內存映射的方法還是read()讀取的方法. 採用read系統調用來讀取圖像數據的話在連續抓取的情況下會發生頻繁的用戶態和內核態的切換,效率低. 通過mmap內存映射的話,把攝像頭對應的設備文件映射到進程內存中,減少I/O操作,提高了效率. 因此啓動servfox時不加"-g"選項的話默認採用grabmethod=1爲mmap方式.

在for循環體裏面還根據用戶輸入的選項分配了存儲分辨率大小width/height,創建套接字時用的端口號serverport(默認爲7070).
2.2 初始化視頻採集設備

接下來主要要執行的語句有:
memset (&videoIn, 0, sizeof (struct vdIn)); // 將結構體videoIn初始化爲0

先來看看videoIn這個結構體:
vdIn 結構體(在spcav4l.h中定義,它裏面的成員都是依據Video4Linux接口標準而定義的):
struct vdIn {
int fd; // 設備文件描述符
char *videodevice ; // 設備,視頻捕捉接口文件
struct video_mmap vmmap;
/* 用於內存映射方法時進行圖像數據的獲取,
* 裏面的成員.frame表示當前將獲取的幀號,
* 成員.height和.width表示圖像高度和寬度,
* 成員.format表示圖像格式.
*/

struct video_capability videocap;
/* 包含設備的基本信息(設備名稱,支持的最大最小分辨率,信號源信息等)
*/

int mmapsize;
struct video_mbuf videombuf;
/* 利用mmap映射到攝像頭存儲緩衝區的幀信息,
* 包括幀的大小(size),最多支持的幀數(frames),
* 每幀相對基址的偏移(offset)
*/

struct video_picture videopict; // 採集到的圖像的各種屬性
struct video_window videowin; // 包含capture area的信息
struct video_channel videochan; // 各個信號源的屬性
struct video_param videoparam;
int cameratype ; // 是否能capture,彩色還是黑白,是否能裁剪等等
char *cameraname; // 設備名稱
char bridge[9];
int sizenative; // available size in jpeg.
int sizeothers; // others palette.
int palette; // available palette.
int norme ; // set spca506 usb video grabber.
int channel ; // set spca506 usb video grabber 信號源個數
int grabMethod ;
unsigned char *pFramebuffer; // 指向內存映射的指針
unsigned char *ptframe[4]; // 指向壓縮後的幀的指針數組
int framelock[4];
pthread_mutex_t grabmutex; // 視頻採集線程和傳輸線程的互斥信號
int framesizeIn ; // 視頻幀的大小
volatile int frame_cour; // 指向壓縮後的幀的指針數組下標
int bppIn; // 採集的視頻幀的BPP
int hdrwidth; // 採集的視頻幀的寬度
int hdrheight; // 採集的視頻幀的高度
int formatIn; // 採集的視頻幀的格式
int signalquit; // 停止視頻採集的信號
};

接下來執行:
if (init_videoIn
(&videoIn, videodevice, width, height, format,grabmethod) != 0)

這個函數主要是設置了grabmethod:用mmap方式還是read方式;
設置videodevice成員設備文件名稱,默認是 "/dev/video0";
設置信號vd->signalquit=1,圖像寬高:vd->hdrwidth=width;vd->hdrheight=height;
設置圖像格式爲VIDEO_PALETTE_JPEG:vd->formatIn = format;
獲得色深:vd->bppIn = GetDepth (vd->formatIn);

調用init_v4l():
=================進入init_v4l()================================================
init_v4l()是初始化v4l視頻設備的函數,它首先通過系統調用open()打開視頻設備,成功打開後主要執行下面幾個步驟:
1. 通過系統調用ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))取得設備信息。讀取struct video_capability中有關攝像頭的信息,保存到vd->videocap中.
2. 初始化圖像.

ioctl (vd->fd, VIDIOCGPICT, &vd->videopict);
帶VIDIOCGPICT參數的ioctl調用會獲取圖像的屬性,並保存在vd->videopict指向的結構體中.
3. 讀取ruct video chanel中有關設備通道的信息,保存到vd->videochan指向的結構體中。

ioctl (vd->fd, VIDIOCGCHAN, &vd->videochan);
4. 設置攝像頭參數.

讀取攝像頭數據前,需要對攝像頭進行設置,主要包括圖像參數和分辨率.
ioctl (vd->fd, VIDIOCSPICT, &vd->videopict)

設置分辨率主要是對vd->videowin各分量進行修改,若爲read方式,具體實現爲:
if (ioctl (vd->fd, VIDIOCGWIN, &(vd->videowin)) < 0) // 獲得捕獲源的大小
perror ("VIDIOCGWIN failed \n");
vd->videowin.height = vd->hdrheight;
vd->videowin.width = vd->hdrwidth;
if (ioctl (vd->fd, VIDIOCSWIN, &(vd->videowin)) < 0)
perror ("VIDIOCSWIN failed \n");
5. 攝像頭設備文件映射初始化或read方式初始化

完成上述初始化設備工作後,就可以對訪問到攝像頭設備文件的內容了. 如果選用mmap()內存映射方式的話,下面的步驟將攝像頭設備文件映射到進程內存,這樣就可以直接讀取映射了的這片內存,而不必read設備文件了:

a. 獲取攝像頭緩衝區幀信息:
ioctl (vd->fd, VIDIOCGMBUF, &(vd->videombuf));

該操作獲取攝像頭存儲緩衝區的幀信息:包括幀的大小(size),最多支持的幀數(frames),每幀相對基址的偏移(offset). 這些參數都是由攝像頭設備硬件決定的. 這些信息將被保存在videombuf結構體裏面,下面的映射攝像頭設備文件到內存操作馬上就要用到了:

b. 映射攝像頭設備文件到內存:
vd->pFramebuffer =
(unsigned char *) mmap (0, vd->videombuf.size, PROT_READ | PROT_WRITE,
MAP_SHARED, vd->fd, 0);

該操作把攝像頭對應的設備文件映射到內存區. 該映射內容區可讀可寫並且不同進程間可共享. 幀的大小(vd->videombuf. size)是a步驟獲取的. 該函數成功返回映像內存區的指針,該指針賦值給vd->pFramebuffer,失敗時返回-1.

c. 視頻圖像捕捉測試:
/* Grab frames 抓取一幀*/
if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->vmmap))) {
perror ("cmcapture");
}

該操作捕捉一幀圖像,獲取圖像信息到vmmap裏. 它會根據vmmap中設置的屬性參數(frame,height,width和format)通知驅動程序啓動攝像頭抓拍圖像. 該操作是非阻塞的,是否截取完畢留給VDIOCSYNC來判斷. 在init_v4l()這裏只是爲了測試是否可以成功捕獲一幀圖像,真正採集圖像是在採集線程時執行v4lGrab()這個函數的時候.

以上是用mmap內存映射方式,如果採用直接讀取攝像頭設備文件的方式獲取圖像的話,將執行:
els {
/* read method */
/* allocate the read buffer */
vd->pFramebuffer = (unsigned char *) realloc(vd->pFramebuffer, \
(size_t) vd->framesizeIn);
/* 爲pFrameffer分配內存 */

if (ioctl (vd->fd, VIDIOCGWIN, &(vd->videowin)) < 0) // 獲得捕獲源的大小
perror("VIDIOCGWIN failed \n");
vd->videowin.height = vd->hdrheight;
vd->videowin.width = vd->hdrwidth;
if (ioctl(vd->fd, VIDIOCSWIN, &(vd->videowin)) < 0)
perror("VIDIOCSWIN failed \n");
}

攝像頭設備文件映射初始化或read方式初始化完成後,返回init_videoIn().

=============從init_v4l() 返回==========================================================
從init_v4l()返回到init_videoIn()後,分配vd->ptframe[i]空間.

for (i = 0; i < OUTFRMNUMB; i++) {
vd->ptframe[i] = NULL;
vd->ptframe[i] = (unsigned char *) realloc (vd->ptframe[i],\
sizeof(struct frame_t) + (size_t) vd->framesizeIn );
vd->framelock[i] = 0;
}

unsigned char* ptframe[4]:指向四個buffer緩衝數組,用來存放已壓縮完成的圖像數據.
2.3 採集圖像數據線程

init_videoIn()執行完後返回main(),接下來創建採集視頻圖像的線程:
pthread_create (&w1, NULL, (void *) grab, NULL);

進入grab()函數:可以看到在死循環體裏面調用v4lGrab()函數.
進入v4lGrab()函數,先判斷一下是用mmap方法還是用read方法. 下面僅就mmap方法分析:
ioctl (vd->fd, VIDIOCSYNC, &vd->vmmap.frame);

這條語句是等待捕捉完這一幀圖像,調用成功後表明一幀圖像捕捉完畢,可以開始進行下一次圖像捕捉. vd->vmmap.frame是當前捕捉到幀的序號.

接下來的是個循環睡眠等待:
while((vd->framelock[vd->frame_cour] != 0) && vd->signalquit)
usleep(1000);

它是等待之後執行的另一個用來的發送採集到的圖像數據給客戶端的線程,直到它把這一幀圖像完整地發送出去. 每隔1毫秒就檢查一次是否發完. 如果不等待就執行下面的操作的話,那麼還沒發送完就把本來要發送的圖像數據重寫掉,採集到的數據沒用上. 可以採用更好的同步機制--信號量來實現.

等到上一幀圖像數據發送出去之後,這個線程等待直到獲得一把線程互斥鎖:
pthread_mutex_lock (&vd->grabmutex);

它把臨界區資源vd->ptframe鎖住,防止下面獲取時間和拷貝數據到ptframe及設置一幀圖像的頭部時被別的線程搶佔. 雖然在發送線程裏並沒有找到相關互斥鎖的操作(這個應該是要加的),但爲了擴展,有可能以後我們添加一些訪問臨界區vd->ptframe的線程時可以用它這把鎖.

然後執行:
tems = ms_time();

tems獲得的是距離UNIX的Epoch時間即:1970年1月1日0時0分0秒算起的毫秒數. 它可以用在視頻圖像的時間戳.
然後執行:
jpegsize= convertframe(vd->ptframe[vd->frame_cour]+ sizeof(struct frame_t),
vd->pFramebuffer + vd->videombuf.offsets[vd->vmmap.frame],
vd->hdrwidth,vd->hdrheight,vd->formatIn,vd->framesizeIn);

跟蹤進去可以看出要是視頻圖像格式是VIDEO_PALETTE_JPEG的話,直接將pFramebuffer中的數據拷貝到ptframe緩存中去,而不壓縮處理,因爲獲得的就是已經壓縮過的jpeg格式了(是硬件或底層驅動做了,一般USB攝像頭對採集到的圖像都作了jpeg格式壓縮(內置JPEG硬件壓縮)). 獲得jpeg格式文件的大小是通過調用get_jpegsize()實現的. 進入get_jpegsize()可以發現,它利用了jpeg文件格式中是以0xFF 0xD9結尾的這個特性. ptframe裏面的經壓縮過的圖像數據就是發送線程要發送出去的內容了.

pFramebuffer中的數據拷貝進ptframe完成後,就截取下一幀圖像數據了:
/* Grab frames */
if ((ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap))) < 0) {
perror ("cmcapture");
if(debug) printf (">>cmcapture err \n");
erreur = -1;
}
vd->vmmap.frame = (vd->vmmap.frame + 1) % vd->videombuf.frames;
vd->frame_cour = (vd->frame_cour +1) % OUTFRMNUMB;

執行完後,跳出v4lGrab()函數體,返回到grab()去. 正常運行狀態下,將不斷循環調用v4lGrab()採集圖像數據. 採集線程分析完畢.
2.4 建立TCP套接字服務端,爲圖像數據發送線程做好準備

回到main(),繼續往下執行:
serv_sock = open_sock(serverport);

跟蹤進入open_sock()裏面可以看到通過執行socket(),bind(),listen()建立了一個TCP套接字服務端並在指定端口上監聽,等待客戶端連接. 緊跟在socket()後面有一句:
setsockopt(server_handle, SOL_SOCKET, SO_REUSEADDR, &O_on, sizeof (int));

這個語句應該是爲了允許啓動多個服務端或多個servfox. 參見:http://blog.csdn.net/liusujian02/article/details/1944520 (關於SO_REUSEADDR的使用說明)

執行完serv_sock = open_sock(serverport)這個語句之後,下一條語句是:
signal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */

這是爲了忽略SIGPIPE信號:若客戶端關閉了和服務端的連接,但服務端依然試圖發送圖像數據給客戶端(write to pipe with no readers),系統就會發出一個SIGPIPE信號,默認對SIGPIPE的處理是terminate(終止),那麼負責發送圖像數據的服務端就掛掉了,即使還有別的客戶端連接. 這當然不是我們想要的,因此把我們要執行這句語句把SIGPIPE信號忽略掉.
2.5 發送圖像數據到客戶端的線程

接下來,是一個while(videoIn.signalquit)循環體,如果沒有接收到退出信號,它就一直循環運行裏面的語句:
while (videoIn.signalquit) {
sin_size = sizeof(struct sockaddr_in);

/* 等待客戶端的連接,如果沒有連接就一直阻塞下去,
* 如果有客戶連接就創建一個線程,
* 在新的套接口上與客戶端進行數據交互
*/
if ((new_sock = accept(serv_sock, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
continue;
}
syslog(LOG_ERR,"Got connection from %s\n",inet_ntoa(their_addr.sin_addr));
printf("Got connection from %s\n",inet_ntoa(their_addr.sin_addr));
pthread_create(&server_th, NULL, (void *)service, &new_sock);
}

之前建立的服務端一直監聽等待客戶端來連接,一旦有客戶端connect()過來,服務端執行accept()建立連接後,就創建了發送圖像數據到客戶端的線程了:
pthread_create(&server_th, NULL, (void *)service, &new_sock);

我們再進入這個線程執行的service()函數去分析:
=============進入service()==============================

/* initialize video setting */
bright = upbright(&videoIn);
contrast = upcontrast(&videoIn);
bright = downbright(&videoIn);
contrast = downcontrast(&videoIn);

上面所謂的初始話視頻設置,是先增大一下亮度和對比度,在減小亮度和對比度恢復到原來的狀態,順便將亮度值保存在bright變量,將對比度值保存在contrast變量.
然後是一個死循環體:
for (; ;) {
memset(&message,0,sizeof(struct client_t));
ret = read(sock,(unsigned char*)&message,sizeof(struct client_t));
......
if (message.updobright){
switch (message.updobright){
case 1: bright = upbright(&videoIn);
break;
case 2: bright = downbright(&videoIn);
break;
}
ack = 1;
} else if (message.updocontrast){
switch (message.updocontrast){
case 1: contrast = upcontrast(&videoIn);
break;
case 2: contrast = downcontrast(&videoIn);
break;
}
ack = 1;
} else if (message.updoexposure){
switch (message.updoexposure){
case 1: spcaSetAutoExpo(&videoIn);
break;
case 2:;
break;
}
ack = 1;
} else if (message.updosize){ //compatibility FIX chg quality factor ATM
switch (message.updosize){
case 1: qualityUp(&videoIn);
break;
case 2: qualityDown(&videoIn);
break;
}
ack = 1;
} else if (message.fps){
switch (message.fps){
case 1: timeDown(&videoIn);
break;
case 2: timeUp(&videoIn);
break;
}
ack = 1;
} else if (message.sleepon){
ack = 1;
} else ack =0;
while ((frameout == videoIn.frame_cour) && videoIn.signalquit)
usleep(1000);
if (videoIn.signalquit){
videoIn.framelock[frameout]++;
headerframe = (struct frame_t *) videoIn.ptframe[frameout];
headerframe->acknowledge = ack;
headerframe->bright = bright;
headerframe->contrast = contrast;
headerframe->wakeup = wakeup;
ret = write_sock(sock, (unsigned char *)headerframe, sizeof(struct frame_t)) ;
/* 發送幀信息頭 */

if(!wakeup)
ret = write_sock(sock,(unsigned char*)(videoIn.ptframe[frameout] + \
sizeof(struct frame_t)),headerframe->size);

videoIn.framelock[frameout]--;
frameout = (frameout+1)%4;
} else {
if(debug)
printf("reader %d going out \n",*id);
break;
}
}

和客戶端建立連接後,客戶端會先將設置圖像的信息發給服務端,因此上面代碼,首先讀取客戶端對圖像的設置,把設置信息存放在message結構體裏,然後是根據message裏的信息對採集圖像的顯示屬性(如亮度bright,對比度contrast等)進行設置,具體操作是通過ioctl()調用底層驅動來完成對攝像頭抓拍圖像的顯示設置.

設置完採集圖像顯示屬性後,執行:
while ((frameout == videoIn.frame_cour) && videoIn.signalquit)
usleep(1000);

frame_cour是指向壓縮後的圖像幀的指針數組下標,我們一共存儲4幀(unsigned char *ptframe[4]),爲了按順序讀取每一幀,就等待知道frameout和videoIn.frame_cour相等時才執行後面的發送操作,發送這幀圖像完成後會執行frameout = (frameout+1)%4使得下一次發送下一幀圖像. 個人覺得這裏採取信號量的同步機制更好.
等採集線程完成一幀採集使得videoIn.frame_cour等於frameout之後(因爲這裏沒有采用同步機制,有可能這一輪會落空),就開始執行發送這一幀圖像給客戶端的操作了:先將讓headerframe指向幀信息頭,然後發送headerframe指向的信息頭給客戶端,再發送剩下的圖像數據. 這樣就把完整的一幀圖像發送給客戶端了.
只要沒有收到客戶端退出的信號,以上的發送過程會循環執行.

當收到客戶端退出的信息後,它就退出循環,執行close_sock(sock)關閉套接字,終止線程.

=============從service()返回=======================================================================
服務器發送圖像線程終止後,只要進程沒有退出信號還會在while (videoIn.signalquit)這個循環體繼續,阻塞等待客戶端的連接,重複上面的過程.


若videoIn.signalquit等於0了,就不再執行這個循環體,等待採集線程退出:pthread_join (w1, NULL);關閉套接字:close(serv_sock);回收以前分配的資源:close_v4l (&videoIn);整個程序就正常退出了.
3. servfox與底層驅動的接口

前面說過,servfox只是個應用程序,它初始化設備,獲取設備屬性和圖像屬性,設置圖像參數,捕捉圖像數據,都是通過V4L1接口標準調用驅動的相關函數完成的.V4L1就是Video4Linux的版本1,Video4Linux已整合進Linux內核裏面了.新版本是v4l2,它和v4l1不是完全兼容的.而V4L1已經是過時了.從Linux 2.6.38 內核就已經完全放棄了對v4l1的支持,因此不修改過的servfox不能在2.6.38以上的內核上運行了.不過有功能更強大的mjpeg-streamer來取代servfox.而mjpeg-streamer是基於v4l2接口的.

由於servfox體積小,在它上面進行擴展是很容易的,比如加入基於libjpeg庫的本地解碼jpeg顯示到lcd屏的線程,加入截屏的線程等.

下面列出了servfox用到的一些v4l1的接口,如果非要把servfox移植到2.6.38的Linux內核上運行的話,必須修改這些v4l1的接口使之兼容於v4l2.
攝像頭驅動裏要實現的ioctl()
1. ioctl(vd->fd, VIDIOCSYNC, &vd->vmmap.frame)

/* VIDIOCSYNC: Sync with mmap grabbing */

/* 等待捕捉到這一幀圖象.
* 即等待一幀截取結束.
* 若成功,表明一幀截取已完成。
* 可以開始做下一次 VIDIOCMCAPTURE
*/
2. if ((ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap))) < 0)

/* Mmap方式下做視頻截取的 VIDIOCMCAPTURE.
* 若調用成功,開始一幀的截取,是非阻塞的,
* 是否截取完畢留給VIDIOCSYNC來判斷
*/
3. 讀video_picture中信息
ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));

if (ioctl (vd->fd, VIDIOCGPICT, &vd->videopict) < 0) /* Get picture properties */
訪問攝像頭設備採集的圖像的各種屬性。然後通過訪問結構體vd->videopict 就可以讀出圖像的各種信息。

vd->videopict中分量的值是可以改變的,實現方法爲:先爲分量賦新值,再調用VIDIOCSPICT. 如:

4.
if (ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap)) == -1) /* Get capabilities */
exit_fatal ("Couldn't get videodevice capability");

讀video_capability 中信息
ioctl(vd->fd, VIDIOCGCAP, &(vd->capability))
成功後可讀取vd->capability各分量 eg.
Printf(”maxwidth = %d”vd->capability.maxwidth);
5. 初始化channel:
if (ioctl (vd->fd, VIDIOCGCHAN, &vd->videochan) == -1) /* Get channel info (sources) */
// 用來取得和設置channel信息,例如使用那個輸入源,制式等
{
if(debug) printf ("Hmm did not support Video_channel\n");
vd->cameratype = UNOW;
}
6. 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
if (ioctl (vd->fd, VIDIOCGMBUF, &(vd->videombuf)) < 0) /* Memory map buffer info */
{
perror (" init VIDIOCGMBUF FAILED\n");
}
// 要確定是否捕捉到圖象,要用到下一個命令。
if (ioctl (vd->fd, VIDIOCMCAPTURE, &(vd->vmmap))) /* Grab frames 抓取幀*/
{
perror ("cmcapture");
}
7. if (ioctl (vd->fd, VIDIOCGWIN, &(vd->videowin)) < 0) // 獲得捕獲源的大小
perror ("VIDIOCGWIN failed \n");
v4l2中的ioctl()的cmd:

在進行V4L2開發中,一般會用到以下的命令標誌符:

1. VIDIOC_REQBUFS:分配內存

2. VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數據緩存轉換成物理地址

3. VIDIOC_QUERYCAP:查詢驅動功能

4. VIDIOC_ENUM_FMT:獲取當前驅動支持的視頻格式

5. VIDIOC_S_FMT:設置當前驅動的頻捕獲格式

6. VIDIOC_G_FMT:讀取當前驅動的頻捕獲格式

7. VIDIOC_TRY_FMT:驗證當前驅動的顯示格式

8. VIDIOC_CROPCAP:查詢驅動的修剪能力

9. VIDIOC_S_CROP:設置視頻信號的邊框

10. VIDIOC_G_CROP:讀取視頻信號的邊框

11. VIDIOC_QBUF:把數據從緩存中讀取出來

12. VIDIOC_DQBUF:把數據放回緩存隊列

13. VIDIOC_STREAMON:開始視頻顯示函數

14. VIDIOC_STREAMOFF:結束視頻顯示函數

15. VIDIOC_QUERYSTD:檢查當前視頻設備支持的標準,例如PAL或NTSC。

這些IO調用,有些是必須的,有些是可選擇的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章