6.android 自定義相機(jī)詳解(markdown版)

情景

開發(fā)過程中,經(jīng)常會(huì)遇到自定義開發(fā)相機(jī)。下面詳細(xì)介紹:

Android中開發(fā)相機(jī)的兩種方式

Android系統(tǒng)提供了兩種使用手機(jī)相機(jī)資源實(shí)現(xiàn)拍攝功能的方法,一種是直接通過Intent調(diào)用系統(tǒng)相機(jī)組件欢顷,這種方法快速方便,適用于直接獲得照片的場景冀自,如上傳相冊,微博秒啦、朋友圈發(fā)照片等熬粗。另一種是使用相機(jī)API來定制自定義相機(jī),這種方法適用于需要定制相機(jī)界面或者開發(fā)特殊相機(jī)功能的場景余境,如需要對照片做裁剪驻呐、濾鏡處理,添加貼紙芳来,表情含末,地點(diǎn)標(biāo)簽等。

1.調(diào)用系統(tǒng)自帶相機(jī)

關(guān)于系統(tǒng)自帶相機(jī)的調(diào)用非常簡單即舌,這里我就不過多敘述了佣盒,具體可以參考谷歌的Training。我只說容易被大家忽視的幾個(gè)點(diǎn):

如果我們的應(yīng)用使用相機(jī)侥涵,但相機(jī)并不是應(yīng)用的正常運(yùn)行所必不可少的組件沼撕,可以將權(quán)限聲明中的android:required設(shè)置為”false”。這樣的話芜飘,Google Play 也會(huì)允許沒有相機(jī)的設(shè)備下載該應(yīng)用务豺。當(dāng)然我們有必要在使用相機(jī)之前通過調(diào)用hasSystemFeature(PackageManager.FEATURE_CAMERA)方法來檢查設(shè)備上是否有相機(jī)。如果沒有嗦明,我們應(yīng)該禁用和相機(jī)相關(guān)的功能笼沥!

在調(diào)用startActivityForResult()方法之前,先調(diào)用resolveActivity()娶牌,這個(gè)方法會(huì)返回能處理該Intent的第一個(gè)Activity(譯注:即檢查有沒有能處理這個(gè)Intent的Activity)奔浅。執(zhí)行這個(gè)檢查非常重要,因?yàn)槿绻谡{(diào)用startActivityForResult()時(shí)诗良,沒有應(yīng)用能處理你的Intent汹桦,應(yīng)用將會(huì)崩潰。所以只要返回結(jié)果不為null鉴裹,使用該Intent就是安全的舞骆。

使用Android框架所提供的API來直接控制相機(jī)硬件

使用API來控制相機(jī)我們需要用到關(guān)鍵類和接口

使用Camera對象來控制相機(jī)

使用SurfaceView來展現(xiàn)照相機(jī)采集的圖像

通過surfaceholder來控制surfac的尺寸和格式,修改surface的像素径荔,監(jiān)視surface的變化等等

通過SurfaceHolder.Callback 接口督禽,監(jiān)聽surface狀態(tài)變化

接下來我們分為以下三部分來介紹:關(guān)鍵類以及接口的作用和方法,Camera控制拍照步驟总处,自定義相機(jī)容易踩到的坑以及解決辦法狈惫。

API說明

Camera :最主要的類,用于管理和操作camera資源鹦马。它提供了完整的相機(jī)底層接口胧谈,支持相機(jī)資源切換,設(shè)置預(yù)覽/拍攝尺寸荸频,設(shè)定光圈第岖、曝光、聚焦等相關(guān)參數(shù)试溯,獲取預(yù)覽/拍攝幀數(shù)據(jù)等功能蔑滓,主要方法有以下這些:
open():獲取camera實(shí)例。
setPreviewDisplay(SurfaceHolder):綁定繪制預(yù)覽圖像的surface遇绞。surface是指向屏幕窗口原始圖像緩沖區(qū)(raw buffer)的一個(gè)句柄键袱,通過它可以獲得這塊屏幕上對應(yīng)的canvas,進(jìn)而完成在屏幕上繪制View的工作摹闽。通過surfaceHolder可以將Camera和surface連接起來蹄咖,當(dāng)camera和surface連接后,camera獲得的預(yù)覽幀數(shù)據(jù)就可以通過surface顯示在屏幕上了付鹿。
setPrameters設(shè)置相機(jī)參數(shù)澜汤,包括前后攝像頭蚜迅,閃光燈模式、聚焦模式俊抵、預(yù)覽和拍照尺寸等谁不。
startPreview():開始預(yù)覽,將camera底層硬件傳來的預(yù)覽幀數(shù)據(jù)顯示在綁定的surface上徽诲。
stopPreview():停止預(yù)覽刹帕,關(guān)閉camra底層的幀數(shù)據(jù)傳遞以及surface上的繪制。
release():釋放Camera實(shí)例
takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):這個(gè)是實(shí)現(xiàn)相機(jī)拍照的主要方法谎替,包含了三個(gè)回調(diào)參數(shù)偷溺。shutter是快門按下時(shí)的回調(diào),raw是獲取拍照原始數(shù)據(jù)的回調(diào)钱贯,jpeg是獲取經(jīng)過壓縮成jpg格式的圖像數(shù)據(jù)的回調(diào)挫掏。

