Camera Api2 预览,拍照,录像

1引言

1.1编写目的

Camera2 主要类的介绍,预览,拍照,录像的流程介绍。

1.2适用范围

Camera API2的使用。

2 主要类的介绍

  1. CameraManager是一个用于检测、连接和描述相机设备的系统服务,负责管理所有的CameraDevice相机设备。
    可以通过调用Context.getSystemService(java.lang.String)方法来获取一个CameraManager的实例:
    CameraManager manager=(CameraManager)getSystemService(Context.CAMERA_SERVICE);


  1. CameraDevice连接到 Android 设备的单个摄像头的表示,描述一个照相机设备,
    一个 Android 设备可能会有多个摄像头,通过 CameraId 可以进行区别,当相机
    状态回调函数执行的时候,可以从回调中拿到当前 CameraDevice,并通过
    CameraDevice 对当前相机进行一些列操作。

  2. CameraCharacteristics 是描述相机设备的属性类,其中的属性都是固定的,继承自 CameraMetadata 类。类比于旧 API 中的 CameraInfo 类。

包括:曝光补偿(Exposure compensation)、自动曝光/自动对焦/自动白平衡模式(AE / AF / AWB mode)、自动曝光/自动白平衡锁(AE / AWB lock)、自动对焦触发器(AF trigger)、拍摄前自动曝光触发器(Precapture AE trigger)、测量区域(Metering regions)、闪光灯触发器(Flash trigger)、曝光时间(Exposure time)、感光度(ISO Sensitivity)、帧间隔(Frame duration)、镜头对焦距离(Lens focus distance)、色彩校正矩阵(Color correction matrix)、JPEG 元数据(JPEG metadata)、色调映射曲线(Tonemap curve)、裁剪区域(Crop region)、目标 FPS 范围(Target FPS range)、拍摄意图(Capture intent)、硬件视频防抖(Video stabilization)等。

通过 CameraManager 的 getCameraCharacteristics(String cameraId) 方法获取指定相机设备的 CameraCharacteristics 对象。

  1. CameraCaptureSession 是一个事务,用来向相机设备发送获取图像的请求。

主要有 setRepeatingRequest()capture() 方法。
setRepeatingRequest() 是重复请求获取图像数据,常用于预览或连拍,capture() 是获取一次,常用于单张拍照。

CameraCaptureSession 类是一个抽象类.

  1. CameraRequest 代表了一次捕获请求,从相机设备捕获单个图像所需的不可变的设置和输出包,当程序调用 setRepeatingRequest()方法进行预览时,或调用 capture()方法进行拍照时,都需要传入 CameraRequest 参数。
    CameraRequest.Builder 则负责生成CameraRequest 对象

如:预览前设置自动对焦
CaptureRequestBuilder.set(CaptureRequest.CONTORL_AF_MODE,CaptureRequest.CONTROL_AF_MOODE_CONTINUOUS_PICTURE);

CaptureRequest = CaptureRequestBuilder.build();
CameraCaptureSession.setRepeatingRequest(CaptureRequest,CaptureCallback,Handler);

  1. CaptureResult
    CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)当CaptureRequest被处理之后由CameraDevice生成。

  2. CameraMetadata Camera API2/HAL3架构下使用了全新的CameraMetadata结构取代了之前的SetParameter/Paramters等操作,实现了Java到native到HAL3的参数传递。

3 app 层的主要流程

3.1 预览的流程

3.1.1 先上图,看图讲故事

图1 预览流程图

3.1.2 预览流程说明

  1. 获取CameraManager
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  1. openCamera 打开相应ID的相机

·String CameraId:传入要打开的摄像头的 Id
·CameraDevice.stateCallback:相机的状态回调接口
·Handler handler:用来确定Callback在哪个线程执行,为null的话就在当前线程执行

    public void openCamera( String cameraId,
            final CameraDevice.StateCallback callback,  Handler handler)
            throws CameraAccessException {
        openCameraForUid(cameraId, callback,
                CameraDeviceImpl.checkAndWrapHandler(handler), USE_CALLING_UID);
    }
  1. 实例化 CameraDevice.statCallback
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            //开启预览
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };
  1. 生成CaptureRequest.Builder
    private CaptureRequest.Builder mPreviewRequestBuilder;

    // 创建预览请求的Builder(TEMPLATE_PREVIEW表示预览请求)
    private void getPreviewRequestBuilder() {
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //设置预览的显示界面,即将显示预览用的 surface 的实例,作为一个显示层添加到该请求的目标列表中.
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            CamLog.d("PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    }


  1. mCameraDevice.createCaptureSession 创建CaptureSession

·List<Surface>:捕获数据的输出Surface列表,此处传入用于显
示预览图像的 Surface 即可,即 Arrays.asList(surface),
·CameraCaptureSession.stateCallback:CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法.
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

public abstract void createCaptureSession( List<Surface> outputs,
             CameraCaptureSession.StateCallback callback,  Handler handler)
            throws CameraAccessException;
  1. CameraCaptureSession.StateCallback 回调

new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(CameraCaptureSession session) {
                            //该方法会从底层传回一个CameraCaptureSession,该 session 可以开始处理捕获请求,
                            mCaptureSession = session;
                            repeatPreview();
                        }

                        @Override
                        public void onConfigureFailed(CameraCaptureSession session) {

                        }
                    }

  1. CaptureRequest和mCaptureSession.setRepeatingRequest
private void repeatPreview() {
    mPreviewRequestBuilder.setTag(TAG_PREVIEW);
    mPreviewRequest = mPreviewRequestBuilder.build();
    //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
    try {
        mCaptureSession.setRepeatingRequest(mPreviewRequest,mPreviewCaptureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}



·CaptureReqeust:无限重复的请求,
·CameraCaptureSession.CaptureCallback:即 callback 的回调,不过预览中
该回调中不用进行任何处理
·Handler:第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行

public abstract int setRepeatingRequest( CaptureRequest request,
             CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

3.2 拍照的流程

3.2.1 先上图,看图讲故事

图2 预览和拍照流程图

3.2.2 拍照流程说明

  1. ImageReader 的使用

点击拍照,使用 ImageReader 访问呈现到 Surface 中的图像,并进行保存。在预览的 Surface 捕获图像的同时,我们也需要 ImageReader 来同时捕获图像数据,所以在预览的第五步中,CameraDevice.CreateCaptureSession()方法中,我们将 ImageReader 的实例也传入第一个参数中,即 Arrays.asList(surface,ImageReader.getSurface())


    private ImageReader mImageReader;

    private void setupImageReader() {
        //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                ImageFormat.JPEG, 1);
        //监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireLatestImage();
                // 开启线程异步保存图片
                new Thread(new ImageSaver(image)).start();
            }
        }, null);
    }


public static class ImageSaver implements Runnable {
        private Image mImage;
        private File mImageFile;

        public ImageSaver(Image image) {
            mImage = image;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(mImageFile);
                fos.write(data, 0, data.length);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImageFile = null;
                if (fos != null) {
                    try {
                        fos.close();
                        fos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }




    //预览第五步的代码
    mCameraDevice.createCaptureSession(
            Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
            new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                            mCaptureSession = session;
                            repeatPreview();
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {

                }
            }, null);

  1. 生成CaptureRequest
    //首先我们创建请求拍照的CaptureRequest
    final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);

    mCaptureBuilder.addTarget(mPreviewSurface);
    mCaptureBuilder.addTarget(mImageReader.getSurface());

    //获取屏幕方向
    int rotation = getWindowManager().getDefaultDisplay().getRotation();
    //设置拍照方向
    mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));

    CaptureRequest captureRequest = mCaptureBuilder.build();
  1. CameraCaputureSession.capture()方法进行拍照

