Android多媒體開發 Pro Android Media 第二章 創建自定義相機應用 1

在上一章中,我們看瞭如何利用Android內置的相機應用為其他應用提供一個現成的照片拍攝組件。它提供標準界面給最終用戶,對我們程序員而言又簡單直接,不過它也因此缺乏靈活度。例如,如果我們想要我們的相機應用支持延時拍攝,使用內置應用就不好實現。

幸運的是,使用內置應用並不是訪問攝像頭的唯一途徑。底層硬件的開放程度以及系統提供的訪問方法,對我們和相機應用來說是一樣的,我們可以在任意類型的應用中使用這些功能。

在這一章,我們將使用底層Camera類,創建一個拍照應用,以此來學習如何利用 Android 提供給我們的功能。為此我們將逐步創建一些不同的應用:

簡單的相機應用具有倒計時計時器的相機應用具備定時拍照功能的相機應用

Camera類的使用

我們使用Android的Camera類來訪問設備上攝像頭。我們使用它來采集圖像,它的嵌套類Camera.Parameters來設置它的各種屬性,如是否打開閃光燈,給白平衡設置一個合適的值。
https://developer.android.com/reference/android/hardware/Camera.html

Camera權限

為瞭使用Camera類來拍攝照片,我們需要在AndroidManifest.xml文件中聲明我們需要CAMERA權限。

預覽Surface

在開始使用Camera之前,我們還需創建某類Surface供Camera繪制取景器或者預覽圖像。Surface是一個抽象類,代表一個可以繪制圖形或者圖像的區域。取得一個可繪制的Surface的簡單方法是使用SurfaceView。SurfaceView是一個具體類,它在標準的View中提供Surface。

要在佈局中指定SurfaceView,我們隻需在任何常規佈局XML中使用即可。這裡是一個基本的佈局,在LinearLayout中指定瞭一個SurfaceView,供Camera做預覽。

  
  
    
    
     
 

在我們的代碼中,為瞭將SurfaceView和Camera聯系起來,我們需要把SurfaceHolder加進來。SurfaceHolder類扮演瞭Surface監視器的角色,通過回調接口,我們知道Surface什麼時候創建,銷毀,或者改變。SurfaceView類提供瞭函數getHolder,我們可以通過它方便地取得其Surface的SurfaceHolder。

這是獲取佈局XML中聲明的SurfaceView,及其SurfaceHolder的代碼片段。並且,它還將此Surface設置為Push型Surface,其意思是它的繪制緩沖區是由外部維護的。在這個例子中,緩沖區由 Camera 類管理的,Camera預覽需要push型Surface。

SurfaceView cameraView = (CameraView) this.findViewById(R.id.CameraView);  
SurfaceHolder surfaceHolder = cameraView.getHolder();  
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 

另外,我們要在activity裡實現SurfaceHolder.Callback接口。這樣,我們的activity就能在Surface被創建,改變和被銷毀時得到通知。為實現這個回調,我們加入下列方法。

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {}  
public void surfaceCreated(SurfaceHolder holder) {}  
public void surfaceDestroyed(SurfaceHolder holder) {} 

最後,我需要告知SurfaceHolder用此activity作為其Callback的實現類。

surfaceHolder.addCallback(this);

現在,我們的activity看起來應該像這樣。

package com.apress.proandroidmedia.ch2.snapshot;  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.SurfaceHolder;  
import android.view.SurfaceView;  

public class SnapShot extends Activity implements SurfaceHolder.Callback {  
    SurfaceView cameraView;  
    SurfaceHolder surfaceHolder;  

    @Override  public void onCreate(Bundle savedInstanceState)  {  
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        cameraView = (SurfaceView) this.findViewById(R.id.CameraView);
        surfaceHolder = cameraView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(this);
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    }
    public void surfaceCreated(SurfaceHolder holder) {
    }
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
} 

相機實現

現在,activity和預覽Surface都已經就緒,我們可以開始使用Camera對象瞭。當Surface被創建時,因SorfaceHolder.Callback的緣故,觸發surfaceCreated方法的調用。在該函數中,我們通過調用Camera的靜態函數open,取得一個Camera對象。

Camera camera;
public void surfaceCreated(SurfaceHolder holder) {  
    camera = Camera.open();

接著,我們將Camera的預覽設置到由回調函數傳入的SurfaceHolder中顯示。調用設置函數需要包含在try catch塊中,因為它可能會拋出一個IOException。如果真有異常拋出,我們要釋放Camera,否則我們占用攝像頭硬件資源,會影響其他應用使用。

    try 
    {
        camera.setPreviewDisplay(holder);  
    }  
    catch (IOException exception)  
    {  
        camera.release();
    }

最後,我們啟動Camera預覽。

    camera.startPreview();
}

同樣,在surfaceDestoryed中,我們也要釋放Camera。我們首先調用stopPreview,確保完成所有清理工作。

public void surfaceDestroyed(SurfaceHolder holder) {
   camera.stopPreview();
   camera.release();
}

運行這段代碼,你可能會發現預覽畫面有些奇怪。預覽圖像逆時針旋轉瞭90度,如圖2-1所示。

vcD4KPHA+PHN0cm9uZz7NvDItMS48L3N0cm9uZz4gIMnjz/HNt9SkwMCjrND916rByzkwtsg8YnI+CjwvcD4KPHA+t6LJ+tD916q1xNSt0vLKx0NhbWVyYbzZtqi3vc/yysfLrsa9u/K64c/ytcSho77A1f3Q/deq1+688rWltcS3vbeoysfIw87Sw8e1xGFjdGl2aXR50tS64c/yxKPKvbP2z9aho86qtMujrM7Sw8fU2mFjdGl2aXR5tcRvbkNyZWF0Zbe9t6jW0NT2vNPPwsPmtcS0+sLroaM8YnI+CjwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ==”brush:java;”> @Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
現在,我們的相機預覽正常顯示瞭,如圖2-2. 不幸的是,我們的應用停留在瞭橫向模式

