Android自定義視頻錄制

設置權限

如果要在App中使用攝像頭,必須先在Manifest中聲明攝像頭權限

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

以及聲明應用需要有攝像頭

<uses-feature android:name="android.hardware.camera" /

如果攝像頭并非應用必不可少的功能,則可以聲明如下

<uses-feature android:name="android.hardware.camera" android:required="false" />

如果應用要在SD卡上存儲圖像或者視頻,則需要在Manifest文件中指定以下權限:

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

在錄制視頻的時候,同時需要錄制音頻,所以也要聲明錄音權限

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

自定義相機的一般步驟

  • 是否有攝像頭、是否有訪問攝像頭的權限
  • 創建相機預覽類,繼承SurfaceView并實現SurfaceHolder接口,用來預覽相機的即時圖片;
  • 有了相機預覽類之后,搭建預覽布局將預覽視圖和界面控件結合起來。
  • 連接控件的監聽器以響應用戶的動作,拍攝照片或視頻,比如一個按鈕點擊事件
  • 拍攝照片和視頻并保存輸出
  • 攝像頭使用完畢后,要正確地釋放相機以便于其他應用使用

相機是共享資源,必須合理管理才不至于和其他同樣用到相機的應用發生沖突。

注意 : 當你的應用使用完相機之后,記得調用Camera.release()方法以釋放Camera對象。如果沒有正確地釋放相機,設備上的任何應用,隨后任何訪問相機的嘗試,都有可能失敗、退出。

檢測相機

如果沒有在manifest中配置需要攝像頭功能,則需要在運行時檢測是否有相機。

private boolean checkCameraHardware(Context context) {
  // 支持所有版本
   return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
  //  Android 2.3 (API Level 9) 及以上的
  // return  Camera.getNumberOfCameras() > 0;
}

訪問相機

public static Camera getCameraInstance(){
    Camera c = null;
    try {
        // 在多個攝像頭時,默認打開后置攝像頭
        c = Camera.open(); 
         // Android 2.3(API 9之后可指定cameraId攝像頭id,可選值為后置(CAMERA_FACING_BACK)/前置( CAMERA_FACING_FRONT)
        //  c = Camera.open(cameraId); 
    } catch (Exception e){
        // Camera被占用或者設備上沒有相機時會崩潰。
    }
    return c;  // returns null if camera is unavailable
}

注意: 在部分設備上,打開攝像頭時可能會花費較長的時間,因此此操作應該在子線程上執行,然后以回調的方式傳遞Camera 對象,避免出現ANR;

檢測相機功能

在獲取到Camera對象之后,可以調用Camera.getParameters()方法并檢測該方法返回的Camera.Parameters對象所支持的功能以獲取到更多關于相機功能的信息。當使用API 9及以上時, 使用Camera.getCameraInfo()檢測攝像頭是前置還是后置、圖像的方向.

創建預覽類

攝像頭預覽類繼承自SurfaceView,能夠實時的展示來自攝像頭的圖像數據,方便用戶捕捉圖片和視頻。
下面的代碼演示了預覽類的基本創建。這個類繼承自SurfaceHolder.Callback以獲取創建和銷毀該預覽視圖的回調事件,這些事件在指派相機預覽輸入時所需要的。

