Android系統編程入門系列之硬體交互——多媒體攝影機

多媒體系列硬體

多媒體包括圖片、動畫、音頻、影片,這些多媒體素材的採集(輸入)主要依靠攝影機和麥克風等硬體設備轉化為基礎數據,而他們的播放渲染(輸出),則需要依靠具有相關功能的編解碼軟體。當然隨著硬體集成度越來越高,也有些基礎功能內置到硬體中解碼,以此減少軟體解碼過程中的CPU耗時操作,這種方式稱為硬體加速。由於多媒體的播放渲染(輸出)是由系統主動向用戶發出的,通常不需要向用戶申請許可權。系統將數據直接發給應用程式,進而在應用程式內編程實現相關數據的解碼播放渲染(輸出)操作。故文章重點介紹在多媒體採集(輸入)過程中可能用到的硬體及相關使用流程。

攝影機及相關硬體

攝影機作為移動手機設備的重要硬體之一,從最初的單一攝影機,到最新的浴霸式四孔攝影機,不管是數量,還是焦距性能上,在不同設備上都有不同的區別。與感測器系列硬體交互一文相似的是,這些繁雜的類型,都由系統適配完成。而應用程式只需要使用系統提供的相關類即可。

對於攝影機硬體的使用,在Android5.0即API級別21以下的系統版本中,可以使用android.hardware.Camera攝影機類的相關方法來獲取攝影機數據,以用來實時預覽攝影機採集的數據、拍照保存某一時刻的數據、或錄製影片保存一段時刻內的數據,但是從Android5.0開始,上述類由於過於臃腫而廢棄,進而使用android.hardware.camra2. 包下的相關類開發更訂製化的應用。

許可權聲明

對於使用攝影機硬體的應用程式,都需要聲明許可權為Manifest.permissions.CAMERA=”android.permission.CAMERA”
同樣也可以在應用程式清單文件中聲明需要攝影機硬體的設備支援,也可以增加標籤資訊<uses-feature android:name="android.hardware.camera"/>

另外,如果在使用攝影機拍照時,需要在照片中保存位置資訊,應用程式需要申請位置許可權;而想將照片存儲到外部存儲設備,還需要應用程式申請讀寫外部存儲的相關許可權;如果是使用攝影機錄製有聲影片,應用程式還需要申請麥克風許可權。

使用流程

目標版本為API 21以下

在使用前首先檢測攝影機硬體,在能獲取到Context上下文環境對象的位置,調用上下文環境對象的getPackageManager()方法獲取android.content.pm.PackageManager包管理類的實例化對象,進而通過該對象的hasSystemFeature(String featureName)方法,使參數 featureName 值為PackageManager.FEATURE_CAMERA 代表攝影機功能,來判斷當前系統是否有攝影機硬體的支援。

對於有攝影機硬體支援的設備,可以使用Camera.getNumberOfCameras()靜態方法獲取當前設備的所有可用攝影機數量,而每個攝影機硬體都對應一個int類型的 cameraId 屬性編號,其值大於等於0,且小於靜態方法獲取可用攝影機數量,在下面獲取攝影機資訊和打開指定攝影機時均是根據 cameraId 屬性值確定的。

對於每一個具有 cameraId 屬性值的攝影機,都可以調用Camera.open(int cameraId)方法獲取到對應的Camera攝影機類的實例化對象。參數 cameraId 即上文提到的攝影機硬體編號,該參數默認值為0;如果編號參數對應的攝影機硬體不存在時,該方法則返回空指針。

在得到Camera實例化對象後,可以查看該攝影機硬體的詳細資訊。調用該對象的getParameters()方法,得到返回值為android.hardware.Camera.Parameters攝影機參數類型的對象。在Camera.Parameters參數類型的對象中,可以使用getX系列方法獲取包括閃光燈、聚焦、解析度等系列資訊;同時也可以使用setX系列方法重新調整設置包括閃光燈、聚焦、解析度等系列資訊。如果修改攝影機硬體的參數對象後,可以調用Camera攝影機對象的setParameters(Camera.Parameters params)方法,將修改後的參數應用到對應的攝影機硬體中。

