攝像頭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;

相關文章
相關標籤/搜索