Android平台Camera开发实践指南

关于做者html

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工做,欢迎交流技术方面的问题,能够去个人Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。java

文章目录android

  • 一 Camera实践指南
    • 1.1 打开相机
    • 1.2 关闭相机
    • 1.3 开启预览
    • 1.4 关闭预览
    • 1.5 拍照
    • 1.6 开始视频录制
    • 1.7 结束视频录制
  • 二 Camera2实践指南
    • 2.1 打开相机
    • 2.2 关闭相机
    • 2.3 开启预览
    • 2.4 关闭预览
    • 2.5 拍照
    • 2.6 开始视频录制
    • 2.7 结束视频录制

Android Camera 相关API也是Android生态碎片化最为严重的一块,首先Android自己就有两套API,Android 5.0如下的Camera和Android 5.0以上的Camera2,并且 更为严重的时,各家手机厂商都Camera2的支持程度也各不相同,这就致使咱们在相机开发中要花费很大精力来处理兼容性问题。git

相机开发的通常流程是什么样的?🤔程序员

  1. 检测并访问相机资源 检查手机是否存在相机资源,若是存在则请求访问相机资源。
  2. 建立预览界面,建立继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。有了拍摄预览类,便可建立一个布局文件,将预览画面与设计好的用户界面控件融合在一块儿,实时显示相机的预览图像。
  3. 设置拍照监听器,给用户界面控件绑定监听器,使其能响应用户操做, 开始拍照过程。
  4. 拍照并保存文件,将拍摄得到的图像转换成位图文件,最终输出保存成各类经常使用格式的图片。
  5. 释放相机资源,相机是一个共享资源,当相机使用完毕后,必须正确地将其释放,以避免其它程序访问使用时发生冲突。

相机开发通常须要注意哪些问题?🤔github

  1. 版本兼容性问题,Android 5.0如下的Camera和Android 5.0以上使用Camera2,Android 4.0如下的SurfaceView和Android 4.0以上的TextureView,Android 6.0以上要作相机等运行时权限兼容。
  2. 设备兼容性问题,Camera/Camera2里的各类特性在有些手机厂商的设备实现方式和支持程度是不同的,这个须要作兼容性测试,一点点踩坑。
  3. 各类场景下的生命周期变化问题,最多见的是后台场景和锁屏场景,这两种场景下的相机资源的申请与释放,Surface的建立与销毁会带来一些问题,这个咱们 后面会仔细分析。

关于Camera/Camear2session

既然要解决这种兼容性问题,就要两套并用,那是否是根据版原本选择:Android 5.0 如下用Camera,Android 5.0以上用Camera2呢?🤔架构

事实上,这样是不可取的。前面说过不一样手机厂商对Camera2的支持程度各不相同,即使是Android 5.0 以上的手机,也存在对Camera2支持很是差的状况,这个时候就要降级使用Camera,如何判断对Camera的支持 程度咱们下面会说。框架

关于SurfaceView/TextureViewide

  • SurfaceView是一个有本身Surface的View。界面渲染能够放在单独线程而不是主线程中。它更像是一个Window,自身不能作变形和动画。
  • TextureView一样也有本身的Surface。可是它只能在拥有硬件加速层层的Window中绘制,它更像是一个普通View,能够作变形和动画。

更多关于SurfaceView与TextureView区别的内容能够参考这篇文章Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView.

那么如何针对版本进行方案的选择呢?🤔

官方的开源库cameraview给出了方案:

既然要两套并用,就要定义统一的接口,针对不一样场景提供不一样的实现,使用的时候也是根据不一样的场景来建立不一样的实例。

咱们不难发现,这个接口通常须要定义如下功能:

  • 打开相机
  • 关闭相机
  • 开启预览
  • 关闭预览
  • 拍照
  • 开始视频录制
  • 结束视频录制

定义好了接口,咱们就有了思路,针对相机的具体特性实现相应的方案,那么另外一个问题就出来了,相机在平常开发中通常做为一个SDK的形式存在供各个业务方调用,那么如何设计 出一个功能与UI相分离,高度可定制的相机SDK呢?🤔

