Android Camera開發之基礎知識篇

概述

Android框架支持設備的相機拍照和錄像功能,你的應用可以直接調用系統的Camera應用來拍照或者錄像(比如微信拍照),當然也可以利用Android系統提供的API開發一個Camera應用來實現相機拍照和錄像功能(比如市面上流行的360相機)。此篇文章主要記錄相機開發有關的基礎知識,以及帶着自己的理解翻譯Camera官方文檔,如有翻譯不恰當支出,還請指出改正。當然我會開一個有關相機開發的一個系列,該系列主要內容包括如下:

  1. 相機基本預覽拍照功能。

  2. 實現相機的Flash,Hdr,濾鏡,前後攝像頭切換功能。

  3. 實現16:9和4:3預覽比例自由切換。

  4. 正方形拍照。

  5. 自動添加水印功能。

  6. 實現視頻錄像功能。

  7. 實現支持第三方應用請求拍照和錄像功能。

注意事項

在你的應用程序能夠在Android設備上使用相機之前,你應該考慮幾個問題,那就是你的app打算如何使用相機拍照或者錄像?

  • Camera需求的聲明 - 使用相機功能對於你的應用程序來說是否很重要並且你不希望你的應用程序被安裝在沒有相機的機器上?如果是這樣,那麼你需要把 相機需求聲明在配置文件裏.

  • 快速拍照還是自定義相機 -你的應用程序該如何使用相機?你是否僅僅對捕捉一個快照或者一個視頻剪輯, 或者你的應用程序希望提供一種使用相機的新的方式?獲取快照或者視頻,建議 使用已存在的系統相機應用。開發一個自定義相機請查看創建一個相機應用。

  • 存儲 - 是否你的應用生成的圖片和視頻僅對你的應用可見,或者是用來分享,這樣的話,其他應用程序例如相冊或者其他的多媒體和社交app可以使用它們?你是否希望你的應用程序被卸載後,這些照片和視頻仍然可用?保存圖片和視頻文件。

基礎知識

Android系統提供API和Intent來支持自定義相機拍照和快速拍照,以下是有關的類:

  • Camera 
    該類提供基礎API來使用設備上的相機,且該類可以爲你的應用提供拍照和錄像相關的API。

  • SurfaceView 
    該類用於顯示相機的預覽數據。

  • MediaRecorder 
    該類提供相機錄像相關的API。

  • Intent 
    使用MediaStore.ACTION_IMAGE_CAPTURE 和MediaStore.ACTION_VIDEO_CAPTURE Intent action可以快速拍照或者錄像。

Manifest權限申明

在開發設備相機之前,你需要在Manifest申請如下權限纔可以使用相機。

Camera Permission - 你的應用必須申請相機權限纔可以使用設備相機。

<uses-permission android:name="android.permission.CAMERA" />11

注意:如果你使用Intent發送快速拍照請求,你的應用無需申請該權限。

Storage Permission - 如果你的應用需要保持照片或者視頻到設備存儲中,你必須在Manifest指定文件的寫權限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />11

Audio Recording Permission - 你必須申請錄音權限才能使用相機來錄像.

<uses-permission android:name="android.permission.RECORD_AUDIO" />11

Location Permission - 當然如果你需要拍攝的照片記錄地理位置,你同樣需要申請如下權限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />11


使用已存在的相機應用拍攝

你的應用可以用過發送一個Intent 到系統相機應用來實現一個快速拍照或者錄像的功能。一個Camera intent可以通過已存在的相機應用來抓取一張照片或者一段視頻剪輯,然後將它們返回給你的應用。這一部分主要演示如何通過發送intent來抓取一張照片或者視頻。

使用camera intent拍照的流程如下:

  1. Compose a Camera Intent - 創建一個Intent請求用來拍照或者錄像,有關的Intent類型如下: 

  • MediaStore.ACTION_IMAGE_CAPTURE - 該Intent action 類型用於請求系統相機拍照。

  • MediaStore.ACTION_VIDEO_CAPTURE - 該Intent action 類型用於請求系統相機錄像。