SurfaceView :用于繪制相機(jī)預(yù)覽圖像的類,提供給用戶實(shí)時(shí)的預(yù)覽圖像秩命。普通的view以及派生類都是共享同一個(gè)surface的砍濒,所有的繪制都必須在UI線程中進(jìn)行。而surfaceview是一種比較特殊的view硫麻,它并不與其他普通view共享surface爸邢,而是在內(nèi)部持有了一個(gè)獨(dú)立的surface,surfaceview負(fù)責(zé)管理這個(gè)surface的格式、尺寸以及顯示位置拿愧。由于UI線程還要同時(shí)處理其他交互邏輯杠河,因此對view的更新速度和幀率無法保證,而surfaceview由于持有一個(gè)獨(dú)立的surface浇辜,因而可以在獨(dú)立的線程中進(jìn)行繪制券敌,因此可以提供更高的幀率。自定義相機(jī)的預(yù)覽圖像由于對更新速度和幀率要求比較高柳洋,所以比較適合用surfaceview來顯示待诅。 SurfaceHolder :surfaceholder是控制surface的一個(gè)抽象接口,它能夠控制surface的尺寸和格式熊镣,修改surface的像素卑雁,監(jiān)視surface的變化等等,surfaceholder的典型應(yīng)用就是用于surfaceview中绪囱。surfaceview通過getHolder()方法獲得surfaceholder 實(shí)例测蹲,通過后者管理監(jiān)聽surface 的狀態(tài)。 SurfaceHolder.Callback 接口 :負(fù)責(zé)監(jiān)聽surface狀態(tài)變化的接口鬼吵,有三個(gè)方法:

surfaceCreated(SurfaceHolder holder):在surface創(chuàng)建后立即被調(diào)用扣甲。在開發(fā)自定義相機(jī)時(shí),可以通過重載這個(gè)函數(shù)調(diào)用camera.open()齿椅、camera.setPreviewDisplay()琉挖,來實(shí)現(xiàn)獲取相機(jī)資源启泣、連接camera和surface等操作。

surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface發(fā)生format或size變化時(shí)調(diào)用示辈。在開發(fā)自定義相機(jī)時(shí)寥茫,可以通過重載這個(gè)函數(shù)調(diào)用camera.startPreview來開啟相機(jī)預(yù)覽,使得camera預(yù)覽幀數(shù)據(jù)可以傳遞給surface顽耳,從而實(shí)時(shí)顯示相機(jī)預(yù)覽圖像坠敷。

