由DELPHI控制摄像头的问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由DELPHI控制摄像头的问题相关的知识,希望对你有一定的参考价值。
DELPHI 编程问题:小弟在网上找到一个由DELPHI控制摄像头的程序,对SENDMESSAGE中的参数不是很明白;我要把摄像头中的图像直接在我程序的一个IMAGE1中显出来,若保存之后再打开就很麻烦,如何按BUTTON 不保存图像就显示出来;还有一个就是同时有两个摄像头又如何处理?请高人赐教。
function capcreatecapturewindowa(lpszwindowname : pchar;
dwstyle : longint;x : integer;y : integer;nwidth : integer;
nheight : integer;parentwin : hwnd;nid : integer): hwnd;
stdcall external ‘avicap32.dll‘;
参数表: WM_CAP_START = WM_USER
WM_CAP_GET_CAPSTREAMPTR = WM_CAP_START + 1
WM_CAP_SET_CALLBACK_ERROR = WM_CAP_START + 2
WM_CAP_SET_CALLBACK_STATUS = WM_CAP_START + 3
WM_CAP_SET_CALLBACK_YIELD = WM_CAP_START + 4
WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5
WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_CAP_START + 6
WM_CAP_SET_CALLBACK_WAVESTREAM = WM_CAP_START + 7
WM_CAP_GET_USER_DATA = WM_CAP_START + 8
WM_CAP_SET_USER_DATA = WM_CAP_START + 9
WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
WM_CAP_DRIVER_GET_NAME = WM_CAP_START + 12
WM_CAP_DRIVER_GET_VERSION = WM_CAP_START + 13
WM_CAP_DRIVER_GET_CAPS = WM_CAP_START + 14
WM_CAP_FILE_SET_CAPTURE_FILE = WM_CAP_START + 20
WM_CAP_FILE_GET_CAPTURE_FILE = WM_CAP_START + 21
WM_CAP_FILE_ALLOCATE = WM_CAP_START + 22
WM_CAP_FILE_SAVEAS = WM_CAP_START + 23
WM_CAP_FILE_SET_INFOCHUNK = WM_CAP_START + 24
WM_CAP_FILE_SAVEDIB = WM_CAP_START + 25
WM_CAP_EDIT_COPY = WM_CAP_START + 30
WM_CAP_SET_AUDIOFORMAT = WM_CAP_START + 35
WM_CAP_GET_AUDIOFORMAT = WM_CAP_START + 36
WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41
WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42
WM_CAP_DLG_VIDEODISPLAY = WM_CAP_START + 43
WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44
WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45
WM_CAP_DLG_VIDEOCOMPRESSION = WM_CAP_START + 46
WM_CAP_SET_PREVIEW = WM_CAP_START + 50
WM_CAP_SET_OVERLAY = WM_CAP_START + 51
WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
WM_CAP_SET_SCALE = WM_CAP_START + 53
WM_CAP_GET_STATUS = WM_CAP_START + 54
WM_CAP_SET_SCROLL = WM_CAP_START + 55
WM_CAP_GRAB_FRAME = WM_CAP_START + 60
WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61
WM_CAP_SEQUENCE = WM_CAP_START + 62
WM_CAP_SEQUENCE_NOFILE = WM_CAP_START + 63
WM_CAP_SET_SEQUENCE_SETUP = WM_CAP_START + 64
WM_CAP_GET_SEQUENCE_SETUP = WM_CAP_START + 65
WM_CAP_SET_MCI_DEVICE = WM_CAP_START + 66
WM_CAP_GET_MCI_DEVICE = WM_CAP_START + 67
WM_CAP_STOP = WM_CAP_START + 68
WM_CAP_ABORT = WM_CAP_START + 69
WM_CAP_SINGLE_FRAME_OPEN = WM_CAP_START + 70
WM_CAP_SINGLE_FRAME_CLOSE = WM_CAP_START + 71
WM_CAP_SINGLE_FRAME = WM_CAP_START + 72
WM_CAP_PAL_OPEN = WM_CAP_START + 80
WM_CAP_PAL_SAVE = WM_CAP_START + 81
WM_CAP_PAL_PASTE = WM_CAP_START + 82
WM_CAP_PAL_AUTOCREATE = WM_CAP_START + 83
WM_CAP_PAL_MANUALCREATE = WM_CAP_START + 84
WM_CAP_SET_CALLBACK_CAPCONTROL = WM_CAP_START + 85
三星平台fimc驱动详解
一、硬件接口
摄像头
摄像头传感器由摄像头接口和控制接口(一般为i2c)组成
摄像头接口用于传输传感器采集到的数据
控制接口用于控制摄像头传感器(例如设置图像格式…)
芯片
芯片上由多个摄像头控制器,多个摄像头接口,多个i2c控制器(i2c总线)
摄像头控制器负责控制摄像头接口和处理接收到的数据,摄像头接口负责传输图像数据,i2c控制器负责传输控制信息
摄像头传感器和芯片的接法如下
其中摄像头控制器和摄像头接口是分离的,摄像头控制器可以选择控制哪一个摄像头接口
fimc是三星平台摄像头控制器的一套驱动程序
二、fimc驱动总览
fimc的文件集中在drivers/media/video/samsung/fimc目录下
有以下文件
fimc_dev.c
fimc的平台驱动
fimc_v4l2.c
实现了一系列的ioctl操作
fimc_capture.c
实现了capture功能
fimc_output.c
实现了output功能
fimc_overlay.c
实现了overlay功能
fimc_regs.c
fimc控制器的寄存器操作
csis.c
csis接口的摄像头
各文件的组织形式如下
fimc_dev.c是平台的驱动,负责一些初始化的工作,fimc_v4l2.c设置了一系列的ioctl操作,fimc_capture.c、fimc_output.c、fimc_overlay.c实现了一些具体功能的ioctl,通过fimc_regs.c实现对摄像头控制器的硬件操作
三、源码分析
3.1 几个主要对象
在分析源码前,先介绍几个对象
s3c_platform_camera
摄像头传感器
struct s3c_platform_camera
/* 标记摄像头在哪个摄像头接口 */
enum fimc_cam_index id;
/* 摄像头接口类型 */
enum fimc_cam_type type; //ITU or MIPI(csi)
/* 像素格式 */
enum fimc_cam_format fmt;
/* i2c相关信息 */
int i2c_busnum; //摄像头所接的i2c总线
struct i2c_board_info *info; //摄像头的i2c信息
struct v4l2_subdev *sd; //表明这是一个v4l2_subdev
int width; //图像的宽
int height; //图像的高
/* 摄像头接口的时序极性 */
int inv_pclk;
int inv_vsync;
int inv_href;
int inv_hsync;
/* 使能摄像头 */
int (*cam_power)(int onoff);
;
s3c_platform_fimc
摄像头接口的平台信息
struct s3c_platform_fimc
/* 时钟相关 */
const char srclk_name[16]; /* source of interface clock name */
const char clk_name[16]; /* interface clock name */
const char lclk_name[16]; /* interface clock name */
u32 clk_rate; /* clockrate for interface clock */
/* 保存摄像头的信息 */
struct s3c_platform_camera *camera[5]; /* FIXME */
void (*cfg_gpio)(struct platform_device *pdev);
int (*clk_on)(struct platform_device *pdev, struct clk *clk);
int (*clk_off)(struct platform_device *pdev, struct clk *clk);
;
fimc_control
摄像头控制器
struct fimc_control
/* 寄存器地址 */
void __iomem *regs;
/* video_device和v4l2_device */
struct video_device *vd;
struct v4l2_device v4l2_dev;
...
;
fimc_global
fimc驱动的全局变量
struct fimc_global
struct fimc_control ctrl[FIMC_DEVICES]; //摄像头控制器
struct s3c_platform_camera camera[FIMC_MAXCAMS]; //摄像头
int camera_isvalid[FIMC_MAXCAMS]; //标记是否存在摄像头
int active_camera; // 当前使用的摄像头
;
介绍完上面几个结构体后,开始分析源码
3.2 fimc的平台设备
fimc的驱动采用的platform总线,分成设备和驱动,我们先介绍设备
首先看mach-smdkc110.c文件,这里描述了一系列的硬件信息
这里有多个摄像头描述,其中一个如下
static struct s3c_platform_camera s5k4ba =
.id = CAMERA_PAR_A, //摄像头在接口A
.type = CAM_TYPE_ITU, //ITU模式
.fmt = ITU_601_YCBCR422_8BIT, //传感器输入格式YCbCr422
.order422 = CAM_ORDER422_8BIT_CBYCRY, //输入Y U V的顺序
.i2c_busnum = 0, //i2c总线0
.info = &s5k4ba_i2c_info, //i2c的设备描述
.pixelformat = V4L2_PIX_FMT_UYVY, //经过摄像头控制器后的输出格式
.srclk_name = "mout_mpll",
.clk_name = "sclk_cam1",
.clk_rate = 44000000, //时钟频率
.line_length = 1920,
.width = 800, //图像宽
.height = 600, //图像高
/* 裁剪 */
.window =
.left = 0,
.top = 0,
.width = 800,
.height = 600,
,
/* 时序极性 */
.inv_pclk = 0,
.inv_vsync = 1,
.inv_href = 0,
.inv_hsync = 0,
.initialized = 0,
.cam_power = s5k5ba_power_en,
;
看一看s5k4ba_i2c_info
static struct i2c_board_info s5k4ba_i2c_info =
I2C_BOARD_INFO("S5K4BA", 0x2d),
.platform_data = &s5k4ba_plat,
;
其中表明i2c设备名称为S5K4BA,i2c从地址0x2d
再看一看s5k5ba_power_en如何使能摄像头
static int s5k5ba_power_en(int onoff)
smdkv210_cam1_power(onoff);
/* 设置GPIO */
static int smdkv210_cam0_power(int onoff)
int err;
/* Camera A */
err = gpio_request(GPIO_PS_VOUT, "GPH0");
if (err)
printk(KERN_ERR "failed to request GPH0 for CAM_2V8\\n");
s3c_gpio_setpull(GPIO_PS_VOUT, S3C_GPIO_PULL_NONE);
gpio_direction_output(GPIO_PS_VOUT, 0);
gpio_direction_output(GPIO_PS_VOUT, 1);
gpio_free(GPIO_PS_VOUT);
return 0;
s3c_platform_camera描述一个摄像头的信息(哪个摄像头接口,在哪个i2c总线,像素格式…)
s5k4ba被嵌入到fimc的平台数据中
static struct s3c_platform_fimc fimc_plat_lsi =
.srclk_name = "mout_mpll",
.clk_name = "sclk_fimc",
.lclk_name = "sclk_fimc_lclk",
.clk_rate = 166750000,
.default_cam = CAMERA_PAR_A, //默认接口
/* 保存摄像头信息,可以多个 */
.camera =
&s5k4ba,
,
.hw_ver = 0x43,
;
s3c_platform_fimc存放着所有摄像头的信息,供驱动程序读取
在mach-smdkc110.c文件中,fimc_plat_lsi最终被设置到fimc的平台设备的平台数据中
s3c_fimc0_set_platdata(&fimc_plat_lsi);
s3c_fimc1_set_platdata(&fimc_plat_lsi);
s3c_fimc2_set_platdata(&fimc_plat_lsi);
void __init s3c_fimc0_set_platdata(struct s3c_platform_fimc *pd)
struct s3c_platform_fimc *npd;
/* 复制一份 */
npd = kmemdup(pd, sizeof(struct s3c_platform_fimc), GFP_KERNEL);
npd->cfg_gpio = s3c_fimc0_cfg_gpio;
npd->clk_on = s3c_fimc_clk_on;
npd->clk_off = s3c_fimc_clk_off;
/* 分配视频缓存 */
npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMC0, 1);
/* 将器设置到fimc平台设备的平台数据中 */
s3c_device_fimc0.dev.platform_data = npd;
其中s3c_device_fimc0是一个platform总线的平台设备,在内核启动时,会被注册进内核
struct platform_device s3c_device_fimc0 =
.name = "s3c-fimc",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_fimc0_resource),
.resource = s3c_fimc0_resource,
;
static struct resource s3c_fimc0_resource[] =
[0] =
.start = S5P_PA_FIMC0, //寄存器地址
.end = S5P_PA_FIMC0 + S5P_SZ_FIMC0 - 1,
.flags = IORESOURCE_MEM,
,
[1] =
.start = IRQ_FIMC0, //中断
.end = IRQ_FIMC0,
.flags = IORESOURCE_IRQ,
,
;
对于S5PV210来说,芯片上有三个摄像头控制器,那么就有三个fimc的平台设备,三个控制器在经过驱动程序后会生成/dev/video0、/dev/video1、/dev/video2三个设备节点
上面各对象的包裹关系如下
然后再将platform_device注册进内核
3.2 fimc的平台驱动
接下来分析platform_driver
platform_driver在fimc_dev.c中注册
static struct platform_driver fimc_driver =
.probe = fimc_probe,
.remove = fimc_remove,
.suspend = fimc_suspend,
.resume = fimc_resume,
.driver =
.name = FIMC_NAME,
.owner = THIS_MODULE,
,
;
static int fimc_register(void)
platform_driver_register(&fimc_driver);
return 0;
当platform_dev和platform_driver匹配的时候,会调用probe函数
struct fimc_global *fimc_dev;
...
static int __devinit fimc_probe(struct platform_device *pdev)
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
/* 为全局变量分配内存 */
if (!fimc_dev)
fimc_dev = kzalloc(sizeof(*fimc_dev), GFP_KERNEL);
/* 通过平台设备的信息注册摄像头控制器 */
ctrl = fimc_register_controller(pdev);
/* 注册v4l2_device,主要是为了管理v4l2_subdev */
v4l2_device_register(&pdev->dev, &ctrl->v4l2_dev);
/* 初始化fimc的全局变量 */
if (!fimc_dev->initialized)
fimc_init_global(pdev);
/* 注册video_device字符设备,生成设备节点 */
video_register_device(ctrl->vd, VFL_TYPE_GRABBER, ctrl->id);
首先来看一看fimc_register_controller
struct fimc_control *fimc_register_controller(struct platform_device *pdev)
struct s3c_platform_fimc *pdata;
struct fimc_control *ctrl;
/* 从platform_dev中中获取fimc的平台设备信息 */
pdata = to_fimc_plat(&pdev->dev);
/* 从全局变量中得到fimc ctrl */
ctrl = get_fimc_ctrl(id);
/* 设置ctrl的video_device */
ctrl->vd = &fimc_video_device[id];
/* 申请寄存器地址空间 */
platform_get_resource(pdev, IORESOURCE_MEM, 0);
request_mem_region(res->start, res->end - res->start + 1, pdev->name);
ctrl->regs = ioremap(res->start, res->end - res->start + 1);
/* 申请中断 */
ctrl->irq = platform_get_irq(pdev, 0);
request_irq(ctrl->irq, fimc_irq, IRQF_DISABLED, ctrl->name, ctrl)
/* 初始化寄存器,复位摄像头控制器 */
fimc_hwset_reset(ctrl);
fimc_video_device是一个video_device的全局数组,每一个摄像头控制器对应其中一项
struct video_device fimc_video_device[FIMC_DEVICES] =
[0] =
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
,
[1] =
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
,
[2] =
.fops = &fimc_fops,
.ioctl_ops = &fimc_v4l2_ops,
.release = fimc_vdev_release,
,
;
static const struct v4l2_file_operations fimc_fops =
.owner = THIS_MODULE,
.open = fimc_open,
.release = fimc_release,
.ioctl = video_ioctl2,
.read = fimc_read,
.write = fimc_write,
.mmap = fimc_mmap,
.poll = fimc_poll,
;
const struct v4l2_ioctl_ops fimc_v4l2_ops =
.vidioc_querycap = fimc_querycap,
.vidioc_reqbufs = fimc_reqbufs,
.vidioc_querybuf = fimc_querybuf,
.vidioc_g_ctrl = fimc_g_ctrl,
.vidioc_s_ctrl = fimc_s_ctrl,
.vidioc_s_ext_ctrls = fimc_s_ext_ctrls,
.vidioc_cropcap = fimc_cropcap,
.vidioc_g_crop = fimc_g_crop,
.vidioc_s_crop = fimc_s_crop,
.vidioc_streamon = fimc_streamon,
.vidioc_streamoff = fimc_streamoff,
.vidioc_qbuf = fimc_qbuf,
.vidioc_dqbuf = fimc_dqbuf,
.vidioc_enum_fmt_vid_cap = fimc_enum_fmt_vid_capture,
.vidioc_g_fmt_vid_cap = fimc_g_fmt_vid_capture,
.vidioc_s_fmt_vid_cap = fimc_s_fmt_vid_capture,
.vidioc_try_fmt_vid_cap = fimc_try_fmt_vid_capture,
.vidioc_enum_input = fimc_enum_input,
.vidioc_g_input = fimc_g_input,
.vidioc_s_input = fimc_s_input,
.vidioc_g_parm = fimc_g_parm,
.vidioc_s_parm = fimc_s_parm,
.vidioc_queryctrl = fimc_queryctrl,
.vidioc_querymenu = fimc_querymenu,
.vidioc_g_fmt_vid_out = fimc_g_fmt_vid_out,
.vidioc_s_fmt_vid_out = fimc_s_fmt_vid_out,
.vidioc_try_fmt_vid_out = fimc_try_fmt_vid_out,
.vidioc_g_fbuf = fimc_g_fbuf,
.vidioc_s_fbuf = fimc_s_fbuf,
.vidioc_try_fmt_vid_overlay = fimc_try_fmt_overlay,
.vidioc_g_fmt_vid_overlay = fimc_g_fmt_vid_overlay,
.vidioc_s_fmt_vid_overlay = fimc_s_fmt_vid_overlay,
;
可以看到fimc_register_controller中设置了ctrl的video_device,申请了寄存器的地址空间,申请了中断(中断程序我们后面会分析),复位摄像头控制器的寄存器
接下来再来看一看fimc_probe的fimc_init_global函数
static int fimc_init_global(struct platform_device *pdev)
struct s3c_platform_fimc *pdata;
pdata = to_fimc_plat(&pdev->dev);
for()
cam = pdata->camera[i];
memcpy(&fimc_dev->camera[i], cam, sizeof(*cam));
fimc_dev->camera_isvalid[i] = 1;
从中可以看出,fimc_init_global函数会将fimc平台信息的所有摄像头信息拷贝到fimc的全局变量中
如如果有三个fimc的platform_dev,那么就会调用三次fimc_probe函数,最后会创建三个设备节点/dev/videox,并且将所有摄像头信息保存到fimc的全局变量中
接下俩就是一系列的ioctl操作了,此部分将在稍后介绍
需要注意的是,在此之前,我们介绍的都是摄像头控制器的驱动程序,并没有涉及到真正的摄像头的驱动程序,摄像头在这里被抽象成一个v4l2_subdev,而摄像头控制器是v4l2_device
再继续fimc的驱动前,我们先来看一个摄像头的驱动程序
3.3 s5k4ba的驱动程序
内核源码s5k4ba.c
首先看驱动的入口函数,当你拖动到最下面的时候,你会惊奇地发现,怎么没有定义module_init入口呢?
这是不可能地,真相只有一个,那就是在头文件中
在v4l2-i2c-drv.h中
struct v4l2_i2c_driver_data
const char * const name;
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
int (*suspend)(struct i2c_client *client, pm_message_t state);
int (*resume)(struct i2c_client *client);
const struct i2c_device_id *id_table;
;
static struct v4l2_i2c_driver_data v4l2_i2c_data;
static struct i2c_driver v4l2_i2c_driver;
static int __init v4l2_i2c_drv_init(void)
v4l2_i2c_driver.driver.name = v4l2_i2c_data.name;
v4l2_i2c_driver.command = v4l2_i2c_data.command;
v4l2_i2c_driver.probe = v4l2_i2c_data.probe;
v4l2_i2c_driver.remove = v4l2_i2c_data.remove;
v4l2_i2c_driver.suspend = v4l2_i2c_data.suspend;
v4l2_i2c_driver.resume = v4l2_i2c_data.resume;
v4l2_i2c_driver.id_table = v4l2_i2c_data.id_table;
return i2c_add_driver(&v4l2_i2c_driver);
static void __exit v4l2_i2c_drv_cleanup(void)
i2c_del_driver(&v4l2_i2c_driver);
module_init(v4l2_i2c_drv_init);
module_exit(v4l2_i2c_drv_cleanup);
注册了v4l2_i2c_driver,而v4l2_i2c_driver得数据来源自v4l2_i2c_data,所以对于编写驱动程序得人来说,就需要去填充v4l2_i2c_data
下面回到s5k4ba.c中
static const struct i2c_device_id s5k4ba_id[] =
S5K4BA_DRIVER_NAME, 0 ,
,
;
static struct v4l2_i2c_driver_data v4l2_i2c_data =
.name = S5K4BA_DRIVER_NAME,
.probe = s5k4ba_probe,
.remove = __devexit_p(s5k4ba_remove),
.id_table = s5k4ba_id,
;
当该驱动得id_table中得名字与i2c设备的名字匹配时,就调用probe函数
static int s5k4ba_probe(struct i2c_client *client,
const struct i2c_device_id *id)
struct v4l2_subdev *sd;
sd = &state->sd;
v4l2_i2c_subdev_init(sd, client, &s5k4ba_ops);
probe函数会分配一个v4l2_subdev,并调用v4l2_i2c_subdev_init初始化
接下来看一看v4l2_i2c_subdev_init
void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,
const struct v4l2_subdev_ops *ops)
/* 设置v4l2_subdev的ops */
v4l2_subdev_init(sd, ops);
/* 将v4l2_subdev设置为i2c_client的client_data */
i2c_set_clientdata(client, sd);
我们看以下s5k4ba_ops的定义
static const struct v4l2_subdev_core_ops s5k4ba_core_ops =
.init = s5k4ba_init, /* initializing API */
.s_config = s5k4ba_s_config, /* Fetch platform data */
.queryctrl = s5k4ba_queryctrl,
.querymenu = s5k4ba_querymenu,
.g_ctrl = s5k4ba_g_ctrl,
.s_ctrl = s5k4ba_s_ctrl,
;
static const struct v4l2_subdev_video_ops s5k4ba_video_ops =
.g_fmt = s5k4ba_g_fmt,
.s_fmt = s5k4ba_s_fmt,
.enum_framesizes = s5k4ba_enum_framesizes,
.enum_frameintervals = s5k4ba_enum_frameintervals,
.enum_fmt = s5k4ba_enum_fmt,
.try_fmt = s5k4ba_try_fmt,
.g_parm = s5k4ba_g_parm,
.s_parm = s5k4ba_s_parm,
;
static const struct v4l2_subdev_ops s5k4ba_ops =
.core = &s5k4ba_core_ops,
.video = &s5k4ba_video_ops,
;
定义了一系列的回调函数,估计会被v4l2_dev调用
总结一下:s5k4ba.的驱动程序注册了一个i2c_driver,当i2c_driver遇上匹配的i2c_client时,就会调用probe函数,probe函数会生成一个v4l2_subdev,并设置好它的ops(一系列的回调函数),然后将v4l2_subdev设置为i2c_client的clientdata
那么什么时候会有匹配的i2c_client呢?我们接下来分析fimc的驱动
3.4 fimc驱动详细分析
为了更加详细地分析fimc的驱动,接下来我们按照v4l2的应用编程流程来分析fimc的驱动
首先回顾以下v4l2的应用编程流程
查询设备功能(VIDIOC_QUERYCAP)
枚举输入设备(VIDIOC_ENUMINPUT)
设置输入设备(VIDIOC_S_INPUT)
枚举像素格式(VIDIOC_ENUM_FMT)
设置像素格式(VIDIOC_S_FMT)
申请缓存(VIDIOC_REQBUFS)
映射缓存(mmap)
缓存入队列(VIDIOC_QBUF)
打开流(VIDIOC_STREAMON)
等待数据可读(poll)
缓存出队列(VIDIOC_DQBUF)
下面继续分析fimc驱动
VIDIOC_QUERYCAP
获取设备支持的功能
static int fimc_querycap(struct file *filp, void *fh,
struct v4l2_capability *cap)
cap->capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT |
V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_STREAMING);
VIDIOC_ENUMINPUT
枚举输入设备
这个函数是实现v4l2_subdev的重点
int fimc_enum_input(struct file *file, void *fh, struct v4l2_input *inp)
struct fimc_global *fimc = get_fimc_dev();
strcpy(inp->name, fimc->camera[inp->index].info->type);
拷贝fimc全局变量里面的摄像头信息
VIDIOC_S_INPUT
int fimc_s_input(struct file *file, void *fh, unsigned int i)
/* 注册子设备 */
fimc_configure_subdev(ctrl);
static int fimc_configure_subdev(struct fimc_control *ctrl)
struct i2c_adapter *i2c_adap;
struct i2c_board_info *i2c_info;
struct v4l2_subdev *sd;
unsigned short addr;
i2c_adap = i2c_get_adapter(ctrl->cam->i2c_busnum);
/* 获取摄像头的i2c信息 */
i2c_info = ctrl->cam->info;
name = i2c_info->type;
addr = i2c_info->addr;
/* 注册v4l2_subdev */
sd = v4l2_i2c_new_subdev_board(&ctrl->v4l2_dev, i2c_adap,
name, i2c_info, &addr);
我们继续看v4l2_i2c_new_subdev_board
struct v4l2_subdev *v4l2_i2c_new_subdev_board(struct v4l2_device *v4l2_dev,
struct i2c_adapter *adapter, const char *module_name,
struct i2c_board_info *info, const unsigned short *probe_addrs)
struct v4l2_subdev *sd = NULL;
struct i2c_client *client;
client = i2c_new_device(adapter, info); //注册i2c设备
sd = i2c_get_clientdata(client); //从注册的i2c设备获取v4l2_subdev
/* 注册v4l2_subdev */
v4l2_device_register_subdev(v4l2_dev, sd);
上面程序中使用i2c_new_device注册一个i2c设备,那么此时如果有匹配的i2c_driver,就会调用其probe函数
sd = i2c_get_clientdata(client);这行代码直接从注册的i2c设备获取client_data,并指明client_data就是v4l2_subdev,那么一定是在i2c驱动中,生成了一个v4l2_subdev,并将其设置到i2c_client的client_data里,没错,这更我们之前分析的摄像头驱动时吻合的
VIDIOC_ENUM_FMT
枚举像素格式
static const struct v4l2_fmtdesc capture_fmts[] =
.index = 0,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = FORMAT_FLAGS_PACKED,
.description = "RGB-5-6-5",
.pixelformat = V4L2_PIX_FMT_RGB565,
,
.index = 2,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = FORMAT_FLAGS_PACKED,
.description = "YUV 4:2:2 packed, YCbYCr",
.pixelformat = V4L2_PIX_FMT_YUYV,
,
.index = 3,
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.flags = FORMAT_FLAGS_PACKED,
.description = "YUV 4:2:2 packed, CbYCrY",
.pixelformat = V4L2_PIX_FMT_UYVY,
,
...
;
...
int fimc_enum_fmt_vid_capture(struct file *file, void *fh,
struct v4l2_fmtdesc *f)
memcpy(f, &capture_fmts[i], sizeof(*f));
VIDIOC_S_FMT
设置像素格式
int fimc_s_fmt_vid_capture(struct file *file, void *fh, struct v4l2_format *f)
struct fimc_capinfo *cap; //捕获设备的信息
ctrl->cap = kmalloc(sizeof(*cap), GFP_KERNEL);
cap = ctrl->cap;
memcpy(&cap->fmt, &f->fmt.pix, sizeof(cap->fmt));
depth = fimc_fmt_depth(ctrl, f);
设置像素格式就是将像素格式拷贝到fimc_capinfo中
fimc_capinfo时描述的是捕获设备的信息,如下
struct fimc_capinfo
struct v4l2_cropcap cropcap;
struct v4l2_rect crop;
struct v4l2_pix_format fmt;
struct fimc_buf_set bufs[FIMC_CAPBUFS]; //视频缓存
struct list_head inq;
int outq[FIMC_PHYBUFS]; //dma地址
int nr_bufs;
int irq;
int lastirq;
/* flip: V4L2_CID_xFLIP, rotate: 90, 180, 270 */
u32 flip;
u32 rotate;
;
VIDIOC_REQBUFS
申请缓存
int fimc_reqbufs_capture(void *fh, struct v4l2_requestbuffers *b)
int size[4] = 0, 0, 0, 0; // 颜色分量的大小
/* 计算像素各颜色分量的大小 */
switch()
...
case V4L2_PIX_FMT_YUV420:
size[0] = cap->fmt.width * cap->fmt.height;
size[1] = cap->fmt.width * cap->fmt.height >> 2;
size[2] = cap->fmt.width * cap->fmt.height >> 2;
size[3] = 16; /* Padding buffer */
break;
...
/* 分配dma缓存 */
fimc_alloc_buffers(ctrl, size, align);
为cap中的buf分配缓存
static int fimc_alloc_buffers(struct fimc_control *ctrl, int size[], int align)
for()
fimc_dma_alloc(ctrl, &cap->bufs[i], plane, align);
mmap
内存映射
static int fimc_mmap(struct file *filp, struct vm_area_struct *vma)
fimc_mmap_cap(filp, vma);
static inline int fimc_mmap_cap(struct file *filp, struct vm_area_struct *vma)
pfn = __phys_to_pfn(ctrl->cap->bufs[idx].base[0]);
remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
VIDIOC_QBUF
缓存入队列
static int fimc_qbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
fimc_qbuf_capture(fh, b);
int fimc_qbuf_capture(void *fh, struct v4l2_buffer *b)
fimc_add_inqueue(ctrl, b->index);
将指定的buf加入队列中
static int fimc_add_inqueue(struct fimc_control *ctrl, int i)
list_add_tail(&cap->bufs[i].list, &cap->inq);
poll
等待数据准备好
static u32 fimc_poll(struct file *filp, poll_table *wait)
/* 加入等待队列 */
poll_wait(filp, &ctrl->wq, wait);
唤醒
什么会唤醒等待队列?
fimc_dev.c的probe函数中,一开始就申请了中断,前面在分析的时候并没有仔细看,现在来好好看一看
当有一帧数据准备完成的时候,就会调用fimc的中断函数,中断会唤醒等待队列,poll就会返回
static irqreturn_t fimc_irq(int irq, void *dev_id)
fimc_irq_cap(ctrl);
static inline void fimc_irq_cap(struct fimc_control *ctrl)
fimc_hwset_clear_irq(ctrl); //清中断
/* 唤醒等待队列 */
wake_up(&ctrl->wq);
VIDIOC_DQBUF
出队列
在poll返回后,就调用dqbuf获取缓存
static int fimc_dqbuf(struct file *filp, void *fh, struct v4l2_buffer *b)
fimc_dqbuf_capture(fh, b);
int fimc_dqbuf_capture(void *fh, struct v4l2_buffer *b)
/* 找到准备好的ping-pong */
pp = ((fimc_hwget_frame_count(ctrl) + 2) % 4);
b->index = cap->outq[pp];
/* 重新设置ping-pong的buf */
fimc_add_outqueue(ctrl, pp);
这里说一下什么是ping-pong
ping-pong是一个硬件上的东西,视频采集需要一个缓存区,可以把ping-pong看作是一个循环队列,以S5PV210为例,有四个ping-pong,每个ping-pong都存放着目的buf的地址,每当有图像数据准备好,就会通过dma存到对应的ping-pong所指定的目的地址处
以上是关于由DELPHI控制摄像头的问题的主要内容,如果未能解决你的问题,请参考以下文章