摄像头bug查找工做总结


近期花了很长时间在libcamera中查找和解决一个bug。下面将这段时间中的工做过程,以及对camera的认识总结以下:java

首先是问题的发生,在UM2801中,摄像头的代码已经基本实现,而且相应功能也已经进行了完善。可是,在屡次测试使用中发现,摄像头在preview模式下的显示会不常常性的出现屏幕分红两段的状况,上边一段为正常数据的垂直方向压缩图形(压缩为一半),下边一段则为上次开启摄像头时的旧数据的一半。因为状况不明,而且问题是随机发生,难以分析问题的出现究竟是在上层仍是在驱动层,因此采起从分排除的办法进行分析解决。c++


一. libcamera的分析:vim

查找bug的方法是从整个框架的中间段进行分割查找。因此我首先从libcamera文件夹中的相关代码入手进行分析。 libcamera是上层调用摄像头以及设定摄像头功能模式的接口,是c++的最底端的代码。java层经过JNI接口调用C++层。C++中又分为服务端和客户端两个部分,最后调用到了libcamera中。Libcamera中有Seccamera.cpp, Seccamera.h, SecCameraHWInterface.cpp,  SecCameraHWInterface.h四个文件。在SecCameraHWInterface.cpp中有Preview,TakePicture,Record三种模式,开启摄像头默认进入Preview模式,StartPreview函数会在进行相应格式和参数设置后建立一个m_previewThreadFunc()线程,在线程中进行图像预览的处理流程。而takePicture也采用了类似的建立线程处理的模式。而他们的主要功能的函数实现则在Seccamera.cpp文件中。在Seccamera.cpp中能够找到相应的设置格式,参数,以及不一样模式的处理过程当中的处理函数的实现函数。在这些函数中,他们经过ioctrl的方式控制和调用驱动层来实现相应功能。安全

经过代码分析,我发如今libcamera中并无直接处理图像数据,它只是将参数和格式设置经过接口传递到驱动层,并将底层的buffer中的物理地址进行传递到上层的方式进行。具体的数据处理函数则在底层驱动中实现,这样既减小的处理时间,也提升了安全性。为了查看图像数据,我在Seccamera.cpp中经过mmap函数将底层的buffer映射到了上层的m_buffer_c中,而后再将图片数据取出查看。这里的相关代码已经有一部分代码已经有实现的现成代码,因此能够直接使用。架构

起初,我想经过对TakePicture中的功能进行改写的方式进行捕捉数据,由于这样方便取出单张图片数据。所以,我将TakePicture中的大部分代码注释掉了,而后模仿Preview模式中的方式进行改写。但愿在再也不重设camera寄存器的状况下,达到取到Preview模式下的图片的效果。可是,因为修改了TakePicture的处理流程中的处理流程,因此不能正确向上层返回,出现死机情况。而若是要正确返回数据需重设数据格式,从新设置摄像头格式后又不能取出须要查看的Preview格式的图片。最后只能抓取TakePicture模式下的JPG和yuyv文件。经过JPG图像和yuyv数据,只能确认在TakePicture模式下的数据为正确数据,不能作出最后判断。框架

因而,采起从Preview模式下取出数据的方式。经过分析Preview模式的处理流程,发如今这种模式下,它是一个不断进行的线程,在m_previewThreadFunc()线程中不断向上层传递新的数据的物理地址,LCD则才能经过地址调用下层获取新的图像进行显示。因此为了取出单张图片数据,我想经过建立一个全局变量,而后让变量的值知足某种条件来做为抓取数据的条件的方式来完成。可是,编译显示,在libcamera中不容许在其中建立其它的全局变量。因此后来想到了系统time(NULL)函数时间变量,它很好的解决了这个问题。可是,因为要抓取数据,它的数据处理的流程做了相应的改变,在运行中会出现与Preview模式的正常进行产生冲突的问题,抓取数据会出现死机,或者在不暂停数据传输的状况下抓取的数据错乱的状况。为了解决与Preview的正常进行产生冲突的问题。我有分析了TakePicture模式的处理流程,认真分析了预览模式下的实现流程,成功在getPreview函数中找到了安插捕捉图像的代码的地方,正确取出了Preview模式下的图像。因为抓取到的图像是samsung的NV12T模式的数据文件,我写了一个将NV12T图片数据转为RGB32的数据(个人电脑的framebuffer支持格式为RGB32),并将它输出到/dev/fb0中显示的函数。最后,成功查看到了Preview中libcamera的framebuffer原始数据。(归档文件文件夹中nv12t_rgb.c为nv12t转rgb32并输出的函数,yuyv_rgb.c为yuyv422转rgb32并输出的函数)。ide

