Android Camera、Camera2详解

前言

Android5.0之前使用android.hardware包下的Camera类进行拍照、录视频等功能。5.0以后,新增了android.hardware.camera2包,利用新的机制、新的类进行拍照、录视频。

使用Camera

一、拍照

由于手机摄像头配置不同、摄像头摆放方向不同、位置不同等等因素,与摄像头相关参数如:摄像头个数、支持的像素、画面预览方向、闪光灯、对焦方式、帧率等等都不一样,必须根据当前手机的配置动态获取。获取方法如下:

Camera.Parameters p = mCamera.getParameters();
List.Size> preSizes = p.getSupportedPreviewSizes();
List.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 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 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;
    }
}

你可能感兴趣的:(Android Camera、Camera2详解)