多媒體包括圖片、動畫、音頻、視頻,這些多媒體素材的采集(輸入)主要依靠攝像頭和麥克風等硬件設備轉化為基礎數據,而他們的播放渲染(輸出),則需要依靠具有相關功能的編解碼軟件。當然隨着硬件集成度越來越高,也有些基礎功能內置到硬件中解碼,以此減少軟件解碼過程中的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()
中添加代碼,通常該參數 shutter 為 null
;
參數 raw 、 postview 、 jpeg 三個都是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-core
和androidx.camera:camera-camera2
等官方提供的CameraX框架的依賴庫。該庫將攝像頭的功能分別作為單獨的類處理,而不是繼續使用低版本將功能都添加到同一個Camera
類中。
首先是檢測設備是否支持攝像頭硬件,同樣是在能獲取Context
上下文環境對象的地方,借助androidx.camera.lifecycle.ProcessCameraProvider攝像頭提供者類的靜態方法getInstance(Context context)
獲取提供者的進程間唯一的單例對象,返回的是ListenableFuture<ProcessCameraProvider>
類型的結果,這里的ListenableFuture
是谷歌提供的 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)
方法將攝像頭與當前拍照對象綁定,參數 lifecycleOwner 和 cameraSelector 與上文使用相同,而參數 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 回調方法被運行時所在的線程;
參數 imageSavedCallback 是androidx.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()
方法停止錄制。如此,便可完成視頻的錄制流程。