surfaceDestroyed(SurfaceHolder holder):在surface銷毀之前被調(diào)用妙同。在開發(fā)自定義相機(jī)時(shí)射富,可以通過重載這個(gè)函數(shù)調(diào)用camera.stopPreview(),camera.release()來實(shí)現(xiàn)停止相機(jī)預(yù)覽及釋放相機(jī)資源等操作粥帚。

Camera控制拍照的過程

調(diào)用Camera的open()方法打開相機(jī)胰耗。 調(diào)用Camera的getParameters()獲取拍照參數(shù),該方法返回一個(gè)Cmera.Parameters對象芒涡。 調(diào)用Camera.Parameters對象對照相的參數(shù)進(jìn)行設(shè)置柴灯。 調(diào)用Camera的setParameters(),并將Camera.Parameters對象作為參數(shù)傳入费尽,這樣就可以對拍照進(jìn)行參數(shù)控制赠群,Android2.3.3以后不用設(shè)置。 調(diào)用Camerade的startPreview()的方法開始預(yù)覽取景旱幼,在之前需要調(diào)用Camera的setPreviewDisplay(SurfaceHolder holder)設(shè)置使用哪個(gè)SurfaceView來顯示取得的圖片查描。 調(diào)用Camera的takePicture()方法進(jìn)行拍照。 程序結(jié)束時(shí)柏卤,要調(diào)用Camera的stopPreview()方法停止預(yù)覽冬三,并且通過Camera.release()來釋放資源。

預(yù)覽方向

先看下官方文檔的說明 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ù)相機(jī)程序會(huì)鎖定預(yù)覽為橫屏狀態(tài)缘缚,因?yàn)樵摲较蚴窍鄼C(jī)傳感器的自然方向勾笆。當(dāng)然這一設(shè)定并不會(huì)阻止我們?nèi)ヅ呢Q屏的照片,因?yàn)樵O(shè)備的方向信息會(huì)被記錄在EXIF頭中桥滨。setCameraDisplayOrientation()方法可以讓你在不影響照片拍攝過程的情況下窝爪,改變預(yù)覽的方向。然而齐媒,對于Android API Level 14及以下版本的系統(tǒng)酸舍,在改變方向之前,我們必須先停止預(yù)覽里初,然后再去重啟它啃勉。

特別提示

