1. 程式人生 > 實用技巧 >解讀Android 4.0 Camera原生應用程式的設計思路

解讀Android 4.0 Camera原生應用程式的設計思路

>>> hot3.png

1. 設定攝像頭方向

2. 開啟執行緒與預覽執行緒

3. 設定引數

4. Camera外設按鍵

5. 自動對焦與觸控對焦

6. 拍照

7. 人臉檢測

8. 位置管理

9. 旋轉管理

10. 變焦

11. 錄影



Camera的架構為典型的C/S架構,Client端,使用者的行為,是為應用程式程序,Server端,裝置的功能,是為Camera服務守護進 程,客戶端程序承載使用者的需求,由Binder程序間通訊送往服務端實現裝置的功能,服務端由回撥函式和訊息機制反饋資料返還給使用者。

ps檢視程序,類似 com.android.camera是為客戶端Camera程序,/system/bin/mediaserver是為服務端守護程序,由系統啟動時開啟。


1. 設定攝像頭方向

Framework框架層的Camera物件(camera.java)裡有一個類class CameraInfo,裡面存放了兩個公有成員變數facing和orientation,即我們要討論的前後置和方向。 程式第一次初始化時initializeFirstTime(),通過getCameraInfo()得到前後置和方向的資訊,客戶端傳送請求 getCameraInfo()詢問服務端,服務端呼叫抽象層拿資料,抽象層參考底層camera sensor驅動的資料facing和orientation,這兩個值在驅動裡是寫死的,不能由使用者改變,camera程式啟動以後就把它們作為全域性變 量存放起來。

1.1 前置與後置

後置back camera背對手機螢幕,朝外,畫素高,前置front camera,面對自己,朝內,畫素低。

1.2 方向

攝像頭模組有長邊和短邊,比如採集影象的比例為4:3,那麼4為長邊,3為短邊。裝置螢幕也有長邊和短邊,理論上,攝像頭模組的長邊不能與螢幕的長邊垂直,至於為什麼呢,我語文水平太差,沒辦法很好地表達出來...總之目的就是為了顯示效果。


2 開啟執行緒與預覽執行緒

onCreate()裡會先後開啟CameraOpenThread和CameraPreviewThread。

開啟camera還需要執行緒?CameraOpenThread名為開啟,實為C/S connect連線服務端,binder程序間通訊,開銷較大。預覽執行緒必須在開啟執行緒完成以後執行,它貫穿始終直到程序消亡為止,整個預覽過程相對復 雜,在抽象層和底層驅動實現,概括講,預覽執行緒再開啟兩個執行緒,一個拿sensor的frame,一個送往framebuffer經 surfaceflinger顯示出來。


3 設定引數

預覽拍照錄像之前,使用者需要設定很多引數,比如閃光,白平衡,場景,對比度等。

程式裡這些引數儲存在SharedPreferences共享優選項和Parameters兩個地方,Preferences包含 Parameters,開啟程式讀取優選項引數,關閉程式儲存優選項引數,考慮到使用者經常會調整引數,引入Parameters來儲存從開啟以後到關閉以 前這個中間過程的引數變數,Parameters的鍵值由抽象層根據硬體sensor的能力決定。


4. Camera外設按鍵

Manifest裡註冊broadcast receiver,

<receiver android:name="com.android.camera.CameraButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.CAMERA_BUTTON"/>
</intent-filter>
</receiver>

有些手機上有camera按鍵,使用者按下按鍵,android輸入系統有兩種實現方法,

1)傳送廣播CAMERA_BUTTON,收到廣播後開啟主Activity。

2)上報鍵值KEYCODE_CAMERA,程式收到訊息,可自定義實現功能,比如拍照。

public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CAMERA:
if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
onShutterButtonClick();
}
return true;


5. 自動對焦與觸控對焦

外界事物由光線經凸透鏡聚焦到sensor上成像,camera模組開啟馬達前後平移鏡頭取得最佳成像效果,這個過程稱之為對焦。

