今天是2013-3-22,在前一段時間看了很多I2C以後(雖然沒有經過什麼實際檢驗,但是感覺還是對I2C有了一點點的瞭解),今天開始來學習學習有關視頻方面的東西。
首先我看的參考入門文檔:
http://zjbintsystem.blog.51cto.com/964211/464729
還有幾個datasheet,如:VPFE、VPBE以及TMS320DigitSubsystem
應用程式層是怎麼做的,先參看這個文檔,可以參看這個文檔:
http://www.rosoo.net/a/201001/8382.html
我覺得說清楚一個東西,把他的來龍去脈講清楚是比較困難的,但是按照以下幾個方面來講,效果一定不錯:是什麼,為什麼,怎麼樣。記得這是初中政治老師對我說的,但是我覺得在任何地方都是通的。
V4L2是什麼:video for linux 2的簡稱吧。所以就變成V4L2了。有2,當然之前有1了,至於未來怎麼樣,有沒有3,這個我就不管了。
為什麼要用V4L2:或者說有啥用呢。看這個:
參考文檔:http://dongyulong.blog.51cto.com/1451604/344987
V4L2主要用來搞視頻的。視頻並不是說我加個網路攝影機,然後往我畫的板子上一接,就可以在我的板子的LCD上顯示資料了。那我們要做什麼呢?要讓板子認識外設晶片(比如tvp5146),要對我採集到的視頻資料進行處理(這個是不是屬於應用程式層?)等等工作。V4L2就提供了許多這樣的介面。因為我們網路攝影機不同,板子晶片更不同,所以要有一套可移植的東東。我感覺大概是這樣。(才看兩天,以後有了更深刻的理解,再來補充更改吧。)
V4L2有一個重要的標頭檔,大部分文章都會說到:videodev2.h
注釋中都寫了:/**Video for Linux Two header file**/
這個標頭檔看的真蛋疼!
文檔:http://blog.csdn.net/hongtao_liu/article/details/5894089
這篇文檔寫的不錯,初步入門值得細看。
好幾天沒看了,都不知道該怎麼看了,真蛋疼。
給個連結:http://blog.csdn.net/shui1025701856/article/details/7459868
這個文章裡面說:V4L2驅動對使用者空間提供字元裝置,主裝置號為81,對於視頻裝置,其次裝置號為0-63.初次之外,次裝置號為64-127的radio裝置,次裝置號為192-223的teletext裝置,次裝置號為224-255的VBI裝置。
這個跟我剛才在v4l2-dev.c中看到的是相關的。
minor_offset是位移,minor_cnt表示的是裝置號的count。
即這段:switch (type) {
case VFL_TYPE_GRABBER:
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO:
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI:
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128;
minor_cnt = 64;
break;
}
這裡並沒有給出192-223這一部分。
正如之前看的那個參考文檔中寫的那些:大部分是通過ioctl來進行控制的,而那些命令就定義在include/linux/videodev2.h中。
在命令中用到了一些像_IOR、_IOWR等相關的宏,按照CSDN上一個文章:
http://bbs.csdn.net/topics/240007617描述:是對ioctl命令號的宏轉換定義,用於對命令進行分類。
另外還有這個連結寫的較為詳細:http://www.groad.net/bbs/read.php?tid-1212.html
關於V4L2幾個比較重要的檔案為:
Include/linux/videodev2.h
Include/media/v4l2-dev.h
V4L2驅動核心實現檔案:driver/media/video/v4l2-dev.c
//video核心層:drivers/media/video/videodev.c
許多文章裡面提到了上面這個檔案videodev.c,在老版的2.x核心中似乎存在這個檔案,但是我是基於3.x核心看的,已經不存在這個檔案了,現在原來存在於其中的函數定義現在都已經在v4l2-dev.h中了,比如video_register_device和video_unregister_device函數。
而V4L2提供的統一的應用程式層的介面都已經實現在v4l2-dev.c檔案中。
static const structfile_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
現在以vivi.c和v4l2來分析一下整個過程是如何?的吧。
假設我們現在有應用程式,其中使用了open、close、ioctl等函數。
應用程式層的調用,在核心中會調用到相對應的驅動函數。之前看到說V4L2其實就是又封裝了一層。感覺對這裡說的還不是很理解。
在v4l2-dev.c檔案中,有相應的驅動函數。當應用程式層執行open函數時,核心會執行v4l2_open函數。v4l2_open函數也是定義在v4l2-dev.c檔案中。
static int v4l2_open(struct inode *inode, struct file*filp);
傳進的參數為inode節點和檔案指標。
在v4l2_open函數中,定義了struct video_device結構體vdev,並調用video_devdata函數:vdev =video_devdata(filp);獲得視頻裝置結構體。
if(video_is_registered(vdev))
ret = vdev->fops->open(filp);
如果裝置已經註冊過了,那麼就會去執行vdev->fops->open函數。
我們再來看vdev,即struct video_device結構體。
這個結構體的定義在v4l2-dev.h檔案中。
其中,包括:
/* device ops */
const struct v4l2_file_operations*fops;
關於v4l2的裝置檔案操作集
/*sysfs */
struct device dev; /* v4ldevice */
struct cdev *cdev; /*character device */
在sysfs下建立裝置和字元裝置
/* Set either parent or v4l2_dev if your driver uses v4l2_device */
structdevice *parent; /* device parent*/
struct v4l2_device *v4l2_dev; /* v4l2_device parent */
/*ioctl callbacks */
const struct v4l2_ioctl_ops*ioctl_ops;
關於ioctl的檔案操作函數。
在vivi.c中,我載入裝置驅動的時候,會執行module_init(vivi_init);在vivi_init中調用了vivi_create_instance函數。我們來看這個函數:
其中定義了structvideo_device *vfd;
struct vivi_dev *dev(這個應該就是自訂的裝置類型了);
在函數中調用函數v4l2_device_register註冊v4l2裝置。
後面:vfd = &dev->vdev;
讓vfd指標指向註冊的v4l2裝置。
接下來調用了兩個函數:video_set_drvdata(vfd,dev);
和video_register_device函數。註冊video device。
這兩個函數,第一個查到說是設定驅動程式專有資料;第二個函數之前分析過,是註冊video裝置的函數,比如是網路攝影機,或者是VBI裝置等等。
那麼回到一開始,現在我們應該就是傳遞了vfd這個參數了。
也就是實際上會執行v4l2_file_operations這個結構體中的相關驅動函數了,在本例中,在vivi.c檔案中有如下定義:
static const struct v4l2_file_operations vivi_fops = {
.owner =THIS_MODULE,
.open = v4l2_fh_open,
.release =vb2_fop_release,
.read =vb2_fop_read,
.poll =vb2_fop_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap =vb2_fop_mmap,
};
也就是實際上最終會執行v4l2_fh_open這個函數。
其餘的release、read、write等也是類似。但是好像ioctl不是一樣的。這裡繼續來分析一下:
我看的是3.6.x核心,跟2.6等核心還是有不少變化的,這個變化有哪些,我也搞不全。
當我在應用程式層調用ioctl的時候,在驅動層會調用相應的ioctl函數,但是這裡有點區別,因為在file_operations中我沒看到ioctl的定義,而有:
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
百度之,這兩個跟ioctl的聲明:int(*ioctl)(struct inode *node, struct file *filp,unsigned int cmd, unsigned longarg);相比,主要是缺少了inode參數。
查看到兩個文檔:http://blog.sina.com.cn/s/blog_693301190100vyhh.html
http://blog.csdn.net/cbl709/article/details/7295772
其實沒什麼大的差別,就是參數少了一個而已。核心驅動中,會優先執行unlocked_ioctl函數,如果沒有unlocked_ioctl,那麼就會去執行ioctl函數。應用程式層是沒有差別的。
在v4l2-dev.c的v4l2_fops定義中,有:
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl= v4l2_compat_ioctl32,
#endif
在應用程式層執行ioctl時候,會執行v4l2_ioctl函數。
在v4l2_ioctl的函數定義中(仍然在v4l2-dev.c中):
if (video_is_registered(vdev))
ret= vdev->fops->unlocked_ioctl(filp, cmd, arg);
註冊了video裝置後,會去執行video_device中fops的unlocked_ioctl函數。
再看我們的vivi.c檔案:
static const struct v4l2_file_operations vivi_fops = {
.owner =THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read =vb2_fop_read,
.poll =vb2_fop_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap =vb2_fop_mmap,
}; //之前好像貼過了。
對我們的vivi.c驅動程式來說:也就是會去執行video_ioctl2這個函數。
這個video_ioctl2函數是定義在v4l2-ioctl.c檔案中:
long video_ioctl2(structfile *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
百度video_usercopy函數的作用說是將資訊傳送到使用者進程,執行__video_do_ioctl函數。
參考文檔:http://bbs.chinaunix.net/thread-2156911-1-1.html
跟蹤__video_do_ioctl函數:
static long__video_do_ioctl(struct file *file, unsigned int cmd, void *arg)
其中:struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops*ops = vfd->ioctl_ops;
也就是說;我在應用程式層執行ioctl時,一開始執行video_ioctl2函數,並傳遞file檔案指標,實際上調用了__video_do_ioctl函數,並實際上是執行了video_device中ioctl_ops的函數操作,也就是相應的v4l2-ioctl-ops函數操作。
在vivi.c中,有:
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap =vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs =vb2_ioctl_reqbufs,
.vidioc_create_bufs =vb2_ioctl_create_bufs,
.vidioc_prepare_buf =vb2_ioctl_prepare_buf,
.vidioc_querybuf =vb2_ioctl_querybuf,
.vidioc_qbuf =vb2_ioctl_qbuf,
.vidioc_dqbuf =vb2_ioctl_dqbuf,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input =vidioc_s_input,
.vidioc_streamon =vb2_ioctl_streamon,
.vidioc_streamoff =vb2_ioctl_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
這個就對應了應用程式中的那些擷取視頻capability、開啟streamon、streamoff等命令吧。