Start the Camera Intent - 調用activity的startActivityForResult()方法來發送camera intent請求拍照或者錄像,當發送camera intent 以後,當前應用會跳轉到系統相機應用app界面,讓用戶可以拍照或者錄像。Receive the Intent Result - 在你的應用中實現onActivityResult()回調方法去接收來自系統相機的拍攝結果。該方法在用戶完成拍照或者錄像以後有系統調用。

使用Intent拍照

使用camera intent拍照是一個快速的,最簡單的方法。發送Intent拍照攜帶的外部數據extra的信息如下:

  • MediaStore.EXTRA_OUTPUT - 這個關鍵字用於創建一個Uri對象來指定一個路徑和文件名保存照片。當然,這個設置是可選的,不過強烈推薦使用該方法來保存照片。如果你沒有在指定該關鍵字的值,系統的camera應用會將照片以默認的名字保存在一個默認的地方,當你指定了該關鍵字的值,數據以Intent.getData()方法返回Uri對象。

接下來的實例代碼將演示如何構建一個Intent用來拍照,getOutputMediaFileUri()方法定義在保存多媒體文件章節。

private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private Uri fileUri;

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

    // create Intent to take a picture and return control to the calling application
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

    fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the p_w_picpath
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the p_w_picpath file name

    // start the p_w_picpath capture Intent
    startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}12345678910111213141516171234567891011121314151617

當調用startActivityForResult() 方法以後,用戶可以看到系統相機的拍照界面。在用戶拍照結束以後(或者取消拍照),系統相機會把照片數據返回給你的應用,當然你必須在自己的應用中實現onActivityResult()方法來接收照片數據。更多有關如何在自己的應用接收拍照結果的信息請參考接收相機返回的數據。

使用Intent錄像

使用camera intent錄像是一個快速的,最簡單的方法。發送Intent錄像攜帶的外部數據extra的信息如下:

  • MediaStore.EXTRA_OUTPUT - 該關鍵字和拍照使用的關鍵字一樣,意思就是制定一個路徑和文件名來構建一個U日對象來保存錄像結果,同樣錄像結果會以Intent.getData()的方法返回Uri對象。

  • MediaStore.EXTRA_VIDEO_QUALITY - 該關鍵字用於指定拍攝的錄像質量,參數0表示低質量,參數1表示高質量。

  • MediaStore.EXTRA_DURATION_LIMIT - 該關鍵之用於指定拍攝的錄像的時間限制,單位是秒。

  • MediaStore.EXTRA_SIZE_LIMIT - 該關鍵字用於指定拍攝的錄像文件大小限制,單位值byte。

接下來的實例代碼將演示如何構建一個Intent用來錄像,getOutputMediaFileUri()方法定義在保存多媒體文件章節。

private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
private Uri fileUri;

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

    //create new Intent
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);

    fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);  // create a file to save the video
    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);  // set the p_w_picpath file name

    intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video p_w_picpath quality to high

    // start the Video Capture Intent
    startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
}1234567891011121314151617181912345678910111213141516171819

當調用startActivityForResult() 方法以後,用戶可以看到系統相機的拍照界面。在用戶錄像結束以後(或者取消錄像),系統相機會把視頻數據返回給你的應用,當然你必須在自己的應用中實現onActivityResult()方法來接收視頻數據。更多有關如何在自己的應用接收拍照結果的信息請參考接收相機返回的數據。


接收相機返回的數據

一旦你發送了一個拍照或者錄像的intent,你的應用必須去接收Intent的結果數據。這一小節將演示如何在本地應用中實現接收Intent的結果數據(照片或者視頻數據)。

爲了接收Intent的結果數據,你必須重寫activity的onActivityResult()方法。接下來的代碼將演示如何實現onActiviytResult()方法來接收照片或者視頻數據。

