Android相机(Camera)使用总结及代码示例

#.Android中Camera1使用主要流程:
开启相机使用的关键步骤:
0.获取相应权限
1.检查相机可用性
2.遍历相机列表,打开指定方向(前置/后置)相机,获取对应实例对象
3.设置预览参数
4.设置预览画面偏转方向
5.设置预览画面输出方式
6.开启预览
结束相机使用的关键步骤:
1.关闭对应相机预览
2.释放对应相机资源
##.一些要点
1.摄像头相关回调执行的线程
Camera.open([index])会启动相应的摄像头,并返回其实例对象。该方法在哪个线程调用,意味着摄像头将被哪个线程占用和绑定,后继的回调都会在该线程中调用。
2.可以通过设置帧回调接口,获取相机传回的帧画面,来做自己的处理。
private final int PREVIEW_BUFFER_COUNT = 3;
private byte[][] mPreviewCallbackBuffer;
private void otherApiExplain(){
    //方式1:设置回调接口,每当相机有新生成的帧画面,都会自动创建缓冲区,并将画面数据回调
    //          但是会频繁的创建缓冲区和GC销毁,会有较高内存消耗,并不推荐该方式
    mCamera.setPreviewCallback(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            //这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据
        }
    });
    //方式2:设置该接口,只会回调一次,可用于拍照。会自动创建缓冲区,并将画面数据回调
    mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            //这里面的Bytes的数据就是NV21格式的数据,或者YUV_420_888的数据
        }
    });

    //方式3:设置带缓冲区的回调接口。推荐的方式
    //   需要自己addCallbackBuffer()把缓冲区数组传入缓冲区队列,相机画面会存储在这些缓冲区中回调回来;
    //   每次回调,都会从队列取出一个缓冲区数组;当缓冲区队列中没有数组时,就不再继续回调。
    //   因此,可以在每次回调时,都把返回的数据缓冲区,重新放回缓冲区队列中。
    //   这样可以有效复用这些缓冲区,降低内存消耗。  
    if (mPreviewCallbackBuffer == null) {
        // nv21 一个像素点1.5byte
        mPreviewCallbackBuffer = new byte[PREVIEW_BUFFER_COUNT][DESIRED_WIDTH * DESIRED_HEIGHT * 3 / 2];
    }
    // 加入预先生成的指定数量预览缓冲buffer,这里是3个
    for (int i = 0; i < PREVIEW_BUFFER_COUNT; i++){
        mCamera.addCallbackBuffer(mPreviewCallbackBuffer[i]);
    }
    mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            //如果这里做费时的操作的话,最好优先把缓冲区放回去,再进行处理
            //  以免队列中缓冲区暂时耗尽,不再回调
        }
    });
}
3.设置预览画面输出的两种方式
//设置预览画面输出方式
//    也可以不设置,而是通过setPreviewCallbackWithBuffer()等方式来获取相机预览画面帧。
//方式1:输出到一个SurfaceView的Surface上,
//     这种方式新的画面帧一旦生成,就会被绘制到SurfaceView的Surface上显示出来
mCamera.setPreviewDisplay(surfaceView.getHolder());

//方式2:输出到SurfaceTexture的缓冲区上,会被转化为一个OpenGl纹理
//      SurfaceTexture通过调用updateTexImage()可以用画面流中最新的画面来更新纹理的内容
//      然后开发者可以将这个纹理做自己需要的业务处理。
//      例如用OpenGl进一步处理后显示到GlSurfaceView上,或者编码到直播视频流中。
//      可通过SurfaceTexture的API:getTransformMatrix(矩阵),来获取纹理采样坐标的变换矩阵;  
mCamera.setPreviewTexture(SurfaceTexture);
4.关于预览中途手机画面方向旋转后,摄像头预览画面旋转角度变化的处理
4.1 手机预览中途,也可以通过调用 mCamera .setDisplayOrientation(result) 来重新设置预览画面的偏转角度。
这一步很简单,只需要重新获取当前画面的旋转角度,然后计算预览画面的应偏转角度并设置就可以了。
4.2 其实有些麻烦的是,动态监听手机画面方向发生了变化。一般容易想到的是下面方法1和方法2两种方法,但都有一定问题。
    自己想的方法是,添加一个每个1s执行1次的循环,每次都检查方向是够改变,再结合方法2,来监听方向改变,即下面的方法3。