預覽

要實現Camera攝影機的預覽功能,只需要藉助自定義控制項類,該類繼承自系統控制項android.view.SurfaceView類。
在自定義控制項類的構造方法中,傳入上文獲取的Camera攝影機對象作為該類的全局變數,以供在預覽功能開啟或關閉時調用攝影機對象的相關方法。
之後可以在自定義控制項類內部調用自己的getHolder()方法,返回android.view.SurfaceHolder類型的對象,該對象是綁定當前SurfaceView控制項與其中的控制資訊的。可以調用該對象的setX系列方法設置當前自定義的SurfaceView中的顯示資訊,同時調用該對象的addCallback(SurfaceHolder.Callback callback)方法為當前自定義SurfaceView增加介面更新的回調,參數 callback 為回調介面android.view.SurfaceHolder.Callback實現的實例化對象。

SurfaceHolder.Callback介面的實例化對象中,分別實現surfaceCreated(SurfaceHolder holder)在自定義控制項創建時回調的方法,通常在該方法中調用當前類的全局變數Camera對象的setPreviewDisplay(holder)方法將攝影機與當前控制項綁定,之後調用Camera對象的startPreview()方法啟動攝影機的預覽,這樣攝影機採集的數據就會實時展示在當前自定義SurfaceView控制項中了;surfaceChanged(SurfaceHolder holder, int format, int width, int height)在自定義控制項包括後邊三個參數所代表的資訊發生改變時回調的方法,此時一般要先調用全局變數Camera對象的stopPreview()停止預覽,之後完成該控制項內部的一些更新資訊,最後再重新調用Camera對象的setPreviewDisplay(holder)startPreview()方法重新綁定並啟動預覽;surfaceDestroyed(SurfaceHolder holder)在自定義控制項被銷毀時回調的方法,通常在剛方法中會調用Camera對象的stopPreview()停止預覽,最後調用release()方法直接釋放相關資源,這樣該Camera對象的相關數據便都清空了。

拍照

要實現攝影機的拍照功能,只需要調用Camera對象的takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg)方法。其中參數 shutter 是拍照那一刻回調的android.hardware.Camera.ShutterCallback介面對象,在拍攝照片時,會回調該對象的唯一方法onShutter(),因此如果想在拍照時搞些小動作,可以在該對象的onShutter()中添加程式碼,通常該參數 shutternull
參數 rawpostviewjpeg 三個都是android.hardware.Camera.PictureCallback圖片回調介面的實例化對象,在該對象中實現了onPictureTaken(byte[] data, Camera camera)方法,是攝影機採集到拍攝的數據後回調該方法,其中的 data 參數便是具體的照片數據,而 camera 則是對應的攝影機對象;三個參數不同的是,參數 raw 是返回的原始數據、參數 postview 是返回的是縮略圖數據、參數 jpeg 則是返回的經過jpeg編碼的壓縮數據。

影片錄製

如果想實現實現攝影機的錄製影片功能,在調用Camera對象的startPreview()方法開啟預覽後,還要調用其unlock()方法將該攝影機對象從當前進程解鎖,以便之後將該攝影機對象配置到android.media.MediaRecorder多媒體錄製類中,在多媒體錄製類結束錄製並關閉釋放相關資源後,調用Camera對象的reconnect ()方法重新將該攝影機與當前進程鎖定。這樣便可以在當前進程中繼續使用該攝影機對象了。

關於使用MediaRecorder多媒體錄製類的相關流程,將在後續文章中詳細講解。

目標版本為API 21及以上

從Android5.0版本系統開始,可以在應用程式項目配置文件中增加androidx.camera:camera-coreandroidx.camera:camera-camera2等官方提供的CameraX框架的依賴庫。該庫將攝影機的功能分別作為單獨的類處理,而不是繼續使用低版本將功能都添加到同一個Camera類中。

