Android中開發相機的兩種方式
Android系統提供了兩種使用手機相機資源實現拍攝功能的方法,一種是直接通過Intent調用系統相機組件,這種方法快速方便,適用于直接獲得照片的場景,如上傳相冊,微博、朋友圈發照片等。另一種是使用相機API來定制自定義相機,這種方法適用于需要定制相機界面或者開發特殊相機功能的場景,如需要對照片做裁剪、濾鏡處理,添加貼紙,表情,地點標簽等。
1.調用系統自帶相機
關于系統自帶相機的調用非常簡單,這里我就不過多敘述了,具體可以參考谷歌的Training。我只說容易被大家忽視的幾個點:
如果我們的應用使用相機,但相機并不是應用的正常運行所必不可少的組件,可以將權限聲明中的android:required設置為”false”。這樣的話,Google Play 也會允許沒有相機的設備下載該應用。當然我們有必要在使用相機之前通過調用hasSystemFeature(PackageManager.FEATURE_CAMERA)方法來檢查設備上是否有相機。如果沒有,我們應該禁用和相機相關的功能!
在調用startActivityForResult()方法之前,先調用resolveActivity(),這個方法會返回能處理該Intent的第一個Activity(譯注:即檢查有沒有能處理這個Intent的Activity)。執行這個檢查非常重要,因為如果在調用startActivityForResult()時,沒有應用能處理你的Intent,應用將會崩潰。所以只要返回結果不為null,使用該Intent就是安全的。
使用Android框架所提供的API來直接控制相機硬件
使用API來控制相機我們需要用到關鍵類和接口:
使用Camera對象來控制相機
使用SurfaceView來展現照相機采集的圖像
通過surfaceholder來控制surfac的尺寸和格式,修改surface的像素,監視surface的變化等等
通過SurfaceHolder.Callback 接口,監聽surface狀態變化
接下來我們分為以下三部分來介紹:關鍵類以及接口的作用和方法,Camera控制拍照步驟,自定義相機容易踩到的坑以及解決辦法。
API說明
Camera :最主要的類,用于管理和操作camera資源。它提供了完整的相機底層接口,支持相機資源切換,設置預覽/拍攝尺寸,設定光圈、曝光、聚焦等相關參數,獲取預覽/拍攝幀數據等功能,主要方法有以下這些:
open():獲取camera實例。
setPreviewDisplay(SurfaceHolder):綁定繪制預覽圖像的surface。surface是指向屏幕窗口原始圖像緩沖區(raw buffer)的一個句柄,通過它可以獲得這塊屏幕上對應的canvas,進而完成在屏幕上繪制View的工作。通過surfaceHolder可以將Camera和surface連接起來,當camera和surface連接后,camera獲得的預覽幀數據就可以通過surface顯示在屏幕上了。
setPrameters設置相機參數,包括前后攝像頭,閃光燈模式、聚焦模式、預覽和拍照尺寸等。
startPreview():開始預覽,將camera底層硬件傳來的預覽幀數據顯示在綁定的surface上。
stopPreview():停止預覽,關閉camra底層的幀數據傳遞以及surface上的繪制。
release():釋放Camera實例
takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):這個是實現相機拍照的主要方法,包含了三個回調參數。shutter是快門按下時的回調,raw是獲取拍照原始數據的回調,jpeg是獲取經過壓縮成jpg格式的圖像數據的回調。
SurfaceView :用于繪制相機預覽圖像的類,提供給用戶實時的預覽圖像。普通的view以及派生類都是共享同一個surface的,所有的繪制都必須在UI線程中進行。而surfaceview是一種比較特殊的view,它并不與其他普通view共享surface,而是在內部持有了一個獨立的surface,surfaceview負責管理這個surface的格式、尺寸以及顯示位置。由于UI線程還要同時處理其他交互邏輯,因此對view的更新速度和幀率無法保證,而surfaceview由于持有一個獨立的surface,因而可以在獨立的線程中進行繪制,因此可以提供更高的幀率。自定義相機的預覽圖像由于對更新速度和幀率要求比較高,所以比較適合用surfaceview來顯示。
SurfaceHolder :surfaceholder是控制surface的一個抽象接口,它能夠控制surface的尺寸和格式,修改surface的像素,監視surface的變化等等,surfaceholder的典型應用就是用于surfaceview中。surfaceview通過getHolder()方法獲得surfaceholder 實例,通過后者管理監聽surface 的狀態。
SurfaceHolder.Callback 接口 :負責監聽surface狀態變化的接口,有三個方法:
surfaceCreated(SurfaceHolder holder):在surface創建后立即被調用。在開發自定義相機時,可以通過重載這個函數調用camera.open()、camera.setPreviewDisplay(),來實現獲取相機資源、連接camera和surface等操作。
surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface發生format或size變化時調用。在開發自定義相機時,可以通過重載這個函數調用camera.startPreview來開啟相機預覽,使得camera預覽幀數據可以傳遞給surface,從而實時顯示相機預覽圖像。
surfaceDestroyed(SurfaceHolder holder):在surface銷毀之前被調用。在開發自定義相機時,可以通過重載這個函數調用camera.stopPreview(),camera.release()來實現停止相機預覽及釋放相機資源等操作。
Camera控制拍照的過程
調用Camera的open()方法打開相機。
調用Camera的getParameters()獲取拍照參數,該方法返回一個Cmera.Parameters對象。
調用Camera.Parameters對象對照相的參數進行設置。
調用Camera的setParameters(),并將Camera.Parameters對象作為參數傳入,這樣就可以對拍照進行參數控制,Android2.3.3以后不用設置。
調用Camerade的startPreview()的方法開始預覽取景,在之前需要調用Camera的setPreviewDisplay(SurfaceHolder holder)設置使用哪個SurfaceView來顯示取得的圖片。
調用Camera的takePicture()方法進行拍照。
程序結束時,要調用Camera的stopPreview()方法停止預覽,并且通過Camera.release()來釋放資源。
預覽方向
先看下官方文檔的說明
Most camera applications lock the display into landscape mode because that is the natural orientation of the camera sensor. This setting does not prevent you from taking portrait-mode photos, because the orientation of the device is recorded in the EXIF header. The setCameraDisplayOrientation() method lets you change how the preview is displayed without affecting how the image is recorded. However, in Android prior to API level 14, you must stop your preview before changing the orientation and then restart it.
大多數相機程序會鎖定預覽為橫屏狀態,因為該方向是相機傳感器的自然方向。當然這一設定并不會阻止我們去拍豎屏的照片,因為設備的方向信息會被記錄在EXIF頭中。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下,改變預覽的方向。然而,對于Android API Level 14及以下版本的系統,在改變方向之前,我們必須先停止預覽,然后再去重啟它。
SurfaceView預覽圖像拉伸變形,拍攝照片尺寸不對
說明這個問題之前,同樣先說一下幾個跟相機有關的尺寸。
SurfaceView尺寸 :即自定義相機應用中用于顯示相機預覽圖像的View的尺寸,當它鋪滿全屏時就是屏幕的大小。這里surfaceview顯示的預覽圖像暫且稱作手機預覽圖像。
Previewsize :相機硬件提供的預覽幀數據尺寸。預覽幀數據傳遞給SurfaceView,實現預覽圖像的顯示。這里預覽幀數據對應的預覽圖像暫且稱作相機預覽圖像。
Picturesize :相機硬件提供的拍攝幀數據尺寸。拍攝幀數據可以生成位圖文件,最終保存成.jpg或者.png等格式的圖片。這里拍攝幀數據對應的圖像稱作相機拍攝圖像。圖4說明了以上幾種圖像及照片之間的關系。手機預覽圖像是直接提供給用戶看的圖像,它由相機預覽圖像生成,拍攝照片的數據則來自于相機拍攝圖像。
原因是沒有正確設置比例 parameter.setPictureSize(width,height),這個比例不是你決定的,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機支持的尺寸。
/*** 設置照片格式*/
private void setParameter() {
Camera.Parameters parameters = camera.getParameters(); // 獲取各項參數
parameters.setPictureFormat(PixelFormat.JPEG); // 設置圖片格式
parameters.setJpegQuality(100); // 設置照片質量//獲得相機支持的照片尺寸,選擇合適的尺寸
List sizes = parameters.getSupportedPictureSizes();
int maxSize = Math.max(display.getWidth(), display.getHeight());
int length = sizes.size();
if (maxSize > 0) {
for (int i = 0; i < length; i++) {
? ? ? if (maxSize <= Math.max(sizes.get(i).width, sizes.get(i).height)) {
? ? ? ? ? ?parameters.setPictureSize(sizes.get(i).width, sizes.get(i).height);
break;
}
}
}
List ShowSizes = parameters.getSupportedPreviewSizes();
int showLength = ShowSizes.size();
if (maxSize > 0) {for (int i = 0; i < showLength; i++) {
if (maxSize <= Math.max(ShowSizes.get(i).width, ShowSizes.get(i).height)) {parameters.setPreviewSize(ShowSizes.get(i).width, ShowSizes.get(i).height);
break;
}
}
}
camera.setParameters(parameters);
}
前置攝像頭的鏡像效果
Android 相機硬件有個特殊設定,就是對于前置攝像頭,在展示預覽視圖時采用類似鏡面的效果,顯示的是攝像頭成像的鏡像。而拍攝出的照片則仍采用攝像頭成像。看到這里,大家可能會有些懷疑,不妨現在就試試自己 Android 手機上的前置攝像頭,對比下預覽圖像和拍攝出照片的區別。這是由于底層相機在傳遞前置攝像頭預覽數據時做了水平翻轉變換,即將x方向鏡像翻轉180度。這個變化對之前豎屏預覽的方向也會造成影響,本來對于后置攝像頭旋轉90度即可使預覽視圖正確,而對前置攝像頭,如果也旋轉90度的話,看到的預覽圖像則是上下顛倒的(因為x方向翻轉了180度),因此必須再旋轉180度,才能顯示正確。
解決方案,在保存圖片的時候根據選擇的攝像頭做對應的翻轉。
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Matrix matrix = new Matrix();
switch (cameraPosition) {
? ? ?case 0://前matrix.preRotate(270);
? ? ?break;
case 1:matrix.preRotate(90);
? ?break;
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
同時在開發的過程中發現了一個有趣的東西,我們用前置攝像頭拍出來的照片其實是左右翻轉的。但我用小米自帶的相機測試發現,當攝像頭中有人臉出現的時候,相機會做左右翻轉的操作,以給用戶更好的體驗。
SurfaceView預覽圖像拉伸變形,拍攝照片尺寸不對
說明這個問題之前,同樣先說一下幾個跟相機有關的尺寸。
SurfaceView尺寸 :即自定義相機應用中用于顯示相機預覽圖像的View的尺寸,當它鋪滿全屏時就是屏幕的大小。這里surfaceview顯示的預覽圖像暫且稱作手機預覽圖像。
Previewsize :相機硬件提供的預覽幀數據尺寸。預覽幀數據傳遞給SurfaceView,實現預覽圖像的顯示。這里預覽幀數據對應的預覽圖像暫且稱作相機預覽圖像。
Picturesize :相機硬件提供的拍攝幀數據尺寸。拍攝幀數據可以生成位圖文件,最終保存成.jpg或者.png等格式的圖片。這里拍攝幀數據對應的圖像稱作相機拍攝圖像。圖4說明了以上幾種圖像及照片之間的關系。手機預覽圖像是直接提供給用戶看的圖像,它由相機預覽圖像生成,拍攝照片的數據則來自于相機拍攝圖像。
原因是沒有正確設置比例 parameter.setPictureSize(width,height),這個比例不是你決定的,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機支持的尺寸。
前置攝像頭的鏡像效果
Android 相機硬件有個特殊設定,就是對于前置攝像頭,在展示預覽視圖時采用類似鏡面的效果,顯示的是攝像頭成像的鏡像。而拍攝出的照片則仍采用攝像頭成像。看到這里,大家可能會有些懷疑,不妨現在就試試自己 Android 手機上的前置攝像頭,對比下預覽圖像和拍攝出照片的區別。這是由于底層相機在傳遞前置攝像頭預覽數據時做了水平翻轉變換,即將x方向鏡像翻轉180度。這個變化對之前豎屏預覽的方向也會造成影響,本來對于后置攝像頭旋轉90度即可使預覽視圖正確,而對前置攝像頭,如果也旋轉90度的話,看到的預覽圖像則是上下顛倒的(因為x方向翻轉了180度),因此必須再旋轉180度,才能顯示正確。
解決方案,在保存圖片的時候根據選擇的攝像頭做對應的翻轉。
---
更多了解,可關注公眾號:人人懂編程