答案就是利用Fragment,将各类点击事件(点击拍照、点击切换摄像头、点击切换闪光模式等)对应的功能封装在Fragment里,业务方在用的时候能够在Fragment之上蒙一层 UI(固然咱们也须要提供默认的实现),这样就可让功能和UI相分离,集成起来也很是的简便。

相机SDK框架图以下所示:

  • CameraActivity:相机界面,主要用来实现UI的定制,实际功能(点击事件)交由CameraFragment完成。
  • CameraFragment:向CameraActivity提供功能接口,完成CameraActivity里的点击事件,例如:拍照、录像等。
  • CameraLifecycle:处理相机随着Activity生命周期变化的状况,内部持有CameraManager,处理相机初始化和释放,预览的建立与销毁等问题。
  • CameraManager:相机的实际管理者,调用相机API来操做相机,进行拍照和录像等操做。
  • Camera/Camera2:相机API。

phoenix项目已经实现了这套方案,效果图以下所示:

理解了总体的架构,咱们接着就来分析针对这套架构,Camera/Camera2分别该如何实现。

一 Camera实践指南

Camera API中主要涉及如下几个关键类:

  • Camera:操做和管理相机资源,支持相机资源切换,设置预览和拍摄尺寸,设置光圈、曝光等相关参数。
  • SurfaceView:用于绘制相机预览图像,提供实时预览的图像。
  • SurfaceHolder:用于控制Surface的一个抽象接口,它能够控制Surface的尺寸、格式与像素等,并能够监视Surface的变化。
  • SurfaceHolder.Callback:用于监听Surface状态变化的接口。

SurfaceView和普通的View相比有什么区别呢?🤔

普通View都是共享一个Surface的,全部的绘制也都在UI线程中进行,由于UI线程还要处理其余逻辑,所以对View的更新速度和绘制帧率没法保证。这显然不适合相机实时 预览这种状况,于是SurfaceView持有一个单独的Surface,它负责管理这个Surface的格式、尺寸以及显示位置,它的Surface绘制也在单独的线程中进行,于是拥有更高 的绘制效率和帧率。

SurfaceHolder.Callback接口里定义了三个函数:

  • surfaceCreated(SurfaceHolder holder); 当Surface第一次建立的时候调用,能够在这个方法里调用camera.open()、camera.setPreviewDisplay()来实现打开相机以及链接Camera与Surface 等操做。
  • surfaceChanged(SurfaceHolder holder, int format, int width, int height); 当Surface的size、format等发生变化的时候调用,能够在这个方法里调用camera.startPreview()开启预览。
  • surfaceDestroyed(SurfaceHolder holder); 当Surface被销毁的时候调用,能够在这个方法里调用camera.stopPreview(),camera.release()等方法来实现结束预览以及释放

1.1 打开相机

打开相机以前咱们须要先获取系统相机的相关信息。

//有多少个摄像头
numberOfCameras = Camera.getNumberOfCameras();

for (int i = 0; i < numberOfCameras; ++i) {
    final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();

    Camera.getCameraInfo(i, cameraInfo);
    //后置摄像头
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
        faceBackCameraId = i;
        faceBackCameraOrientation = cameraInfo.orientation;
    } 
    //前置摄像头
    else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        faceFrontCameraId = i;
        faceFrontCameraOrientation = cameraInfo.orientation;
    }
}
复制代码

知道了相机相关信息,就能够经过相机ID打开相机了。

camera = Camera.open(cameraId);
复制代码

另外,打开相机之后你会得到一个Camera对象,从这个对象里能够获取和设置相机的各类参数信息。

//获取相机参数
camera.getParameters();
//设置相机参数
camera.getParameters();
复制代码

常见的参数有如下几种。

闪光灯配置参数,能够经过Parameters.getFlashMode()接口获取。

  • Camera.Parameters.FLASH_MODE_AUTO 自动模式,当光线较暗时自动打开闪光灯;
  • Camera.Parameters.FLASH_MODE_OFF 关闭闪光灯;
  • Camera.Parameters.FLASH_MODE_ON 拍照时闪光灯;
  • Camera.Parameters.FLASH_MODE_RED_EYE 闪光灯参数,防红眼模式。

