1.概述
Android自帶camera API有兩種,Camera1和Camera2。其中,
Camera1始於Android最初版本,僅爲java層實現。
Camera2則有兩種實現。
Java層Camera2始於andorid 5.0。
Native Camera2則開始於android7.0(API level24)。
本文將對Native Camera2的使用(僅預覽功能)進行總結。總結將針對於如何使用和邏輯流程,不糾結於代碼,示例可參照google ndk samples(可從github查找)。
2.使用流程
通過Camera2實現預覽,需要三步。
第一步,開啓設備。
第二步,發送請求。
第三步,接收視頻數據,並轉換爲圖像格式顯示。
本文的講解基於UML Component圖,描述Camera2核心對象及API的調用生成關係。受版面所限,拆爲三圖。如縮略圖不清晰,可點擊放大。
Component圖中,模塊爲函數,節點爲對象。橫向(左或右)箭頭爲資源釋放操作調用。調用過程與順序/時機無關。
2.1開啓設備
該階段目的,在於獲取設備指針,以及獲取設備相應的信息,例如分別率,旋轉角度等。
通過系統所提供的ACameraManager_create方法,可獲得ACameraManager指針。
進而通過ACameraManager_getCameraIdList方法,可從ACameraManager中獲取所有Camera的ID。
獲取ID後,輔助以屬性TAG,可以通過ACameraMetadata_getConstEntry方法獲取對應Camera的屬性。
例如,傳入ACAMERA_SENSOR_ORIENTATION的TAG,可以獲取Camera的旋轉角度;ACAMERA_LENS_FACING,可以判斷是前置(ACAMERA_LENS_FACING_FRONT)或後置(ACAMERA_LENS_FACING_BACK)攝像頭;而ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,可以獲得攝像頭的分辨率。
其它TAG,可參照NdkCameraMetadataTags.h。
此處需要注意的是,
(1)ACameraMetadata_getConstEntry方法返回的是一個ACameraMetadata_const_entry結構體,需根據type的值讀取data。
typedef struct ACameraMetadata_const_entry {
/**
* The tag identifying the entry.
* See {@link NdkCameraMetadataTags.h} for more details. </p>
*/
uint32_t tag;
/**
* The data type of this metadata entry.
*/
uint8_t type;
/**
* Count of elements (NOT count of bytes) in this metadata entry.
*/
uint32_t count;
/**
* Pointer to the data held in this metadata entry.
*/
union {
const uint8_t *u8;
const int32_t *i32;
const float *f;
const int64_t *i64;
const double *d;
const ACameraMetadata_rational* r;
} data;
} ACameraMetadata_const_entry;
(2)不是所有API LEVEL>=24的android os都支持Camera2,所以有時通過ACameraManager_getCameraIdList方法獲取都ID數量可能爲0。
在獲取到Camera ID之後,通過ACameraManager_openCamera方法,可以開啓Camera,獲得一個ACameraDevice對象指針。
該指針將在發送請求階段使用。在調用ACameraManager_openCamera方法時,ACameraDevice_StateCallbacks可以爲null。
2.2發送請求
在獲取設備(指針)後,需要通過不斷的發送請求(ACaptureRequest),以得到圖像信息。沒有請求,便沒有數據。沒有數據,便無法生成之後的預覽圖像。因此,該步驟是最關鍵的一步,也是最爲複雜的一步。
對於整個發送請求(ACaptureRequest)過程,可以理解爲,
(1)生成一個向ACameraDevice發送的,類型爲ACameraDevice_request_template的ACaptureRequest。
(2)該ACaptureRequest被不斷重複發送。
(3)ACaptureRequest所返回的響應數據,由ACaptureSessionOutputContainer中的ACaptureSessionOutput接收。
(4)ACaptureRequest通過ACameraOutputTarget,將ACaptureSessionOutput與AImageReader綁定。
在預覽階段,AImageReader將用於數據的讀取。
不斷的發送請求,是如何完成的?
只需要調用ACameraCaptureSession_setRepeatingRequest方法即可。
camera_status_t ACameraCaptureSession_setRepeatingRequest(
ACameraCaptureSession* session,
/*optional*/ACameraCaptureSession_captureCallbacks* callbacks,
int numRequests, ACaptureRequest** requests,
/*optional*/int* captureSequenceId);
而當需要停止發送請求時,調用ACameraCaptureSession_stopRepeating方法即可。
camera_status_t ACameraCaptureSession_stopRepeating(ACameraCaptureSession* session);
題外話:預覽需要不斷的發送請求,獲取實時畫面。而拍照就不同了,僅需要發送單個(一次)請求即可。
ACameraCaptureSession_setRepeatingRequest方法中核心的傳參有三個,ACameraCaptureSession,ACaptureRequest和numRequests。接下來,開始詳細講述發送請求的過程。
2.2.1創建請求
requests,是一個ACaptureRequest列表,numRequests當然指的是ACaptureRequest的數量了。可見,可以同時發送多個請求。但對於單一的預覽,一個請求足已。
那麼,ACaptureRequest是向誰發出的請求?
camera_status_t ACameraDevice_createCaptureRequest(
const ACameraDevice* device, ACameraDevice_request_template templateId,
/*out*/ACaptureRequest** request);
ACaptureRequest由ACameraDevice生成,因此,是向ACameraDevice發送的請求。ACameraDevice在開啓設備階段已被獲取。
請求的類型,由枚舉ACameraDevice_request_template決定。參見NdkCameraDevice.h。對於預覽,需選擇TEMPLATE_PREVIEW。
因此,創建請求的過程,可以理解爲:
生成一個向ACameraDevice發送的,類型爲ACameraDevice_request_template的請求。
2.2.2建立會話
ACameraCaptureSession是一個會話。它也是由ACameraDevice生成,同時還綁定來一個ACaptureSessionOutputContainer。
camera_status_t ACameraDevice_createCaptureSession(
ACameraDevice* device,
const ACaptureSessionOutputContainer* outputs,
const ACameraCaptureSession_stateCallbacks* callbacks,
/*out*/ACameraCaptureSession** session);
ACaptureSessionOutputContainer,通過ACaptureSessionOutputContainer_create方法創建。
camera_status_t ACaptureSessionOutputContainer_create(
/*out*/ACaptureSessionOutputContainer** container);
ACaptureSessionOutputContainer是一個可以容納多個ACaptureSessionOutput的集合。
ACaptureSessionOutput是一個輸出流,用於接收預覽所用的圖像數據。該對象由AImageReader間接創建。關於AImageReader,見圖片讀取章節。間接創建步驟如下
(1)AImageReader通過AImageReader_getWindow方法,獲得一個ANativeWindow對象(注意,該ANativeWindow對象與此後預覽圖像章節所述的ANativeWindow對象,並非同一對象)。
(2)ANativeWindow對象通過ACaptureSessionOutput_create方法,獲得一個ACaptureSessionOutput對象。
因此,一個ACameraCaptureSession包含了以下信息:
(1)與哪個ACameraDevice進行會話。
(2)用哪些ACaptureSessionOutput來接收數據。
而ACameraCaptureSession又通過ACameraCaptureSession_setRepeatingRequest方法,與ACaptureRequest建立關聯。
因此,ACaptureRequest與ACaptureSessionOutput,ACameraDevice都是有關聯的。
那麼ACaptureRequest如何確定數據格式,大小?AImageReader即爲關鍵。
ACaptureRequest需要與AImageReader建立關聯。
2.2.3接收圖片數據
在創建AImageReader時,需要提供四個參數:圖像寬度,高度,圖像格式,最大圖片存儲量。
media_status_t AImageReader_new(
int32_t width, int32_t height, int32_t format, int32_t maxImages,
/*out*/AImageReader** reader);
其中,圖像寬度和高度的單位爲像素。
圖像格式,可參見枚舉類型AIMAGE_FORMATS(NdkImage.h)。由於預覽時,前端爲視頻格式,此處應使用AIMAGE_FORMAT_YUV_420_888。
最大圖片存儲量maxImages,需要特別注意。若數量少(例如1,2),則會在預覽時,產生卡頓。而數量多,則會引起延時。例如 數量40,寬高爲640*480時,將引起3至4秒的延時。
AImageReader如何與ACaptureRequest建立關聯呢?也是通過間接的途徑。
(1)AImageReader通過AImageReader_getWindow方法,獲得一個ANativeWindow對象(注意,該ANativeWindow對象與此後預覽圖像章節所述的ANativeWindow對象,並非同一對象)。
(2)ANativeWindow對象通過ACameraOutputTarget_create方法,創建一個ACameraOutputTarget對象。
(3)通過ACaptureRequest_addTarget方法,將ACameraOutputTarget與AImageReader建立關聯。
至此,ACaptureRequest可以通過AImageReader確定數據格式,高寬,及最大數量,並將圖像數據寫入AImageReader所對應的ACaptureSessionOutput中。
接下來,只剩下如何顯示的問題了。
2.3預覽圖像
2.3.1關鍵因素
AImageReader,ANativeWindow和ANativeWindow_Buffer是預覽階段的關鍵因素。
其中AImageReader起到一個承上啓下的作用。
在發送請求階段,綁定請求(ACaptureRequest)和輸出(ACaptureSessionOutput),以獲取實時視頻數據。
在預覽階段,作爲數據源,用於數據轉換(YUV數據轉ARGB等圖像數據類型)。
ANativeWindow做爲畫布(由外部傳入,並非由AImageReader,與發送請求階段的ANativeWindow對象不同),用於顯示預覽圖像。
ANativeWindow_Buffer則可以看作是畫布(ANativeWindow)的顯存。當數據以某一圖片格式寫入ANativeWindow_Buffer時,圖像便呈現在畫布(ANativeWindow)上。
2.3.2獲取圖片
在ACaptureRequest被髮出後,將會有AImage不斷的存入AImageReader對應的緩衝區。緩衝區內存儲的圖片數量上限,在創建AImageReader時已被設定。
題外話:
對於性能較低的設備,當緩衝區內的圖片上限過大,則會出現預覽延時。因爲有太多的舊圖片在排隊等待處理。
而緩衝區內圖片上限過小,不論設備的性能好壞,則都會出現預覽卡頓,因爲沒有連續的圖片可以被連續的畫到畫布上。
AImageReader通過AImageReader_acquireNextImage方法,獲得最早進入緩衝區的AImage。
在獲取到AImage後,可以通過AImage_getPlaneRowStride和AImage_getPlaneData方法,獲取YUV格式的視頻數據。
關於YUV格式可參照之前的博文:YUV簡介,這裏不在詳述。
2.3.3初始化ANativeWindow_Buffer
在獲取ANativeWindow_Buffer前,需要通過ANativeWindow_setBuffersGeometry方法,對其屬性進行初始化。
int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
int32_t width, int32_t height, int32_t format);
其中,width和height爲ANativeWindow_Buffer的寬高。
ANativeWindow_Buffer的寬高不同於畫布View的寬高。例如畫布View寬高爲1080*2018,ANativeWindow_Buffer寬高爲480*640,ANativeWindow_Buffer會自動被映射到畫布View上,由硬件或底層完成,無需干預。
ANativeWindow_Buffer的寬高也不同於AImage的寬高。AImage帶有ACameraDevice的偏轉角度,需要調整爲自然0度(會造成寬高的數值交換)後,再做格式轉換。
format爲YUV轉換後的圖片格式(例如WINDOW_FORMAT_RGBA_8888等,可參見Native_Window.h)。
2.3.4獲取ANativeWindow_Buffer
獲取過程相對程序化。需要依次調用ANativeWindow_acquire和ANativeWindow_lock方法。ANativeWindow_acquire及其對應的ANativeWindow_release方法,可以理解爲安全策略,爲技術層次,無業務邏輯解釋。可參見其官方註解。
/**
* Acquire a reference on the given {@link ANativeWindow} object. This prevents the object
* from being deleted until the reference is removed.
*/
void ANativeWindow_acquire(ANativeWindow* window);
/**
* Remove a reference that was previously acquired with {@link ANativeWindow_acquire()}.
*/
void ANativeWindow_release(ANativeWindow* window);
而ANativeWindow_lock方法將返回一個ANativeWindow_Buffer。
該ANativeWindow_Buffer,即爲ANativeWindow需要顯示的下一個畫面所對應的顯存。
2.3.5寫入ANativeWindow_Buffer
通過實現自定義方法TODO_YUV_2_ImageFormat,將數據進行轉換,並映射到上一步所獲得的ANativeWindow_Buffer中。
TODO_YUV_2_ImageFormat方法,至少應包含三個功能,
(1)角度的修正。將圖像由翻轉角度,矯正爲自然0度。
(2)格式轉換。YUV轉ImageFormat。其中ImageFormat爲ANativeWindow_Buffer初始化時,所設置的格式。例如ARGB_8888。
(3)座標映射。將寬高爲A*B的AImage,映射到寬高爲C*D的ANativeWindow_Buffer中。
2.3.6釋放ANativeWindow_Buffer
在每次寫入完成後,需要釋放ANativeWindow_Buffer。步驟十分簡單,與獲取時相反,依次調用
(1)ANativeWindow_unlockAndPost
(2)ANativeWindow_release
在釋放完ANativeWindow_Buffer後,一定要通過AImage_delete方法,將AImage移除緩衝區。
至此,整個預覽完成。
2.4關閉Camera
關閉Camera,有一點需要強調。
ACameraDevice_close較爲耗時,在MI 8和Samsung SM G9500上測試,需要約200ms左右。而Activity onPause/onStop/onDestory的單個超時上限爲500ms。若有其它耗時操作與ACameraDevice_close一起進行,將有可能引起Timeout。
其它不再詳細描述,只需要調用相關的free,delete,close方法即可。
3.結束語
相對於Camera1而言,NDK Camera2的邏輯較爲複雜。但隨着API Level的提升,Camera1終將被替換。Google最新提出的CameraX其實也是對Camera2的封裝。因此,Camera2在未來還會存在一段很長的時間。