5.1 自動對焦

1) camera模組會因感光強度變化而自動開啟對焦,驅動控制。

2) 使用者長按快門,軟體控制自動對焦。

3) 使用者按下快門拍照,拍攝前自動對焦。

程式裡,Camera物件實現類ShutterButton的介面OnShutterButtonListener裡的方法 onShutterButtonFocus(boolean pressed)和onShutterButtonClick(),後者是拍照,下節討論,先看 onShutterButtonFocus(boolean pressed),這個pressed判斷是否為一次有效的長按,如果是的話,執行autoFocus(),這個autoFocus()也是Camera 物件實現類FocusManager的介面Listener裡的方法,由binder交給camera service,最後在底層驅動實現自動對焦。

5.2 觸控對焦

自動對焦的對焦區域位於螢幕正中,使用者也可觸控特定區域對焦。

Camera物件實現類View的介面OnTouchListener裡的方法onTouch(),輸入系統上報MotionEvent的xy座標,儲存在Parameters,執行autoFocus(),抽象層讀取Paramters的觸控點座標,實現區域對焦。


6. 拍照

拍照分四步,對焦,拍照,接收圖片,儲存圖片。

mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,
mPostViewPictureCallback, new JpegPictureCallback(loc));

需要理解四個回撥函式,參考上一篇文章。

1)對焦

拍照前如果已經區域對焦,則取消自動對焦,反之,開啟一次自動對焦。對焦完成後,底層傳送對焦成功與否的訊息給camera對 象,FocusManager把狀態mState儲存起來,如果正在對焦未完成(mState == STATE_FOCUSING)則不可拍照,直到對焦完畢。

2)拍照

onShutterButtonClick() -> doSnap() -> capture() -> takePicture(),具體實現在抽象層和底層驅動,實質就是拿一張預覽的影象,抽象層讀取拍照時的Parameters引數配置,包括使用者選擇的 照片大小。

3)接收圖片

通過回撥,由底層傳送圖片給camera物件。

RawPictureCallback,得到原始圖片,需要軟體壓縮Jpeg。(YUV轉Jpeg)

JpegPictureCallback,直接得到Jpeg圖片,需要硬體壓縮Jpeg。

PostViewPictureCallback,拍完後預覽圖片。

4)儲存圖片

交由執行緒ImageSaver儲存圖片和生成thumbnails。

預設路徑位於/mnt/sdcard/DCIM/Camera/


7. 人臉檢測

人臉檢測可以軟體實現,可以硬體實現,軟體實現增加CPU開銷,硬體實現增大耗電,鼓勵硬體實現...

上層Camera物件實現了 framework層Camera的介面FaceDetectionListener的方法onFaceDetection(Face[] faces, Camera camera),回撥機制同上,硬體sensor識別臉部資訊,傳送face給camera物件,framework定義face的特徵,比如眼睛,嘴 巴,上層儲存mFaces資料,更新UI。


8. 位置管理

位置管理LocationManager用來記錄拍攝圖片的GPS位置資訊(經維度),存入JPEG頭部插入的Exif裡。

使用者在選單“相機設定”裡的"儲存所在位置"選擇開啟(前提是GPS已開啟),螢幕左上方會出現一個GPS圖示,表示現在可以記錄GPS資訊了。

程式裡,Camera物件實現了位置管理監聽器LocationManager.Listener的介面showGpsOnScreenIndicator()和hideGpsOnScreenIndicator(),顯示或者隱藏GPS圖示。

程式第一次初始化時initializeFirstTime(),通過讀取優選項Preference得到bool值 recordLocation,判斷是否需要記錄GPS資訊,如果需要,在拍照capture()裡呼叫LocationManager的方法得到 Location loc,並將其存入Exif。


9. 旋轉管理

假設一臺手機,camera正常安裝,豎直方向作為預設方向(orientation == 0)拍攝照片,即拍攝“肖像照”(portrait),得到的照片顯示在螢幕上也是豎直方向。