SurfaceView預(yù)覽圖像拉伸變形,拍攝照片尺寸不對 說明這個(gè)問題之前双妨,同樣先說一下幾個(gè)跟相機(jī)有關(guān)的尺寸淮阐。 SurfaceView尺寸 :即自定義相機(jī)應(yīng)用中用于顯示相機(jī)預(yù)覽圖像的View的尺寸叮阅,當(dāng)它鋪滿全屏?xí)r就是屏幕的大小。這里surfaceview顯示的預(yù)覽圖像暫且稱作手機(jī)預(yù)覽圖像泣特。 Previewsize :相機(jī)硬件提供的預(yù)覽幀數(shù)據(jù)尺寸浩姥。預(yù)覽幀數(shù)據(jù)傳遞給SurfaceView,實(shí)現(xiàn)預(yù)覽圖像的顯示状您。這里預(yù)覽幀數(shù)據(jù)對應(yīng)的預(yù)覽圖像暫且稱作相機(jī)預(yù)覽圖像勒叠。 Picturesize :相機(jī)硬件提供的拍攝幀數(shù)據(jù)尺寸。拍攝幀數(shù)據(jù)可以生成位圖文件膏孟,最終保存成.jpg或者.png等格式的圖片眯分。這里拍攝幀數(shù)據(jù)對應(yīng)的圖像稱作相機(jī)拍攝圖像。圖4說明了以上幾種圖像及照片之間的關(guān)系柒桑。手機(jī)預(yù)覽圖像是直接提供給用戶看的圖像弊决,它由相機(jī)預(yù)覽圖像生成,拍攝照片的數(shù)據(jù)則來自于相機(jī)拍攝圖像魁淳。 原因是沒有正確設(shè)置比例 parameter.setPictureSize(width,height)飘诗,這個(gè)比例不是你決定的,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機(jī)支持的尺寸界逛。

    /*** 設(shè)置照片格式*/
    private void setParameter() {

        Camera.Parameters parameters = camera.getParameters(); // 獲取各項(xiàng)參數(shù)

        parameters.setPictureFormat(PixelFormat.JPEG); // 設(shè)置圖片格式

        parameters.setJpegQuality(100); // 設(shè)置照片質(zhì)量//獲得相機(jī)支持的照片尺寸,選擇合適的尺寸

        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 相機(jī)硬件有個(gè)特殊設(shè)定昆稿,就是對于前置攝像頭,在展示預(yù)覽視圖時(shí)采用類似鏡面的效果息拜,顯示的是攝像頭成像的鏡像溉潭。而拍攝出的照片則仍采用攝像頭成像「盟荩看到這里岛抄,大家可能會(huì)有些懷疑,不妨現(xiàn)在就試試自己 Android 手機(jī)上的前置攝像頭狈茉,對比下預(yù)覽圖像和拍攝出照片的區(qū)別夫椭。這是由于底層相機(jī)在傳遞前置攝像頭預(yù)覽數(shù)據(jù)時(shí)做了水平翻轉(zhuǎn)變換,即將x方向鏡像翻轉(zhuǎn)180度氯庆。這個(gè)變化對之前豎屏預(yù)覽的方向也會(huì)造成影響蹭秋,本來對于后置攝像頭旋轉(zhuǎn)90度即可使預(yù)覽視圖正確,而對前置攝像頭堤撵,如果也旋轉(zhuǎn)90度的話仁讨,看到的預(yù)覽圖像則是上下顛倒的(因?yàn)閤方向翻轉(zhuǎn)了180度),因此必須再旋轉(zhuǎn)180度实昨,才能顯示正確洞豁。

解決方案:在保存圖片的時(shí)候根據(jù)選擇的攝像頭做對應(yīng)的翻轉(zhuǎn)。

 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);

同時(shí)在開發(fā)的過程中發(fā)現(xiàn)了一個(gè)有趣的東西,我們用前置攝像頭拍出來的照片其實(shí)是左右翻轉(zhuǎn)的丈挟。但我用小米自帶的相機(jī)測試發(fā)現(xiàn)刁卜,當(dāng)攝像頭中有人臉出現(xiàn)的時(shí)候,相機(jī)會(huì)做左右翻轉(zhuǎn)的操作曙咽,以給用戶更好的體驗(yàn)蛔趴。

SurfaceView預(yù)覽圖像拉伸變形,拍攝照片尺寸不對

說明這個(gè)問題之前例朱,同樣先說一下幾個(gè)跟相機(jī)有關(guān)的尺寸孝情。

SurfaceView尺寸 :即自定義相機(jī)應(yīng)用中用于顯示相機(jī)預(yù)覽圖像的View的尺寸,當(dāng)它鋪滿全屏?xí)r就是屏幕的大小洒嗤。這里surfaceview顯示的預(yù)覽圖像暫且稱作手機(jī)預(yù)覽圖像箫荡。

Previewsize :相機(jī)硬件提供的預(yù)覽幀數(shù)據(jù)尺寸。預(yù)覽幀數(shù)據(jù)傳遞給SurfaceView烁竭,實(shí)現(xiàn)預(yù)覽圖像的顯示菲茬。這里預(yù)覽幀數(shù)據(jù)對應(yīng)的預(yù)覽圖像暫且稱作相機(jī)預(yù)覽圖像吉挣。