private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {
            // Image captured and saved to fileUri specified in the Intent
            Toast.makeText(this, "Image saved to:\n" +
                     data.getData(), Toast.LENGTH_LONG).show();
        } else if (resultCode == RESULT_CANCELED) {
            // User cancelled the p_w_picpath capture
        } else {
            // Image capture failed, advise user
        }
    }

    if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {
            // Video captured and saved to fileUri specified in the Intent
            Toast.makeText(this, "Video saved to:\n" +
                     data.getData(), Toast.LENGTH_LONG).show();
        } else if (resultCode == RESULT_CANCELED) {
            // User cancelled the video capture
        } else {
            // Video capture failed, advise user
        }
    }
}12345678910111213141516171819202122232425262728291234567891011121314151617181920212223242526272829

一旦你的activity成功接收返回結果,相機拍攝的照片或者視頻是一個指向你應用可以訪問的路徑。也就是getOutputMediaFileUri()方法返回的Uri文件地址。

【CSDN 廢墟的樹 Android Camera開發之基礎知識篇】

創建一個相機應用

創建一個獨立自定義的相機app基本遵循如下步驟:

  • 檢測和訪問相機 - 首先代碼檢測該設備相機是否存在,如果存在才能請求訪問設備相機.

  • 創建一個預覽來顯示相機圖像 - 在你的佈局中使用SurfaceView控件,然後在代碼中繼承SurfaceHolder.Callback接口並且實現接口中的方法來顯示來自相機的圖像信息。

  • 設置相機基本參數 - 根據需求設置相機預覽尺寸,圖片大小,預覽方向,圖片方向等。

  • 設置拍照錄像監聽 - 當用戶按下按鈕時調用Camera#takePicture或者MediaRecorder#start()來進行拍照或錄像。

  • 文件保存 - 當拍照結束或者錄像視頻結束時,需要開啓一個後臺線程去保存圖片或者視頻文件。

  • 釋放相機資源 - Camera硬件是一個共享資源,所以你必須小心的編寫你的應用代碼來管理相機資源。一般在Activity的生命週期的onResume中開機相機,在onPause中釋放相機。

注意: 當你不在使用相機資源時,記得調用Camera#release方法來釋放相機資源,否則其他應用甚至你自己的應用再次請求訪問設備相機時會失敗,並且crash。

檢測相機硬件是否存在

一般情況,我們會在運行代碼時檢測該設備是否有相機硬件,如果有相機硬件,才進一步去訪問相機,如下是檢測相機硬件是否存在是代碼示例:

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}1234567891012345678910

Android 設備可以有多個相機硬件,現在一般手機都是前後兩個camera,因此我們在Android2.3以後也可以使用Camera#getNumberOfCameras()方法來獲得當前設備camera個數來判斷相機硬件是否存在。


訪問相機設備

如果在你的應用中已經檢測到了該設備有相機,那麼你必須得到一個Camera類的實例才能訪問相機(除非你使用Intent快速訪問相機)。

爲了訪問相機基本功能,可以使用Camera#open()方法來獲得一個Camera的實例,示例代碼如下:

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}12345678910111234567891011

注意: 在調用Camera#open()方法時總是要去捕獲一個異常,以免打開相機設備失敗導致整個應用crash。在Android2.3以及更高api上,你可以使用Camera#open(int)來打開指定的相機。以上代碼示例總是默認打開後置camera,一般情況參數爲0表示打開後置camera,參數爲1表示打開後置camera。

獲取相機的屬性特性

一旦你可以成功訪問相機設備,你可以使用Camera#getParameters()方法來獲取相機參數信息,可以根據 
返回值 Camera.Parameters 類來查看當前camea支持哪些參數設置等。當使用API 9或者更高時,你可以使用Camera.getCameraInfo()靜態方法來獲取前後camera的ID,以及camera數據流的方向和是否能禁止拍照快門聲音標記。示例代碼如下:

/**
     * get current camera info
     *
     * @param cameraId current camera id
     * @return camera info
     */
    public static Camera.CameraInfo getCameraInfo(int cameraId) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);
        return cameraInfo;
    }12345678910111234567891011

創建一個預覽類