对焦模式配置参数,能够经过Parameters.getFocusMode()接口获取。

  • Camera.Parameters.FOCUS_MODE_AUTO 自动对焦模式,摄影小白专用模式;
  • Camera.Parameters.FOCUS_MODE_FIXED 固定焦距模式,拍摄老司机模式;
  • Camera.Parameters.FOCUS_MODE_EDOF 景深模式,文艺女青年最喜欢的模式;
  • Camera.Parameters.FOCUS_MODE_INFINITY 远景模式,拍风景大场面的模式;
  • Camera.Parameters.FOCUS_MODE_MACRO 微焦模式,拍摄小花小草小蚂蚁专用模式;

场景模式配置参数,能够经过Parameters.getSceneMode()接口获取。

  • Camera.Parameters.SCENE_MODE_BARCODE 扫描条码场景,NextQRCode项目会判断并设置为这个场景;
  • Camera.Parameters.SCENE_MODE_ACTION 动做场景,就是抓拍跑得飞快的运动员、汽车等场景用的;
  • Camera.Parameters.SCENE_MODE_AUTO 自动选择场景;
  • Camera.Parameters.SCENE_MODE_HDR 高动态对比度场景,一般用于拍摄晚霞等明暗分明的照片;
  • Camera.Parameters.SCENE_MODE_NIGHT 夜间场景;

1.2 关闭相机

关闭相机很简单,只须要把相机释放掉就能够了。

camera.release();
复制代码

1.3 开启预览

Camera的预览时经过SurfaceView的SurfaceHolder进行的,先经过,具体说来:

private void startPreview(SurfaceHolder surfaceHolder) {
    try {
        final Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(currentCameraId, cameraInfo);
        int cameraRotationOffset = cameraInfo.orientation;

        //获取相机参数
        final Camera.Parameters parameters = camera.getParameters();
        //设置对焦模式
        setAutoFocus(camera, parameters);
        //设置闪光模式
        setFlashMode(mCameraConfigProvider.getFlashMode());

        if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)
            turnPhotoCameraFeaturesOn(camera, parameters);
        else if (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_PHOTO)
            turnVideoCameraFeaturesOn(camera, parameters);

        final int rotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break; // Natural orientation
            case Surface.ROTATION_90:
                degrees = 90;
                break; // Landscape left
            case Surface.ROTATION_180:
                degrees = 180;
                break;// Upside down
            case Surface.ROTATION_270:
                degrees = 270;
                break;// Landscape right
        }

        //根据前置与后置摄像头的不一样,设置预览方向,不然会发生预览图像倒过来的状况。
        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            displayRotation = (cameraRotationOffset + degrees) % 360;
            displayRotation = (360 - displayRotation) % 360; // compensate
        } else {
            displayRotation = (cameraRotationOffset - degrees + 360) % 360;
        }
        this.camera.setDisplayOrientation(displayRotation);

        if (Build.VERSION.SDK_INT > 13
                && (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
// parameters.setRecordingHint(true);
        }

        if (Build.VERSION.SDK_INT > 14
                && parameters.isVideoStabilizationSupported()
                && (mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_VIDEO
                || mCameraConfigProvider.getMediaAction() == CameraConfig.MEDIA_ACTION_UNSPECIFIED)) {
            parameters.setVideoStabilization(true);
        }

        //设置预览大小
        parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
        parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight());

        //设置相机参数
        camera.setParameters(parameters);
        //设置surfaceHolder
        camera.setPreviewDisplay(surfaceHolder);
        //开启预览
        camera.startPreview();

    } catch (IOException error) {
        Log.d(TAG, "Error setting camera preview: " + error.getMessage());
    } catch (Exception ignore) {
        Log.d(TAG, "Error starting camera preview: " + ignore.getMessage());
    }
}
复制代码

1.4 关闭预览