###方法1,该方法不可取。
//添加手机当前旋转方向的监听器,值返回范围0~359度,"自然方向"放置时回调角度为0.
// 但是仅仅是监听手机当前的旋转角度,手机中画面的旋转角度未必与之同步。
// 例如很缓慢地把手机转动180度时,这里的返回角度一直在变,但画面方向始终未变。
//所以,该方法不可取。
OrientationEventListener mOrientationListener = new OrientationEventListener(this) {
    @Override
    public void onOrientationChanged(int orientation) {
        ILog.d(TAG, "onOrientationChanged(), orientation=" + orientation);
    }
};
mOrientationListener.enable();
###方法2:
private int mOrientation = Configuration.ORIENTATION_PORTRAIT;//记录当前画面
//1.如果希望画面方向改变时,触发该回调,需要AndroidManifest.xml中注册该Activity时
//          配置项android:configChanges=""中要添加参数值orientation
//2.即使添加了方向改变触发回调,也只有在横向、竖向改变时,会触发该回调。
//      当手机画面方向直接旋转180度,例如从一侧横屏转到另一侧横屏时,不会触发该方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    ILog.d(TAG, "onConfigurationChanged()");
    if(newConfig == null){
        return;
    }
    ILog.d(TAG, "onConfigurationChanged(), newConfig.orientation=" + newConfig.orientation + ", mOrientation= "+mOrientation);
    if(newConfig != null && newConfig.orientation != mOrientation){
        mOrientation = newConfig.orientation;
        Camera1Manager.getInstance().setDisplayOrientation(this);
    }
}
###.方法3:自己用的方法
private int mOrientation = Configuration.ORIENTATION_PORTRAIT;//记录当前画面方向
//1.如果希望画面方向改变时,触发该回调,需要AndroidManifest.xml中注册该Activity时
//          配置项android:configChanges=""中要添加参数值orientation
//2.即使添加了方向改变触发回调,也只有在横向、竖向改变时,会触发该回调。
//      当手机画面方向直接旋转180度,例如从一侧横屏转到另一侧横屏时,不会触发该方法
//3.针对第2点中的情况,做补充处理,添加一个变量记录当前方向,进行循环,每1s检测一次当前方向
//  3.1若方向改变了,就更改摄像头预览画面方向。这样从用户操作,到处理改变,最多延迟1s;
//  3.2若onConfigurationChanged()被触发了,则可实时处理对应逻辑。
//      同时更新当前方向,所以3.1中检测方向时一定是相同的,不会重复触发对应处理逻辑。
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    ILog.d(TAG, "onConfigurationChanged()");
    if(newConfig == null){
        return;
    }
    ILog.d(TAG, "onConfigurationChanged(), newConfig.orientation=" + newConfig.orientation + ", mOrientation= "+mOrientation);
    if(newConfig != null && newConfig.orientation != mOrientation){
        mRotation = DeviceUtils.getRotation(this);//更新mRotation方向
        mOrientation = newConfig.orientation;
        Camera1Manager.getInstance().setDisplayOrientation(this);
    }
}

private int mRotation = Surface.ROTATION_0;//记录当前画面旋转角度
//检查画面方向变化
private void checkRotation(){
    int rotation = DeviceUtils.getRotation(this);
    if(mRotation != rotation){
        ILog.d(TAG, "checkRotation(), rotation changed!");
        mRotation = rotation;
        Camera1Manager.getInstance().setDisplayOrientation(this);
    }
    mHandler.sendEmptyMessageDelayed(MSG_ROTATION_CHECK, 1000);
}
private final int MSG_ROTATION_CHECK = 1;//检查画面方向变化

@Override
public void handleMessage(Message msg) {
    super.handleMessage(msg);
    if(msg == null){
        return;
    }
    if(msg.what == MSG_ROTATION_CHECK){
        checkRotation();
    }
}

