Video for linux 2驱动分为两层: VIDEO CORE LAYER(videodev.c) --------------------------------- video heardward layer(such as mxc_v4l2_capture.c) 上一层文件是videodev.c,这个文件其实就是相当于usbcore.c文件一样,提供一些核心函数给下一层调用如video_register_device等,其实这个videodev.c文件就是注册一个字符型设备,向操作系统提供file_operation这个结构体,而我们在video heardware layer里定义的一些函数就是通过file_operation里的一些指针来回调的。
camera_init: kmalloc一个cam_data对象g_cam,调用init_camera_struct初始化该对象: init_camera_struct(cam): 初始化两个mutex--param_lock和busy_lock,然后调用video_device_alloc申请cam->video_dev对象,然后将 static struct video_device mxc_v4l_template = { .owner = THIS_MODULE, .name = "Mxc Camera", .type = 0, .type2 = VID_TYPE_CAPTURE, .hardware = 0, .fops = &mxc_v4l_fops, .release = video_device_release, }; file_operations mxc_v4l_fops = { .owner = THIS_MODULE, .open = mxc_v4l_open, .release = mxc_v4l_close, .read = mxc_v4l_read, .ioctl = mxc_v4l_ioctl, .mmap = mxc_mmap, .poll = mxc_poll, }; 赋值给cam->video_dev对象,调用video_set_drvdata将cam设置成为cam->video_dev的priv指向的对象,将cam->driver_data指向 static struct platform_device mxc_v4l2_devices = { .name = "mxc_v4l2", .dev = { .release = camera_platform_release, }, .id = 0, }; 中的.dev。初始化cam->frame[3]的width,height及paddress。初始化两个等待队列cam->enc_queue与cam->still_queue,然后初始化cropping,standard,streamparm,overlay_on,capture_on,skip_frame,v4l2fb,v2f,win.然后是cam_sensor,即摄像头提供的一些调用函数(如白平衡等)。然后是enc_callback,将它指向camera_callback函数(该函数好像是有关工作队列的?????)。然后初始化cam->power_queue队列,初始化cam->int_lock自旋锁为未上锁。 end init_camera_struct 调用platform_device_register注册mxc_v4l2_devices对象,调用platform_driver_register注册 static struct platform_driver mxc_v4l2_driver = { .driver = { .name = "mxc_v4l2", .owner = THIS_MODULE, .bus = &platform_bus_type, }, .probe = NULL, .remove = NULL, .suspend = mxc_v4l2_suspend, .resume = mxc_v4l2_resume, .shutdown = NULL, }; 对象,调用video_register_device(cam->video_dev, VFL_TYPE_GRABBER, video_nr)注册一个视频设备在/dev/video0下, end camera_init
static int mxc_v4l_open(struct inode *inode, struct file *file): 先初始化dq_intr_cnt,dq_timeout_cnt及empty_wq_cnt三个变量(估计与工作队列有关,???),调用mxc_get_video_input函数,其实就是调用cam->cam_sensor->get_status(),也就是摄像头提供的函数。上busy_lock锁,调用signal_pending(current)判断当前是否有信号要处理。 if是初次打开:调用wait_event_interruptible(其实在我们的系统中没有启动电源管理,所以cam->low_power == false应该一直都为真,也就是本函数都不会进入),然后调用prp_enc_select函数设置cam->enc_update_eba指向prp_enc_update_eba,cam->enc_enable指向prp_enc_enable,cam->enc_disable指向prp_enc_disable。初始化cam->enc_counter跟skip_frame,初始化链表cam->ready_q,cam->working_q及cam->done_q。使能IIC,调用param = cam->cam_sensor->reset(),设置摄像头端的参数,返回摄像头初始化参数。根据返回的参数设置csi_param,然后调用csi_init_interface将参数设置入ARM端。调用cam->cam_sensor->get_color,cam->cam_sensor->get_ae_mode及cam->cam_sensor->get_control_params,最后调用prp_init,也就是申请INT_EMMAPRP中断,即函数prp_isr。释放busy_lock. end mxc_v4l_open
mxc_v4l_do_ioctl: VIDIOC_QUERYCAP: 查询能力 end VIDIOC_QUERYCAP VIDIOC_G_FMT: 调用mxc_v4l2_g_fmt(cam, gf): 如果type是V4L2_BUF_TYPE_VIDEO_CAPTURE则返回cam->v2f,如果是V4L2_BUF_TYPE_VIDEO_OVERLAY则返回cam->win. end VIDIOC_G_FMT VIDIOC_S_FMT: 调用mxc_v4l2_s_fmt: 如果是V4L2_BUF_TYPE_VIDEO_CAPTURE: 这种情况下只支持两种格式--V4L2_PIX_FMT_YUYV与V4L2_PIX_FMT_YUV420,但MX27的PRP通道2支持YUV444。然后将size,bytesperline,witdh,height,pixelformat设置入cam->v2f中。 如果是V4L2_BUF_TYPE_VIDEO_OVERLAY: 检查参数,将f->fmt.win设置入cam->win end VIDIOC_S_FMT VIDIOC_REQBUFS: 要求传入来的req->type要为V4L2_BUF_TYPE_VIDEO_CAPTURE,req->memory要为V4L2_MEMORY_MMAP。然后调用cam->enc_disable(cam)关闭PRP通道2,置三个frame,cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED,初始化enc_counter,skip_frame还有cam->ready_q,cam->working_q及cam->done_q三个链表。 调用mxc_free_frame_buf,即调用dma_free_coherent释放掉三个frame缓存(如果有的话)。 调用mxc_allocate_frame_buf申请req->count个buffer,其中有一个ENABLE_DEINTERLACE_SCALE宏选项暂时不知道为什么要用??? BUFFER的flag默认为V4L2_BUF_FLAG_MAPPED,type默认为V4L2_BUF_TYPE_VIDEO_CAPTURE,memory默认为V4L2_MEMORY_MMAP。 end VIDIOC_REQBUFS VIDIOC_QUERYBUF: 查询BUFFER的属性,即调用mxc_v4l2_buffer_status将cam->frame[buf->index].buffer考给用户。 end VIDIOC_QUERYBUF VIDIOC_QBUF: 好像是把一贞入cam->working_q队列。 end VIDIOC_QBUF VIDIOC_DQBUF 未知 end VIDIOC_DQBUF VIDIOC_STREAMON 调用mxc_streamon end VIDIOC_STREAMON VIDIOC_STREAMOFF 调用mxc_streamoff end VIDIOC_STREAMOFF VIDIOC_G_CTRL 调用mxc_get_v42l_control并依据c->id返回一些参数给用户 end VIDIOC_G_CTRL VIDIOC_S_CTRL 调用mxc_set_v42l_control设置摄像头参数,如白平衡等。 end VIDIOC_S_CTRL VIDIOC_CROPCAP 返回cam->crop_bounds及cam->crop_defrect给用户 end VIDIOC_CROPCAP VIDIOC_G_CROP 返回cam->crop_current给用户 end VIDIOC_G_CROP end mxc_v4l_do_ioctl
Preview::::: 1:先IOCTL传VIDIOC_S_OUTPUT命令设置输出,(其实按照实际情况还应该增加VIDIOC_G_STD命令获取CCD摄像头的类型为PAL还是NTSC,这样才能确定CSI端输入的是720*576还是720*487) 2:再传VIDIOC_S_CROP命令设置剪裁的大小,注意传入的type为V4L2_BUF_TYPE_VIDEO_OVERLAY,传入驱动后赋值给cam->crop_current 3:再传入VIDIOC_S_FMT命令设置显示的大小,注意传入的type为V4L2_BUF_TYPE_VIDEO_OVERLAY,传入驱动后调用verify_preview()检查一下,然后赋值给cam->win 4:传入VIDIOC_S_FBUF命令设置cam->v4l2_fb.flags的值为V4L2_FBUF_FLAG_OVERLAY,这个值决定是用baselay还是overlay显示preview 5:传入VIDIOC_OVERLAY开始preview,调用start_preview() start_preview() 调用prp_vf_select设置cam->vf_start_sdc = prp_vf_start和cam->vf_stop_sdc = prp_vf_stop两个函数指针。设置cam->overlay_pid为当前进程ID,调用prp_vf_start: prp_vf_start() 如果cam->v4l2_fb.flags的值为V4L2_FBUF_FLAG_OVERLAY,即如果是要在overlay里显示,那么调用prp_vf_mem_alloc申请两块dma缓存给LOOP模式缓冲帧数据,prp支持旋转,如果有图像旋转的需要的话就调用prp_rot_mem_alloc申请缓冲(没具体分析)。 调用prp_v4l2_cfg(&g_prp_cfg, cam)设置prp的各项参数,这里设置了很多PRP的参数,包括LOOP缓冲区的地址,CH1和CH2的图像格式(YUV,RGB),工作模式及根据IN和OUT算得的缩放参数等等,这里只是将这些参数写入全局变量g_prp_cfg中。 prp_v4l2_cfg() 设置PRP通道的输入参数,当然先是设置在g_prp_cfg中,包括宽,高, line stride,skip line,还有是否LOOP双缓冲;下面如果是overlay显示的话,通过查询cam->v4l2_fb.fmt.pixelformat变量获得显示的格式(这个参数在系统设计时通过VIDIOC_S_FBUF命令设置,但我们的APP当中OVERLAY preview的时候并没有设置该参数,所以它应该CASE到DEFAULT,也就是PRP_PIX1_RGB565);下面设置channel1的输出宽跟高(即为cam->win.w.width与cam->win.w.height),调用set_ch1_addr设置通道1的两个缓冲区地址,也就是前面prp_vf_mem_alloc所申请的。 end prp_v4l2_cfg() 调用csi_enable_mclk()设置CSI模块的时钟,调用prphw_reset复位prp模块,然后调用prphw_cfg()将刚才配置的各个PRP参数g_prp_cfg设置入寄存器。 调用tasklet_init(&prp_vf_tasklet, rotation, (unsigned long)private);启动一个tasklet,这个是用作图像旋转的。 end prp_vf_start() end start_preview
end Preview
capture::::::: 通过VIDIOC_S_FMT命令,设置fmt,即包括type为V4L2_BUF_TYPE_VIDEO_CAPTURE,数据格式为V4L2_PIX_FMT_YUV420,还有输出的画面大小。 在mxc_v4l2_s_fmt函数中: mxc_v4l2_s_fmt() 当fmt.type为V4L2_BUF_TYPE_VIDEO_CAPTURE时,下面根据fmt.pix.pixelformat类型来设置size,取圆整fmt.pix.width与fmt.pix.hight,最后将fmt设置入cam->v2f.fmt中。 end mxc_v4l2_s_fmt() 通过VIDIOC_S_PARM命令,设置v4l2_streamparm,即包括V4L2_BUF_TYPE_VIDEO_CAPTURE,还有帧率,传入内核中时: mxc_v4l2_s_param() 如果parm->type不为V4L2_BUF_TYPE_VIDEO_CAPTURE,则出错返回。接下来检查传入的帧率是否支持,然后判断parm.capture.capturemode是否改变,即是否要改变摄像头输入的分辨率,如果要改变的放就再一次调用cam_sensor->config及csi_init_interface重新初始化摄像头及CSI模块。 end mxc_v4l2_s_param() 通过VIDIOC_REQBUFS命令,设置v4l2_requestbuffers,即包括type 为V4L2_BUF_TYPE_VIDEO_CAPTURE,buffer数量(驱动中最大支持3个缓冲)及抓取方式为V4L2_MEMORY_MMAP,在我们的系统驱动中只支持V4L2_MEMORY_MMAP方式,调用mxc_streamoff及mxc_free_frame_buf确保当前各模块为空闲状态,最后调用mxc_allocate_frame_buf申请cnt个缓冲数量(不能超过3个),并设置flag为V4L2_BUF_FLAG_MAPPED方式。 通过VIDIOC_QUERYBUF命令,获取v4l2_buffer参数,包括缓冲的length及offset,然后APP中调用mmap函数将内核的缓冲映射到用户空间。注意这个命令调用cnt次,也即是映射最多3个缓冲。 通过VIDIOC_QBUF命令,将这些帧缓冲区(最多3个)插入驱动的队列。。。驱动中:如果cam->overflow为1,(????),然后调用spin_lock_irqsave上锁cam->int_lock,接下下判断buffer的flags,为V4L2_BUF_FLAG_MAPPED的话,这时判断是否skip_frame,然后调用list_add_tail(&cam->frame[index].queue,&cam->ready_q)将其插入ready_q队列。最后调用spin_lock_irqsave解锁。 通过VIDIOC_STREAMON命令,让驱动开始抓取数据。。。。驱动中: mxc_streamon() 调用list_empty(&cam->ready_q)判断ready_q是否为空,如果为空,返回 。设置cam->capture_pid为当前进程ID,调用cam->enc_enable回调,即prp_enc_enable 调用prp_v4l2_cfg设置PRP参数,调用csi_enable_mclk使能csi的时钟,prphw_reset,复位PRP,调用prphw_cfg将参数设置入寄存器,调用prphw_enable使能PRP通道。 接下来设置cam->ping_pong_csi为0(???),在cam->ready_q中取出一帧,然后将它从ready_q中删除,接着将它插入cam->working_q中,然后调用cam->enc_update_eba回调函数即prp_enc_update_eba: prp_enc_update_eba() 该函数更新g_prp_cfg.ch2_ptr2,即是将通道2的地址设置为帧的起始地址,然后还有一个变量是buffer_num,看程序好像是在0与1之间切换,第一次传入来时为0,然后将它设置为1。 end prp_enc_update_eba() 接下来再同样从cam->ready_q中取出一帧 ,插入wrking_q中,也调用prp_enc_update_ebq。 end mxc_streamon() 通过VIDIOC_DQBUF命令,让驱动将帧队列出队,驱动中: mxc_v4l_dqueue() 函数开始就调用wait_event_interruptible_timeout(cam->enc_queue,cam->enc_counter != 0, 10 * HZ),将该进程等待在cam->enc_queue队列上,而这个队列是在prp的中断处理函数中调用camera_callback函数,有一行wake_up_interruptible(&cam->enc_queue),它唤醒了mxc_v4l_dqueue函数。我们还是回来看下该函数,当超时的时候,(????)或者有信号中断来了(???好像跟超时的处理一样),接下来判断是否cam->overflow(???这时候的处理跟上面一样?) 下面就是正常情况下的处理了,先cam->enc_counter--,上spin_lock_irqsave锁cam->int_lock,在cam->done_q队列中取出一帧,并将这一帧从done_q队列中删除。解锁spin_unlock_irqrestore。然后判断这帧的buffer.flags,如果它被置为V4L2_BUF_FLAG_DONE,清除V4L2_BUF_FLAG_DONE,其它情况均返回出错。接下来如果要de-interlace的话,就根据YUV420格式来memcpy,最后设置该帧缓冲的属性,如帧大小,FALGS,还有timestamp等等。 end mxc_v4l_dqueue() end capture
prp_isr()这个是PRP中断处理函数,系统设置是当一个帧传输完的时候会触发一个中断。 函数先调用prphw_isr读取中断的状态,并根据状态标志位打印一些错误,然后清除中断位,返回中断状态。接下来分几种情况处理,分别为--拍照片时,预览时,录像时这三种。 当为录像时,判断该中断是否为buffer1或buffer2所触发的,如果是,则调用cam->enc_callback(0, cam)回调,即是static void camera_callback(u32 mask, void *dev): camera_callback() 传入的mask为是否overflow的标志,如果它为1,则将cam->overflow置为1,下面用spin_lock_irqsave锁cam->int_lock,接下来判断cam->working_q是否为空,前面我们已经将APP里的帧插入到这人working_q中,所以这里为空的话应该是不正常的情况,如果为空的话,先判断全局变量empty_wq_cnt,如果为0,则打印working queue为空错误,下面将该变量自加,下来判断cam->ready_q是否为空,如果为空的话,cam->skip_frame自加,也即跳过该帧,否则的话将ready_q中出队一帧,插入working_q中,并调用cam->enc_update_eba(ready_frame->paddress,&cam->ping_pong_csi);更新帧的地址。解锁cam->int_lock,返回。。 接下来是正常情况的处理,也即是working_q不为空的情况,这时在该队列中取出一帧,如果该帧的falgs有V4L2_BUF_FLAG_QUEUED标志的话,清除该标志,并置上V4L2_BUF_FLAG_DONE位。下面同样是判断ready_q是否有帧在排队,有的话插入working_q队列,再下面是将该帧从working_q出队,因为它已经被prp模块填满数据了,将它插入cam->done_q队列。将cam->enc_counter自加,然后调用wake_up_interruptible(&cam->enc_queue);唤醒等待在cam->enc_queue队列的进程。最后do_gettimeofday,将该帧打上时间戳。调用spin_unlock_irqrestore解锁cam->int_lock end camera_callback() end prp_isr()
typedef struct _cam_data { struct video_device *video_dev;
/* semaphore guard against SMP multithreading */ struct semaphore busy_lock;
int open_count;
/* params lock for this camera */ struct semaphore param_lock;
/* Encorder */ struct list_head ready_q; struct list_head done_q; struct list_head working_q; int ping_pong_csi; spinlock_t int_lock; struct mxc_v4l_frame frame[FRAME_NUM]; int skip_frame; int overflow; wait_queue_head_t enc_queue; int enc_counter; dma_addr_t rot_enc_bufs[2]; void *rot_enc_bufs_vaddr[2]; int rot_enc_buf_size[2]; enum v4l2_buf_type type;
/* still image capture */ wait_queue_head_t still_queue; int still_counter; dma_addr_t still_buf; void *still_buf_vaddr;
/* overlay */ struct v4l2_window win; struct v4l2_framebuffer v4l2_fb; dma_addr_t vf_bufs[2]; void *vf_bufs_vaddr[2]; int vf_bufs_size[2]; dma_addr_t rot_vf_bufs[2]; void *rot_vf_bufs_vaddr[2]; int rot_vf_buf_size[2]; bool overlay_active; int output; struct fb_info *overlay_fb;
/* v4l2 format */ struct v4l2_format v2f; int rotation; struct v4l2_mxc_offset offset;
/* V4l2 control bit */ int bright; int hue; int contrast; int saturation; int red; int green; int blue; int ae_mode; int ae_enable; int awb_enable; int flicker_ctrl;
/* standart */ struct v4l2_streamparm streamparm; struct v4l2_standard standard;
/* crop */ struct v4l2_rect crop_bounds; struct v4l2_rect crop_defrect; struct v4l2_rect crop_current;
int (*enc_update_eba) (dma_addr_t eba, int *bufferNum); int (*enc_enable) (void *private); int (*enc_disable) (void *private); void (*enc_callback) (u32 mask, void *dev); int (*vf_start_adc) (void *private); int (*vf_stop_adc) (void *private); int (*vf_start_sdc) (void *private); int (*vf_stop_sdc) (void *private); int (*csi_start) (void *private); int (*csi_stop) (void *private);
/* misc status flag */ bool overlay_on; bool capture_on; int overlay_pid; int capture_pid; bool low_power; wait_queue_head_t power_queue;
/* camera sensor interface */ struct camera_sensor *cam_sensor; } cam_data;
另外附上PAL与NTSC制式的区别: 很多人都知道有NTSC和PAL两大制式,那到底什么是NTSC制式?什么是PAL制式呢?简单的说,NTSC和PAL属于全球两大主要的电视广播制式,但是由于系统投射颜色影像的频率而有所不同。NTSC是National Television System Committee的缩写,其标准主要应用于日本、美国,加拿大、墨西哥等等,PAL 则是Phase Alternating Line的缩写,主要应用于中国,香港、中东地区和欧洲一带。
这两种制式是不能互相兼容的,如果在PAL制式的电视上播放NTSC的影响,画面将变成黑白,NTSC制式的也是一样。而做为视频拍摄工具的数码摄像机,也同样有制式的问题,比如我国使用PAL制式,在我国销售的数码摄像机都是PAL制式的,如果是NTSC制式的摄像机拍摄出来的图象不能在PAL制式的电视机上正常播放。因此,可以肯定的说,在我国销售的数码摄像机行货一定是PAL制式的,如果是NTSC制式的数码摄像机,则一定是水货。
PAL制式和NTSC的分辨率也有所不同,PAL制式使用的是720*576,而NTSC制式使用的是760*480,在分辨率上PAL稍稍占有优势。而PAL制式的画面解析度720*576,约40万象素,也决定了PAL制式的数码摄像机的CCD大小应该为40万的倍数或者半倍数,比如2倍或者1.5倍,所以PAL制式数码摄像机都是80万,或者107万(接近100万,40万的2.5倍)、155万(接近160万,40万的4倍)。而NTSC制式的画面解析度为720*480,约34万象素,所以NTSC制式的数码摄像机一般为68万象素等等。
目前的视频采集软件都支持PAL和NTSC制式,但是在编辑过程中是不能同时使用NTSC制式的素材和PAL制式的素材,必须用过转换才能在同一时间轴上使用两个素材。
在PC领域,由于使用的制式不同,存在不兼容的情况。就拿分辨率来说,有的制式每帧有625线(50Hz),有的则每帧只有525线(60 Hz)。后者是北美和日本采用的标准,统称为NTSC。通常,一个视频信号是由一个视频源生成的,比如摄像机、VCR或者电视调谐器等。为传输图像,视频源首先要生成-个垂直同步信号(V SYNC)。这个信号会重设接收端设备(PC显示器),保征新图像从屏幕的顶部开始显示。发出VSYNC信号之后,视频源接着扫描图像的第一行。完成后,视频源又生成一个水平同步信号,重设接收端,以便从屏幕左侧开始显示下一行。并针对图像的每一行,都要发出一条扫描线,以及一个水平同步脉冲信号。
另外,NTSC标准还规定视频源每秒钟需要发送30幅完整的图像(帧)。假如不作其它处理,闪烁现象会非常严重。为解决这个问题,每帧又被均分为两部分,每部分2 62.5行。一部分全是奇数行,另一部分则全是偶数行。显示的时候,先扫描奇数行,再扫描偶数行,就可以有效地改善图像显示的稳定性,减少闪烁。目前世界上彩色电视主要有三种制式,即N TSC、PAL和SECAM制式,三种制式目前尚无法统一。
|