1. 程式人生 > >高通Android平臺硬體除錯之Camera篇

高通Android平臺硬體除錯之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呼叫。

高通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的值實現的