video 4 linux 2驱动的一种实现

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制式,三种制式目前尚无法统一。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值