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

更改相機預覽大小

Camera.Parameters中另一個特別有用設置是設置預覽大小。正如使用其他設置,我們首先要查詢的參數對象,取得其支持列表。取得預覽尺寸列表之後,我們遍歷它,以確保在設置之前,我們想要設置的大小是相機支持的。

在本示例中,我們不設定精確的預定尺寸,而選擇一個設備支持,最接近且不大於預定值的尺寸。圖 2-4 顯示瞭此示例的輸出。

...
public static final int LARGEST_WIDTH = 200;
public static final int LARGEST_HEIGHT= 200;
... 

如同所有的 Camera.Parameters,我們在SurfaceCreated中,打開相機和設置其預覽顯示Surface之後,獲取和設置他們的值。

public void surfaceCreated(SurfaceHolder holder) 
{
    camera = Camera.open();
    try {  
        camera.setPreviewDisplay(holder);
        Camera.Parameters parameters = camera.getParameters();

我們將用兩個變量,記錄最接近,但小於我們的預定大小的值。

        int bestWidth = 0;
        int bestHeight = 0; 

然後我們取得設備所支持的預覽尺寸的列表。這將返回 Camera.Size 對象列表,供我們循環遍歷。

        List previewSizes = parameters.getSupportedPreviewSizes();
        if (previewSizes.size() > 1)  
        {  
            Iterator cei = previewSizes.iterator();
            while (cei.hasNext())  
            { 
                Camera.Size aSize = cei.next(); 

如果列表的當前尺寸,大於我們保存的最佳尺寸,並且小於等於 LARGEST_WIDTH 和 LARGEST_HEIGHT,那麼我們在 bestWidth 和 bestHeight 變量中保存當前高度和寬度,並繼續檢查。

                Log.v("SNAPSHOT","Checking " + aSize.width + " x " + aSize.height);
                if (aSize.width > bestWidth && aSize.width  bestHeight && aSize.height <= LARGEST_HEIGHT)
               {
                    // 到目前為止它是最大的而且沒有超過屏幕尺寸
                    bestWidth = aSize.width; 
                    bestHeight = aSize.height;
               }
            }

我們遍歷完相機支持的尺寸列表之後,我們肯定會得到某些值。如果我們的 bestHeight 和 bestWidth 變量等於 0,說明我們沒能找到符合我們需要的尺寸,又或者Camera隻支持一個固定的預覽大小,這兩種情況,我們不做處理。如果他們的值不為0,我們就以 bestWidth 和 bestHeight 為參數,調用 Camera.Parameters 的 setPreviewSize。

此外,我們還要告訴我們的相機預覽 SurfaceView 對象 cameraView,以此大小顯示預覽。如果SurfaceView 不改變大小,相機的預覽圖像將被扭曲或以極低的質量顯示。

            if (bestHeight != 0 && bestWidth != 0)
            { 
                Log.v("SNAPSHOT", "Using " + bestWidth + " x " + bestHeight);
                parameters.setPreviewSize(bestWidth, bestHeight);
                cameraView.setLayoutParams(new LinearLayout.LayoutParams( bestWidth,bestHeight));
            }
        }
        camera.setParameters(parameters);

設置參數之後,剩下的就是收尾工作。

    } 
    catch (IOException exception)
    {
        camera.release();
    }
}

圖2-4. 相機以小尺寸預覽

捕獲和保存圖像

使用Camera類捕獲圖像,我們得調用takePicture方法。此方法接收三個或四個參數,都為回調函數。使用takePicture方法的最簡單形式是所有參數都設為 null。但是在拍攝瞭圖像之後,沒法得到圖像的引用。至少要實現其中的一個回調函數。最安全的是 Camera.PictureCallback.onPictureTaken。當圖像采集壓縮就緒後,它一定會被調用。為此,我們讓我們的activity實現Camera.PictureCallback接口,添加onPictureTaken方法。