首先是檢測設備是否支援攝影機硬體,同樣是在能獲取Context上下文環境對象的地方,藉助androidx.camera.lifecycle.ProcessCameraProvider攝影機提供者類的靜態方法getInstance(Context context)獲取提供者的進程間唯一的單例對象,返回的是ListenableFuture<ProcessCameraProvider>類型的結果,這裡的ListenableFuture是Google提供的 guava 框架下com.google.common.util包中的非同步任務,簡單來說就是該類型的對象可以調用addListener(Runnable runnable, Executor executor)監聽方法,在該對象所綁定的非同步任務完成後會回調監聽方法中的參數 runnable 運行,而參數 executor 則指定了運行 runnable 所在的執行緒,通過使用ContextCompat.getMainExecutor(Context context)方法獲取UI主執行緒的Executor對象。而這裡通過攝影機提供者類的靜態方法獲取的單例對象,就是對應的非同步任務,在返回ListenableFuture<ProcessCameraProvider>對象後,為該對象增加監聽方法,在非同步任務完成後才會調用監聽方法中的內容。

在參數 runnable 定義的運行過程中,便可以直接使用之前的ListenableFuture<ProcessCameraProvider>對象的get()方法,返回ProcessCameraProvider類型的單例對象以實現攝影機功能。

同樣可以調用ProcessCameraProvider對象的getAvailableCameraInfos()方法獲取可以訪問的攝影機詳細資訊,得到androidx.camera.core.CameraInfo攝影機資訊對象組成的列表。

預覽

實現預覽功能,主要依靠androidx.camera.view.PreviewView預覽視圖類作為系統控制項來實時展示攝影機採集的數據。最終在程式碼中調用PreviewView對象的getSurfaceProvider()方法,可以獲取androidx.camera.core.Preview.SurfaceProvider預覽提供者類型的對象,為之後將該控制項與androidx.camera.core.Preview預覽類綁定。

之後需要創建androidx.camera.core.Preview預覽類,其創建方式遵循建造者模式,構造androidx.camera.core.Preview.Builder建造者對象,使用該對象的setX系列方法可以配置預覽資訊,最終調用建造者對象的build()方法返回創建Preview預覽類對象。

得到Preview對象後,調用setSurfaceProvider(Preview.SurfaceProvider surfaceProvider)方法綁定預覽視圖控制項,參數 surfaceProvider 即上文預覽視圖控制項對象中的Preview.SurfaceProvider類型的預覽提供者對象。

最終,只需將該Preview對象綁定到ProcessCameraProvider攝影機提供者對象中,在上文獲取到攝影機提供者的非同步任務完成監聽中,調用ProcessCameraProvider對象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)方法將攝影機、預覽、分別與當前介面生命周期綁定即可。其中參數 lifecycleOwner 為當前介面Activity 對象;
參數 cameraSelector 是通過建造者模式創建的androidx.camera.core.CameraSelector攝影機選擇器對象,通過先構造androidx.camera.core.CameraSelector.Builder建造者對象,使用該對象的requireLensFacing(int lensFacing)方法來選擇要使用的攝影機類型,其參數 lensFacing 值只能為前置攝影機的CameraSelector.LENS_FACING_FRONT=0或後置攝影機的CameraSelector.LENS_FACING_BACK=1,之後同樣調用build()方法返回創建的CameraSelector對象;
可變參數 useCases 即包括上文中的Preview對象和下文的其他功能對應的案例對象。

拍照

實現拍照功能,主要依靠androidx.camera.core.ImageCapture圖片捕獲類。該類同樣使用建造者模式創建,首先構造androidx.camera.core.ImageCapture.Builder建造者對象,調用該對象的setX系列方法,可以設置拍照時的參數資訊,最終調用該對象的build()方法,返回創建的圖片捕獲對象。

