上一篇博客已經把Camera開發的主要流程梳理了一遍,包括調用系統Camera和自定義自己的Camera,如果你還沒有閱讀,請移步至鏈接吧。本文主要是Camera開發的一些遺留問題,包括如何設置Camera的特性參數,讓它更好的輔助相機拍照,以及對照片和視頻的旋轉處理。
一旦你成功訪問相機設備,你可以使用camera.getParameters()方法來獲取相機參數信息,可以根據返回值 Camera.Parameters 類來查看當前camera支持哪些參數設置等。當使用API 9或者更高時,你可以使用Camera.getCameraInfo()靜態方法來獲取前后camera的信息,如camera數據流的方向和是否能禁止拍照快門聲音標記。示例代碼如下:
public static Camera.CameraInfo getCameraInfo(int cameraId) { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, cameraInfo); return cameraInfo; }
CameraInfo類:
public static class CameraInfo { public static final int CAMERA_FACING_BACK = 0; //后置camera public static final int CAMERA_FACING_FRONT = 1; //前置camera public boolean canDisableShutterSound; //是否能禁止拍照快門聲音 public int facing; public int orientation; //camera數據流的方向 } }
Android支持一組相機功能來控制你的相機應用,比如:生成的照片格式,閃光燈模式,對焦設置等等。這里羅列出通用的相機功能并且顯示怎么使用它們來輔助我們拍照。有關相機更多的參數設置,可以直接閱讀Camera.Parameters類。下表羅列出通用的相機參數:
注意:以上列表中的功能并是不在所有的Android設備上都支持,因此你在使用以上參數時需要去檢測當前Android設備是否支持該參數,然后再去使用它們來輔助相機拍照。
首先你要知道,并不是所有android設備都支持全部的camera特性功能,因此在應用總使用camera特性功能需要先檢測是否支持,然后在去使用。否則你使用了不支持的camera特性功能將會報錯。
在應用中可以通過得到camera 的參數parameters類,然后通過該類中的一些方法來檢測當前設備是否支持camea特性功能。如下代碼示例演示了如何獲得一個Camera.Parameters對象且檢測camera是否支持自動對焦特性:
Camera.Parameters params = mCamera.getParameters(); ListfocusModes = params.getSupportedFocusModes(); if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { /** Autofocus mode is supported */ }
大部分Android 相機特性功能都可以通過 Camera.Parameters類來控制。首先你可以獲得一個Camera實例,然后調用camera.getParameters()方法的返回值來得到Camera.Parameters實例,之后就可以通過Parameters.setxxx()系列方法來設置一些參數使用相機的一些特性功能。以下是實例代碼:
Camera.Parameters params = mCamera.getParameters(); /** set the focus mode*/ params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); // set Camera parameters camera.setParameters(params);
相機的所有參數都可以通過類似以上方法來設置,一般在打開相機成功以后就可以設置相機的基本參數。
注意:相機的一些特性不能在任意時刻改變,比如改變預覽的尺寸和方向時,首先需要停止preview,修改預覽尺寸之后再次重啟preview。自從Android4.0以后修改預覽方向以后無需 再次重啟preview。
下面介紹三個相機的其他功能的使用:
Metering and focus areas Face detection Time lapse video在某些攝像情景中,自動調焦和測光可能不能達到設計結果。從Android4.0(API Level 14)開始,你的Camera應用程序能夠提供另外的控制允許應用程序或用戶指定圖像中特定區域用于進行調焦或光線級別的設置,并且把這些值傳遞給Camera硬件用于采集圖片或視頻。
測光和調焦區域的工作與其他Camera功能非常類似,你可以通過Camera.Parameters對象中的方法來控制它們。下列代碼演示如何給Camera示例設置兩個測光區域:
private void setMeteringFocusAreas(Camera camera) { Camera.Parameters params = camera.getParameters(); if (params.getMaxNumMeteringAreas() > 0){ //檢測是否支持測光和對焦區域功能 ListmeteringAreas = new ArrayList (); Rect areaRect1 = new Rect(-100, -100, 100, 100); //定義圖像中間的一個區域 meteringAreas.add(new Camera.Area(areaRect1, 600)); //權重60% Rect areaRect2 = new Rect(800, -1000, 1000, -800); //定義圖像右上側的一個區域 meteringAreas.add(new Camera.Area(areaRect2, 400)); //權重40% params.setMeteringAreas(meteringAreas); } camera.setParameters(params); }
Camera.Area對象包含了兩個數據參數:Rect對象,它用于指定Camera預覽窗口一塊矩形區域;一個權重值:它告訴Camera這塊指定區域應該給予的測光或調焦計算的重要性等級。
Camera預覽窗口被映射成2000x2000單元格的矩形。坐標(-1000,-1000)代表Camera圖像的左上角,(1000,1000)代表Camera圖像的右下角,如下圖所示:
圖中的紅線說明了Camera預覽窗口中給Camera.Area指定的坐標系統。用Rect的值是(-100, -100, 100, 100)和(800, -1000, 1000, -800)藍色框代表了需要設置的測光或調焦區域。
對于包含人的圖片,通常人臉是圖片的最重要的部分,并且在采集圖像時,應該使用調焦和白平衡來進行檢測。Android4.0(API Level 14)框架提供了用于識別人臉和使用人臉識別技術來計算圖片設置的API。
注意:當人臉識別在運行時,setWhiteBalance(String), setFocusAreas(List) 和setMeteringAreas(List)方法都無效。
在你的應用中使用人臉識別技術一般需要如下幾步:
人臉識別功能并不是在所有設備上都支持。你應當調用getMaxNumDetectedFaces()方法來檢測當前設備是否支持人臉識別技術,只有當以上方法返回值大于0時,你才可以去調用startFaceDetection()方法去啟動人臉識別。
為了通知和響應人臉識別,你的相機應用必須設置一個人臉檢測監聽事件,為了到達這個目的,你必須要創建一個實現Camera.FaceDetectionListener接口的監聽器類,如下所示:
class MyFaceDetectionListener implements Camera.FaceDetectionListener { @Override public void onFaceDetection(Face[] faces, Camera camera) { if (faces.length > 0){ showlog("face detected: "+ faces.length + " Face 1 Location X: " + faces[0].rect.centerX() + "Y: " + faces[0].rect.centerY() ); } } }
創建這個類之后,把它設置給你的應用程序的Camera對象:
camera.setFaceDetectionListener(new MyFaceDetectionListener());
你的應用必須在每次重啟相機preview時候去啟動一個人臉檢測。創建一個啟動人臉識別的方法,在你需要的時候調用它。示例代碼如下:
public void startFaceDetection(){ Camera.Parameters params = camera.getParameters(); if (params.getMaxNumDetectedFaces() > 0){ camera.startFaceDetection(); } }
你必須在每次啟動Camera預覽窗口時都要啟動面部識別。
@Override public void surfaceCreated(SurfaceHolder holder) { try { camera = getCamera(); camera.setFaceDetectionListener(new MyFaceDetectionListener()); //添加監聽回調 camera.setPreviewDisplay(holder); camera.setDisplayOrientation(90); camera.startPreview(); startFaceDetection(); //開始檢測 } catch (Exception e) { finish(); } }
延時攝影允許用戶把幾張圖片合成為一個幾秒或幾分鐘的視頻剪輯。這個功能要使用MediaRecorder對象來記錄圖像的延時序列。
要用MediaRecorder對象來記錄延時視頻,和錄制普通視頻一樣,必須要配置MediaRecorder對象,并把每秒采集的幀數設置到較小的數字,并且要使用一個延時品質設置,如下代碼所示:
... mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH)); /**QUALITY_TIME_LAPSE_HIGH*/ mediaRecorder.setCaptureRate(0.1); /**每10秒抓取一幀*/
這些設置是要對MediaRecorder對象所要做的必要設置的一大部分。對于完全的配置代碼示例,請看上篇博客內容。一旦配置完成,你就可以把它當做普通的視頻剪輯來錄制視頻了。
使用上篇文章的demo,拍攝照片,如果你找到拍攝后的照片,會發現照片是橫向的,也就是有90度的旋轉,那么如何解決這個問題呢?還有,如果我們想不管相機在拍照時的旋轉角度如何,都希望拍出來的照片是正向的,又該怎么辦呢?
我的解決思路是在Camera.PictureCallback中將圖片的byte[]數據轉化為Bitmap,然后根據拍攝角度將Bitmap進行相應的矩陣旋轉操作,最后將旋轉后的Bitmap再次轉化為byte[]數組。這樣不管你相機的拍攝角度如何,拍出來的照片都是正向的。
private OrientationEventListener mOrientationListener; private int orientations; ... @Override protected void onResume() { super.onResume(); mOrientationListener = new OrientationEventListener(this){ @Override public void onOrientationChanged(int orientation) { orientations = orientation; } }; if(mOrientationListener != null){ mOrientationListener.enable(); } @Override protected void onStop() { super.onStop(); if(mOrientationListener != null){ mOrientationListener.disable(); } } //拍攝照片后的回調 private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) { File pictureFile = MainActivity.getOutputMediaFile(MEDIA_TYPE_IMAGE); if (pictureFile == null){ return; } /***********************解決照片旋轉問題start************************/ if (null != data && data.length > 0) { // data[]轉為bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); int h1 = bitmap.getHeight(); int w1 = bitmap.getWidth(); Matrix matrix = new Matrix(); int initialAngle = 0; if (h1 < w1) { initialAngle = 90; } showlog("orientations : " + orientations); if (orientations > 325 || orientations <= 45) { matrix.setRotate(0 + initialAngle); } else if (orientations > 45 && orientations <= 135) { matrix.setRotate(90 + initialAngle); } else if (orientations > 135 && orientations < 225) { matrix.setRotate(180 + initialAngle); } else { matrix.setRotate(270 + initialAngle); } bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); data = Bitmap2Bytes(bitmap); } /***********************解決照片旋轉問題end************************/ try { FileOutputStream fos = new FileOutputStream(pictureFile); fos.write(data); fos.close(); } catch (Exception e) { e.printStackTrace(); } Toast.makeText(RecordVedioAct.this, "圖像已保存", Toast.LENGTH_SHORT).show(); camera.startPreview(); //拍完繼續預覽 } }; public byte[] Bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); }
同樣,如果我們想不管相機在拍攝時的旋轉角度如何,都希望拍出來的視頻是正向的,怎么辦呢?可以參考上面拍照時的思路。
拍攝時先獲取相機角度turnAngle,初始化MediaRecorder時,將MediaRecorder通過函數mediaRecorder.setOrientationHint(turnAngle);旋轉角度turnAngle,再進行拍攝。
// 開始錄像 private void startRecord() { /***********************解決視頻旋轉問題start************************/ int turnAngle = 0; showlog("orientations : " + orientations); if (orientations > 325 || orientations <= 45) { turnAngle = 90; } else if (orientations > 45 && orientations <= 135) { turnAngle = 180; } else if (orientations > 135 && orientations < 225) { turnAngle = 270; } else { turnAngle = 0; } /***********************解決視頻旋轉問題end************************/ if (prepareVideoRecorder(turnAngle)) { mediaRecorder.start(); isRecording = true; Toast.makeText(RecordVedioAct.this, "開始錄像", Toast.LENGTH_SHORT).show(); btn_start_recording.setBackgroundResource(R.drawable.recording_act_vedio_stop); start_time = 0; timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { start_time++; handler.sendEmptyMessage(0); } }, 0, 1000); } else { mediaRecorder.release(); camera.lock(); } } //初始化MediaRecorder private boolean prepareVideoRecorder(int turnAngle){ mediaRecorder = new MediaRecorder(); camera.unlock(); mediaRecorder.setCamera(camera); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)); mediaRecorder.setOutputFile(MainActivity.getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()); mediaRecorder.setPreviewDisplay(myHolder.getSurface()); mediaRecorder.setOrientationHint(turnAngle); //mediaRecorder旋轉角度turnAngle try { mediaRecorder.prepare(); } catch (Exception e) { mediaRecorder.release(); camera.lock(); return false; } return true; }