/** 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) {
        // 通知攝像頭可以在這里繪制預覽了
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // 什么都不做,但是在Activity中Camera要正確地釋放預覽視圖
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 如果預覽視圖可變或者旋轉,要在這里處理好這些事件
        // 在重置大小或格式化時,確保停止預覽

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

        // 變更之前要停止預覽
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // 在這里重置預覽視圖的大小、旋轉、格式化

        // 使用新設置啟動預覽視圖
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

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

如果要給預覽視圖設置指定的大小,請在surfaceChanged()方法中像上面的注釋中提到的一樣設置。當設置預覽視圖大小時,必須使用從getSupportedPreviewSizes()獲取到的值。不要在setPreviewSize()中隨意設置值。

注意:Android 7.0(API 24及以上), Depending on the window size and aspect ratio, you may may have to fit a wide camera preview into a portrait-orientated layout, or vice versa, using a letterbox layout.

在布局文件中放預覽視圖

下面是一個簡單的例子,其中FrameLayout作為預覽視圖的容器:

<?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>

通常以橫屏錄制視頻,所以在Manifest中配置對應的Activity中配置:

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

在Activity中獲取預覽視圖的容器視圖,然后將預覽視圖放置在容器視圖中

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

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

        // 創建Camera實例
        mCamera = getCameraInstance();

        // 創建預覽視圖,并作為Activity的內容
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }

  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
}
}

拍照

(后面再補上,暫時用不到)

錄制視頻

錄制視頻需要注意管理Camera以及MediaRecorder類。通過攝像頭錄制視頻時,必須控制 Camera.lock()Camera.unlock()的調用以允許MediaRecorder訪問攝像頭設備,另外還有 Camera.open()Camera.release()的調用。

從Android 4.0 (API 14)開始, Camera.lock() 和 Camera.unlock() 的調用已經被自動管理了。

使用攝像頭錄制視頻和拍照不同,錄制視頻需要特別的調用順序。要想做好準備工作,必須遵從特定的執行順序。

  1. 打開攝像頭 - 使用Camera.open()獲取Camera實例
  2. 連接預覽視圖 - 使用Camera.setPreviewDisplay()連接SurfaceView
  3. 開始預覽 - 使用Camera.startPreview() 方法顯示即時錄像圖片
  4. 開始錄制視頻 - 要完成視頻錄制,必須按順序完成下面的不步驟:
    • 解鎖攝像頭 - 調用Camera.unlock()解鎖攝像頭以供MediaRecorder使用

    • 配置MediaRecorder - 依次調用以下MediaRecorder的方法

      • setCamera() - 設置視頻錄制所用到的攝像頭
      • setAudioSource - 設置音頻源,使用 MediaRecorder.AudioSource.CAMCORDER
      • setVideoSource() - 設置視頻源,使用MediaRecorder.VideoSource.CAMERA
      • 設置視頻的輸出格式和編碼方式。Android 2.2 (API 8)及以上版本,用MediaRecorder.setProfile方法,用CamcorderProfile.get()方法獲取 profile 實例
      • setOutputFile() - 設置輸出文件
      • setPreviewDisplay()

        注意:MediaRecorder的這些方法必須依次調用,否則應用會出錯,錄制失敗

    • 準備MediaRecorder - 調用MediaRecorder.prepare()方法

    • 開始錄制 - 調用MediaRecorder.start()

  5. 停止錄制 - 依次調用下面的方法以完成錄制
    • Stop MediaRecorder - 調用 MediaRecorder.stop()
    • Reset MediaRecorder - 非必須的,調用MediaRecorder.reset()方法移除Recorder的配置設置
    • Release MediaRecorder - 調用MediaRecorder.release()方法
    • Lock the Camera - 調用Camera.lock()鎖定攝像頭,隨后其他的MediaRecorder才能使用它。從Android 4.0(API 14)開始,只有在MediaRecorder.prepare()方法調用失敗時,才需要調用這個方法。
  6. Stop the Preview - 當Activity完成使用完攝像頭后,調用Camera.stopPreview()停止預覽;
  7. Release Camera - 調用Camera.release()釋放攝像頭,其他應用才能使用。

溫馨提示:如果應用通常用來拍攝視頻,啟動預覽視圖是使用 設置setRecordingHint(boolean)true。這個設置可以減少啟動錄制的時間。

配置MediaRecorder

當使用MediaRecorder錄制視頻時,必須以指定的順序執行配置步驟,然后調用MediaRecorder.prepare()檢查和使用配置。

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

以下MediaRecorder的視頻錄制參數已經有了預設值,不過你也可以用下面的方法調整以適用自己的應用:

  setVideoEncodingBitRate()
  setVideoSize()
  setVideoFrameRate()
  setAudioEncodingBitRate()
  setAudioChannels()
  setAudioSamplingRate()

啟動和停止MediaRecorder
當使用MediaRecorder啟動和停止錄制時,必須遵從指定的順序:

  1. Camera.unlock()
  2. 如以上的代碼示例配置MediaRecorder
  3. MediaRecorder.start()啟動錄制
  4. 錄制視頻
  5. MediaRecorder.stop()停止錄制
  6. MediaRecorder.release()釋放MediaRecorder
  7. Camera.lock()鎖定攝像頭

以下代碼演示了怎樣使用Camera和MediaRecorder通過一個按鈕正確的啟動和停止視頻錄制

當完成一個視頻錄制后,不要釋放Camera,否則預覽視圖也會停止

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

釋放相機
一個設備上,攝像頭是所有應用的共享資源,因此在自身的應用處于onPause狀態時,要立即釋放掉正在使用的Camera對象

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

    ...

    @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;
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容