【轉】Android Camera 相機開發詳解


在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架構上做了巨大的變動,
但是基於眾所周知的原因,我們還必須基於 Android 4.+ 系統進行開發。本文介紹的是Camera接口開發及其使用方法,通過本文章,你將全面地學會Camera接口的開發流程。

本圖文與GitHubPages原文均為本人原創

 
Paste_Image.png

調用系統相機/其它App完成拍攝操作

如果你的App的需求只是調用攝像頭拍照並拿到照片,老司機的建議是別自己實現拍照模塊,這里面坑多水深。你完全可以使用Intent來調用系統相機或第三方具備拍照功能的App來拍照並獲取返回照片數據。

創建一個Intent,指定兩個拍攝類型之一:

  • MediaStore.ACTION_IMAGE_CAPTURE 拍攝照片;
  • MediaStore.ACTION_VIDEO_CAPTURE 拍攝視頻;

Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);

通用流程startActivityForResult()onActivityResult()就不表述了。說說拍攝照片的Intent參數吧。

首先是設置拍攝后返回數據的地址:

intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);

MediaStore.EXTRA_OUTPUT 參數用於指定拍攝完成后的照片/視頻的儲存路徑。你可以使用Android默認的儲存照片目錄來保存:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)

也可以是其它任意你喜歡的儲存目錄。如果你使用了App內部目錄,某些臨時文件如拍攝並上傳的頭像文件,在處理完成后,要記得將它刪除。這樣做的好處是減少App占用儲存空間,手機用戶特別喜歡對占用大儲存空間的App下重手刪除和清理空間。如果你必須保存大體積的文件,可以使用公共空間來儲存,把包袱丟出去,私有空間僅保存應用配置數據。

相機其它設置,如指定拍攝照片的尺寸大小,照片質量等,待以后文章更新吧。

// TODO 是程序界最大的謊言

使用Camera開發照相功能

使用Camera API來開發拍照模塊需要費一番大功夫。下面是介紹我在開發NextQRCode項目中使用Camera API的方法和流程。

1.在 Android Manifest.xml 中聲明相機權限

開發第一步是在 Android Manifest.xml 文件中聲明使用相機的權限:

<uses-permission android:name="android.permission.CAMERA" /> 

有些同學在開發時忘了聲明權限,運行時應用可能會崩潰掉。另外也要增加以下兩個特性聲明:

<uses-feature android:name="android.hardware.camera" android:required="true"/> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> 

required屬性是說明這個特性是否必須滿足。比方說示例的設置就是要求必須擁有相機設備但可以沒有自動對焦功能。

這兩個聲明是可選的,它們用於應用商店(Google Play)過濾不支持相機和不支持自動對焦的設備。

另外在保存照片時需要寫入儲存器的權限,也需要加上讀寫儲存器的權限聲明:

<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" /> 

2. 打開相機設備

現在市面上銷售的手機/平板等消費產品基本標配兩個攝像頭。如華為P9,更有前置雙攝像頭。講真,我很好奇開發雙攝像頭的App是怎樣的體驗。在打開相機設備前,先獲取當前設備有多少個相機設備。如果你的開發需求里包含切換前后攝像頭功能,可以獲取攝像頭數量來判斷是否存在后置攝像頭。

int cameras = Camera.getNumberOfCameras(); 

這個接口返回值為攝像頭的數量:非負整數。對應地,攝像頭序號為: cameras - 1。例如在擁有前后攝像頭的手機設備上,其返回結果是2,則第一個攝像頭的cameraId0,通常對應手機背后那個大攝像頭;第二個攝像頭的cameraId1,通常對應着手機的前置自拍攝像頭;

相機是一個硬件設備資源,在使用設備資源前需要將它打開,可以通過接口Camera.open(cameraId)來打開。參考以下代碼:

public static Camera openCamera(int cameraId) { try{ return Camera.open(cameraId); }catch(Exception e) { return null; } } 

注意

打開相機設備可能會失敗,你一定要檢查打開操作是否成功。打開失敗的可能原因有兩種:一是安裝App的設備上根本沒有攝像頭,例如某些平板或特殊Android設備;二是cameraId對應的攝像頭正被使用,可能某個App正在后台使用它錄制視頻。