爲了有效的拍照或者錄像,我們必須在屏幕上能看到相機的預覽。一個相機預覽類是由SurfaceView控件來實時顯示來自camera的預覽數據,如此我們才能看到每一幀數據和捕獲圖片或者視頻。

如下示例代碼演示瞭如何創建一個基本的camera預覽類以及如何佈局。該類繼承SurfaceView.Callback接口類,並且需要實現裏面的接口方法以便監聽SurfaceView控件的創建以及銷燬事件的回調,在回調方法中關聯相機預覽顯示。

/** A basic Camera preview class */
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());
        }
    }
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

預覽添加到佈局中

我們需要創建一個佈局來加載camera預覽類,在該例子中,FrameLayout作爲camera 預覽類的父容器。示例代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>123456789101112131415161718192021123456789101112131415161718192021

在更多的設備上,camera 預覽默認的方向是橫屏的,故在該例子中佈局指定水平方向以及固定該應用爲橫屏顯示。爲了簡便渲染camera預覽,你應該在manifest配置文件中指定CameraActivity的方向爲橫屏。

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>12345678910111234567891011

注意:Camera預覽沒有橫屏模式,你可以使用Camera#setDisplayOrientation()方法去設置預覽畫面顯旋轉的方向。爲了改變預覽方向,請在surfaceChanged()方法中顯示調用Camera#stopPreview()來停止預覽,改變方向以後再次調用Camera#stratPreview()啓動預覽。

首先在activity中創建CameraPreview預覽,然後將camera 預覽添加到佈局FrameLayout中。你必須確保在camera activity 的onPause或者應用退出時去釋放camera資源。如下示例代碼演示怎麼將CameraPreview預覽加載到佈局中去顯示。

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

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

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(id.camera_preview);
        preview.addView(mPreview);
    }
}1234567891011121314151617181912345678910111213141516171819

注意: 以上示例中的getCameraInstance() 方法在 訪問相機設備 小節已經講到。

拍照

一旦你創建了camera preview並且加載到佈局中可以實時顯示預覽畫面了,此時就可以進行拍照了。在代碼中你應該實現一個監聽回調來捕獲用戶拍照的行爲。代碼中調用Camera#takePciture()方法來進行拍照。該方法接受三個參數,第一個參數ShutterCallback響應快門的接口,第二個參數PictureCallback接收raw格式的圖片數據,第三個參數PictureCallback接收jpeg格式的圖片數據。爲了保存圖片數據,你需要分別實現以上三個接口。此處我們暫且實現第三個PictureCallback接口回調,其他日後再說。示例代碼如下:

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions: " +
                e.getMessage());
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};12345678910111213141516171819202122231234567891011121314151617181920212223

應用中調用Camera#takePicture()方法觸發拍照,以下代碼演示在button點擊監聽事件中調用拍照方法。

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an p_w_picpath from the camera
            mCamera.takePicture(null, null, mPicture);
        }
    }
);12345678910111234567891011

錄像

Camera視頻錄像不僅涉及到Camera類還用到了MediaRecorder類。當你使用Camera錄像時,你應該調用Camera#lock()和Camera#unlock()來管理camera硬件,允許MediaRecorder進程訪問caemra硬件。你應該在camera和MediaRecorder關聯之前調用Camera#unlock()來解鎖camera,允許MediaRecorder訪問Camera,在釋放MediaRecorder資源以後調用Camera#lock()來鎖定camera以保證camera硬件資源的共享性。

注意:在Android4.0以後,系統會自動管理Camera#unlock()以及Camera#lock(),無需用戶自己管理。

