2.android開發 自定義相機

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度,才能顯示正確。

解決方案,在保存圖片的時候根據選擇的攝像頭做對應的翻轉。

---

更多了解,可關注公眾號:人人懂編程

![微信公眾號:人人懂編程](https://upload-images.jianshu.io/upload_images/2471034-0dce8137109d82b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容