经过抓取到图像后,能够确认图像也是在Preview模式下显示的画面同样的分段式的错误图像。所以,能够确认错误的图像最初应该产生在驱动层。所以,转入驱动层代码,开始进行分析。函数



二.驱动层cameara相关函数分析(fimc与v4l2)测试

在对驱动层camera相关代码进行阅读分析后。我了解到,在fimc_dev.c函数中注册了三个fimc控制器(fimc0,fimc1,fimc2),而且都注册为V4L2设备子系统(device-subdev)。不一样的fimc控制的地方不一样,fimc0实现数据从sensor中经过scaler由yuyv422格式转换成nv12t格式而且经过DMA方式传送到fimc0的memory中。而fimc1则从这个memory中取出数据,经过fimc1的scaler将数据转换成rgb32格式,并将它存储到LCD的memory中。fimc0的工做部分在fimc_capture.c中实现,fimc1中的工做部分则在fimc_output.c中实现。他们经过V4L2架构为上层提供接口,与上层链接的接口为统一接口,在v4l2_ioctrl.c中实现,经过不一样的type,链接不一样的接口和实现函数,若是type为V4L2_BUF_TYPE_VIDEO_CAPTURE,则链接fimc_capture.c的实现函数,若是type为V4L2_BUF_TYPE_VIDEO_OUTPUT,则链接fimc_capture.c的实现函数。而后经过fimc_v4l2.c中的fimc_v4l2_ops结构体,将v4l2的接口函数与fimc的实现函数链接,最后链接fimc_capture.c和fimc_output.c不一样的实现函数。线程

在fimc_capture.c函数中,实现了libcamera的Seccamera.cpp中的调用函数,分配了四个用于存储图像数据的buffer,按照nv12t的格式又将数据分别存储为Y数据区和cbcr数据区。这里提供了PINGPONG存储模式和直接存储模式,而采用的是直接存储模式(上图为PINGPONG模式)。数据的传输采用的是DMA直接内存访问方式,在fimc_streamon_capture.c中首先进行数据源(src)和的格式和大小的设置,数据转换后的格式和大小以及转换方式的设置,而后开启数据接收。

fimc与sensor的链接则经过v4l2系统的subdev_call函数调用接口。在摄像头驱动gt2005.c中v4l2_subdev_ops结构体中链接了最终的实现函数。分为gt2005_video_ops和gt2005_core_ops两个部分。gt2005_core_ops为初始化,重设,控制相关实现。gt2005_video_ops为设置格式,大小等相关的实现。最后经过I2C实现寄存器的配置写入和读出,将设置参数写入摄像头寄存器中。

在分析驱动层代码的大体结构和功能后,我首先在streamon_capture中实现了将preview开启钱buffer中的旧数据擦除的功能。经过phys_to_virt函数将buffer的首地址由物理地址转为虚拟地址,而后擦除了四个buffer大小的数据空间。因而,因为没有旧的数据填充的状况下,在预览模式下能够看到在出现错误图像时的下半部旧数据图像变成黑屏了,这也说明了数据在传进buffer以前已是一半的错误数据,即数据在进入buffer以前已是有问题的了。因而我在streamon_capture的功能开启以后的fimc_hwset_enable_capture函数中将fimc的全部的寄存器配置状况经过printk打印出来,一一与datasheet中的配置说明进行比对。可是这次没有发现问题,这是对代码的调用状况还不彻底理顺,以及轻易相信数据是streamon_capture的输出信息的缘故。因此,因为打印出来的寄存器配置正确,我将方向转向了buffer中的数据。因而我又在STREAM_PAUSE命令发生后,将buffer[0]的部分数据打印在终端,因为STREAM_PAUSE是在我在上层取出nv12t数据时须要执行调用的命令。因此,这时取出数据与我上层取出的数据达到了同步,我将终端打印出来的16进制的数据与与获取的用vim查看的nv12t的16进制数据进行逐个比对,结果开始因为我将大小搞错,取出的数据不全,比对不完整,判断数据没有发生问题。后来经提醒后改正数据大小,正确结果为数据与上层取出的数据同样,说明错误发生在数据传入buffer以前,这也更加证明了擦除旧数据后显示出半张压缩图像的判断。因而,因为以前认定寄存器配置没有出错,把方向转向了在sensor的配置和时钟的配置。在更换了一个摄像头进行实验后,发现仍然会出现这样的状况,说明不是摄像头的问题,转而指向了时钟出现问题。

