Android中開發(fā)相機的兩種方式
Android系統(tǒng)提供了兩種使用手機相機資源實現(xiàn)拍攝功能的方法拍冠,一種是直接通過Intent調(diào)用系統(tǒng)相機組件,這種方法快速方便,適用于直接獲得照片的場景质涛,如上傳相冊,微博掰担、朋友圈發(fā)照片等汇陆。另一種是使用相機API來定制自定義相機,這種方法適用于需要定制相機界面或者開發(fā)特殊相機功能的場景带饱,如需要對照片做裁剪毡代、濾鏡處理,添加貼紙勺疼,表情教寂,地點標簽等。
1.調(diào)用系統(tǒng)自帶相機
關于系統(tǒng)自帶相機的調(diào)用非常簡單执庐,這里我就不過多敘述了酪耕,具體可以參考谷歌的Training。我只說容易被大家忽視的幾個點:
如果我們的應用使用相機轨淌,但相機并不是應用的正常運行所必不可少的組件因妇,可以將權限聲明中的android:required設置為”false”。這樣的話猿诸,Google Play 也會允許沒有相機的設備下載該應用婚被。當然我們有必要在使用相機之前通過調(diào)用hasSystemFeature(PackageManager.FEATURE_CAMERA)方法來檢查設備上是否有相機。如果沒有梳虽,我們應該禁用和相機相關的功能址芯!
在調(diào)用startActivityForResult()方法之前,先調(diào)用resolveActivity()窜觉,這個方法會返回能處理該Intent的第一個Activity(譯注:即檢查有沒有能處理這個Intent的Activity)谷炸。執(zhí)行這個檢查非常重要,因為如果在調(diào)用startActivityForResult()時禀挫,沒有應用能處理你的Intent旬陡,應用將會崩潰。所以只要返回結果不為null语婴,使用該Intent就是安全的描孟。
使用Android框架所提供的API來直接控制相機硬件
使用API來控制相機我們需要用到關鍵類和接口:
使用Camera對象來控制相機
使用SurfaceView來展現(xiàn)照相機采集的圖像
通過surfaceholder來控制surfac的尺寸和格式驶睦,修改surface的像素,監(jiān)視surface的變化等等
通過SurfaceHolder.Callback 接口匿醒,監(jiān)聽surface狀態(tài)變化
接下來我們分為以下三部分來介紹:關鍵類以及接口的作用和方法场航,Camera控制拍照步驟,自定義相機容易踩到的坑以及解決辦法廉羔。
API說明
Camera :最主要的類溉痢,用于管理和操作camera資源。它提供了完整的相機底層接口憋他,支持相機資源切換孩饼,設置預覽/拍攝尺寸,設定光圈竹挡、曝光镀娶、聚焦等相關參數(shù),獲取預覽/拍攝幀數(shù)據(jù)等功能此迅,主要方法有以下這些:
open():獲取camera實例汽畴。
setPreviewDisplay(SurfaceHolder):綁定繪制預覽圖像的surface旧巾。surface是指向屏幕窗口原始圖像緩沖區(qū)(raw buffer)的一個句柄耸序,通過它可以獲得這塊屏幕上對應的canvas,進而完成在屏幕上繪制View的工作鲁猩。通過surfaceHolder可以將Camera和surface連接起來坎怪,當camera和surface連接后,camera獲得的預覽幀數(shù)據(jù)就可以通過surface顯示在屏幕上了廓握。
setPrameters設置相機參數(shù)搅窿,包括前后攝像頭,閃光燈模式隙券、聚焦模式男应、預覽和拍照尺寸等。
startPreview():開始預覽娱仔,將camera底層硬件傳來的預覽幀數(shù)據(jù)顯示在綁定的surface上沐飘。
stopPreview():停止預覽,關閉camra底層的幀數(shù)據(jù)傳遞以及surface上的繪制牲迫。
release():釋放Camera實例
takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):這個是實現(xiàn)相機拍照的主要方法耐朴,包含了三個回調(diào)參數(shù)。shutter是快門按下時的回調(diào)盹憎,raw是獲取拍照原始數(shù)據(jù)的回調(diào)筛峭,jpeg是獲取經(jīng)過壓縮成jpg格式的圖像數(shù)據(jù)的回調(diào)。
SurfaceView :用于繪制相機預覽圖像的類陪每,提供給用戶實時的預覽圖像影晓。普通的view以及派生類都是共享同一個surface的镰吵,所有的繪制都必須在UI線程中進行。而surfaceview是一種比較特殊的view俯艰,它并不與其他普通view共享surface捡遍,而是在內(nèi)部持有了一個獨立的surface,surfaceview負責管理這個surface的格式、尺寸以及顯示位置。由于UI線程還要同時處理其他交互邏輯,因此對view的更新速度和幀率無法保證葡粒,而surfaceview由于持有一個獨立的surface域蜗,因而可以在獨立的線程中進行繪制,因此可以提供更高的幀率收奔。自定義相機的預覽圖像由于對更新速度和幀率要求比較高,所以比較適合用surfaceview來顯示。
SurfaceHolder :surfaceholder是控制surface的一個抽象接口续挟,它能夠控制surface的尺寸和格式,修改surface的像素侥衬,監(jiān)視surface的變化等等诗祸,surfaceholder的典型應用就是用于surfaceview中。surfaceview通過getHolder()方法獲得surfaceholder 實例轴总,通過后者管理監(jiān)聽surface 的狀態(tài)直颅。
SurfaceHolder.Callback 接口 :負責監(jiān)聽surface狀態(tài)變化的接口,有三個方法:
surfaceCreated(SurfaceHolder holder):在surface創(chuàng)建后立即被調(diào)用怀樟。在開發(fā)自定義相機時功偿,可以通過重載這個函數(shù)調(diào)用camera.open()、camera.setPreviewDisplay()往堡,來實現(xiàn)獲取相機資源械荷、連接camera和surface等操作。
surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface發(fā)生format或size變化時調(diào)用虑灰。在開發(fā)自定義相機時吨瞎,可以通過重載這個函數(shù)調(diào)用camera.startPreview來開啟相機預覽,使得camera預覽幀數(shù)據(jù)可以傳遞給surface穆咐,從而實時顯示相機預覽圖像颤诀。
surfaceDestroyed(SurfaceHolder holder):在surface銷毀之前被調(diào)用。在開發(fā)自定義相機時庸娱,可以通過重載這個函數(shù)調(diào)用camera.stopPreview()着绊,camera.release()來實現(xiàn)停止相機預覽及釋放相機資源等操作。
Camera控制拍照的過程
調(diào)用Camera的open()方法打開相機熟尉。
調(diào)用Camera的getParameters()獲取拍照參數(shù)归露,該方法返回一個Cmera.Parameters對象。
調(diào)用Camera.Parameters對象對照相的參數(shù)進行設置斤儿。
調(diào)用Camera的setParameters()剧包,并將Camera.Parameters對象作為參數(shù)傳入恐锦,這樣就可以對拍照進行參數(shù)控制,Android2.3.3以后不用設置疆液。
調(diào)用Camerade的startPreview()的方法開始預覽取景一铅,在之前需要調(diào)用Camera的setPreviewDisplay(SurfaceHolder holder)設置使用哪個SurfaceView來顯示取得的圖片。
調(diào)用Camera的takePicture()方法進行拍照堕油。
程序結束時潘飘,要調(diào)用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.
大多數(shù)相機程序會鎖定預覽為橫屏狀態(tài)卜录,因為該方向是相機傳感器的自然方向。當然這一設定并不會阻止我們?nèi)ヅ呢Q屏的照片眶明,因為設備的方向信息會被記錄在EXIF頭中艰毒。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下,改變預覽的方向搜囱。然而丑瞧,對于Android API Level 14及以下版本的系統(tǒng),在改變方向之前蜀肘,我們必須先停止預覽绊汹,然后再去重啟它。
SurfaceView預覽圖像拉伸變形幌缝,拍攝照片尺寸不對
說明這個問題之前灸促,同樣先說一下幾個跟相機有關的尺寸诫欠。
SurfaceView尺寸 :即自定義相機應用中用于顯示相機預覽圖像的View的尺寸涵卵,當它鋪滿全屏時就是屏幕的大小。這里surfaceview顯示的預覽圖像暫且稱作手機預覽圖像荒叼。
Previewsize :相機硬件提供的預覽幀數(shù)據(jù)尺寸轿偎。預覽幀數(shù)據(jù)傳遞給SurfaceView,實現(xiàn)預覽圖像的顯示被廓。這里預覽幀數(shù)據(jù)對應的預覽圖像暫且稱作相機預覽圖像坏晦。
Picturesize :相機硬件提供的拍攝幀數(shù)據(jù)尺寸。拍攝幀數(shù)據(jù)可以生成位圖文件嫁乘,最終保存成.jpg或者.png等格式的圖片昆婿。這里拍攝幀數(shù)據(jù)對應的圖像稱作相機拍攝圖像。圖4說明了以上幾種圖像及照片之間的關系蜓斧。手機預覽圖像是直接提供給用戶看的圖像仓蛆,它由相機預覽圖像生成,拍攝照片的數(shù)據(jù)則來自于相機拍攝圖像挎春。
原因是沒有正確設置比例 parameter.setPictureSize(width,height)看疙,這個比例不是你決定的豆拨,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機支持的尺寸。
/*** 設置照片格式*/
private void setParameter() {
Camera.Parameters parameters = camera.getParameters(); // 獲取各項參數(shù)
parameters.setPictureFormat(PixelFormat.JPEG); // 設置圖片格式
parameters.setJpegQuality(100); // 設置照片質(zhì)量//獲得相機支持的照片尺寸,選擇合適的尺寸
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 相機硬件有個特殊設定能庆,就是對于前置攝像頭施禾,在展示預覽視圖時采用類似鏡面的效果,顯示的是攝像頭成像的鏡像搁胆。而拍攝出的照片則仍采用攝像頭成像弥搞。看到這里渠旁,大家可能會有些懷疑拓巧,不妨現(xiàn)在就試試自己 Android 手機上的前置攝像頭,對比下預覽圖像和拍攝出照片的區(qū)別一死。這是由于底層相機在傳遞前置攝像頭預覽數(shù)據(jù)時做了水平翻轉變換肛度,即將x方向鏡像翻轉180度。這個變化對之前豎屏預覽的方向也會造成影響投慈,本來對于后置攝像頭旋轉90度即可使預覽視圖正確承耿,而對前置攝像頭,如果也旋轉90度的話伪煤,看到的預覽圖像則是上下顛倒的(因為x方向翻轉了180度)加袋,因此必須再旋轉180度,才能顯示正確抱既。
解決方案职烧,在保存圖片的時候根據(jù)選擇的攝像頭做對應的翻轉。
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);
同時在開發(fā)的過程中發(fā)現(xiàn)了一個有趣的東西防泵,我們用前置攝像頭拍出來的照片其實是左右翻轉的蚀之。但我用小米自帶的相機測試發(fā)現(xiàn),當攝像頭中有人臉出現(xiàn)的時候捷泞,相機會做左右翻轉的操作足删,以給用戶更好的體驗。
SurfaceView預覽圖像拉伸變形锁右,拍攝照片尺寸不對
說明這個問題之前失受,同樣先說一下幾個跟相機有關的尺寸。
SurfaceView尺寸 :即自定義相機應用中用于顯示相機預覽圖像的View的尺寸咏瑟,當它鋪滿全屏時就是屏幕的大小拂到。這里surfaceview顯示的預覽圖像暫且稱作手機預覽圖像。
Previewsize :相機硬件提供的預覽幀數(shù)據(jù)尺寸码泞。預覽幀數(shù)據(jù)傳遞給SurfaceView兄旬,實現(xiàn)預覽圖像的顯示。這里預覽幀數(shù)據(jù)對應的預覽圖像暫且稱作相機預覽圖像浦夷。
Picturesize :相機硬件提供的拍攝幀數(shù)據(jù)尺寸辖试。拍攝幀數(shù)據(jù)可以生成位圖文件辜王,最終保存成.jpg或者.png等格式的圖片。這里拍攝幀數(shù)據(jù)對應的圖像稱作相機拍攝圖像罐孝。圖4說明了以上幾種圖像及照片之間的關系呐馆。手機預覽圖像是直接提供給用戶看的圖像,它由相機預覽圖像生成莲兢,拍攝照片的數(shù)據(jù)則來自于相機拍攝圖像汹来。
原因是沒有正確設置比例 parameter.setPictureSize(width,height),這個比例不是你決定的改艇,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機支持的尺寸收班。
前置攝像頭的鏡像效果
Android 相機硬件有個特殊設定,就是對于前置攝像頭谒兄,在展示預覽視圖時采用類似鏡面的效果摔桦,顯示的是攝像頭成像的鏡像。而拍攝出的照片則仍采用攝像頭成像承疲×诟看到這里,大家可能會有些懷疑燕鸽,不妨現(xiàn)在就試試自己 Android 手機上的前置攝像頭兄世,對比下預覽圖像和拍攝出照片的區(qū)別。這是由于底層相機在傳遞前置攝像頭預覽數(shù)據(jù)時做了水平翻轉變換啊研,即將x方向鏡像翻轉180度御滩。這個變化對之前豎屏預覽的方向也會造成影響,本來對于后置攝像頭旋轉90度即可使預覽視圖正確党远,而對前置攝像頭削解,如果也旋轉90度的話,看到的預覽圖像則是上下顛倒的(因為x方向翻轉了180度)麸锉,因此必須再旋轉180度钠绍,才能顯示正確舆声。
解決方案花沉,在保存圖片的時候根據(jù)選擇的攝像頭做對應的翻轉。
---
更多了解媳握,可關注公眾號:人人懂編程
![微信公眾號:人人懂編程](https://upload-images.jianshu.io/upload_images/2471034-0dce8137109d82b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)