不像拍照流程,啓動錄像流程需要一個指定調用順序,如下是詳細的步驟流程:

  1. Open Camera - 使用Camera.open()靜態方法來獲得camera對象實例。

  2. Connect Preview - 使用Camera.setPreviewDiaplay()方法將相機的預覽畫面顯示在SurfaceView控件上。

  3. Start Preview - 使用Camera.startPreview()方法 開始啓動預覽畫面.

  4. Start Recording Video - 必須完成以下步驟才能正常開始正常錄音: 
    a. Unlock the Camera - 調用Camera.unlock()方法解鎖caemra,使得MediaRecorder進程能訪問Camera硬件。 
    b. Configure MediaRecorder - 在這一步,分別調用MediaRecorder類中如下方法來配置MediaRecorder:

    1.setCamera() - 設置camera用於錄像。
    2.setAudioSource() - 設置錄像音頻來源, 使用麥克風 MediaRecorder.AudioSource.CAMCORDER作爲音頻來源.
    3.setVideoSource() - 設置錄像視頻來源, 使用Camera MediaRecorder.VideoSource.CAMERA作爲視頻來源.
    4.設置視頻的輸出格式和編碼格式。 對於Android2.2或者更高版本使用 MediaRecorder.setProfile方法即可,使用方法CamcorderProfile.get()來獲得一個配置信息。
    5.setOutputFile() - 設置視頻輸出保存到文件的路徑。
    6.setPreviewDisplay() - 爲你的MediaRecorder指定預覽顯示.使用第2步一樣的參數即可。

    注意:在這一步,你必須調用MediaRecorder類中的以上方法來配置MediaRecorder,否則你的應用將無法正常錄像並且報錯。

    c. Prepare MediaRecorder - 在配置完MediaRecorder參數之後調用MediaRecorder.prepare()方法來準備MediaRecorder. 
    d. Start MediaRecorder - 調用MediaRecorder.start()方法啓動錄像.

  5. Stop Recording Video - 當你結束錄像時調用如下方法:

    a. Stop MediaRecorder - 首先調用 MediaRecorder.stop()方法停止多媒體錄像。 
    b. Reset MediaRecorder - 調用MediaRecorder.reset()方法重置多媒體狀態,調用該方法之後之前的所有MediaRecorder configuration將被移除,你如果還想再次錄像,需要再次配置多媒體參數. 
    c. Release MediaRecorder - 調用 MediaRecorder.release()方法釋放多媒體資源. 
    d. Lock the Camera - 調用Camera.lock()方法來給Camera硬件加鎖. 在Android4.0及以後無需調用該方法,除非在調用MediaRecorder.prepare()失敗時,才需要再次調用該方法。

  6. Stop the Preview - 當你的Activity已經不再使用camera時,調用Camera.stopPreview()方法來停止預覽。

  7. Release Camera - 當不再使用Camera時,調用Camera.release()方法來釋放camera,以便其他應用可以使用camera資源。

MediaRecorder參數配置

當使用MediaRecorder類來錄像時,你必須執行在特定的步驟執行相應的配置操作,然後在調用MediaRecorder.prepare()方法來再次檢查基本配置。接下來的代碼將演示如何配置以及準備MediaRecorder。

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mMediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mMediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mMediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}123456789101112131415161718192021222324252627282930313233343536123456789101112131415161718192021222324252627282930313233343536

由於錄像默認設置了很多參數,無需用戶太關心更細節的參數設置,但是如果需要在你的應用中修改這些默認參數設置,你可以使用如下方法來修改默認參數:

  1. setVideoEncodingBitRate() 設置視頻編碼的字節率,在prepare()方法之前調用

  2. setVideoSize() 設置視頻尺寸大小,在setVideoSource()和 setOutFormat()之後 prepare()之前調用。

  3. setVideoFrameRate() 設置視頻幀率,在setVideoSource()和 setOutFormat()之後 prepare()之前調用。

  4. setAudioEncodingBitRate() 設置音頻編碼的字節率,在prepare()方法之前調用

  5. setAudioChannels() 設置音頻的頻道數目,在prepare()方法之前調用 參數一般1/2

  6. setAudioSamplingRate() 設置音頻採樣率。

停止和啓動MediaRecorder