关闭预览很简单,直接调用camera.stopPreview()便可。

camera.stopPreview();
复制代码

1.5 拍照

拍照时经过调用Camera的takePicture()方法来完成的,

takePicture(ShutterCallback shutter, PictureCallback raw, PictureCallback postview, PictureCallback jpeg)
复制代码

该方法有三个参数:

  • ShutterCallback shutter:在拍照的瞬间被回调,这里一般能够播放"咔嚓"这样的拍照音效。
  • PictureCallback raw:返回未经压缩的图像数据。
  • PictureCallback postview:返回postview类型的图像数据
  • PictureCallback jpeg:返回通过JPEG压缩的图像数据。

咱们通常用的就是最后一个,实现最后一个PictureCallback便可。

camera.takePicture(null, null, new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            //存储返回的图像数据
            final File pictureFile = outputPath;
            if (pictureFile == null) {
                Log.d(TAG, "Error creating media file, check storage permissions.");
                return;
            }
            try {
                FileOutputStream fileOutputStream = new FileOutputStream(pictureFile);
                fileOutputStream.write(bytes);
                fileOutputStream.close();
            } catch (FileNotFoundException error) {
                Log.e(TAG, "File not found: " + error.getMessage());
            } catch (IOException error) {
                Log.e(TAG, "Error accessing file: " + error.getMessage());
            } catch (Throwable error) {
                Log.e(TAG, "Error saving file: " + error.getMessage());
            }
        }
 });
复制代码

拍照完成后若是还要继续拍照则调用camera.startPreview()继续开启预览,不然关闭预览,释放相机资源。

1.6 开始视频录制

视频的录制时经过MediaRecorder来完成的。

if (prepareVideoRecorder()) {
            mediaRecorder.start();
            isVideoRecording = true;
            uiHandler.post(new Runnable() {
                @Override
                public void run() {
                    videoListener.onVideoRecordStarted(videoSize);
                }
            });
}
复制代码

MediaRecorder主要用来录制音频和视频,在使用以前要进行初始化和相关参数的设置,以下所示:

protected boolean preparemediaRecorder() {
    mediaRecorder = new MediaRecorder();
    try {
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        
        //输出格式
        mediaRecorder.setOutputFormat(camcorderProfile.fileFormat);
        //视频帧率
        mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
        //视频大小
        mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
        //视频比特率
        mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
        //视频编码器
        mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec);
        
        //音频编码率
        mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
        //音频声道
        mediaRecorder.setAudioChannels(camcorderProfile.audioChannels);
        //音频采样率
        mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
        //音频编码器
        mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec);
        
        File outputFile = outputPath;
        String outputFilePath = outputFile.toString();
        //输出路径
        mediaRecorder.setOutputFile(outputFilePath);
        
        //设置视频输出的最大尺寸
        if (mCameraConfigProvider.getVideoFileSize() > 0) {
            mediaRecorder.setMaxFileSize(mCameraConfigProvider.getVideoFileSize());
            mediaRecorder.setOnInfoListener(this);
        }
        
        //设置视频输出的最大时长
        if (mCameraConfigProvider.getVideoDuration() > 0) {
            mediaRecorder.setMaxDuration(mCameraConfigProvider.getVideoDuration());
            mediaRecorder.setOnInfoListener(this);
        }
        mediaRecorder.setOrientationHint(getVideoOrientation(mCameraConfigProvider.getSensorPosition()));
        
        //准备
        mediaRecorder.prepare();

        return true;
    } catch (IllegalStateException error) {
        Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage());
    } catch (IOException error) {
        Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage());
    } catch (Throwable error) {
        Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage());
    }
    releasemediaRecorder();
    return false;
}
复制代码

值得一提的是,平常的业务中常常对拍摄视频的时长或者大小有要求,这个能够经过mediaRecorder.setOnInfoListener()来处理,OnInfoListener会监听正在录制的视频,而后咱们 能够在它的回调方法里处理。

@Override
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
    if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED == what) {
        //到达最大时长
    } else if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED == what) {
        //到达最大尺寸
    }
}
复制代码

