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