3. 配置相機參數

在打開相機設備后,你將獲得一個Camera對象,並獨占相機設備資源。
通過Camera.getParameters()接口可以獲取當前相機設備的默認配置參數。下面列舉一些我能理解的參數:

閃光燈配置參數,可以通過Parameters.getFlashMode()接口獲取當前相機的閃光燈配置參數:

  • Camera.Parameters.FLASH_MODE_AUTO 自動模式,當光線較暗時自動打開閃光燈;
  • Camera.Parameters.FLASH_MODE_OFF 關閉閃光燈;
  • Camera.Parameters.FLASH_MODE_ON 拍照時閃光燈;
  • Camera.Parameters.FLASH_MODE_RED_EYE 閃光燈參數,防紅眼模式,科普一下:防紅眼

對焦模式配置參數,可以通過Parameters.getFocusMode()接口獲取:

  • Camera.Parameters.FOCUS_MODE_AUTO 自動對焦模式,攝影小白專用模式;
  • Camera.Parameters.FOCUS_MODE_FIXED 固定焦距模式,拍攝老司機模式;
  • Camera.Parameters.FOCUS_MODE_EDOF 景深模式,文藝女青年最喜歡的模式;
  • Camera.Parameters.FOCUS_MODE_INFINITY 遠景模式,拍風景大場面的模式;
  • Camera.Parameters.FOCUS_MODE_MACRO 微焦模式,拍攝小花小草小螞蟻專用模式;

場景模式配置參數,可以通過Parameters.getSceneMode()接口獲取:

  • Camera.Parameters.SCENE_MODE_BARCODE 掃描條碼場景,NextQRCode項目會判斷並設置為這個場景;
  • Camera.Parameters.SCENE_MODE_ACTION 動作場景,就是抓拍跑得飛快的運動員、汽車等場景用的;
  • Camera.Parameters.SCENE_MODE_AUTO 自動選擇場景;
  • Camera.Parameters.SCENE_MODE_HDR 高動態對比度場景,通常用於拍攝晚霞等明暗分明的照片;
  • Camera.Parameters.SCENE_MODE_NIGHT 夜間場景;

Camera API提供了非常多的參數接口供開發者設置,有必要的話,可以翻閱相關API文檔。

在NextQRCode項目中,需要使用到自動對焦的特性。在一些機型上可能是沒有的自動對焦(雖然比較少見),需要對這種情況進行處理。

4. 設置相機預覽方向

相機預覽圖需要設置正確的預覽方向才能正常地顯示預覽畫面,否則預覽畫面會被擠壓得很慘。
在通常情況下,如果我們需要知道設備的屏幕方向,可以通過Resources.Configuration.orientation來獲取。Android屏幕方向有“豎屏”和“橫屏”兩種,對應的值分別是ORIENTATION_PORTRAITORIENTATION_LANDSCAPE。但相機設備的方向卻有些特別,設置預覽方向的接口Camera.setDisplayOrientaion(int)的參數是以角度為單位的,而且只能是0,90,180,270其中之一,默認為0,是指手機的左側為攝像頭頂部畫面。記得只能是[0、90、180、270]其中之一,輸入其它角度數值會報錯。

如果你想讓相機跟隨設備的方向,預覽界面頂部一直保持正上方,以下代碼供參考:

public static void followScreenOrientation(Context context, Camera camera){ final int orientation = context.getResources().getConfiguration().orientation; if(orientation == Configuration.ORIENTATION_LANDSCAPE) { camera.setDisplayOrientation(180); }else if(orientation == Configuration.ORIENTATION_PORTRAIT) { camera.setDisplayOrientation(90); } } 

5. 預覽View與拍照

我們一般使用SurfaceView作為相機預覽View,你也可以使用Texture。在SurfaceView中獲取得SurfaceHolder,並通過setPreviewDisplay()接口設置預覽。在設置預覽View后,一定要記得以下兩點:

  • 調用startPreview()方法啟動預覽,否則預覽View不會顯示任何內容;
  • 拍照操作需要在startPreview()方法執行之后調用;
  • 每次拍照后,預覽View會停止預覽。所以連續拍照,需要重新調用startPreview()來恢復預覽;