如果把手機旋轉90度水平過來拍攝“山水照”(landscape),對於camera sensor來說,沒有旋轉的概念,所以軟體上要把圖片旋轉90度回來。


軟體上,需要藉助方向監聽器隨時更新方向資訊,並儲存在Parameters裡,抽象層實現拍照功能時從Parameters裡讀取方向。

具體的,camera物件內部類MyOrientationEventListener的方法onOrientationChanged()儲存方 向orientation的值,MyOrientationEventListener繼承 OrientationEventListener,OrientationEventListener的onSensorChanged()把從 sensorManager拿到的xyz座標轉換成方向。

程式啟動,註冊sensor監聽器並使能,sensorManager不斷上報底層sensor資料,通過訊息機制傳送到camera物件,後者計 算座標資料得到方向orientation的值(實際外包給orientationListener完成),最後儲存Parameters。


10. 變焦

使用者拖動Zoom橫條可放大縮小預覽畫面連續變焦,另一種所謂狀態變焦,其原理是一樣的。

camera物件的內部類ZoomChangeListener實現ZoomControl的方法,實質是把變焦的任務全權交給 ZoomControl。ZoomControl監聽處理使用者的觸控事件dispatchTouchEvent(),用來得到並處理變焦倍數 mListener.onZoomValueChanged(index),它由mCameraDevice.startSmoothZoom()通過 binder交給camera service,camera service再通過sendComand命令機制交給抽象層實現變焦,抽象層開啟變焦執行緒,變焦改變預覽,通過回撥機制傳送訊息 CAMERA_MSG_ZOOM把變焦倍數返還給camera物件,最終camera物件收到訊息 後,ZoomListener.onZoomChange()把變焦倍數儲存到Parameters.


11. 錄影

ModePicker負責切換模式,一共有三種模式,普通模式,錄影模式和全景模式,Manifest裡依次宣告這三個activity。

切換模式,銷燬原有activity,開啟新activity,伴隨關閉preview,重啟preview,儲存配置,讀取配置,開銷很大。

錄影VideoCamera.java同預覽Camera.java的思路類似,按下錄影按鈕,程式監聽使用者事件,開啟錄影,錄影交給MediaRecoder,StagefrightRecorder負責。

android video linux 資料結構 框架 影象處理


1. Overview

1.1 物理架構

1.2 Android架構

2. CameraService

3. HAL

4.Overlay

5. Video for Linux

1. Overview

本文以Freescale IMX為例剖析camera攝像頭的系統架構。

1.1 物理架構

硬體方面,camera系統分為主控制器和攝像頭裝置,功能上主要有preview預覽,takePicture拍照和recording錄影。

1) IPU - Image Process Unit 影象處理單元,用於控制攝像機和顯示屏。

2)影象採集 - 由camera採集的影象資料資訊通過IPU的CSI介面控制。

3)DMA對映到記憶體 - IPU將採集到得資料通過DMA對映到一段記憶體。

4)佇列機制 - 為了更高效地傳送資料,將記憶體中的資料取出加入一佇列,並傳送到另一佇列。

5)視訊輸出 - 將視訊資料從佇列中取出,通過IPU控制這段獨立視訊記憶體,最終將視訊顯示出來。

1.2 Android架構

Android的camera系統架構自上而下分別為應用層-框架層-硬體抽象層-linux驅動層。

1)APP - Framework

應用層與java框架層的間主要由Binder機制進行通訊。

系統初始化時會開啟一個CameraService的守護程序,為上層應用提供camera對的功能介面。

2) Framework - HAL

框架層與硬體抽象層間通過回撥函式傳遞資料。

3) Overlay

Overlay層由ServiceFlinger和OverlayHal組成,實現視訊輸出功能,只有camera框架層或者視訊框架層能呼叫它,上層無法直接呼叫。

4) HAL - driver