當使用MediaRecorder類啓動和停止錄像時,你必須按照如下步驟來操作:

  1. 調用Camera.unlock()解鎖camera。

  2. 按照上一節配置MediaRecorder參數。

  3. 調用MediaRecorder.start()啓動錄像。

  4. 調用MediaRecorder.stop()停止錄像。

  5. 調用MediaRecorder.release()釋放多媒體資源。

  6. 調用Camera.lock()鎖定camrea硬件資源。

如下代碼示例演示在button的點擊事件中去啓動和停止視頻錄像操作:

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mMediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mMediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);123456789101112131415161718192021222324252627282930313233343536123456789101112131415161718192021222324252627282930313233343536

注意: 當完成一段視頻錄像時,不要馬上去釋放camera資源或者停止當前預覽,因爲有可能用戶會再次啓動錄像操作。在上面示例中,prepareVideoRecorder() 方法定義在MediaRecorder參數配置.

釋放caemra資源

在一臺設備上,相機作爲一個共享的資源。在你的應用中必須確保在使用完相機以後去釋放camera的資源。一般建議在Activity.onResume()方法中打開相機,在Activity.onPause()方法中去是讓相機資源。如果你的應用在退出以後還沒有釋放camera資源,包括自己的應用在內的其他應用再次去啓動相機將會失敗。

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;

    ...

    @Override
    protected void onResume() {
        super.onResume();
        mCamera = Camera.open();//open the camera for the application
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();   // clear recorder configuration
            mMediaRecorder.release(); // release the recorder object
            mMediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}123456789101112131415161718192021222324252627282930313233343536123456789101112131415161718192021222324252627282930313233343536


保存多媒體文件

拍照或者錄像生成的多媒體文件需要保存到手機存儲中目錄中(SD Card),所以在應用中必須有往手機中寫文件的權限。一般可以有多種本地路徑來保存多媒體文件,但是主要有如下兩種常用的路徑:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - 該方法返回一個標準的外部存儲路徑去保存照片和視頻。這個路徑是公共的,所以其他應用也可以訪問,修改,刪除該路徑下的照片和視頻,如果你的應用被卸載了,媒體文件依然存在本地儲存中. 爲了避免和其他多媒體文件混淆,你應該在公共目錄下創建一個子目錄來保存你自己應用中的多媒體數據。

  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 該方法返回一個標準的,唯獨當前應用自己可見的路徑去保存照片和視頻。如果該應用被卸載,在該目錄下的所有多媒體數據將會被移除.但是有一個好處就是其他應用無法去訪問,修改,刪除該路徑下的文件。

如下示例代碼演示如何創建一個路徑用來保存照片和視頻:

public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an p_w_picpath or video */
private static Uri getOutputMediaFileUri(int type){
      return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an p_w_picpath or video */
private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES), "MyCameraApp");
    // This location works best if you want the created p_w_picpaths to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "IMG_"+ timeStamp + ".jpg");
    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "VID_"+ timeStamp + ".mp4");
    } else {
        return null;
    }

    return mediaFile;
}12345678910111213141516171819202122232425262728293031323334353637383940411234567891011121314151617181920212223242526272829303132333435363738394041

相機特徵描述

Android支持一組相機功能來控制你的相機應用,比如:生成的照片格式,閃光燈模式,對焦設置等等。這裏羅列出通用的相機功能並且顯示怎麼使用它們來輔助我們拍照。有關相機更多的參數設置,可以直接閱讀Camera.Parameters類。一下列表羅列出通用的相機參數:

參數API等級描述
Face Detection14識別畫面中的人臉,用於對焦,測量,白平衡
Metering Areas14在畫面中指定一個或者多個區域用來計算白平衡
Focus Areas14在畫面中指定一個或者多個區域用來對焦
White Balance Lock14停止或者啓動自動白平衡調節
Exposure Lock14停止或者啓動自動曝光調節
Video Snapshot14支持視頻錄像時拍照
Time Lapse Video11支持延時錄像
Multiple Cameras9一臺設備支持多個camera,包括前置和後置camera
Focus Distance9支持焦距調節
Zoom8支持縮放
GPS Data5支持往照片中寫地理位置數據
White Balance5設置白平衡模式,該參數會影響照片顏色效果
Focus Mode5設置對焦模式,比如:自動對焦,固定對焦,微距,遠距
Scene Mode5設置場景模式,比如:夜景,沙灘,雪景,燭光等場景
JPEG Quality5設置生成的jepg圖片質量等級
Flash Mode5閃光燈模式,on,off,auto
Color Effects5支持濾鏡效果
Anti-Banding5反帶效應(防止閃爍),參數有off,auto,50hz,60hz
Picture Format1圖片格式,默認圖片格式爲jpeg
Picture Size1圖片尺寸,指定一個大小的尺寸用於保存圖片大小

注意:以上列表中的功能並是不在所有的Android設備上都支持,因此你在使用以上參數時需要去檢測當前Android設備是否支持該參數,然後再去使用它們來輔助相機拍照。

檢測相機特性是否可用

首先你要知道,並不是所有android設備都支持全部的camera特性功能,因此在應用總使用camera特性功能需要先檢測是否支持,然後在去使用。否則你使用了不支持的camera特性功能將會報錯。

在應用中可以通過得到camera 的參數 parameters類,然後通過該類中的一些方法來檢測當前設備是否支持camea特性功能。如下代碼示例演示瞭如何獲得一個Camera.Parameters對象且檢測camera是否支持自動對焦特性:

// get Camera parameters
Camera.Parameters params = mCamera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}12345671234567

使用相機的特性功能

大部分Android 相機特性功能都可以通過 Camera.Parameters類來控制。首先你可以獲得一個Camera實例,然後調用Camera.getParameters()方法的返回值來得到Caemra.Parameters實例,之後就可以通過Parameters.setxxx()系列方法來設置一些參數使用相機的一些特性功能。以下是實例代碼:

// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);12345671234567

相機的所有參數都可以通過類似以上方法來設置,一般在打開相機成功以後就可以設置相機的基本參數。

注意:相機的一些特性不能再任意時刻改變,比如改變預覽的尺寸和方向時,首先需要停止preview,修改預覽尺寸之後再次重啓preview。自從Android4.0以後修改預覽方向以後無需 再次重啓preview。

相機的其他功能介紹如下:

  • Metering and focus areas

  • Face detection

  • Time lapse video

接下來就介紹相機的以上三個功能在代碼中如何使用

測光和對焦區域

在某些攝像情景中,自動調焦和測光可能不能達到設計結果。從Android4.0(API Level 14)開始,你的Camera應用程序能夠提供另外的控制允許應用程序或用戶指定圖像中特定區域用於進行調焦或光線級別的設置,並且把這些值傳遞給Camera硬件用於採集圖片或視頻。

測光和調焦區域的工作與其他Camera功能非常類似,你可以通過Camera.Parameters對象中的方法來控制它們。下列代碼演示如何給Camera示例設置兩個測光區域:

// Create an instance of Camera
mCamera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = mCamera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of p_w_picpath
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of p_w_picpath
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

mCamera.setParameters(params);12345678910111213141516171234567891011121314151617

Camera.Area對象包含了兩個數據參數:Rect對象,它用於指定Camera預覽窗口一塊矩形區域;一個權重值:它告訴Camera這塊指定區域應該給予的測光或調焦計算的重要性等級。 
在Camera.Area對象中的Rect字段,代表了一個被映射成2000x2000單元格的矩形。座標(-1000,-1000)代表Camera圖像的左上角,(1000,1000)代表Camera圖像的右下角,如下圖所示:

這裏寫圖片描述

圖中的紅線說明了在Camera預覽窗口中給Camera.Area指定的座標系統。用Rect的值是(333,333,667,667)藍色框顯示了攝像區域的位置和形狀。

這個座標系統的邊框總是對應着Camera預覽窗口中所顯示的圖像的外邊緣,並且不會使用縮放級別來縮小或放大。類似的,使用Camera.setDisplayOrientation()方法來選擇圖像的預覽,不會重新映射座標系統。