Camera接受一個SurfaceHolder接口,這個接口可以通過SurfaceHolder.Callback獲得。我們可以通過繼承SurfaceView來實現相機預覽效果。在NextQRCode項目中,實現了LiveCameraView類,它內部已實現了相機預覽所需要的處理過程,很簡潔的類,以下是它的全部源碼:

public class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback { private final static String TAG = LiveCameraView.class.getSimpleName(); private Camera mCamera; private SurfaceHolder mSurfaceHolder; public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mSurfaceHolder = this.getHolder(); mSurfaceHolder.addCallback(this); } @Override public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "Start preview display[SURFACE-CREATED]"); startPreviewDisplay(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mSurfaceHolder.getSurface() == null){ return; } Cameras.followScreenOrientation(getContext(), mCamera); Log.d(TAG, "Restart preview display[SURFACE-CHANGED]"); stopPreviewDisplay(); startPreviewDisplay(mSurfaceHolder); } public void setCamera(Camera camera) { mCamera = camera; final Camera.Parameters params = mCamera.getParameters(); params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE); } private void startPreviewDisplay(SurfaceHolder holder){ checkCamera(); try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { Log.e(TAG, "Error while START preview for camera", e); } } private void stopPreviewDisplay(){ checkCamera(); try { mCamera.stopPreview(); } catch (Exception e){ Log.e(TAG, "Error while STOP preview for camera", e); } } private void checkCamera(){ if(mCamera == null) { throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set"); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]"); stopPreviewDisplay(); } } 

從上面代碼可以看出LiveCameraView的核心代碼是SurfaceHolder.Callback的回調:在創建/銷毀時啟動/停止預覽動作。在LiveCameraView類中,我們利用了View的生命周期回調來實現自動管理預覽生命周期控制:

  • 當SurfaceView被創建后自動開啟預覽;
  • 當SurfaceView被銷毀時關閉預覽;
  • 當SurfaceView尺寸被改變時重置預覽;

預覽View需要注意預覽輸出畫面的尺寸。相機輸出畫面只支持部分尺寸。關於尺寸部分,后面再更新。

在啟用預覽View后,就可以通過Camera.takePicture()方法拍攝一張照片,返回的照片數據通過Callback接口獲取。takePicture()接口可以獲取三個類型的照片:

  • 第一個,ShutterCallback接口,在拍攝瞬間瞬間被回調,通常用於播放“咔嚓”這樣的音效;
  • 第二個,PictureCallback接口,返回未經壓縮的RAW類型照片;
  • 第三個,PictureCallback接口,返回經過壓縮的JPEG類型照片;

我們使用第三個參數,JPEG類型的照片的圖片精度即可滿足識別二維碼的需求。在NextQRCode項目中,ZXing識別二維碼的數據格式為Bitmap,通過BitmapFactory可以很方便方便地將byte數組轉換成Bitmap。

public abstract class BitmapCallback implements Camera.PictureCallback { @Override public void onPictureTaken(byte[] data, Camera camera) { onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length)); } public abstract void onPictureTaken(Bitmap bitmap); } 

詳細關於Android中Bitmap的說明,請參見文章Android: Bitmap與Drawable這件小事

如果你需要將照片保存為文件,可以參考這個類的實現:FilePhotoCallback.java

6. 釋放相機設備

在打開一個相機設備后,意味着你的App就獨占了這個設備,其它App將無法使用它。因此在你不需要相機設備時,記得調用release()方法釋放設備,再使用時可以重新打開,這並不需要多大的成本。可以選擇在stopPreview()后即釋放相機設備。

附加工具性代碼實現

1 - 判斷手機設備是否有相機設備

public static boolean hasCameraDevice(Context ctx) { return ctx.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_CAMERA); } 

2 - 判斷是否支持自動對焦

public static boolean isAutoFocusSupported(Camera.Parameters params) { List<String> modes = params.getSupportedFocusModes(); return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO); } 

如何正確地使用Camera來開發視頻拍攝功能

抱歉,這個我真沒研究過。

提供一個鏈接地址供你參考:Camera開發視頻拍攝

關於Camera2

后續再更新Camera2的開發教程



作者:陳小鍋
鏈接:https://www.jianshu.com/p/7dd2191b4537
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM