1. 程式人生 > >Android自定義相機超詳細講解

Android自定義相機超詳細講解

了解 catch 實現 4.4 required form 需要 eset 自己

Android自定義相機超詳細講解

轉載請標明出處: http://blog.csdn.net/vinicolor/article/details/49642861;

由於網上關於Android自定義相機的文章寫得不是太詳細,Google官方的文檔又說得不太容易理解,所以今天我來詳細講解一下Android自定義相機。

這篇文章主要寫給一些剛剛接觸Android的那些看官方API困難以及不太了解Android機制的同學們,所以熟練開發者可以繞道了。

最近在使用Camera類的時候發現居然被棄用了,API 21中出現了camera2這個類來代替camera類,但是筆者的手機才andorid 4.4,國內要用上6.0至少明年去了,所以本次還是講解Camera這個類。

首先是加入權限,這個直接按照google的api向導或者看api文檔會有詳細說明的,所以這裏不講了。

那麽接下來,使用相機我們總需要一個能夠看到圖像的地方吧,這裏Google叫我們使用SurfaceView這個類,那麽SurfaceView這個類是什麽呢,首先這個類是繼承View的,可以在將圖像繪制在屏幕上並顯示給用戶。其實能夠顯示的原因是SurfaceView中包含一個Surface對象,Surface是SurfaceView的可見部分,好了我們提到了Surface,又是一個讓很多人頭疼的概念,好吧讓我們重頭來講解。

首先我們在手機屏幕上看到的是這些畫面都可以算是View(當然SurfaceView也算View),那麽View是什麽?View其實就是手機內存中的一小塊區域,所謂顯示,就是顯卡等硬件將內存中的信息顯示在屏幕上的過程,這下我想大家應該清楚一點了吧,我們繼續,那我們說到的可見部分又是怎麽回事呢,其實我們看到的屏幕可以說是2維的,也就是長和寬,但是在它的內部其實是3維的,還有一個維度就是層Layer,也就是層的概念,用過Visio或者AutoCAD的同學應該很好理解,在畫圖的時候,上層有時會將下層的遮擋,我們看到的圖像就是這樣一層一層堆疊起來的,這當中有些層不可見,有些層部分可見,有些層完全可見,我們看到的就是它們之中可見的部分,而Surface就是SurfaceView中的一個可見的部分,我們在攝像或者拍照用的就是它顯示了。

了解了View的作用,我想有人會問了:為什麽不使用View,而用SurfaceView,首先在這裏我想說用View這是可以的,但是用SurfaceView會更好,SurfaceView中Google為攝像等方法重寫了很多方法,而且SurfaceView類是在一個新起的單獨線程中重新繪制畫面,而View是在UI線程上繪制畫面,可以想象,如果你用View來預覽圖像(當然你必須要重寫View中的大量方法來實現預覽,這是我們不願意看到的),那麽在攝像的時候你就什麽都別想做了,因為如果你打算更新UI的話,線程就可能會阻塞,你的APP就可能未響應了,Android系統就自動提示關閉了,這是用戶極其不好的體驗,而我們希望在攝像的也能更新UI,所以我們用SurfaceView類來預覽圖像。

接下來我們看官方給我們的代碼:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

首先別忘了最重要的事:就是獲取Camera實例,不然肯定會報空指針異常的,我們用Camera.open()方法來獲得實例對象,對於Andorid 2.3以前的手機其實是沒有前置攝像頭的,所以直接Camera.open()方法就行了,但是之後版本的手機擁有了前置攝像頭,所以手機有了兩個攝像頭,那麽Camera.open()就不能清楚表明到底開啟那個攝像頭了,所以Google提供了Camera.open(0)方法,這個方法其實就是指用手機的後置攝像頭,而前置攝像頭用Camera.open(1)方法,這樣就可以獲得Camera對象了。

需要註意的是:由於Android機制的緣故,Android相機只能夠被一個APP線程所綁定,也就是說如果你正在使用相機進行拍照攝像的時候,另一個程序便不能使用Camera類來啟用相機,而且會報出Can not release的錯誤,那麽如何判定你是否使用了相機呢,其實只要你使用了Camera.open()方法獲得了相機的示例,系統就認為你使用了相機,所以當你使用了完了相機一定記得要釋放相機的資源,不然別的應用程序用不了呀,我們可以使用 Camera.release()來釋放相機資源,Google官方的意見是重寫Activity的onPause()方法來Camera.release(),其實也可以重寫Activity的onBackPressed()方法來釋放相機,也就是用戶按Back鍵的時候釋放相機資源。

看了上面的講述我們知道了Surface其實就是對應的一個內存區域,而在內存區中的數據是有生存周期的,可以動態申請創建和銷毀,當然也會更新,於是就有了對內存區的操作,在本例中就是surfaceCreated/Changed/Destroyed,也就是創建/修改/銷毀,而3個操作放在一起就是Callback。
Surface代碼中要求我們implements SurfaceHolder.Callback,那麽為什麽要使用SurfaceHolder.Callback呢,callback 意思是回調,那麽它為什麽要回調呢?這裏我解釋一下回調,回調在大部分情況就是程序在運行到需要一個函數(但是它本身沒有)這裏就是程序需要查看一下它內部記錄的能處理這種情況的函數了,借用網上的比方:A人有能力做某件事但是現在不用他去做,所以他去登記一下自己的能力,到了需要用到他的時候,就會有人叫他去做,到程序裏面就是A人能做surfaceCreated/Changed/Destroyed 3件事,他去登記了,並有個統稱就叫SurfaceHolder.Callback,而在此程序中需要做這3件事,所以會Callback。而SurfaceHolder是什麽呢,它在本例中就好比用開發商,而SurfaceView就像一個建房子計劃負責人,它知道要首先要找到開發商,所以mHolder = getHolder()找開發商,而開發商就找到能夠有建房能力的這個人A,就是回調他登記的信息mHolder.addCallback(this),而A的能力可以比喻成surfaceCreated建房子,surfaceDestroyed拆房子,surfaceChanged裝修房子,故名思意,surfaceChanged只能在建房和拆房之間了。

mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
這是給mHolder設置緩存信息了,這個在Android 3.0之後就是自動設置的了,所以我們可以忽略這句代碼了。

好了到了surfaceCreated了,這個就是在屏幕創建時的準備了,首先我們需要將相機的預覽界面綁定到我們的可見區域SurfaceView上,而getHolder()得到的Holder是對Surface有絕對控制權的,所以我們使用了holder,這個方法也就是把SurfaceView跟Camera連接起來,準備一個實時的Camera圖像預覽界面。
mCamera.setPreviewDisplay(holder);
接下來就可以設置開始預覽了
mCamera.startPreview();
當然別高興了,這還沒完呢,程序在執行完了surfaceCreated後是肯定會接著執行surfaceChanged的,這個方法的意思就是只要屏幕改變就會調用一個它,在首次啟動程序時會調用surfaceCreated接著就會調用一次surfaceChanged,所以surfaceChanged方法在整個使用相機過程中必定至少調用一次,所以我們通常在surfaceChanged方法中進行判斷可見區域Surface是否正常,也控制當界面改變時相機的改變,如橫豎屏切換時相機的改變,下面這段代碼大家就應該能夠看懂了,其實意思就是當檢測到surface改變的時候檢測Surface是否正常,所有環節都是先停止相機的預覽,再根據Surface的變化,改變相機的相關屬性後再按如上述surfaceChanged方法中的mCamera.setPreviewDisplay(mHolder)mCamera.startPreview()啟動預覽即可。

需要說一句:如果你要使用相機的自動聚焦以及閃光燈等一系列功能也可以在surfaceChanged方法中設置,這裏貼出少量代碼:

Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();     
if(
focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);}
mCamera.setParameters(params);

主要就是先判斷手機有不有這個功能focusModes.contains(),然後再設置這個功能params.setFocusMode(),更多的功能設置可以在API中Camera.Parameters類中查看並使用。

可以拍照了

接下來我們該拍照了,那麽就面對怎麽將照片儲存的問題了,其實Google提供了mCamera.takePicture()方法,這個方法有3個參數,如下:

takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)

前兩個參數Camera.ShutterCallback是在拍照瞬間回調的,可以用來添加拍照聲音之類的,也可以在照相之後預覽給用戶看,詢問是否需要保留,Camera.PictureCallback則是返回一個原生的圖像數據,這兩個參數其實在正常使用中並不實用(如果要預覽我們也可以用Camera.PictureCallback中的數據來預覽),所以我們設null表示不用,Camera.PictureCallback這個才是我們需要的,它是返回一個經過壓縮的jpeg文件,正好適合我們的日常使用,所以我們選擇這個方法,具體代碼如下:

public void onClick(View v) {
    switch (v.getId()) {
       case R.id.button_capturePicture:
mCamera.takePicture(null, null, new Camera.PictureCallback() {
       @Override
public void onPictureTaken(byte[] data, Camera camera) {  

                        }
                    });

好了,其實這段代碼就比較明顯了,onPictureTaken是andorid自動回調的方法,onPictureTaken方法中的data參數就是獲取的照片的數據了,只需要一個輸出流將數據寫入到外部存儲就行了,這裏對於數據的輸出就不詳細講解了。當在調用takePicture的時候相機會自動停止預覽了,也就是圖像停止了,我們可以在重寫回調函數onPictureTaken中再次啟動預覽就行了:

mCamera.startPreview();

該攝像了:

攝影的代碼稍微麻煩一點,我們需要用到MediaRecorder這個類,首先我們獲得實例:
mMediaRecorder = new MediaRecorder();
對於使用攝像機我們需要將相機解鎖也就是: mCamera.unlock();
那麽我們到底要解鎖相機的什麽呢?原來相機在我們使用了Camera.open()後,就被綁定到了這個程序的進程上,那麽其他的進程自然也就訪問不了了,也就是我們需要用來錄制圖像和聲音的MediaRecorder類就無法使用Camera了,所以我們才需要解鎖,讓Camera能夠被MediaRecorder類使用,這個解鎖是暫時的,在使用後(也就是攝像完成之後)我們可以通過reconnect ()方法讓相機重新連接到我們的之前程序的進程上,當然了相機的解鎖機制在Android 4.0之後我們就不需要手動解鎖了,Andorid會自動幫我們完成的。
接下來我們就可以用MediaRecorder來連接相機了:
mMediaRecorder.setCamera(mCamera);
下面我們需要設置一些攝像時的資源了:

mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);           mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

setAudioSource就是設置在攝像時獲取聲音的組件,CAMCORDER大致就是相機中的感聲元件吧,當然我們也可以用MIC代替,也就是手機的麥克風了,setVideoSource就是設置攝像時獲取圖像的組件,這個無異議了,肯定是CAMERA相機了,那麽mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))呢,這是設置相機的配置文件,會讀入前面設置的setAudioSource和setVideoSource屬性,分配給我們說所的音頻和視頻,並設定錄制的質量,所以這個方法必須在setAudioSource和setVideoSource方法之後才可以調用,否則就會出錯哦。好了接下來我們要指定我們錄制的文件放在哪,setOutputFile()方法就是設置放在哪了,我們需要傳遞一個地址參數給它,也就是地址字符串了,註意了,地址需要加上拓展名如.mp4(其實在Android 2.2之前我們是需要setOutputFormat()也就是設置它的格式的,但是之後的版本Android就會自動幫我們完成了)
好了我們的準備工作都做完了(不少同學肯定會罵這個都多代碼才把準備工作工作做完-_-),我們調用mMediaRecorder.prepare()方法讓Android幫我們檢測之前的設置對不對,所以需要捕獲異常哦

mMediaRecorder.prepare();
  • 1

我們的攝像的數據Android會自動幫我們存儲到我們給予的路徑上。
準備完成了,我們就開始攝像吧!

mMediaRecorder.start();
  • 1

當然如果你要停止攝像記得先用stop()方法哦mMediaRecorder.stop();
然後釋放mMediaRecorder占用的系統資源:

if (mMediaRecorder != null) {
            mMediaRecorder.reset();   
            mMediaRecorder.release(); 
            mMediaRecorder = null;
            mCamera.lock();  
            }

mMediaRecorder.reset()就是重置你的mMediaRecorder配置信息,mMediaRecorder.release()也就是釋放mMediaRecorder所占用的資源,當然我們之前解鎖的相機,這個時候既然我們不用攝像了,我們的就給它鎖住mCamera.lock(),但是記得如果你還想要拍照或者再次攝像的話先不要調用Camera.release()來釋放相機的資源,否則你的預覽會立即關閉了,如果想要再次攝像就重復上面的準備步驟,再start()就行了。

Android自定義相機超詳細講解