抽象層位於使用者空間,通過系統呼叫如open(),read(),ioctl()等與核心空間進行資料傳遞。

2 CameraService

Camera的主要功能有取景Preview,拍照takePicture和攝影Recording,下文以取景為例,剖析camera系統架構。

要實現取景Preview功能,主要須呼叫CameraService::Client::startPreview()和 CameraService::Client::setOverlay(),前者通過mHardware->startPreview();呼叫 cameraHal硬體抽象層以實現取景的整個流程,後者通過mSurface->createOverlay();呼叫 surfaceFlinger層建立overlay_object物件。

3HAL

startPreview主要完成三項任務,配置圖象,配置記憶體,開啟兩個存取buf佇列的執行緒。

1) cameraPreviewConfig()配置預覽圖象引數

CameraOpen() -通過開啟裝置節點/dev/video0得以由系統介面與裝置驅動互動。

S_FMT - ioctl()的指令,設定圖象畫素格式,將資料由硬體抽象層傳遞至Linux驅動,這裡也就是v4l2。

G_FMT - 得到圖象畫素格式,將資料由底層驅動v4l2返回至硬體抽象層。

S_PARM - 設定模式的指令,這個指令傳到底層後,將會實現對camera硬體的控制。

2) cameraPreviewStart()開啟預覽,實際上配置了記憶體

REQBUFS - 申請記憶體,通過dma_alloc_coherent()為camera申請一端連續的dma記憶體。

QUERYBUF - 詢問記憶體,將申請到記憶體的實體地址,虛擬地址等資料從核心空間傳遞到使用者空間。

QBUF - 加入佇列,將通過詢問得到的buf加入一個佇列。

3) PreviewShowFrameThread()和PreviewShowFrameThread()

PreviewCaptureFrameThread()捕捉一幀資料的執行緒,通過DQBUF,從佇列中取出一個buf資料,這裡,一個buf即一幀資料即一張圖片。注意,如果camera沒有采集到圖片,這個執行緒會在DQBUF阻塞。

PreviewShowFrameThread()顯示一幀資料的執行緒。

mDataCb() - 回撥函式,將採集到的圖象資料傳回CameraService,再由CameraService傳遞給上層應用。

mOverlay->dequeueBuffer() - 呼叫Overlay層,從Overlay層得到一個空閒的overlaybuffer,將圖象資料拷貝到這個buffer裡。至於這個buffer後續的工作,即視訊輸出,則交給了Overlay去完成。

QUERYBUF& QBUF - 由於已經從佇列裡取出了一個buf,需要再詢問並加入另一個buf到佇列裡。

4) Overlay

CameraService::Client::startPreview()完成mHardware->startPreview();後 便去執行CameraService::Client::setOverlay(),如果沒有任何overlay,則建立一個新的,通過 mHardware->setOverlay(new Overlay(mOverlayRef))呼叫到SurfaceFlinger層,再由 overlay_dev->createOverlay();呼叫到overlay的硬體抽象層,抽象層建立並初始化overlay物件,與 cameraHal類似,通過ioctl()指令與底層v4l2通訊,配置視訊引數和記憶體空間。隨後開啟一個overlay執行緒,用於存取佇列中的視訊數 據。

注意,SurfaceFlinger裡也會開啟一個處理overlay的surfaceFlinger執行緒,用於等待使用者事件,作相應的overlay控制。

5 Video for Linux

v4l2 - video for linux 2是linux為視訊驅動抽象出的一層統一的介面,資料結構如下,

v4l2作為master主裝置由(*attach)與camera從裝置進行繫結。

初始化函式probe()如下,

1) init_camera_struct()初始化v4l2主裝置的資料結構,實現open(), read(), ioctl(), mmap()等操作。

2) v4l2_int_device_register(),註冊v4l2主裝置,繫結camera從裝置。

3) video_register_device()註冊linux video裝置,建立/dev/video0裝置節點。


轉載於:https://my.oschina.net/jerikc/blog/90791