更多关于MediaRecorder的介绍能够参考MediaRecorder官方文档

1.7 结束视频录制

结束视频录制也很简单,只须要调用mediaRecorder.stop()方法便可。

mediaRecorder.stop();
复制代码

此外,若是再也不使用相机,也要注意释放相机资源。

以上即是Camera的所有内容,仍是比较简单的,下面咱们接着来说Camera2的相关内容,注意体会二者的区别。

二 Camera2实践指南

Camera2 API中主要涉及如下几个关键类:

  • CameraManager:摄像头管理器,用于打开和关闭系统摄像头
  • CameraCharacteristics:描述摄像头的各类特性,咱们能够经过CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法来获取。
  • CameraDevice:描述系统摄像头,相似于早期的Camera。
  • CameraCaptureSession:Session类,当须要拍照、预览等功能时,须要先建立该类的实例,而后经过该实例里的方法进行控制(例如:拍照 capture())。
  • CaptureRequest:描述了一次操做请求,拍照、预览等操做都须要先传入CaptureRequest参数,具体的参数控制也是经过CameraRequest的成员变量来设置。
  • CaptureResult:描述拍照完成后的结果。

Camera2拍照流程以下所示:

开发者经过建立CaptureRequest向摄像头发起Capture请求,这些请求会排成一个队列供摄像头处理,摄像头将结果包装在CaptureMetadata中返回给开发者。整个流程创建在一个CameraCaptureSession的会话中。

2.1 打开相机

打开相机以前,咱们首先要获取CameraManager,而后获取相机列表,进而获取各个摄像头(主要是前置摄像头和后置摄像头)的参数。

mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
    final String[] ids = mCameraManager.getCameraIdList();
    numberOfCameras = ids.length;
    for (String id : ids) {
        final CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);

        final int orientation = characteristics.get(CameraCharacteristics.LENS_FACING);
        if (orientation == CameraCharacteristics.LENS_FACING_FRONT) {
            faceFrontCameraId = id;
            faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            frontCameraCharacteristics = characteristics;
        } else {
            faceBackCameraId = id;
            faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            backCameraCharacteristics = characteristics;
        }
    }
} catch (Exception e) {
    Log.e(TAG, "Error during camera initialize");
}
复制代码

Camera2与Camera同样也有cameraId的概念,咱们经过mCameraManager.getCameraIdList()来获取cameraId列表,而后经过mCameraManager.getCameraCharacteristics(id) 获取每一个id对应摄像头的参数。

关于CameraCharacteristics里面的参数,主要用到的有如下几个:

  • LENS_FACING:前置摄像头(LENS_FACING_FRONT)仍是后置摄像头(LENS_FACING_BACK)。
  • SENSOR_ORIENTATION:摄像头拍照方向。
  • FLASH_INFO_AVAILABLE:是否支持闪光灯。
  • CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:获取当前设备支持的相机特性。

注:事实上,在各个厂商的的Android设备上,Camera2的各类特性并不都是可用的,须要经过characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 来根据返回值来获取支持的级别,具体说来:

  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,容许手动控制全高清的摄像、支持连拍模式以及其余新特性。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,这个须要单独查询。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:全部设备都会支持,也就是和过期的Camera API支持的特性是一致的。

利用这个INFO_SUPPORTED_HARDWARE_LEVEL参数,咱们能够来判断是使用Camera仍是使用Camera2,具体方法以下:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean hasCamera2(Context mContext) {
    if (mContext == null) return false;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
    try {
        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        String[] idList = manager.getCameraIdList();
        boolean notFull = true;
        if (idList.length == 0) {
            notFull = false;
        } else {
            for (final String str : idList) {
                if (str == null || str.trim().isEmpty()) {
                    notFull = false;
                    break;
                }
                final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str);

                final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    notFull = false;
                    break;
                }
            }
        }
        return notFull;
    } catch (Throwable ignore) {
        return false;
    }
}
复制代码

更多ameraCharacteristics参数,能够参见CameraCharacteristics官方文档

