Android Camera、Camera2詳解

前言

Android5.0之前使用android.hardware包下的Camera類進行拍照、錄視頻等功能。5.0以後,新增了android.hardware.camera2包,利用新的機制、新的類進行拍照、錄視頻。

使用Camera

一、拍照

由於手機攝像頭配置不同、攝像頭擺放方向不同、位置不同等等因素,與攝像頭相關參數如:攝像頭個數、支持的像素、畫面預覽方向、閃光燈、對焦方式、幀率等等都不一樣,必須根據當前手機的配置動態獲取。獲取方法如下:

Camera.Parameters p = mCamera.getParameters();
List<Camera.Size> preSizes = p.getSupportedPreviewSizes();
List<Camera.Size> preSizes = p.getSupportedPictureSizes();
......
......

獲取其它參數就是getSupportedXXX,返回的都是list,手動遍歷,選擇最合適的參數。

拍照主要由兩部分功能組成,一個是預覽界面,一個是“獲取正確的照片”。實現步驟如下:
1、初始化Camera

public boolean initCamera() {
    mCamera = null;
    try {
        mCamera = Camera.open();
    } catch (Exception e) {
    }
    return null != mCamera;
}

2、設置預覽orientation

private void setPreviewOrientation(){
    int result = 0;

    // 計算Activity轉動的角度degree。如果在manifest中設置爲portrait,則degree總是爲0
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    int degree = 0;
    switch (rotation) {
        case Surface.ROTATION_0:
            degree = 0;
            break;
        case Surface.ROTATION_90:
            degree = 90;
            break;
        case Surface.ROTATION_180:
            degree = 180;
            break;
        case Surface.ROTATION_270:
            degree = 270;
            break;
    }

    // 獲取後置攝像頭信息,獲取方法見後面
    Camera.CameraInfo info = getBackCameraInfo();

    // 攝像頭轉動角度減去Activity轉動角度,得到角度差,然後在設置預覽畫面角度時把這個角度差補償回來
    if (null != info) {
        int offset = info.orientation - degree;

        // 如果角度差爲正值,result就等於offset;
        // 如果角度差爲-90度,result就轉270度,剛好到-90度的位置;如果角度差爲-180度....以此類推
        result = (offset + 360) % 360;
    }

    // 設置預覽畫面轉動角度
    mCamera.setDisplayOrientation(result);
}


//遍歷所有攝像頭信息,根據info.facing判斷該攝像頭是前置還是後置
public static Camera.CameraInfo getBackCameraInfo() {
    int cameraNums = Camera.getNumberOfCameras();
    for (int i = 0; i < cameraNums; i++) {
        // CameraInfo類只是存儲了一些字段,剛new出來,這些字段都爲null
        Camera.CameraInfo info = new Camera.CameraInfo();
        // 雖然方法名爲getXXX,但實際上的作用是:獲取第cameraId個攝像頭,把這個攝像頭的信息存儲到info中
        Camera.getCameraInfo(i, info);
        if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing) {
            return info;
        }
    }
    return null;
}

3、設置預覽畫面大小、比例

// 獲取該攝像頭支持的預覽尺寸sizes,遍歷sizes,計算出最合適的尺寸bestSize。具體算法就不貼出了
private void setPreviewSize(){
    Camera.Parameters p = mCamera.getParameters();
    List<Camera.Size> preSizes = p.getSupportedPreviewSizes();
    Camera.Size bestSize = KuCameraUtil.getBestSize(preSizes, minTotalPix, maxTotalPix, rate);
    if (null != bestSize && bestSize.width > 0 && bestSize.height > 0) {
        p.setPreviewSize(bestSize.width, bestSize.height);
        // camera設置完previewSize後,負責顯示預覽的surfaceView/textureView也需要進行設置大小
        mCamera.setParameters(p);
    }
}

4、初始化顯示預覽畫面的控件surfaceView或textureView