圖2-2. 攝像頭橫向預覽

設置攝像頭參數

前面提到過,Camera類有一個嵌套類Camera.Parameters。這個類有一系列重要的屬性或者設置可以用來改變Camera的運行。其中一個能馬上幫助我們解決預覽的旋轉、橫向問題。

相機使用的參數可以像下面這樣修改:

Camera.Parameters parameters = camera.getParameters();

parameters.set("some parameter", "some value");
// 或者
parameters.set("some parameter", some_int);

camera.setParameters(parameters);

有兩種不同的通用的 Parameters.set 方法。第一個采用采用字符串作為參數名稱和值,第二個采用字符串作名稱但值是一個整數。

參數的設置位於surfaceCreated方法中,放在Camera創建並指定其預覽Surface之後。這裡演示瞭我們如何使用參數來請求相機縱向而非橫向顯示。

public void surfaceCreated(SurfaceHolder holder)
{
    camera = Camera.open();
    try {
        Camera.Parameters parameters = camera.getParameters();
        if (this.getResources().getConfiguration().orientation 
            !=  Configuration.ORIENTATION_LANDSCAPE)
       {
            // 這是一個沒有文檔說明,但是廣為人知的功能
            parameters.set("orientation", "portrait");
          
            // Android 2.2 及以上版本
            //camera.setDisplayOrientation(90);

            // Android 2.0及以上,可取消註釋
            //parameters.setRotation(90);
        }
        else 
        {
             // 這是一個沒有文檔說明,但是廣為人知的功能
             parameters.set("orientation", "landscape"); 

             // Android 2.2 及以上版本
             //camera.setDisplayOrientation(0); 

             // Android 2.0及以上,可取消註釋
             //parameters.setRotation(0);
        }
      
      camera.setParameters(parameters);
      camera.setPreviewDisplay(holder);  
    }
    catch (IOException exception)
    {
        camera.release();
        Log.v(LOGTAG,exception.getMessage());
    }
    camera.startPreview();
}

上述代碼首先檢查該設備的配置 (通過調用 Context.getResources().getConfiguration()) 來看看當前的方向是什麼。如果不是橫向,它將設置 Camera.Parameters 的 “orientation” 為 “portrait”。此外,調用 Camera.Parameters 的 setRotation 方法,傳入參數90度。這個方法,包含在 API 級別 5 (版本 2.0)及以上版本,它實際沒有做任何旋轉 ;而是告訴 Camera,在 EXIF 數據中指定圖像在顯示時,旋轉
90 度。如果不寫這一行,在其他應用程序中查看此圖像時,它可能會橫向顯示。

註意: 使用Camera.Parameter來修改Camera旋轉屬性的方法,針對的是Android 2.1 及以下版本。在 Android 2.2 版本,Camera類引入瞭一個新方法:setDisplayOrientation(int degrees)。這種方法接受一個整數做參數,表示圖像應旋轉的度數。有效的度數隻有0、 90、 180、 270。

大多數可以或者應當修改的參數,都有特定的方法與之關聯。我們可以從 setRotation 方法看到,它們遵循的 Java getter 和 setter 設計模式。例如,設置相機的閃光模式可以用getFlashMode (Camera.Parameters.FLASH_MODE_AUTO),獲取當前值可以用 getFlashMode(),而不必通過通用Parameters.set 方法來完成。

從 Android 2.0開始,一個有趣的屬性允許我們更換顏色效果。現在我們用它來做演示。它的Getter 和 setter 是 getColorEffect 和 setColorEffect。還有一個 getSupportedColorEffects 方法,返回一個字符串對象列表,表示指定設備支持的各種顏色效果。事實上,所有具有 getter 和 setter方法的屬性都有跟這個類似的方法,用於確保在使用該功能之前,該功能可用。

Camera.Parameters parameters = camera.getParameters();
List colorEffects = parameters.getSupportedColorEffects();
Iterator cei = colorEffects.iterator();


while (cei.hasNext())
{
    String currentEffect = cei.next();
    Log.v("SNAPSHOT","Checking " + currentEffect);


    if (currentEffect.equals(Camera.Parameters.EFFECT_SOLARIZE))
    {
        Log.v("SNAPSHOT","Using SOLARIZE");
        parameters.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE);
        break;
    }
}
Log.v("SNAPSHOT","Using Effect: " + parameters.getColorEffect());
camera.setParameters(parameters);

在上述代碼中,我們首先通過 getSupportedColorEffect 方法查詢 Camera.Parameters 對象支持哪些效果。然後我們使用一個迭代器來遍歷效果列表,檢查其中是否有我們想要的效果,這個例子中我們要的是 Camera.Parameters.EFFECT_SOLARIZE。如果它出現在列表中,那麼表示 Camera 是支持它的。我們可以繼續向前,調用 Camera.Parameters 對象的setColorEffect方法,傳入曝光過度參數。圖 2-3 顯示 Camera.Parameters.EFFECT_SOLARIZE
生效。

圖 2-3. 攝像頭過度曝光預覽圖像

其他可能的效果也作為常量列在 Camera.Parameter 類中:

EFFECT_NONE EFFECT_MONOEFFECT_NEGATIVEEFFECT_SOLARIZEEFFECT_SEPIAEFFECT_POSTERIZEEFFECT_WHITEBOARDEFFECT_BLACKBOARDEFFECT_AQUA

還存在類似常量, 分別用於antibanding、 閃光模式、 焦點模式、 場景模式和白平衡。

發佈留言