打开相机主要调用的是mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler)方法,如你所见,它有三个参数:

  • String cameraId:摄像头的惟一ID。
  • CameraDevice.StateCallback callback:摄像头打开的相关回调。
  • Handler handler:StateCallback须要调用的Handler,咱们通常能够用当前线程的Handler。
mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler);
复制代码

上面咱们提到了CameraDevice.StateCallback,它是摄像头打开的一个回调,定义了打开,关闭以及出错等各类回调方法,咱们能够在 这些回调方法里作对应的操做。

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        //获取CameraDevice
        mcameraDevice = cameraDevice;
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        //关闭CameraDevice
        cameraDevice.close();

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int error) {
        //关闭CameraDevice
        cameraDevice.close();
    }
};
复制代码

2.2 关闭相机

经过上面的描述,关闭就很简单了。

//关闭CameraDevice
cameraDevice.close();
复制代码

2.3 开启预览

Camera2都是经过建立请求会话的方式进行调用的,具体说来:

  1. 调用mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法建立CaptureRequest,调用
  2. mCameraDevice.createCaptureSession()方法建立CaptureSession。
CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) 复制代码

createCaptureRequest()方法里参数templateType表明了请求类型,请求类型一共分为六种,分别为:

  • TEMPLATE_PREVIEW:建立预览的请求
  • TEMPLATE_STILL_CAPTURE:建立一个适合于静态图像捕获的请求,图像质量优先于帧速率。
  • TEMPLATE_RECORD:建立视频录制的请求
  • TEMPLATE_VIDEO_SNAPSHOT:建立视视频录制时截屏的请求
  • TEMPLATE_ZERO_SHUTTER_LAG:建立一个适用于零快门延迟的请求。在不影响预览帧率的状况下最大化图像质量。
  • TEMPLATE_MANUAL:建立一个基本捕获请求,这种请求中全部的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。
createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
复制代码

createCaptureSession()方法一共包含三个参数:

  • List outputs:咱们须要输出到的Surface列表。
  • CameraCaptureSession.StateCallback callback:会话状态相关回调。
  • Handler handler:callback能够有多个(来自不一样线程),这个handler用来区别那个callback应该被回调,通常写当前线程的Handler便可。

关于CameraCaptureSession.StateCallback里的回调方法:

  • onConfigured(@NonNull CameraCaptureSession session); 摄像头完成配置,能够处理Capture请求了。
  • onConfigureFailed(@NonNull CameraCaptureSession session); 摄像头配置失败
  • onReady(@NonNull CameraCaptureSession session); 摄像头处于就绪状态,当前没有请求须要处理。
  • onActive(@NonNull CameraCaptureSession session); 摄像头正在处理请求。
  • onClosed(@NonNull CameraCaptureSession session); 会话被关闭
  • onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface准备就绪

理解了这些东西,建立预览请求就十分简单了。

previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(workingSurface);

//注意这里除了预览的Surface,咱们还添加了imageReader.getSurface()它就是负责拍照完成后用来获取数据的
mCameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()),
        new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "Fail while starting preview: ");
            }
        }, null);
复制代码

能够发现,在onConfigured()里调用了cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler),这样咱们就能够 持续的进行预览了。

注:上面咱们说了添加了imageReader.getSurface()它就是负责拍照完成后用来获取数据,具体操做就是为ImageReader设置一个OnImageAvailableListener,而后在它的onImageAvailable() 方法里获取。

mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            //当图片可获得的时候获取图片并保存
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

 };
复制代码

2.4 关闭预览

关闭预览就是关闭当前预览的会话,结合上面开启预览的内容,具体实现以下:

if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}
复制代码

2.5 拍照

拍照具体来讲分为三步:

  1. 对焦
try {
    //相机对焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
    //修改状态
    previewState = STATE_WAITING_LOCK;
    //发送对焦请求
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
} catch (Exception ignore) {
}
复制代码

咱们定义了一个CameraCaptureSession.CaptureCallback来处理对焦请求返回的结果。