· CaptureRequest:此次拍照的参数设置
· CaptureCallback:callback 对象,当这个请求被处理的时候触发,如果
为 null,不会生成 matedate 信息,但是仍会生成图像信息
· Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null


public abstract int capture( CaptureRequest request,
            CaptureCallback listener,  Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.CaptureCallback

//开始拍照,然后回调上面的接口重启预览,
// 因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                               @NonNull CaptureRequest request,
                               @NonNull TotalCaptureResult result) {
                    //恢复预览状态
                    repeatPreview();
                }
            };

3.3 录像的流程

3.3.1 先上图,看图讲故事

图3 预览、拍照、录像流程图

3.3.2 录像流程说明

  1. MediaRecorder 的使用

MediaRecorder是安卓提供的一个用于音视频采集的类.



        mMediaRecorder = new MediaRecorder();

        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        String mNextVideoAbsolutePath = Environment.getExternalStorageDirectory() + "/DCIM/videoBack.mp4";
        mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        mMediaRecorder.setOrientationHint(90);

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

  1. CaptureRequest.Builder的生成和添加Target

将 MedioRecorder 和预览用的 Surface 的实例,添加到该请求的目标列表中。


    mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    List<Surface> surfaces = new ArrayList<>();
    SurfaceTexture texture = mTextureView.getSurfaceTexture();
    assert texture != null;
    texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

    // Set up Surface for the camera preview
    Surface previewSurface = new Surface(texture);
    surfaces.add(previewSurface);
    mPreviewRequestBuilder.addTarget(previewSurface);

    // Set up Surface for the MediaRecorder
    Surface recorderSurface = mMediaRecorder.getSurface();
    surfaces.add(recorderSurface);
    mPreviewRequestBuilder.addTarget(recorderSurface);

  1. 将预览时创建的 session,close 掉

    private void closePreviewSession() {
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
    }

  1. createCaptureSession
·List<Surface>:新的用于捕获图像信息的 Surface 集合,此处为显示预览
信息的 surface 实例,以及记录图像信息用的 MediaRecorder 的实例
·CameraCaptureSession.StateCallback:用于通知新捕获 session 的
callback
·Handler:为一个句柄,代表执行 callback 的 handler,如果程序希望直
接在当前线程中执行 callback,则可以将 handler 参数设为 null


public abstract void createCaptureSession( List<Surface> outputs,
            CameraCaptureSession.StateCallback callback, 
            Handler handler)
            throws CameraAccessException;

  1. CameraCaptureSession.StateCallback

new CameraCaptureSession.StateCallback()
{

        @Override
        public void onConfigured (@NonNull CameraCaptureSession session){

        mCaptureSession = session;
        try {
            //调用CaptureRequestBuilder.set()方法,设置捕获的参数,此处设置3A算法
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
            CaptureRequest request = mPreviewRequestBuilder.build();

            //调用CameraCaptureSession.setRepeatingReqest()方法,
            //通过此捕获session,持续重复捕获图像
            mCaptureSession.setRepeatingRequest(request, null, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // Start recording
                mMediaRecorder.start();
            }
        });
    }

        @Override
        public void onConfigureFailed (@NonNull CameraCaptureSession session){
    }
}
  1. MediaRecorder 停止录制

    public void videoStop(View view) {
        mMediaRecorder.stop();
        mMediaRecorder.reset();

        Toast.makeText(this, "Video saved: 保存",
                Toast.LENGTH_SHORT).show();
        //重新开启预览
        startPreview();
    }

参考文章

  1. https://blog.csdn.net/qq_33529867/article/details/103484791
  2. https://www.cnblogs.com/renhui/p/8718758.html
  3. 官方Demo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343