Camera
先看一下抽象层的主要流程:
首先启动一个守护进程
Main()(camdaemon.c)
int qcamsvr_start(void)( qcamsvr.c)
{
1. server_fd = open(server_dev_name, O_RDWR);//打开服务对应的文件节点
2. if (mctl_load_comps()) //加载所有需要的组件
3. rc = qcamsvr_load_gesture_lib(&gesture_info.gesture_lib);//加载手势库
4. ez_server_socket_id = eztune_setup_server("127.0.0.1", "55555");
if (pipe(ez_cmd_pipe)
ez_prev_server_socket_id = eztune_setup_server("127.0.0.1", "55556");
if(pipe(ez_prev_cmd_pipe)
//创建两个socket端口,同时建立两个pipe文件对两个端口进行监控
5. if (get_mctl_node_info(server_fd, &mctl_node_info))//通过服务节点获取服务的相关信息
{
//此处获取的是内核中调用msm_sensor_register()注册的sensor节点信息
}
6. sub.type = V4L2_EVENT_ALL;
rc = ioctl(server_fd, VIDIOC_SUBSCRIBE_EVENT, &sub);//通过服务设备文件的ioctl接口,订阅所有的事件
7. config_arg.server_fd = server_fd;
config_arg.ez_read_fd = ez_cmd_pipe[0];
config_arg.ez_write_fd = ez_cmd_pipe[1];
config_arg.ez_prev_read_fd = ez_prev_cmd_pipe[0];
config_arg.ez_prev_write_fd = ez_prev_cmd_pipe[1];//初始化配置线程的参数
8.下面就是一个循环,对这几个文件进行poll
do {
fds[0].fd = server_fd;
fds[0].events = POLLPRI;
fds[1].fd = ez_server_socket_id;
fds[1].events = POLLIN;
fds[2].fd = ez_prev_server_socket_id;
fds[2].events = POLLIN;
rc = poll(fds, 3, timeoutms);
if (fds[0].revents & POLLPRI) { /* Server Node Wake Up *
//对服务的设备文件进行监视,当遇到打开事件的时候,立即创建一个配置线程
rc = qcamsvr_process_server_node_event(&config_arg, &mctl_node_info,
&gesture_info);
}
//线面就是对两个socket进程监视和处理。
if ((fds[1].revents & POLLIN) == POLLIN) { /* EzTune Server */
int client_socket_id;
client_socket_id = accept(ez_server_socket_id,
(struct sockaddr *)&addr_client_inet, &addr_client_len);
write(ez_cmd_pipe[1], &client_socket_id, sizeof(int));
}
}
if ((fds[2].revents & POLLIN) == POLLIN) { /* EzTune Prev Server */
int client_socket_id;
client_socket_id = accept(ez_prev_server_socket_id,
(struct sockaddr *)&addr_client_inet, &addr_client_len);
write(ez_prev_cmd_pipe[1], &client_socket_id, sizeof(int));
}
}
} /* Else for Poll rc */
} while (1);
}
下面进入配置线程创建的流程:
//取出服务节点产生的事件,然后根据配置节点的名称,分发给各自独立的主控制线程
1. static int qcamsvr_process_server_node_event()
{
//下命令让服务模块的事件出队列进行处理
rc = ioctl(config_arg->server_fd, VIDIOC_DQEVENT, &v4l2_evt);
if (v4l2_evt.type == V4L2_EVENT_PRIVATE_START + MSM_GES_RESP_V4L2)
{
//如果是手势事件,则进行一系列的处理
if (ctrl->type == MSM_V4L2_GES_OPEN) {
//设置主控线程的接口
p_gesture_info->cam_mctl.svr_ops.launch_mctl_thread =
create_v4l2_conf_thread;
//设置主控线程的退出接口
p_gesture_info->cam_mctl.svr_ops.release_mctl_thread =
destroy_v4l2_cam_conf_thread;
//设置camera使能
p_gesture_info->cam_mctl.svr_ops.camera_available =
qcamsvr_camera_available;
//设置服务设备文件的文件句柄
p_gesture_info->cam_mctl.svr_ops.server_fd = config_arg->server_fd;
//创建手势服务
status = p_gesture_info->gesture_lib.gesture_service_create(
&p_gesture_info->cam_mctl, &p_gesture_info->observer);
}
else if (ctrl->type == MSM_V4L2_GES_CLOSE) {
//消亡手势服务
status = p_gesture_info->gesture_lib.gesture_service_send_data(ctrl)
}
if ((status == CAMERA_SUCCESS) &&
(ctrl->type != MSM_V4L2_GES_CLOSE)) {
//如果成功,且文件打开,则向手势服务发送数据
status = p_gesture_info->gesture_lib.gesture_service_send_data(ctrl);
if (status != CAMERA_SUCCESS) {
LOGE("gesture_service_send_data failed");
}
} else {
if (ctrl->type == MSM_V4L2_GES_CLOSE) {
ctrl->status = CAM_CTRL_SUCCESS;
} else {
LOGE("gesture send failure message");
ctrl->status = CAM_CTRL_FAILED;
}
//将操作结果反馈给camera服务
v4l2_ioctl.ioctl_ptr = ctrl;
qcamsvr_send_ctrl_cmd_done(config_arg->server_fd, &v4l2_ioctl);
//如果是camera事件,则进行一系列的处理
}
else if (v4l2_evt.type == V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_V4L2)
{
if (ctrl->type == MSM_V4L2_OPEN) {
//通过pipe进行一些初始化工作
//创建一个核心的线程
if ((tmp_mctl_struct->handle =
create_v4l2_conf_thread(config_arg)) == NULL)
//反馈结果给camera服务端
ctrl->status = CAM_CTRL_SUCCESS;
v4l2_ioctl.ioctl_ptr = ctrl;
qcamsvr_send_ctrl_cmd_done(config_arg->server_fd, &v4l2_ioctl);
}
else if (ctrl->type == MSM_V4L2_CLOSE){
//进行一些消亡工作
//通过写一些pipe
if (destroy_v4l2_cam_conf_thread(tmp_mctl_struct->handle) < 0) //消亡只线程
ctrl->status = CAM_CTRL_SUCCESS;
v4l2_ioctl.ioctl_ptr = ctrl;
//反馈结果给camera服务
qcamsvr_send_ctrl_cmd_done(config_arg->server_fd, &v4l2_ioctl);
}
else {
//通过pipe写一些命令,等待配置返回
}
}
//首先来看一下刚刚的线程创建函数
void *create_v4l2_conf_thread(struct config_thread_arguments* arg)
{
//核心工作就是创建了个线程
rc = pthread_create(&pme->cam_mctl_thread_id, NULL, cam_mctl_thread, pme);
}
//下面进入创建的配置线程的主函数:
static void *cam_mctl_thread(void *data)(mctl.c)
{
//首先初始化需要监控的文件句柄
pipe_readfd = arg->read_fd;
pipe_writefd = arg->write_fd;
server_fd = arg->server_fd;
ez_pipe_readfd = arg->ez_read_fd;
ez_client_fd = -1;
ez_prev_pipe_readfd = arg->ez_prev_read_fd;
ez_prev_client_fd = -1;
//向对应的配置节点下命令监控所有事件(此文件句柄具体标识什么意思暂时还没搞清楚)
sub.type = V4L2_EVENT_ALL;
rc = ioctl(cam_fd, VIDIOC_SUBSCRIBE_EVENT, &sub);
//下面开始进入循环的监控
do {
//文件句柄初始化
fds[0].fd = cam_fd;
fds[0].events = POLLPRI;
fds[1].fd = pipe_readfd;
fds[1].events = POLLPRI | POLLIN;
fds[2].fd = ez_pipe_readfd;
fds[2].events = POLLIN;
fds[3].fd = ez_client_fd;
fds[3].events = POLLIN;
fds[4].fd = ez_prev_pipe_readfd;
fds[4].events = POLLIN;
fds[5].fd = ez_prev_client_fd;
fds[5].events = POLLIN;
/* evt/msg from qcam server */
if (ctrl->type == MSM_V4L2_CLOSE) {
//关闭所有的资源
config_shutdown_pp(pme->p_cfg_ctrl);
//反馈结果给服务
rc = mctl_send_ctrl_cmd_done(pme->p_cfg_ctrl, NULL, TRUE);
}
else {
//此函数为用户控件的APP处理对应的命令
if (mctl_proc_v4l2_request(pme, ctrl) < 0)
}
/* evt/msg from config node */
rc = ioctl(cam_fd, VIDIOC_DQEVENT, &v4l2_event);//下事件出队列的命令
if (v4l2_event.type ==
V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_DIV_FRAME_EVT_MSG) {
//进程对应的帧转移
mctl_pp_divert_frame(p_cfg_ctrl,
(void *)&(event_data.isp_data.div_frame));
}else if(v4l2_event.type ==
V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_MCTL_PP_EVENT) {
//处理后置的事件
mctl_pp_proc_event(p_cfg_ctrl,
(void *)&(event_data.isp_data.pp_event_info));
}
else if (v4l2_event.type ==
V4L2_EVENT_PRIVATE_START + MSM_CAM_RESP_STAT_EVT_MSG) {
//处理正常的事件消息
mctl_proc_event_message (pme, isp_adsp);
}
else {
CDBG_HIGH("%s: Error: should not be here", __func__);
}
/* evt/msg from eztune pipe */
if (ez_client_fd > 0)
mctl_eztune_server_connect(pme, ez_client_fd);
/* evt/msg from eztune client */
if (ez_client_fd > 0) {
mctl_eztune_read_and_proc_cmd(EZ_MCTL_SOCKET_CMD);
/* evt/msg from eztune prev pipe */
if (ez_prev_client_fd > 0)
mctl_eztune_prev_server_connect(pme, ez_prev_client_fd);
}
/* evt/msg from eztune prev client */
if ((fds[5].revents & POLLIN) == POLLIN) {
if (ez_prev_client_fd > 0) {
mctl_eztune_read_and_proc_cmd(EZ_MCTL_PREV_SOCKET_CMD);
}
}
}wile(TRUE)
//循环结束取消订阅所有消息
if (ioctl(cam_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0)
}
先来看看camera在硬件抽象层的接口:
主要是三点:
1. preview:预览
2. recording 录像
3. picture 拍照
模块接口函数:
get_number_of_cameras: get_number_of_cameras,
get_camera_info: get_camera_info,
camera_info_t:
typedef struct {
int modes_supported;//支持的模式
int8_t camera_id;//id标识
cam_position_t position;//前摄还是后摄
uint32_t sensor_mount_angle;//角度
}camera_info_t;
我们目前使用的高通8X平台:
Camera模块:
此模块有一个全局的camera服务结构体实例,用于全局管理各种子系统设备。
子系统设备常见的有:
enum msm_cam_subdev_type {
CSIPHY_DEV,
CSID_DEV,
CSIC_DEV,
ISPIF_DEV,
VFE_DEV,
AXI_DEV,
VPE_DEV,
SENSOR_DEV,
ACTUATOR_DEV,
EEPROM_DEV,
GESTURE_DEV,
};
定义一个抽象的camera服务设备:
struct msm_cam_server_dev {
/* config node device*/
struct platform_device *server_pdev;
/* server node v4l2 device */
struct v4l2_device v4l2_dev;
struct video_device *video_dev;
struct media_device media_dev;
/* info of sensors successfully probed*/
struct msm_camera_info camera_info;
/* info of configs successfully created*/
struct msm_cam_config_dev_info config_info;
/* active working camera device - only one allowed at this time*/
struct msm_cam_v4l2_device *pcam_active;
/* number of camera devices opened*/
atomic_t number_pcam_active;
struct v4l2_queue_util server_command_queue;
/* This queue used by the config thread to send responses back to the
* control thread. It is accessed only from a process context.
*/
struct msm_cam_server_queue server_queue[MAX_NUM_ACTIVE_CAMERA];
uint32_t server_evt_id;
struct msm_cam_server_mctl_inst mctl[MAX_NUM_ACTIVE_CAMERA];
uint32_t mctl_handle_cnt;
int use_count;
/* all the registered ISP subdevice*/
struct msm_isp_ops *isp_subdev[MSM_MAX_CAMERA_CONFIGS];
/* info of MCTL nodes successfully probed*/
struct msm_mctl_node_info mctl_node_info;
struct mutex server_lock;
struct mutex server_queue_lock;
/*v4l2 subdevs*/
struct v4l2_subdev *csiphy_device[MAX_NUM_CSIPHY_DEV];
struct v4l2_subdev *csid_device[MAX_NUM_CSID_DEV];
struct v4l2_subdev *csic_device[MAX_NUM_CSIC_DEV];
struct v4l2_subdev *ispif_device;
struct v4l2_subdev *vfe_device[MAX_NUM_VFE_DEV];
struct v4l2_subdev *axi_device[MAX_NUM_AXI_DEV];
struct v4l2_subdev *vpe_device[MAX_NUM_VPE_DEV];
struct v4l2_subdev *gesture_device;
};
从控制流的角度来分析下camera的流程。
首先camera会启动一个daemon进程来进行核心的操作。
启动daemon进程的地方:
在init.target.rc文件中
#start camera server as daemon
service qcamerasvr /system/bin/mm-qcamera-daemon
class late_start
user system
group system camera inet
生成此mm-qcamera-daemon bin档的地方:
android\vendor\qcom\proprietary\mm-camera\apps\appslib\Android.mk
此mk文件生成了mm-qcamera-daemon bin档
Daemon进程的入口函数:mian()(camdaemon.c)
一个camera的守护进程在init进程中,开启的一个service |
此线程与具体的sensor相关联,负责对sensor进行具体细节的操作 |
此为daemon进程的主线程,从server node收集事件,纷发给mctl thread,根据config的name,与server节点进行队列,不断轮询其事件队列,获取command,进行全局处理 |
mctl_pp_poll_thread |
mctl thread |
main daemon thread |
Daemon |
此线程与kernel中config节点进行通信,轮询节点的消息队列中获得command,进行全局处理 (每一个config节点都对应一个mctl thread) |
抽象层到内核层的大致流程:
抽象层主要通过server node和config node将command下到内核,对应的节点驱动将command通过事件队列进行管理。
而daemon进程通过开启对应的线程,不停的对事件队列进行轮询,处理上层下的command
在main daemon thread中重要的任务:
一:将sensor操作关联的硬件组件加载进来,还要加载一些必备的库,为camera的正式工作铺垫环境:
① AXI_comp_create
② sensor_comp_create
③ flash_led_comp_create
④ flash_strobe_comp_create
⑤ CAMIF_comp_create
⑥ VFE_comp_create
⑦ ACTUATOR_comp_create
⑧ eeprom_comp_create
⑨ mctl_load_stats_proc_lib
⑩ mctl_load_frame_proc_lib
二.线程的循环工作
线程,顾名思义,肯定有一个封闭的循环体,在循环体中做一些核心的操作
而Daemon进程的主线程轮询服务节点的event queue,获取事件,纷发给各自的mctl thread
Daemon进行的主线程主要处理一下基类事件“
① MSM_GES_RESP_V4L2 :
Open:主要进行初始化,铺垫环境,开启处理camera细节活动的线程
Close:进行一些善后工作
② MSM_CAM_RESP_V4L2:处理open和colse
Open:主要进行初始化,铺垫环境,开启处理camera细节活动的线程
Close:进行一些善后工作
③ 其他一些事件都是通过pipe通信直接写入到①②两点创建的线程中(send command through pipe and wait for config to return)
在mctl thread中重要的任务:
一. 打开confing节点文件
二. 调用create_camfd_receive_socket猜测是与硬件抽象层进行直接通信的
三. 创建mctl_pp_poll_thread线程,
四. 初始化camera的几个feature:
①zoom_init_ctrl
②bestshot_init
③hdr_init
五.通过pipe通信获取server节点的控制事件,事件由Daemon进程的主控线程获取并且通过pipe传递过来
六.通过监测config节点的事件获取config节点对应的控制command
主要监测三类事件:
① MSM_CAM_RESP_DIV_FRAME_EVT_MSG
② MSM_CAM_RESP_MCTL_PP_EVENT
③ MSM_CAM_RESP_STAT_EVT_MSG
将这三个事件以command的形式,通过pipe通信发送到(一)中创建的PP线程中
在mctl_pp_poll_thread中重要的任务:
一:对几个pipe文件进行监测,与其他线程进行交互
几种事件:
①/* Events on pipe between mctl thread - mctl pp thread */
②/* Events on user created socket */
③/* Events on mctl pp node */
④/* Events on pipe between mctl pp thread and c2d thread */
http://blog.csdn.net/qq69696698/article/details/7399321
1、Camera成像原理介绍
Camera工作流程图
Camera的成像原理可以简单概括如下:
景物(SCENE)通过镜头(LENS)生成的光学图像投射到图像传感器(Sensor)表面上,然后转为电信号,经过A/D(模数转换)转换后变为数字图像信号,再送到数字信号处理芯片(DSP)中加工处理,再通过IO接口传输到CPU中处理,通过DISPLAY就可以看到图像了。
电荷耦合器件(CCD)或互补金属氧化物半导体(CMOS)接收光学镜头传递来的影像,经模/数转换器(A/D)转换成数字信号,经过编码后存储。
流程如下:
1、CCD/CMOS将被摄体的光信号转变为电信号—电子图像(模拟信号)
2、由模/数转换器(ADC)芯片来将模拟信号转化为数字信号
3、数字信号形成后,由DSP或编码库对信号进行压缩并转化为特定的图像文件格式储存
数码相机的光学镜头与传统相机相同,将影像聚到感光器件上,即(光)电荷耦合器件(CCD) 。CCD替代了传统相机中的感光胶片的位置,其功能是将光信号转换成电信号,与电视摄像相同。
CCD是半导体器件,是数码相机的核心,其内含器件的单元数量决定了数码相机的成像质量——像素,单元越多,即像素数高,成像质量越好,通常情况下像素的高低代表了数码相机的档次和技术指标。
2、Android Camera框架
Android的Camera子系统提供一个拍照和录制视频的框架。
它将Camera的上层应用与Application Framework、用户库串接起来,而正是这个用户库来与Camera的硬件层通信,从而实现操作camera硬件。
3、Android Camera的代码结构
Android的Camera代码主要在以下的目录中:
Camera的JAVA部分
packages/apps/Camera/。其中Camera.java是主要实现的文件。这部分内容编译成为目标是Camera.apk
com.android.camera这个包,几个主要的类文件如下:
PhotoViewer:GalleryPicker.java(所有图片集)--->ImageGallery.java(某个Folder下图片列表)--->ViewImage.java(看某张具体图片)
VideoPlayer:GalleryPicker.java(所有视频集) --->MovieView.java(看某一个视频)
Camera:Camera.java(Camera取景及拍照)
VideoCamera:VideoCamera.java(VideoCamera取景及摄像)
Camera的framework供上层应用调用的部分
base/core/java/android/hardware/Camera.java
这部分目标是framework.jar
Camera的JNI部分
frameworks/base/core/jni/android_hardware_Camera.cpp
这部分内容编译成为目标是libandroid_runtime.so。
Camera UI库部分
frameworks/base/libs/ui/camera
这部分的内容被编译成库libcamera_client.so。
Camera服务部分
frameworks/base/camera/libcameraservice/
这部分内容被编译成库libcameraservice.so。
Camera HAL层部分
hardware/msm7k/libcamera
或
vendor/qcom/android-open/libcamera2
为了实现一个具体功能的Camera,在HAL层需要一个硬件相关的Camera库(例如通过调用video for linux驱动程序和Jpeg编码程序实现或者直接用各个chip厂商实现的私有库来实现,比如Qualcomm实现的libcamera.so和libqcamera.so),实现CameraHardwareInterface规定的接口,来调用相关的库,驱动相关的driver,实现对camera硬件的操作。这个库将被Camera的服务库libcameraservice.so调用。
未完待续
在下一篇中,我会以两条路径来详细介绍Camera HAL的实现:自己依据V4l2规范来实现CameraHardwareInterface; Qualcomm的Camera架构(QualcommCameraHardware和mm-camera/mm-still)。当然,在涉及到Qualcomm私有库部分,为避免不必要的麻烦,我会一笔带过。敬请见谅!
高通Android平台硬件调试之Camera
高通android平台上调试2款camera sensor,一款是OV的5M YUV sensor,支持jpeg out,同时也支持AF,调试比较比较简单,因为别的项目已经在使用了,只是把相关的驱动移植过来就好;另一款是Samsung的一款比较新的3M YUV FF sensor,在最新项目中要使用的,本文以调试该sensor为例,从底层驱动的角度分享一下高通android平台下调试camera的经验,而对于高通平台camera部分的架构以及原理不做过多的介绍。
一、准备工作
从项目中看,在硬件(板子)ready前,软件部分是要准备好的。单独从底层驱动来看,软件部分可以分为2个部分,一个是高通平台相关的,再一个就是sensor部分的,通常的做法就是把sensor相关的设定移植到高通平台的框架之中。这样就需要先拿到sensor的spec以及厂商提供的sensor register setting file。Spec的用途是清楚高通平台和sensor通讯(读写寄存器)的时序以及相关参数设定;而厂商提供的setting file则是在使用camera各个功能(preview、snapshot...)时候需要写入到sensor中的.
本项目中,高通平台为MSM7X27,camera为Samsung 5CA。从spec中知道,该sensor的I2C ID为0x78,I2C的通信采用双字节方式,另外也弄清楚了读写sensor寄存器的规则,从调试角度看这些基本上够用了。另外厂商提供的setting file,其实就是寄存器列表,告诉我们再什么时候将哪些寄存器写入什么值,通常是一个寄存器地址再加上一个寄存器的值,不过Samsung提供的是PC上调试使用的文本,需要自己转换成c语言中的二维数组。从文件中看,寄存器数据可以分为几个部分:初始化、IQ设定(tuning相关)、clk设定、preview设定、snapshot设定,基本上有这几个就够了,其他的比如调节亮度啦、设定特殊效果啦、设置白平衡啦等等都可以自己通过spec来完成。
Sensor部分的东西搞定后,接下来就是修改高通camera部分的驱动了,主要有:
Kernal部分:
1、检查Sensor的电源配置,并修改软件中的设定。本项目中使用2.8/1.8/1.5共3个电源。
2、检查并修改sensor reset设置。注意reset的时间设定,务必和spec中一致,否则会导致sensor无法工作。
3、修改I2C驱动,使用双字节读写的接口,并完成读取sensor ID的接口。这个用来检验I2C通讯是否OK
4、导入寄存器设定,分别在初始化、preview、snapshot等几个部分写入对应的寄存器值。
注意:reset以及写寄存器部分一定要按照spec的规定加入一些delay,否则会导致sensor工作异常
User空间部分:
这个部分主要是根据硬件的规格来配置VFE,如sensor输出数据的格式,接口方式、分辨率大小、同步信号模式等,比较简单,但一定要检查仔细,任何一个地方不对都会导致调试失败。
到这里为止,软件部分的准备已经告一段落了。
二、调试环境准备(板子出来了,但sensor sample还没到位)
首先,测试点的准备。
调试前就需要想好,如果sensor无法工作,要怎么去debug,这就需要去测量一些信号,比如power、reset、I2C、M/P CLK、H/V同步信号、数据信号等,要确保这些信号都可以测量到。
其次要选择软件的调试环境,这里选择在ADB环境中执行高通的mm-qcamera-test程序来调试,相关的trace都可以打印出来。
这样就万事俱备,只欠sensor了。
三、调试(sensor终于拿到了)
将sensor接到板子上,开机后,ADB中运行调试程序,preview画面并没有出来,失败,有点小失望,本来觉得可以一气呵成的,但毕竟这是一个全新的sensor,任何一个地方没有想到位做到位都会导致失败。那就找原因吧。
1、首先从trace得知,I2C已经读到了sensor的ID:0x05CA,这可以说明I2C通讯是没有问题的
2、接着检查Sensor的电源配置,测量了供给sensor的3个电源,都是OK的。
3、测量MCLK,这个是提供给sensor使用的,正常(24MHZ)
4、测量PCLK,这个是sensor输出的,正常(58MHZ,高通上限为96MHZ),和寄存器中配置的一致。
5、测量H/V同步信号,这个是sensor输出的,正常。和FPS和分辨率一致。
6、测量数据信号,这个是sensor输出的,正常。(数据信号,示波器上可以看到)
这样看来,sensor已经在正常工作了,但为何preview画面没有出来呢?继续检查高通这边的设定。
从trace看,高通的VFE已经reset并且start了,但一直接没有输出preview数据,这就奇怪了,sensor明明已经输出了,为什么VFE接收后并没有把数据吐出来呢,难道这个sensor输出的数据VFE无法识别?为了验证这个问题,我在另一块板子上测量了OV sensor输出数据的波形,主要是M/P clk、H/V同步信号,然后再拿来对比,不过并没有发现异常,只是H/V同步信号有所不同,主要高低的占空比不太一致,会不会是这样信号的问题呢?为了进一步验证,我同时测量了H/V 信号和数据信号,这时发现OV sensor输出的数据信号是包在V帧同步信号的低电平中;而Samsung 5CA输出的数据信号是包在V帧同步信号的高电平中,会不会是因为V信号极性设置不对导致VFE没有读取到sensor输出的数据呢?重新检查了一下高通VFE的设定,果然有一个参数是用来设定V信号极性的,这个参数默认是Active Low的,我这边并没有去修改它。接着把这个参数修改为Active High,重新build、download后,开机运行,Ok了,preview画面可以正常显示了。到这里为止sensor的硬件调试可以算作完成了,后续的其他功能也可以慢慢完善了。
FSL调试之Camera
fsl的camera hal层没有实现上层到下层的设置参数的接口,所以需要自己实现。好在从应用到hal层的参数已经弄好,否则工作量就更大了。
参数设置在hal层调用的函数是status_t CameraHal::setParameters(const CameraParameters& params)。在这个函数里实现对每个参数的设置。参数设置主要通过 CameraParameters这个类实现的。通过观察这个类发现,里面有个get()函数,可以分别得到各个参数。如
const char *white_balance = params.get(CameraParameters::KEY_WHITE_BALANCE);这个可以得到目前白平衡的参数即返回值。然后根据返回值判断是哪种情况,如
if (strcmp(white_balance, CameraParameters::WHITE_BALANCE_AUTO) == 0) { //判断为自动白平衡
LOGV("white_balance to ioctl is auto !/n");
ctl.id = V4L2_CID_AUTO_WHITE_BALANCE; //自动白平衡命令,ctl为v4l2_control结构,该结构很有用
ctl.value = 1;
if (ioctl(camera_device, VIDIOC_S_CTRL, &ctl) < 0){ //通过 VIDIOC_S_CTRL把ctl结构体传下去
LOGE("set control failed/n");
//return -1;
}
}else if(strcmp(white_balance, CameraParameters::WHITE_BALANCE_INCANDESCENT) == 0){ //白炽灯模式
LOGV("white_balance to ioctl is incandescent !/n");
ctl.id = V4L2_CID_DO_WHITE_BALANCE; //其它白平衡情况都用该命令
ctl.value = 2; //根据用户自己定义的白平衡模式数目排列
if (ioctl(camera_device, VIDIOC_S_CTRL, &ctl) < 0){ //同样通过 VIDIOC_S_CTRL把ctl结构体传下去,然后在根据value值分情况讨论
LOGE("set control failed/n");
//return -1;
}
}
传到驱动的mxc_v4l2_capture.c文件的mxc_v4l_ioctl中,mxc_v4l_ioctl调用mxc_v4l_do_ioctl,mxc_v4l_do_ioctl对命令的解释如下
/*!
* V4l2 VIDIOC_S_CTRL ioctl
*/
case VIDIOC_S_CTRL: {
pr_debug(" case VIDIOC_S_CTRL/n");
retval = mxc_v4l2_s_ctrl(cam, arg);
break;
}
这样就到了mxc_v4l2_s_ctrl。在mxc_v4l2_s_ctrl通过对ctl.id分情况调用
switch (c->id) {
......
case V4L2_CID_AUTO_WHITE_BALANCE:
ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true);
ret = vidioc_int_s_ctrl(cam->sensor, c); //该函数是v4l2对应ov7670驱动中的s_ctl
ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false);
break;
case V4L2_CID_DO_WHITE_BALANCE:
ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, true, true);
ret = vidioc_int_s_ctrl(cam->sensor, c);
ipu_csi_enable_mclk_if(CSI_MCLK_I2C, cam->csi, false, false);
break;
......
其中vidioc_int_s_ctrl()是v4l2对应ov7670驱动中的 ioctl_s_ctrl,具体代码怎么对应由于篇幅原因就不贴出来。
根据ctl结构体的id分情况去实现即可。
switch (vc->id) {
.....
case V4L2_CID_AUTO_WHITE_BALANCE:
retval = ov7670_autowhitebalance(vc->value);
break;
case V4L2_CID_DO_WHITE_BALANCE:
retval = ov7670_dowhitebalance(vc->value);
break;
......
下面是whitebalance函数的实现
static int ov7670_autowhitebalance(int value)
{
unsigned char v = 0;
int ret;
printk("0v7670_autowhitebalance called/n");
ret = ov7670_read(ov7670_data.i2c_client, REG_COM8, &v);
if (value)
v |= COM8_AWB; //自动白平衡
msleep(10); /* FIXME */
ret += ov7670_write(ov7670_data.i2c_client, 0x01, 0x56);
ret += ov7670_write(ov7670_data.i2c_client, 0x02, 0x44);
ret += ov7670_write(ov7670_data.i2c_client, REG_COM8, v);
return ret;
}
static int ov7670_dowhitebalance(int value)
{
unsigned char v = 0;
int ret;
printk("0v7670_dowhitebalance called value:%d/n",value);
ret = ov7670_read(ov7670_data.i2c_client, REG_COM8, &v);
if (value)
v &= ~COM8_AWB; //关闭自动白平衡
msleep(10); /* FIXME */
ret += ov7670_write(ov7670_data.i2c_client, REG_COM8, v);
if(value == 2) //INCANDESCENCE //这个值就是ctl的value值
{
ret += ov7670_write(ov7670_data.i2c_client, 0x01, 0x8c);
ret += ov7670_write(ov7670_data.i2c_client, 0x02, 0x59);
}else if(value == 3) //FLUORESCENT
{
ret += ov7670_write(ov7670_data.i2c_client, 0x01, 0x7e);
ret += ov7670_write(ov7670_data.i2c_client, 0x02, 0x49);
}else if(value == 4) //DAYLIGHT
{
ret += ov7670_write(ov7670_data.i2c_client, 0x01, 0x52);
ret += ov7670_write(ov7670_data.i2c_client, 0x02, 0x66);
}
return ret;
}
其中函数中ox01、0x02分别是蓝红通道的增益的寄存器。
上面是白平衡从hal层最终到sensor的参数设置过程。其它如色彩效果、取景模式等都是同样的过程。
取景模式根据具体的情况如夜间模式等设定具体的寄存器即可
色彩效果主要通过设置uv的值实现的
转自:http://blog.csdn.net/linphusen/article/details/6385236