// 預覽控件既可以用surfaceView,也可以用textureView,這兩個類的詳細介紹有空再整理,推薦使用textureView
private void initTextureView(){
    mTextureView = (TextureView) findViewById(R.id.preview);
    // 獲取camera的previewSize,根據其設置mTextureView控件的大小、比例
    Camera.Size size = mCamera.getPreviewSize();
    if (null != size) {
        // 寬度固定爲屏幕寬度,按照previewSize的寬高比設置高度
        float rate = (float) size.height / (float) size.width;
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)     mTextureView.getLayoutParams();
        params.width = ScreenUtils.getScreenWidth(this);
        params.height = (int) (rate >= 1 ? params.width * rate : params.width / rate);
        mTextureView.setLayoutParams(params);
}

    // mTextureView生命週期回調
    mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            // 開始預覽
            mKuCamera.startPreview(surface);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            // 結束預覽
            mKuCamera.stopPreview();
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        }
    });
}

5、執行拍照,獲取拍照圖片

// 拍照
private void takePhoto(){
    mCamera.takePhoto(new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            if (bitmap != null) {
                try {
                    File file = new File(KuPathUtil.getImageDir(), KuPathUtil.getNowTimeStr() + "jpg");
                    OutputStream os = new FileOutputStream(file);
                    os.write(bytes);
                    os.flush();
                    os.close();
                    // 獲取到的圖片是攝像頭捕獲到的原始圖片,也需要對其進行旋轉,旋轉方法見後面
                    PhotoUtil.setPicOrientation(file.getAbsolutePath(), mNeedOrientation);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    });
}


// 旋轉圖片
public static void setPicOrientation(String filePath, int degree) {
    try {
        ExifInterface exifInterface = new ExifInterface(filePath);

        String orientation = String.valueOf(ExifInterface.ORIENTATION_NORMAL);
        // 角度轉換爲對應的ORIENTATION_ROTATE值
        if (90 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
        } else if (180 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
        } else if (270 == degree) {
            orientation = String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
        }

        //設置選擇角度
        exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
        exifInterface.saveAttributes();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

6、停止預覽、釋放camera資源

private void release() {
    if (null != mCamera) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}


二、錄視頻

1、初始化camera、設置預覽orientation、設置previewSize、初始化surfaceView/textureView與上面拍照的流程一模一樣。其中,如果使用surfaceView,在MediaRecorder中也需要設置,使用textureView則不需要。

2、進行錄製

private void startRecording(){
    try {
        // 初始化recorder
        mRecorder = new MediaRecorder();

        // 解鎖攝像頭,連接到攝像頭
        mCamera.unlock();
        mRecorder.setCamera(mCamera);

        // 設置音、視頻源
        mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        // 設置文件輸出格式、音視頻編碼格式。設置順序必須按照下面的順序來,否則會報錯
        // 關於音視頻格式的優缺點,參考http://blog.csdn.net/wcl0715/article/details/676137和http://blog.csdn.net/androidzhaoxiaogang/article/details/6865644
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

        // 設置orientation,這個跟預覽畫面的角度一樣,按照上面的代碼獲取角度
        mRecorder.setOrientationHint(mNeedOrientation);

        // 設置比特率,這個參數對視頻文件的大小影響最大(格式相同情況下)
        mRecorder.setVideoEncodingBitRate(800 * 800);

        // 設置幀率。設置得過低會閃屏,設置高一點也不會增加文件大小,建議設置30左右
        mRecorder.setVideoFrameRate(30);

        // 設置視頻size。通過camera.getParameters().getSupportedVideoSizes()獲取到所有支持的sizes,選一個最合適的
        if (size.width > 0 && size.height > 0) {
            mRecorder.setVideoSize(size.width , size.height);
        }

        // 到時間後,可以通過MediaRecorder.OnInfoListener接收到回調;到時間後錄製不會自動停止,但最終視頻文件只截取前面10s
        mRecorder.setMaxDuration(10000);

        mRecorder.setOutputFile(videoPath);

        // 準備、開始
        mRecorder.prepare();
        mRecorder.start();
        return true;
    } catch (IOException e) {
        Log.i("wk", "record prepare failed,IOException:" + e.toString());
        return false;
    }
}

3、停止錄製、釋放MediaRecorder資源

private void stopRecording() {
    if (null != mRecorder) {
        mRecorder.stop();
        mRecorder.release();
        mRecorder = null;
    }
}

4、鎖定Camera、停止預覽、釋放Camera資源


使用Camera2

Camera2網上已經有很多介紹了,我寫了一個使用Camera2錄像的demo,使用方法、注意事項等就直接在代碼註釋中,就不單獨用文字描述了,偷懶。

爲了方便理清camera2使用的主邏輯,一些配置代碼、計算代碼放在各個helper類裏。這種代碼設計不是最優設計,實際開發中不要照搬。

先是使用的代碼,各個helper類的代碼放在後面。

/**
 * 一、Camera2使用
 * Camera2的操作都是基於管道的,就是發送請求、等待迴應的過程,使用起來沒有代碼結構不如Camera那種線性調用清晰。通過下面四個回調就能說清楚使用過程:
 * 
 * 1.等待surface創建成功的回調,即SurfaceTextureListener(或者是SurfaceView的listener,demo用的是TextureView)
 * 做Camera開發就必須要預覽,要預覽就得有Surface,所以第一步就是等待Surface創建完成;
 * 
 * 2.等待Camera啓動完成的回調,即CameraDevice.StateCallback
 * Camera的啓動需要一個過程,只有Camera啓動後纔可進行各種操作
 * 
 * 3.等待會話建立的回調,即CameraCaptureSession.StateCallback
 * 要向Camera發送各種操作請求,就必須先建立會話通道
 * 
 * 4.等待操作請求的回調,即CameraCaptureSession.CaptureCallback
 * 向Camera發起了"拍照"請求後,Camera需要一定時間才能完成,等待完成後就可以對圖像數據進行處理了
 * 
 * 總結起來就是:創建surface、啓動camera、創建camera會話、發起拍照請求
 * 
 * 二、預覽、拍照/錄像中的方向問題
 * 1.攝像頭總是採集某個矩形範圍內的圖像,攝像頭的方向決定了這個矩形的哪個邊是底、哪個邊是左/右;
 * 
 * 2.攝像頭會把採集到的圖像按照攝像頭的方向傳輸給屏幕,用於顯示預覽圖,所以需要根據攝像頭方向和屏幕方向,決定預覽顯示的旋轉角度;
 * 
 * 3.攝像頭採集的圖像就是最終的成像數據,所以需要根據攝像頭方向和拍攝時的手機方向,決定最終成像需要旋轉的角度
 */
public class VideoActivity extends Activity implements View.OnClickListener {
    private static final String TAG = "VideoActivity";

    private TextureView mTextureView;

    private CameraDevice mCameraDevice;

    // 方便理清camera2使用的主邏輯,一些配置代碼、計算代碼放在各個helper類裏
    private CameraHelper mCameraHelper;
    private RecorderHelper mRecorderHelper;
    private TextureHelper mTextureHelper;

    // 一個device同一時間只能存在一個session,新的session啓動時會關閉其它session;
    // 一個session對應一個request、surfaceList,注意處理好一一對應關係
    private CaptureRequest.Builder mRequest;
    private List<Surface> mSurfaceList = new ArrayList<>();
    private CameraCaptureSession mSession;

    // camera2中用到的幾個回調,通過指定handler,回調方法就會在該handler所在線程被調用
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);

        mTextureView = findViewById(R.id.texture);
        findViewById(R.id.video_record).setOnClickListener(this);
        findViewById(R.id.video_stop).setOnClickListener(this);

        mCameraHelper = new CameraHelper(this);
        mRecorderHelper = new RecorderHelper(this);
        mTextureHelper = new TextureHelper(this);
    }

    @Override
    public void onResume() {
        super.onResume();

        // 啓動後臺線程,用於執行回調中的代碼
        startBackgroundThread();

        // 如果Activity是從stop/pause回來,TextureView是OK的,只需要重新開啓camera就行
        if (mTextureView.isAvailable()) {
            openCamera();
        } else {
            // Activity創建時,添加TextureView的監聽,TextureView創建完成後就可以開啓camera就行了
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

    @Override
    public void onPause() {
        // 關閉camera,關閉後臺線程
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.video_record:
                if (!mRecorderHelper.isRecording()) {
                    startRecord();
                }
                break;
            case R.id.video_stop:
                if (mRecorderHelper.isRecording()) {
                    stopRecord();
                }
                break;
        }
    }

    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void openCamera() {
        // 設置預覽大小、方向/角度
        mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());

        // 開啓後置攝像頭
        mCameraHelper.openCamera(mCameraHelper.getBackCameraId(), new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                // 如果openCamera()方法的第三個參數指定了handler,那麼下面的代碼就會在該handler所在線程中執行,如果不指定就在openCamera()方法所在線程執行
                mCameraDevice = cameraDevice;
                startPreviewSession();

                if (null != mTextureView) {
                    mTextureHelper.configPreview(mTextureView, mTextureView.getWidth(), mTextureView.getHeight());
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                cameraDevice.close();
                mCameraDevice = null;
            }

            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int error) {
                cameraDevice.close();
                mCameraDevice = null;
                finish();
            }

        }, null);
    }

    private void addTextureViewSurface() {
        // 獲取TextureView中的surface,添加到request中、添加到surfaceList中
        Surface previewSurface = mTextureHelper.getSurface(mTextureView);

        if (null != previewSurface) {
            mRequest.addTarget(previewSurface);
            mSurfaceList.add(previewSurface);
        }
    }

    private void addRecorderSurface() {
        // 獲取MediaRecorder中的surface,添加到request中、添加到surfaceList中
        Surface recorderSurface = mRecorderHelper.getSurface();

        if (null != recorderSurface) {
            mRequest.addTarget(recorderSurface);
            mSurfaceList.add(recorderSurface);
        }
    }

    private void closeCamera() {
        // 關閉camera預覽,關閉MediaRecorder
        closePreviewSession();
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        mRecorderHelper.release();
    }

    private void startPreviewSession() {
        if (null == mCameraDevice || !mTextureView.isAvailable()) {
            return;
        }

        try {
            // 創建新的會話前,關閉以前的會話
            closePreviewSession();

            // 創建預覽會話請求
            mSurfaceList.clear();
            mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            addTextureViewSurface();

            // 啓動會話
            // 參數1:camera捕捉到的畫面分別輸出到surfaceList的各個surface中;
            // 參數2:會話狀態監聽;
            // 參數3:監聽器中的方法會在指定的線程裏調用,通過一個handler對象來指定線程;
            mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mSession = session;
                    updatePreview();
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                }

                @Override
                public void onClosed(@NonNull CameraCaptureSession session) {
                    super.onClosed(session);
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void startRecordSession() {
        if (null == mCameraDevice || !mTextureView.isAvailable()) {
            return;
        }

        try {
            closePreviewSession();

            // 創建錄像會話請求
            mSurfaceList.clear();
            mRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
            addTextureViewSurface();
            addRecorderSurface();

            // 啓動會話。可以看出跟上面的"預覽session"是一樣的,只是surfaceList多加了一個
            mCameraDevice.createCaptureSession(mSurfaceList, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mSession = session;
                    updatePreview();

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mRecorderHelper.start();
                        }
                    });
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(VideoActivity.this, "Failed", Toast.LENGTH_SHORT).show();
                }
            }, mBackgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void updatePreview() {
        if (null == mCameraDevice) {
            return;
        }

        try {
            mRequest.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

            // 這個接口是預覽。作用是把camera捕捉到的畫面輸出到surfaceList中的各個surface上,每隔一定時間重複一次
            mSession.setRepeatingRequest(mRequest.build(), null, mBackgroundHandler);

            // 這個接口是拍照。由於拍照需要獲得圖像數據,所以這裏需要實現CaptureCallback,在回調裏獲得圖像數據
//            mSession.capture(CaptureRequest request, CaptureCallback listener, Handler handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void closePreviewSession() {
        if (mSession != null) {
            try {
                mSession.stopRepeating();
                mSession.abortCaptures();
                mSession.close();
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void startRecord() {
        // 設置Recorder配置,啓動錄像會話
        int sensorOrientation = mCameraHelper.getSensorOrientation(mCameraHelper.getBackCameraId());
        int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
        mRecorderHelper.configRecorder(sensorOrientation, displayRotation);

        startRecordSession();
    }

    private void stopRecord() {
        // 關閉錄像會話,停止錄像,重新進入預覽
        mRecorderHelper.stop();
        startPreviewSession();
    }

    // TextureView狀態監聽
    private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
            mTextureHelper.configPreview(mTextureView, width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        }
    };
}

接下來是三個helper類的代碼,首先是TextureHelper類:

public class TextureHelper {
    private Activity mActivity;

    // 這裏只是展示用法,實際開發中需要根據攝像頭的支持size來取
    private Size mPreviewSize = new Size(960, 720);

    public TextureHelper(Activity activity) {
        mActivity = activity;
    }

    // 配置預覽圖的大小、方向/角度
    public void configPreview(TextureView textureView, int targetWidth, int targetHeight) {
        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, targetWidth, targetHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max((float) targetHeight / mPreviewSize.getHeight(), (float) targetWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    // 根據TextureView和預覽size,獲取surface
    public Surface getSurface(TextureView textureView) {
        SurfaceTexture texture = textureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(960, 720);
        return new Surface(texture);
    }
}

然後是CameraHelper類:

public class CameraHelper {
    private CameraManager mManager;
    private String mBackCameraId;

    public CameraHelper(Context context) {
        mManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    }

    public String getBackCameraId() {
        if(!TextUtils.isEmpty(mBackCameraId)){
            return mBackCameraId;
        }

        try {
            String[] ids = mManager.getCameraIdList();
            for (String cameraId : ids) {
                CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                // 根據攝像頭的朝向判斷是否是後置攝像頭
                if (null != facing && CameraMetadata.LENS_FACING_BACK == facing) {
                    mBackCameraId = cameraId;
                    return cameraId;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return "";
    }

    public int getSensorOrientation(String cameraId) {
        try {
            CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
            return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return -1;
    }

    public Size[] getSupportSize(String cameraId, Class klass) {
        try {
            CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            if (null != map) {
                return map.getOutputSizes(klass);
//                map.getOutputSizes(MediaRecorder.class);// 支持的錄像視頻size
//                map.getOutputSizes(MediaRecorder.class);// 支持的錄像視頻size
//                map.getOutputSizes(ImageReader.class);// 支持的拍照照片size
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 注意,camera、recorder權限都是隱私權限,6.0以後需要動態權限配置
    @SuppressLint("MissingPermission")
    public void openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler) {
        try {
            mManager.openCamera(cameraId,callback,handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

最後是RecorderHelper類:

public class RecorderHelper {
    private Context mContext;
    private MediaRecorder mRecorder;
    private String mPath;
    private boolean hasPrepared;

    private static final int SENSOR_DEFAULT_DEGREES = 90;
    private static final int SENSOR_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();

    static {
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    static {
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
    }

    public RecorderHelper(Context context) {
        mContext = context;
    }

    private void initIfNecessary() {
        if (null == mRecorder) {
            mRecorder = new MediaRecorder();
        }
    }

    private void updatePath() {
        final File dir = mContext.getExternalFilesDir(null);
        if (null != dir) {
            mPath = dir.getAbsolutePath() + "/" + System.currentTimeMillis() + ".mp4";
        }
    }

    public void configRecorder(int sensorOrientation, int displayRotation) {
        initIfNecessary();

        // 設置存儲路徑
        updatePath();
        if (TextUtils.isEmpty(mPath)) {
            return;
        }
        mRecorder.setOutputFile(mPath);

        // 設置音、視頻採集源
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

        // 設置音、視頻編碼格式,以及文件封裝格式。設置順序必須跟下面一模一樣,否則報錯
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

        // 設置比特率、幀率和分辨率
        mRecorder.setVideoEncodingBitRate(800 * 800);
        mRecorder.setVideoFrameRate(30);
        // 這裏只是展示用法,實際開發中需要根據攝像頭的支持size來取
        mRecorder.setVideoSize(960, 720);

        // 根據camera方向和屏幕角度,設置錄製視頻的角度補償
        if (SENSOR_DEFAULT_DEGREES == sensorOrientation) {
            mRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(displayRotation));
        } else if (SENSOR_INVERSE_DEGREES == sensorOrientation) {
            mRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(displayRotation));
        }

        try {
            mRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

        hasPrepared = true;
    }

    public void start() {
        if (hasPrepared) {
            mRecorder.start();
        }
    }

    // 停止之後,MediaRecorder不需要置空,下次使用時需要重新配置
    public void stop() {
        if (hasPrepared) {
            mRecorder.stop();
            mRecorder.reset();
            hasPrepared = false;
        }
    }

    public void release() {
        if (null != mRecorder) {
            mRecorder.release();
            hasPrepared = false;
            mRecorder = null;
        }
    }

    public Surface getSurface() {
        return hasPrepared ? mRecorder.getSurface() : null;
    }

    public boolean isRecording() {
        return hasPrepared;
    }
}
發佈了35 篇原創文章 · 獲贊 52 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章