private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

    @Override
    public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            //等待对焦
            final Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
            if (afState == null) {
                //对焦失败,直接拍照
                captureStillPicture();
            } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState
                    || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState
                    || CaptureResult.CONTROL_AF_STATE_INACTIVE == afState
                    || CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) {
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                    previewState = STATE_PICTURE_TAKEN;
                    //对焦完成,进行拍照
                    captureStillPicture();
                } else {
                    runPreCaptureSequence();
                }
            }
    }
};
复制代码
  1. 拍照

咱们定义了一个captureStillPicture()来进行拍照。

private void captureStillPicture() {
    try {
        if (null == mCameraDevice) {
            return;
        }
        
        //构建用来拍照的CaptureRequest
        final CaptureRequest.Builder captureBuilder =
                mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(imageReader.getSurface());

        //使用相同的AR和AF模式做为预览
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        //设置方向
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(mCameraConfigProvider.getSensorPosition()));

        //建立会话
        CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                Log.d(TAG, "onCaptureCompleted: ");
            }
        };
        //中止连续取景
        captureSession.stopRepeating();
        //捕获照片
        captureSession.capture(captureBuilder.build(), CaptureCallback, null);

    } catch (CameraAccessException e) {
        Log.e(TAG, "Error during capturing picture");
    }
}
复制代码
  1. 取消对焦

拍完照片后,咱们还要解锁相机焦点,让相机恢复到预览状态。

try {
    //重置自动对焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
    //相机恢复正常的预览状态
    previewState = STATE_PREVIEW;
    //打开连续取景模式
    captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
} catch (Exception e) {
    Log.e(TAG, "Error during focus unlocking");
}
复制代码

2.6 开始视频录制

//先关闭预览,由于须要添加一个预览输出的Surface,也就是mediaRecorder.getSurface()
closePreviewSession();

//初始化MediaRecorder,设置相关参数
if (preparemediaRecorder()) {

    final SurfaceTexture texture = Camera2Manager.this.texture;
    texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());

    try {
        //构建视频录制aptureRequest
        previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        final List<Surface> surfaces = new ArrayList<>();

        //设置预览Surface
        final Surface previewSurface = workingSurface;
        surfaces.add(previewSurface);
        previewRequestBuilder.addTarget(previewSurface);

        //设置预览输出Surface
        workingSurface = mediaRecorder.getSurface();
        surfaces.add(workingSurface);
        previewRequestBuilder.addTarget(workingSurface);

        mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                captureSession = cameraCaptureSession;

                previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                try {
                    //持续发送Capture请求,实现实时预览。
                    captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
                } catch (Exception e) {
                }

                try {
                    //开始录像
                    mediaRecorder.start();
                } catch (Exception ignore) {
                    Log.e(TAG, "mediaRecorder.start(): ", ignore);
                }

                isVideoRecording = true;

                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        cameraVideoListener.onVideoRecordStarted(videoSize);
                    }
                });
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "onConfigureFailed");
            }
        }, backgroundHandler);
    } catch (Exception e) {
        Log.e(TAG, "startVideoRecord: ", e);
    }
}
复制代码

关于MediaRecorder上面讲Camera的时候咱们就已经说过,这里再也不赘述。

以上即是视频录制的所有内容,就是简单的API使用,仍是比较简单的。

2.7 结束视频录制

结束视频录制主要也是关闭会话以及释放一些资源,具体说来:

  1. 关闭预览会话
  2. 中止mediaRecorder
  3. 释放mediaRecorder
//关闭预览会话
if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}

//中止mediaRecorder
if (mediaRecorder != null) {
    try {
        mediaRecorder.stop();
    } catch (Exception ignore) {
    }
}

//释放mediaRecorder
try {
    if (mediaRecorder != null) {
        mediaRecorder.reset();
        mediaRecorder.release();
    }
} catch (Exception ignore) {

} finally {
    mediaRecorder = null;
}
复制代码

以上即是Camera/Camera2实践的相关内容,更多关于图像、视频处理的内容能够参见phoenix项目。

相关文章
相关标签/搜索