在得到ImageCapture圖片拍攝類對象後,同樣需要調用ProcessCameraProvider攝影機提供者對象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)方法將攝影機與當前拍照對象綁定,參數 lifecycleOwnercameraSelector 與上文使用相同,而參數 useCases 則是這裡的ImageCapture圖片拍攝類對象。

最終在需要拍照的時刻,調用ImageCapture圖片拍攝類對象的takePicture(ImageCapture.OutputFileOptions outputFileOptions, Executor executor, ImageCapture.OnImageSavedCallback imageSavedCallback)方法即可。其中,
參數 outputFileOptions 是用建造者模式的輸出文件選項,同樣是通過構造androidx.camera.core.ImageCapture.OutputFileOptions.Builder建造者,設置要保存的文件路徑,最終建造返回androidx.camera.core.ImageCapture.OutputFileOptions類型對象使用即可;
參數 executor 是下一個參數 imageSavedCallback 回調方法被運行時所在的執行緒;
參數 imageSavedCallbackandroidx.camera.core.ImageCapture.OnImageCapturedCallback照片捕獲後回調介面的實例化對象,在該對象中需要實現onCaptureSuccess(ImageProxy image)在圖片拍攝成功時的回調方法,和onError(ImageCaptureException exception)在圖片拍攝出錯時的回調方法。

影片錄製

實現影片錄製功能,主要依靠androidx.camera.video.VideoCapture影片捕獲類。

這裡的VideoCapture影片捕獲類可就不是建造者模式創建的了,而是使用其靜態方法withOutput(T videoOutput),傳入參數 videoOutput 為影片輸出流,返回VideoCapture的實例化對象。這裡的影片輸出流通常為androidx.camera.video.Recorder影片錄製類型,Recorder影片錄製類的對象是後面的操作對象,因此他才是用建造者模式創建的。

先構造androidx.camera.video.Recorder.Builder建造者,通過建造者對象的setQualitySelector(QualitySelector qualitySelector)方法為錄製的影片流設置壓縮品質,該方法的參數 qualitySelector 通常是由androidx.camera.video.QualitySelector品質選擇器的靜態方法getSupportedQualities(CameraInfo cameraInfo)獲取支援的壓縮品質列表,其值包括最低解析度的QualitySelector.QUALITY_LOWEST=0、最高解析度的QualitySelector.QUALITY_HIGHEST=2、480P解析度的QualitySelector。QUALITY_SD=4、720P的QualitySelector.QUALITY_HD=5、1080P的QualitySelector.QUALITY_FHD=6、2160P的QualitySelector.QUALITY_UHD=8。在獲取品質列表的靜態方法中,參數 canmeraInfo 是錄製使用的攝影機資訊對象。最終調用建造者的build()方法,返回創建的Recorder錄製影片流對象。

在得到VideoCapture影片捕獲類對象後,同樣需要調用ProcessCameraProvider攝影機提供者對象的bindToLifecycle (LifecycleOwner lifecycleOwner, CameraSelector cameraSelector, UseCase... useCases)方法將攝影機與當前影片捕獲對象綁定,在參數 useCases 中增加VideoCapture對象即可。

VideoCapture影片捕獲類對象綁定之後,通過調用其getOutput()方法返回其設置的Recorder影片錄製對象。
通過調用Recorder對象的prepareRecording(Context context, FileOutputOptions fileOutputOptions)方法準備錄製影片,其參數 context 為上下文環境對象,參數 fileOutputOptions 與拍攝照片時類似的使用androidx.camera.video.FileOutputOptions輸出文件選項對象,用以設置錄製影片的保存路徑。該方法返回androidx.camera.video.PendingRecording預備錄製類型的對象。

在得到的PendingRecording對象中,可以調用start()方法,啟動影片錄製,返回androidx.camera.video.ActiveRecording活動錄製類型對象。

在得到的ActiveRecording對象中,可以調用pause()方法暫停錄製,resume()方法繼續錄製,stop()方法停止錄製。如此,便可完成影片的錄製流程。