##.代码示例:使用SurfaceView+自己新建的HandlerThread
  //管理类
public class Camera1Manager {
    private final String TAG = getClass().getSimpleName();

    private static class SingleTon {
        public static Camera1Manager sInstance = new Camera1Manager();
    }
    private Camera1Manager(){}
    public static Camera1Manager getInstance(){
        return SingleTon.sInstance;
    }

    private final int DESIRED_WIDTH = 1280;
    private final int DESIRED_HEIGHT = 720;
    private boolean mState = false;
    private Camera mCamera;
    private Camera.CameraInfo mCameraInfo;
    private int mFaceType = Camera.CameraInfo.CAMERA_FACING_FRONT;

    public void changeDirection(Activity activity, SurfaceView surfaceView){
        if(activity == null || surfaceView == null){
            return;
        }
        stopPreview();
        startPreview(activity, surfaceView);
        if(mFaceType == Camera.CameraInfo.CAMERA_FACING_FRONT){
            mFaceType = Camera.CameraInfo.CAMERA_FACING_BACK;
        } else {
            mFaceType = Camera.CameraInfo.CAMERA_FACING_FRONT;
        }
    }

    public void setFaceType(boolean isFront){
        if(isFront == (mFaceType == Camera.CameraInfo.CAMERA_FACING_FRONT)){
            return;
        }
        mFaceType = isFront ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
    }

    /**
     * 检查摄像头可用性
     * @param context
     * @return
     */
    private boolean checkCameraEnable(Context context){
        if(context == null){
            return false;
        }
        DevicePolicyManager dpm = (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);
        if(dpm.getCameraDisabled(null)){
            return false;
        }
        return true;
    }

    private boolean openCamera(){
        Camera.CameraInfo info = new Camera.CameraInfo();
        // Try to find a front-facing camera (e.g. for videoconferencing).
        int numCameras = Camera.getNumberOfCameras();
        for(int i = 0; i < numCameras; i++) {
            Camera.getCameraInfo(i, info);
            if (info.facing == mFaceType) {
                mCamera = Camera.open(i);
                mCameraInfo = info;
                break;
            }
        }

        if(mCamera == null) {
            ILog.d(TAG, "No front-facing camera found; opening default");
//            mCamera = Camera.open();    // opens first back-facing camera
            return false;
        }
        return true;
    }

    public void setDisplayOrientation(Activity activity) {
        if(activity == null || mCamera == null || mCameraInfo == null){
            return;
        }
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (mCameraInfo.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (mCameraInfo.orientation - degrees + 360) % 360;
        }
        ILog.d(TAG, "mCameraInfo.orientation= " + mCameraInfo.orientation
                        + ",\ndegrees= " + degrees
                        + ",\nresult= " + result);
        try {
            mCamera.setDisplayOrientation(result);
        } catch (Exception e) {
            ILog.e(TAG, "setDisplayOrientation", e);
        }
    }