public class SnapShot extends Activity implements  SurfaceHolder.Callback, 
    Camera.PictureCallback { 
     public void onPictureTaken(byte[] data, Camera camera) {  } 

OnPictureTaken 方法有兩個參數。第一個是字節數組,為實際的JPEG圖像數據,第二個是捕獲圖像的Camera對象的引用。

因為給我們的是實際的 JPEG 數據,我們要保存它,隻需要將它寫到磁盤的某個地方就行瞭。我們已經知道,利用 MediaStore 指定它的位置和元數據是個好主意。

當onPictureTaken方法被調用時,我們需要調用Camera對象的startPreview。因為當takePicture方法被調用時,預覽就自動暫停瞭。回調函數告訴我們,現在可以重新啟動預覽瞭。

public void onPictureTaken(byte[] data, Camera camera)
{
    Uri imageFileUri = getContentResolver().
        insert(Media.EXTERNAL_CONTENT_URI, new ContentValues());      try {
        OutputStream imageFileOS = getContentResolver().openOutputStream(imageFileUri);
        imageFileOS.write(data);
        imageFileOS.flush();
        imageFileOS.close();      }
    catch (FileNotFoundException e) 
    {  }
    catch (IOException e)
    {  }

    camera.startPreview();
}

在上述代碼段,我們將新記錄插入MediaStore,得到一個URI。我們隨後可用該URI獲取 OutputStream,用於 JPEG 數據寫入。這會在 MediaStore 指定的位置創建一個文件,並將其鏈接到新記錄。如果我們之後想要更新 MediaStore 記錄中存儲的元數據,我們可以用一個新的 ContentValues 對象來更新,如第 1 章中所述。

ContentValues contentValues = new ContentValues(3);
contentValues.put(Media.DISPLAY_NAME, "This is a test title");
contentValues.put(Media.DESCRIPTION, "This is a test description");
getContentResolver().update(imageFileUri,contentValues,null,null);

最後,我們必須實際調用Camera.takePicture。為此,我們讓預覽屏幕變為可點擊,在 onClick 方法中,我們進行拍攝。

我們讓我們的activity實現 OnClickListener 並設置surfaceView 的 onClickListener 為activity。然後,通過setClickable(true),我們讓SurfaceView成為可點擊的。此外,我們需要使 SurfaceView 可獲得焦點。SurfaceView 在默認情況下是不能獲得焦點的,所以我們必須通過 setFocusable(true) 顯式設置。此外,當我們在”觸摸模式”時,焦點一般是禁用的,因此我們必須通過顯性調用 setFocusInTouchMode(true)
使能焦點。

public class SnapShot extends Activity implements OnClickListener,
    SurfaceHolder.Callback, Camera.PictureCallback 
{
    ...
    public void onCreate(Bundle savedInstanceState) 
    {
        ...
        cameraView.setFocusable(true);
        cameraView.setFocusableInTouchMode(true);
        cameraView.setClickable(true);
        cameraView.setOnClickListener(this);
    }

    public void onClick(View v) 
    {
        camera.takePicture(null, null, null, this); 
    }

其他Camera回調方法

除瞭Camera.PictureCallback,還有其他一些回調方法也值得提出來。

Camera.PreviewCallback: 定義瞭方法onPreviewFrame(byte[] data, Camera camera),當預覽幀就緒時被調用。函數傳入一字節數組,包含圖像當前的像素值。Camera有三種使用此回調的方法。

setPreviewCallback(Camera.PreviewCallback): 使用此方法註冊Camera.PreviewCallback,可以確保每當一個新的預覽幀就緒調用onPreviewFrame,並顯示在屏幕上 。傳遞給 onPreviewFrame 的數據字節數組,最可能是YUV格式。不幸的是,直到Android 2.2,才配備瞭 YUV
格式解碼器 (YuvImage) ;在以前版本中,解碼必須手工完成。

setOneShotPreviewCallback(Camera.PreviewCallback): 使用此方法註冊 Camera.PreviewCallback,當下一個預覽圖像就緒時,會調用onPreviewFrame一次。同樣, 傳遞給onPreviewFrame的預覽圖像數據最可能是YUV格式。可以通過定義在 ImageFormat 中的常量與 Camera.getParameters().getPreviewFormat()
返回值進行比較得出。

setPreviewCallbackWithBuffer(Camera.PreviewCallback): 在 Android 2.2 中引入,這種方法跟正常的setPreviewCallback 工作方式相同,但需要我們指定一個字節數組,作為預覽圖像數據緩沖區。這樣做是為瞭在處理預覽圖像時,我們能更好的管理內存。

Camera.AutoFocusCallback: 定義瞭方法onAutoFocus,自動對焦行為完成時被調用。調用Camera對象的autoFocus方法,以這個回調接口的實例作為參數,可能會觸發自動對焦。

Camera.ErrorCallback: 定義瞭方法onError,Camera發生錯誤時被調用。有兩個常量可以與傳入的錯誤代碼進行比較CAMERA_ERROR_UNKNOWN和MERA_ERROR_SERVER_DIED.

Camera.OnZoomChangeListener: 定義瞭方法onZoomChange方法,當正在進行或完成”平滑縮放” (緩慢放大或縮小)時被調用。在 Android 2.2(API 級別 8)中介紹瞭這個類及其方法。

Camera.ShutterCallback: 定義 onShutter 方法,在拍攝圖像時被調用。

代碼整合

讓我們完成整個示例。下面的代碼要運行在Android 2.2 及以上版本,但做細小修改,它可以在 1.6 及更高版本上運行。需要運行在1.6版本上的部分,在註釋中做瞭聲明。

這應該涵蓋瞭建立一個自定義的基於camera的應用程序的基本知識。接下來,讓我們看看如何擴展此應用程序,實現內置相機應用程序中不存在的功能。

發佈留言