標籤:func pre 利用 青年 豎屏 情況下 注意 aik efs
在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
,則第一個網路攝影機的cameraId
是0
,通常對應手機背後那個大網路攝影機;第二個網路攝影機的cameraId
是1
,通常對應著手機的前置自拍網路攝影機;
相機是一個硬體裝置資源,在使用裝置資源前需要將它開啟,可以通過介面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_PORTRAIT
和ORIENTATION_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
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
【轉】Android Camera 相機開發詳解