    private void setCameraParams(){
        if(mCamera == null || mCameraInfo == null){
            return;
        }
        Camera.Parameters parameters = mCamera.getParameters();
        //自动对焦
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)){
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        }
        parameters.setPreviewSize(DESIRED_WIDTH, DESIRED_HEIGHT);
        // Give the camera a hint that we're recording video.  This can have a big
        // impact on frame rate.
        parameters.setRecordingHint(true);
        mCamera.setParameters(parameters); // 摄像头参数设置完成
    }

    public void startPreview(Activity activity, SurfaceView surfaceView){
        if(mState){
            return;
        }
        if(activity == null || surfaceView == null){
            return;
        }
        //1.检查相机可用性
        if(!checkCameraEnable(activity)){
            return;
        }
        //2.遍历相机列表,打开指定相机,获取对应实例对象
        if(!openCamera()){
            return;
        }
        //3.设置预览参数
        setCameraParams();
        //4.设置预览画面偏转方向
        setDisplayOrientation(activity);
        if(mCamera == null){
            return;
        }
        try {
            //5.设置预览画面输出方式
            mCamera.setPreviewDisplay(surfaceView.getHolder());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //开启预览
        mCamera.startPreview();
        mState = true;
    }

    public void stopPreview(){
        if(!mState){
            return;
        }
        mState = false;
        if(mCamera == null){
            return;
        }
        //1.关闭预览
        mCamera.stopPreview();
        try {
            mCamera.setPreviewDisplay(null);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //2.释放资源
        mCamera.release();
        mCamera = null;
    }
}
//使用
public class TestCamera1Activity extends BaseActivity {
    @Override
    protected BaseActivityPresenter getPresenter() {
        return null;
    }

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

        if(!AppOpsChecker.checkOps(this, AppOpsChecker.OP_CAMERA)){
            PermissionUtils.getPermissions(this, new PermissionUtils.Callback() {
                @Override
                public void onPermissionGranted(List<String> data) {
                    T.show("相机权限获取成功");
                }

                @Override
                public void onPermissionDenied(List<String> data) {
                    T.show("相机权限获取失败");
                }
            }, Permission.Group.CAMERA);
        }

//        //添加手机当前旋转方向的监听器,值返回范围0~359度,"自然方向"放置时回调角度为0.
//        // 但是仅仅是监听手机当前的旋转角度,手机中画面的旋转角度未必与之同步。
//        // 例如缓慢把手机转动180度时,这里的返回角度一直在变,但画面方向始终未变。
//        //所以,该方法不可取。
//        OrientationEventListener mOrientationListener = new OrientationEventListener(this) {
//            @Override
//            public void onOrientationChanged(int orientation) {
//                ILog.d(TAG, "onOrientationChanged(), orientation=" + orientation);
//            }
//        };
//        mOrientationListener.enable();
        mCameraThread.start();
        mCameraThread.setSurfaceView(videoSurfaceView);

        checkRotation();
    }

    private VideoSurfaceView videoSurfaceView;
    private Button btnStartPreview;
    private Button btnStopPreview;
    private Button btnChangeDirection;
    private void initView(){
        videoSurfaceView = (VideoSurfaceView) findViewById(R.id.video_surface_view);
        btnStartPreview = (Button) findViewById(R.id.btn_start_preview);
        btnStopPreview = (Button) findViewById(R.id.btn_stop_preview);
        btnChangeDirection = (Button) findViewById(R.id.btn_change_direction);
        btnStartPreview.setOnClickListener(mClickListener);
        btnStopPreview.setOnClickListener(mClickListener);
        btnChangeDirection.setOnClickListener(mClickListener);

        videoSurfaceView = (VideoSurfaceView) findViewById(R.id.video_surface_view);
    }

    @Override
    protected void handleNoDoubleClick(View v) {
        super.handleNoDoubleClick(v);
        if(v == btnStartPreview){
            Camera1Manager.getInstance().startPreview(this, videoSurfaceView);
        } else if(v == btnStopPreview){
            Camera1Manager.getInstance().stopPreview();
        } else if(v == btnChangeDirection){
            Camera1Manager.getInstance().changeDirection(this, videoSurfaceView);
        }
    }

    private int mOrientation = Configuration.ORIENTATION_PORTRAIT;//记录当前画面方向
    //1.如果希望画面方向改变时,触发该回调,需要AndroidManifest.xml中注册该Activity时
    //          配置项android:configChanges=""中要添加参数值orientation
    //2.即使添加了方向改变触发回调,也只有在横向、竖向改变时,会触发该回调。
    //      当手机画面方向直接旋转180度,例如从一侧横屏转到另一侧横屏时,不会触发该方法
    //3.针对第2点中的情况,做补充处理,添加一个变量记录当前方向,进行循环,每1s检测一次当前方向
    //  3.1若方向改变了,就更改摄像头预览画面方向。这样从用户操作,到处理改变,最多延迟1s;
    //  3.2若onConfigurationChanged()被触发了,则可实时处理对应逻辑。
    //      同时更新当前方向,所以3.1中检测方向时一定是相同的,不会重复触发对应处理逻辑。
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ILog.d(TAG, "onConfigurationChanged()");
        if(newConfig == null){
            return;
        }
        ILog.d(TAG, "onConfigurationChanged(), newConfig.orientation=" + newConfig.orientation + ", mOrientation= "+mOrientation);
        if(newConfig != null && newConfig.orientation != mOrientation){
            mRotation = DeviceUtils.getRotation(this);//更新mRotation方向
            mOrientation = newConfig.orientation;
            mCameraThread.updateDisplayOrientation();
        }
    }

    private int mRotation = Surface.ROTATION_0;//记录当前画面旋转角度
    //检查画面方向变化
    private void checkRotation(){
        int rotation = DeviceUtils.getRotation(this);
        if(mRotation != rotation){
            ILog.d(TAG, "checkRotation(), rotation changed!");
            mRotation = rotation;
            mCameraThread.updateDisplayOrientation();
        }
        mHandler.sendEmptyMessageDelayed(MSG_ROTATION_CHECK, 1000);
    }
    private final int MSG_ROTATION_CHECK = 1;//检查画面方向变化

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if(msg == null){
            return;
        }
        if(msg.what == MSG_ROTATION_CHECK){
            checkRotation();
        }
    }

    //新建一个线程,用于打开相机,该线程将占用和绑定相机,后继相机操作在该线程中进行
    private CameraThread mCameraThread = new CameraThread(TAG);
    private class CameraThread extends HandlerThread implements IHandler {

        public CameraThread(String name) {
            super(name);
        }

        public CameraThread(String name, int priority) {
            super(name, priority);
        }

        @Override
        public void run() {
            super.run();
        }

        private VideoSurfaceView mSurfaceView;
        public void setSurfaceView(VideoSurfaceView surfaceView){
            if(surfaceView == null){
                return;
            }
            mSurfaceView = surfaceView;
            mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(@NonNull SurfaceHolder holder) {
                    ILog.d(TAG, "surfaceCreated()");
                    startCameraPreview();
                }

                @Override
                public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                    ILog.d(TAG, "surfaceChanged()");

                }

                @Override
                public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                    ILog.d(TAG, "surfaceDestroyed()");
                    stopCameraPreview();
                }
            });
        }

        public void startCameraPreview(){
            mHandler.sendEmptyMessage(MSG_START_PREVIEW);
        }

        public void stopCameraPreview(){
            mHandler.sendEmptyMessage(MSG_STOP_PREVIEW);
        }

        public void updateDisplayOrientation(){
            mHandler.sendEmptyMessage(MSG_UPDATE_ROTATION);
        }

        private final int MSG_START_PREVIEW = 1;
        private final int MSG_STOP_PREVIEW = 2;
        private final int MSG_UPDATE_ROTATION = 3;
        private WeakHandler<CameraThread> mHandler = new WeakHandler<>(this);
        @Override
        public void handleMessage(Message msg) {
            if(msg == null){
                return;
            }
            int what = msg.what;
            switch (what){
                case MSG_START_PREVIEW:
                    Camera1Manager.getInstance().startPreview(TestCamera1Activity.this, videoSurfaceView);
                    break;
                case MSG_STOP_PREVIEW:
                    Camera1Manager.getInstance().stopPreview();
                    break;
                case MSG_UPDATE_ROTATION:
                    Camera1Manager.getInstance().setDisplayOrientation(TestCamera1Activity.this);
                    break;
                default:
                    break;
            }
        }
    }
}

##.其它补充
1.可通过takePicture()获取图片,拍照时用此方法比较方便。
调用takePicture后预览会停止,需用重新调用startPreview才能再次开始预览。预览开始后,就可以通过Camera.takePicture()方法拍摄一张照片,返回的照片数据通过Callback接口获取。
takePicture()接口可以获取三个类型的照片:
第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效;
第二个,PictureCallback接口,返回未经压缩的RAW类型照片;
第三个,PictureCallback接口,返回经过压缩的JPEG类型照片;
mCamera.takePicture(new Camera.ShutterCallback() {
    @Override
    public void onShutter() {

    }
}, new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

    }
}, new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
       
    }
});

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124693197