Picturesize :相機(jī)硬件提供的拍攝幀數(shù)據(jù)尺寸派撕。拍攝幀數(shù)據(jù)可以生成位圖文件,最終保存成.jpg或者.png等格式的圖片睬魂。這里拍攝幀數(shù)據(jù)對應(yīng)的圖像稱作相機(jī)拍攝圖像终吼。圖4說明了以上幾種圖像及照片之間的關(guān)系。手機(jī)預(yù)覽圖像是直接提供給用戶看的圖像氯哮,它由相機(jī)預(yù)覽圖像生成际跪,拍攝照片的數(shù)據(jù)則來自于相機(jī)拍攝圖像。

原因是沒有正確設(shè)置比例 parameter.setPictureSize(width,height)喉钢,這個(gè)比例不是你決定的姆打,要先通過camera.getParameters().getSupportedPictureSizes()獲得手機(jī)支持的尺寸。

前置攝像頭的鏡像效果

Android 相機(jī)硬件有個(gè)特殊設(shè)定肠虽,就是對于前置攝像頭幔戏,在展示預(yù)覽視圖時(shí)采用類似鏡面的效果,顯示的是攝像頭成像的鏡像税课。而拍攝出的照片則仍采用攝像頭成像闲延。看到這里韩玩,大家可能會(huì)有些懷疑垒玲,不妨現(xiàn)在就試試自己 Android 手機(jī)上的前置攝像頭,對比下預(yù)覽圖像和拍攝出照片的區(qū)別找颓。這是由于底層相機(jī)在傳遞前置攝像頭預(yù)覽數(shù)據(jù)時(shí)做了水平翻轉(zhuǎn)變換合愈,即將x方向鏡像翻轉(zhuǎn)180度。這個(gè)變化對之前豎屏預(yù)覽的方向也會(huì)造成影響,本來對于后置攝像頭旋轉(zhuǎn)90度即可使預(yù)覽視圖正確佛析,而對前置攝像頭妇汗,如果也旋轉(zhuǎn)90度的話,看到的預(yù)覽圖像則是上下顛倒的(因?yàn)閤方向翻轉(zhuǎn)了180度)说莫,因此必須再旋轉(zhuǎn)180度杨箭,才能顯示正確。

解決方案储狭,在保存圖片的時(shí)候根據(jù)選擇的攝像頭做對應(yīng)的翻轉(zhuǎn)互婿。


更多了解,可關(guān)注公眾號:人人懂編程


微信公眾號:人人懂編程
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辽狈,一起剝皮案震驚了整個(gè)濱河市慈参,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刮萌,老刑警劉巖驮配,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異着茸,居然都是意外死亡壮锻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門涮阔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猜绣,“玉大人,你說我怎么就攤上這事敬特£希” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵伟阔,是天一觀的道長辣之。 經(jīng)常有香客問我,道長皱炉,這世上最難降的妖魔是什么怀估? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮娃承,結(jié)果婚禮上奏夫,老公的妹妹穿的比我還像新娘。我一直安慰自己历筝,他們只是感情好酗昼,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梳猪,像睡著了一般麻削。 火紅的嫁衣襯著肌膚如雪蒸痹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天呛哟,我揣著相機(jī)與錄音叠荠,去河邊找鬼。 笑死扫责,一個(gè)胖子當(dāng)著我的面吹牛榛鼎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鳖孤,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼者娱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苏揣?” 一聲冷哼從身側(cè)響起黄鳍,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎平匈,沒想到半個(gè)月后框沟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡增炭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年忍燥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弟跑。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡灾前,死狀恐怖防症,靈堂內(nèi)的尸體忽然破棺而出孟辑,到底是詐尸還是另有隱情,我是刑警寧澤蔫敲,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布饲嗽,位于F島的核電站,受9級特大地震影響奈嘿,放射性物質(zhì)發(fā)生泄漏貌虾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一裙犹、第九天 我趴在偏房一處隱蔽的房頂上張望尽狠。 院中可真熱鬧,春花似錦叶圃、人聲如沸袄膏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沉馆。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斥黑,已是汗流浹背揖盘。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锌奴,地道東北人兽狭。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鹿蜀,于是被迫代替她去往敵國和親椭符。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容