在认为多是时钟的问题后,simon用示波器对时钟进行了测量。经过计算,认为时钟配置正常,没有出现问题,这样便发现本身的方向发生了错误,前面可能产生了误判。

因而我只能往回从新推翻进行查找,在再次确定上层取出的数据能够认定排除上层出错后,我再次分析了fimc中的代码和框架。而且将全部可能产生错误图像效果的状况的变量值所有输出到终端,进行一一比对。没有发现问题。可是,在对代码的梳理和对框架的理解中,我发现,在preview模式进行时,全部的fimc同时在工做中。而且因为他们是同时注册的v4l2子系统,因此,他们的许多代码都是合并在一块儿,共同使用的。他们经过不一样的hw_ver参数和type进行链接不一样的设置,实现各自不一样的功能。也就是说,终端打印出来的信息是capture模式和output模式下的两种不一样的打印信息的总合。同时发现fimc_regs.c中fimc寄存器配置的相关函数也同时被fimc0和fimc1调用着,分别在实现capture和output的功能。因此,我上次查看的寄存器配置信息有误,打印信息并不必定是capture的配置参数。

因而,我一样用printk函数的方式区分了capture方向的函数调用fimc_hwset_enable_capture函数时的打印信息和output方向的函数调用时打印信息,并取出了capture调用时的fimc0的配置状况进行了比对。这次,我发现,在出现错误图像和正确图像的屡次比对中,S3C_CISCCTRL寄存器的信息值正好是两个不一样的值,该寄存器的第25为的设置正好相反。datasheet显示,该位配置的功能为隔行读取。就此,确认错误的发生是在该寄存器的配置上发生的问题,发现了问题的发生处。

为了彻底确认,我强制关闭了发生错误的配置(隔行读取)功能。通过屡次实验,确认问题发生在此处。


三.问题缘由

虽然可以确认问题发生的地方。可是,真正触发问题的缘由并无找到。并且,强制关闭驱动层的功能设置不是正确的解决问题的办法。因此,我向上开始查找问题发生的根本缘由。在出现错误图片的地方,是因为开启了隔行读取,开启了V4L2_FIELD_INTERLACED功能,而这个功能则是由结构体v4l2_pix_format的field参数控制的,通过对代码的分析查找,这个结构体是由上层传递赋值的。在fimc_capture.c中fimc_s_fmt_vid_capturea函数经过V4L2接口将v4l2_format结构体复制给了v4l2_pix_format结构体,实现的数据传递。

最后转向,c++层libcamera中的Seccamera.cpp中的fimc_v4l2_s_fmt函数。在这个函数中,定义了v4l2_pix_format为pixfmt,可是它结构体中的全部元素,只有field没有初始化,在初始化完其它元素后,将它赋值给v4l2_format结构体,而且经过v4l2传递给了驱动层。由此致使了一个没有初始化的变量传递到了底层。因为是局部变量,在使用它时,它读取了申请的空间的的旧数据做为数据使用,致使了每次读取的数据都不同。所以,在当读取到的数据等于隔行读取的值时(4),触发了设置隔行读取寄存器位的状况。因而出现了摄像头出现随即图像分段的错误状况。至此,问题根源发现。

问题解决办法:在fimc_v4l2_s_fmt函数中对field进行初始化为: pixfmt.field = V4L2_FIELD_ANY;

通过反复测试证明,问题解决。

相关文章
相关标签/搜索