人臉識別

對於包含人的圖片,通常人臉是圖片的最重要的部分,並且在採集圖像時,應該使用調焦和白平衡來進行檢測。Android4.0(API Level 14)框架提供了用於識別人臉和使用人臉識別技術來計算圖片設置的API。

注意:當人臉識別在運行時,setWhiteBalance(String), setFocusAreas(List) 和setMeteringAreas(List)方法都無效。

在你的應用中使用人臉識別技術一般需要如下幾步:

  1. 檢測當前設備是否支持人臉識別。

  2. 創建一個人臉識別監聽回調。

  3. 將監聽回調關聯到camera 對象中去。

  4. 在啓動preview預覽之後去啓動人臉識別。(每次重啓preview之後都有去啓動一個人臉識別)

人臉識別功能並是是在所有設備上都支持。你應當調用getMaxNumDetectedFaces()方法來檢測當前設備是否支持人臉識別技術,只有當以上方法返回值大於0時,你纔可以去調用startFaceDetection()方法去啓動人臉識別。

爲了通知和相應人臉識別,你的相機應用必須設置一個人臉檢測監聽事件,爲了到達這個目的,你必須要創建一個實現Camera.FaceDetectionListener接口的監聽器類,如下所示:

class MyFaceDetectionListener implements Camera.FaceDetectionListener {

    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}12345678910111234567891011

創建這個類之後,把它設置給你的應用程序的Camera對象:

mCamera.setFaceDetectionListener(new MyFaceDetectionListener());11

你的應用必須在每次重啓相機preview時候去啓動一個人臉檢測。創建一個啓動人臉識別的方法,在你需要的時候調用它。示例代碼如下:

public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}1234567891012345678910

你必須在每次啓動(或重啓)Camera預覽窗口時都要啓動面部識別。如果你使用前文“創建預覽類”中的預覽類,就要把startFaceDetection()方法添加到預覽類的surfaceCreated()和surfaceChanged()方法中,如下代碼所示:

public void surfaceCreated(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // start face detection feature

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

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

    if (mHolder.getSurface() == null){
        // preview surface does not exist
        Log.d(TAG, "mHolder.getSurface() == null");
        return;
    }

    try {
        mCamera.stopPreview();

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
    }

    try {
        mCamera.setPreviewDisplay(mHolder);
        mCamera.startPreview();

        startFaceDetection(); // re-start face detection feature

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}123456789101112131415161718192021222324252627282930313233343536373839123456789101112131415161718192021222324252627282930313233343536373839

注意:在調用startPreview()方法之後,要記住調用這個方法。不要試圖在你的Camera應用程序的主Activity的onCreate()方法中啓動面部識別方法。在你的應用程序的這個執行時點,預覽還不是有效的。

延時攝影

延時攝影允許用戶把幾張圖片合成一個幾秒或幾分鐘的視頻剪輯。這個功能要使用MediaRecorder對象來記錄圖像的延時序列。

要用MediaRecorder對象來記錄延時視頻,要想錄制普通視頻一樣,必須要配置的記錄器對象,如把每秒採集的幀數設置到較小的數字,並且要使用一個延時品質設置,如下代碼所示:

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)

mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));

...

// Step 5.5: Set the video capture rate to a low number

mMediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds123456789123456789

這些設置是要對MediaRecorder對象所要做的必要設置的一大部分。對於完全的配置代碼示例,請看前文的 MediaRecorder參數配置。一旦配置完成,你就可以把它當做普通的視頻剪輯來錄製視頻了。關於配置和運行MediaRecorder對象的更多信息,請看前文的 錄像.

總結

到此Android Camera開發的基礎知識已經有個大概的瞭解,當然以上是根據自己開發相機的經驗以及翻譯的Camra api官方文檔,翻譯存在很多不足之處,如有發現還懇請支出。後續文章會繼續講解怎麼去實現一個擁有基本完善功能的相機項目。

【CSDN 廢墟的樹 Android Camera開發之基礎知識篇】


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章