Android APP Camera2应用(04)录像&保存视频流程

49 篇文章 15 订阅

说明:camera子系统 系列文章针对Android10.0系统,主要针对 camera API2 + HAL3 框架进行解读。

1 录像&保存视频流程简要解读

@1 当预览创建之后,点击录像 button,触发录像事件,首先是停止预览,准备切换到录制视频模式,该部分关键代码如下所示:

private void closeCameraCaptureSession() {
    if (mCaptureSession != null) {
        Log.i(TAG, "close Session:" + mCaptureSession.toString());
        mCaptureSession.close();
        mCaptureSession = null;
    }
}
//...
//camera record process,step1 停止预览,准备切换到录制视频
try {
    mCaptureSession.stopRepeating();
    closeCameraCaptureSession();
} catch (CameraAccessException e) {
    e.printStackTrace();
}

@2 创建 MediaRecorder 实例,并初始化相关设置,,关键代码如下所示:

//camera record process,step2 mMediaRecorder相关设置
mVideoFile = new File(mContext.getExternalCacheDir(),new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +"demo.mp4");
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频来源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置视频来源
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//设置输出格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
mMediaRecorder.setVideoEncodingBitRate(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率之间波动。比特率越大视频越清晰但是视频文件也越大。
mMediaRecorder.setVideoFrameRate(30);//设置帧数.
mMediaRecorder.setVideoSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
mMediaRecorder.setOrientationHint(90);
Surface surface = new Surface(mTextureView.getSurfaceTexture());
mMediaRecorder.setPreviewDisplay(surface);
mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
try {
    mMediaRecorder.prepare();
} catch (IOException e) {
    e.printStackTrace();
}

@3 调用 CameraDevice.CreateRequest(CameraDevice.TEMPLATE_RECORED)方法, 为新的捕获请求创建一个 CaptureRequest.Build 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化,关键代码如下所示:

//camera record process,step3 创建CaptureRequest.Build 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

@4 调用 CaptureRequestBuilder.addTarget()方法,将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中,关键代码如下所示:

SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surface

//camera record process,step4 将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中
mPreviewRequestBuilder.addTarget(previewSurface);
mPreviewRequestBuilder.addTarget(recorderSurface);

@5 执行CameraDevice.CreateCaptureSession 方法,通过提供目标输出集来创建新 的捕获会话,该方法传入三个参数:

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

同时在这里重写onConfigured()方法,调用CameraCaptureSession . setRepeatingReqest()方法,通过此捕获session,持续重复捕获图像(也可以调用 CaptureRequestBuilder.set()方法,设置捕获的参数,可在此处设置 3A算法相)。最后调用 MediaRecorder.start()方法,开始捕获数据并将数据编码到指定文件。关键代码如下所示:

//camera record process,step5 执行CameraDevice.CreateCaptureSession 方法
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,recorderSurface),new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(CameraCaptureSession cameraCaptureSession) {
        mCaptureSession = cameraCaptureSession;
        Log.i(TAG, "Video Session:" + mCaptureSession.toString());
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mCameraHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mCameraHandler);
                } catch (CameraAccessException e) {
                    throw new RuntimeException("Can't visit camera");
                }
            }
        });
        mMediaRecorder.start();
    }
    @Override
    public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
    }
},mCameraHandler);

@6 录像后 当点击停止录像时,执行CameraCaptureSession的stopRepeating()方法,取消持续捕获,同时调用 CameraCaptureSession的abortCapture()方法,尽可能快地丢弃当前待处理和正在进行的所有捕获(若不做这两步处理,停止录像时会闪退)关键代码如下所示:

Log.d(TAG,"stopRecorder");
try {
    //camera record process,step6 取消持续捕获&
    mCaptureSession.stopRepeating();
    mCaptureSession.abortCaptures();
} catch (CameraAccessException e) {
    e.printStackTrace();
}

@7 调用 MediaRecorder.stop,停止图像捕获 并且重启预览模式(根据需要可以此时处理视频,使得系统Gallery可以直接查看到该视频),关键代码如下所示:

//camera record process,step7 停止图像捕获 并且重启预览模式
mMediaRecorder.stop();
mMediaRecorder.reset();
//可根据需要将视频设置为系统gallery可见。
mCameraHandler.post(new FileUtils.VideoSaver(mContext, mVideoFile));
startCaptureSession();

关于视频对Gallery可见,以下为关键参考代码:

public static class VideoSaver implements Runnable {
        private final File mFile;
        Context mContext;

        VideoSaver(Context context,File file) {
            mContext = context;
            mFile = file;
        }

        private ContentValues getVideoContentValues(File paramFile,long paramLong) {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Video.Media.TITLE, paramFile.getName());
            values.put(MediaStore.Video.Media.DISPLAY_NAME, paramFile.getName());
            values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
            values.put(MediaStore.Video.Media.DATE_TAKEN, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATE_MODIFIED, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATE_ADDED, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATA, paramFile.getAbsolutePath());
            values.put(MediaStore.Video.Media.SIZE, Long.valueOf(paramFile.length()));
            return values;
        }

        @Override
        public void run() {
            Log.d(TAG, "recorder video Run");
            ContentResolver localContentResolver = mContext.getContentResolver();
            ContentValues localContentValues = getVideoContentValues(mFile, System.currentTimeMillis());
            Uri localUri = localContentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, localContentValues);
            OutputStream os = null;
            FileInputStream fis = null;
            byte[] buf = new byte[1024];
            int len;
            try {
                if (localUri != null) {
                    fis = new FileInputStream(mFile);
                    os = localContentResolver.openOutputStream(localUri);
                }
                if (os != null) {
                    while ((len = fis.read(buf)) >= 0) {
                        os.write(buf, 0, len);
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(os!=null) {
                        os.close();
                    }
                    if(fis!=null){
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

2 camera录像&保存视频流程代码完整解读

2.1 java源码部分(草稿)

Camera流程相关代码如下所示:

class CameraCoreManager {
    private static final String TAG = "CameraDemo";

    private Context mContext;
    private CameraManager mCameraManager;
    private String mCameraId;
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;
    private ImageReader mImageReader;
    private CameraDevice mCameraDevice;
    private CameraCharacteristics mCameraCharacteristics;
    private MediaRecorder mMediaRecorder;

    //Max preview width&height that is guaranteed by Camera2 API
    private static final int MAX_PREVIEW_WIDTH = 1080;
    private static final int MAX_PREVIEW_HEIGHT = 720;

    //A Semaphore to prevent the app from exiting before closing the camera.
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    private Size mPreviewSize = new Size(1920, 1080);
    private CaptureRequest.Builder mPreviewRequestBuilder;
    private CaptureRequest.Builder mCaptureRequestBuilder;
    private CaptureRequest.Builder mRecordRequestBuilder;
    private CameraCaptureSession mCaptureSession;
    private int mFacing = CameraCharacteristics.LENS_FACING_BACK;
    private Choreographer.FrameCallback mFrameCallback;
    private SurfaceTexture mSurfaceTexture;
    private File mCameraFile;
    private File mVideoFile;
    private TextureView mTextureView;

    private enum State{
        STATE_PREVIEW,
        STATE_CAPTURE,
    }
    State mState = State.STATE_PREVIEW;

    //camera capture process,step3 创建ImageReader并设置mImageAvailableListener,实现如下:
    private ImageReader.OnImageAvailableListener mImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            if(mState == State.STATE_PREVIEW){
                //Log.d(TAG, "##### onFrame: Preview");
                Image image = reader.acquireNextImage();
                image.close();
            }else if(mState == State.STATE_CAPTURE) {
                Log.d(TAG,"capture one picture to gallery");
                mCameraFile = new File("aa_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".jpg");
                mCameraHandler.post(new FileUtils.ImageSaver(mContext, reader.acquireLatestImage(), mCameraFile));
                mState = State.STATE_PREVIEW;
            }else{
                Log.d(TAG, "##### onFrame: default/nothing");
            }
        }
    };

    //camera preview process,step2 mStateCallback 实例化
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            //重写onOpened方法,最为关键
            mCameraOpenCloseLock.release();
            mCameraDevice = camera;
            startCaptureSession();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            mCameraOpenCloseLock.release();
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            Log.e("DEBUG", "onError: " + error);
            mCameraOpenCloseLock.release();
            camera.close();
            mCameraDevice = null;
            Log.e("DEBUG", "onError:  restart camera");
            stopPreview();
            startPreview();
        }
    };

    CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request, TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
        }

        @Override
        public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
        }
    };

    public CameraCoreManager(Context context, TextureView textureView) {
        mContext = context;
        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        mMediaRecorder = new MediaRecorder();
        mState = State.STATE_PREVIEW;
        mTextureView = textureView;
    }

    public void startPreview() {
        Log.d(TAG,"startPreview");
        if (!chooseCameraIdByFacing()) {
            Log.e(TAG, "Choose camera failed.");
            return;
        }

        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());

        if (mImageReader == null) {
            mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 2);
            mImageReader.setOnImageAvailableListener(mImageAvailableListener, mCameraHandler);
        }else{
            mImageReader.close();
        }
        openCamera();
    }

    public void stopPreview() {
        Log.d(TAG,"stopPreview");
        closeCamera();
        if (mCameraThread != null) {
            mCameraThread.quitSafely();
            mCameraThread = null;
        }
        mCameraHandler = null;
    }

    private boolean chooseCameraIdByFacing() {
        try {
            String ids[] = mCameraManager.getCameraIdList();
            if (ids.length == 0) {
                Log.e(TAG, "No available camera.");
                return false;
            }

            for (String cameraId : mCameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);

                StreamConfigurationMap map = characteristics.get(
                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map == null) {
                    continue;
                }

                Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    continue;
                }

                Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (internal == null) {
                    continue;
                }
                if (internal == mFacing) {
                    mCameraId = cameraId;
                    mCameraCharacteristics = characteristics;
                    return true;
                }
            }

            mCameraId = ids[1];
            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
            Integer level = mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
            if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                return false;
            }

            Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
            if (internal == null) {
                return false;
            }
            mFacing = CameraCharacteristics.LENS_FACING_BACK;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return true;
    }

    @SuppressLint("MissingPermission")
    public void openCamera() {
        if (TextUtils.isEmpty(mCameraId)) {
            Log.e(TAG, "Open camera failed. No camera available");
            return;
        }

        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            //camera preview process,step1 打开camera
            mCameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (InterruptedException | CameraAccessException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (mCaptureSession != null) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (mImageReader != null) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

    private void startCaptureSession() {
        mState = State.STATE_PREVIEW;
        if (mCameraDevice == null) {
            return;
        }

        if ((mImageReader != null || mSurfaceTexture != null)) {
            try {
                closeCameraCaptureSession();
                //camera preview process,step3 创建一个 CaptureRequest.Builder,templateType来区分是拍照还是预览
                mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                //camera preview process,step4 将显示预览用的surface的实例传入,即将显示预览用的 surface 的实例,作为一个显示层添加到该 请求的目标列表中
                mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
                List<Surface> surfaceList = Arrays.asList(mImageReader.getSurface());
                if (mSurfaceTexture != null) {
                    mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
                    Surface surface = new Surface(mSurfaceTexture);
                    mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
                    mPreviewRequestBuilder.addTarget(surface);
                    //camera preview process,step5 将显示预览用的surface的实例传入,即将显示预览用的surface的实例,作为一个显示层添加到该请求的目标列表中
                    surfaceList = Arrays.asList(surface, mImageReader.getSurface());
                }

                Range<Integer>[] fpsRanges = mCameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
                Log.d("DEBUG", "##### fpsRange: " + Arrays.toString(fpsRanges));
                //camera preview process,step6 & 7
                // 6 执行createCaptureSession方法
                // 7 参数中实例化 CameraCaptureSession.stateCallback,并重写 onConfigured 方法
                mCameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        if (mCameraDevice == null) return;
                        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
                        mCaptureSession = session;
                        try {
                            if (mCaptureSession != null)
                                //camera preview process,step8 用 CameraCaptureSession.setRepeatingRequest()方法创建预览
                                mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mCameraHandler);
                        } catch (CameraAccessException | IllegalArgumentException | IllegalStateException | NullPointerException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
                        Log.e(TAG, "Failed to configure capture session");
                    }

                    @Override
                    public void onClosed(CameraCaptureSession session) {
                        if (mCaptureSession != null && mCaptureSession.equals(session)) {
                            mCaptureSession = null;
                        }
                    }
                }, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            } catch (IllegalStateException e) {
                stopPreview();
                startPreview();
            } catch (UnsupportedOperationException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
            }
        }
    }

    public void captureStillPicture() {
        try {
            Log.d(TAG,"captureStillPicture");
            mState = State.STATE_CAPTURE;
            if (mCameraDevice == null) {
                return;
            }
            // camera capture process,step1 创建作为拍照的CaptureRequest.Builder
            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // camera capture process,step2 将imageReader的surface作为CaptureRequest.Builder的目标
            mCaptureRequestBuilder.addTarget(mImageReader.getSurface());
            // 设置自动对焦模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 设置自动曝光模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            // 设置为自动模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
            // 设置摄像头旋转角度
            mCaptureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, Surface.ROTATION_0);
            // 停止连续取景
            mCaptureSession.stopRepeating();
            // camera capture process,step5 &6 捕获静态图像,结束后执行onCaptureCompleted
            mCaptureSession.capture(mCaptureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                @Override// 拍照完成时激发该方法
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                    Log.d(TAG,"onCaptureCompleted");
                    startCaptureSession();
                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void setupMediaRecorder(){
        //camera record process,step1 停止预览,准备切换到录制视频
        try {
            mCaptureSession.stopRepeating();
            closeCameraCaptureSession();
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //camera record process,step2 mMediaRecorder相关设置
        mVideoFile = new File(mContext.getExternalCacheDir(),new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +"demo.mp4");
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频来源
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置视频来源
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//设置输出格式
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
        mMediaRecorder.setVideoEncodingBitRate(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
        mMediaRecorder.setVideoFrameRate(30);//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
        mMediaRecorder.setVideoSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
        mMediaRecorder.setOrientationHint(90);
        Surface surface = new Surface(mTextureView.getSurfaceTexture());
        mMediaRecorder.setPreviewDisplay(surface);
        mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
        Surface previewSurface = new Surface(surfaceTexture);
        Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surface

        try {
            //camera record process,step3 创建CaptureRequest.Build 对象,并用CameraDevice.TEMPLATE_RECORED 参数初始化
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //camera record process,step4 将MediaRecorder和预览用的Surface实例添加到该请求的目标列表中
            mPreviewRequestBuilder.addTarget(previewSurface);
            mPreviewRequestBuilder.addTarget(recorderSurface);
            //请注意这里设置了Arrays.asList(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,第一个是预览的Surface,第二个是录制视频使用的Surface
            //camera record process,step5 执行CameraDevice.CreateCaptureSession 方法
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface,recorderSurface),new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                    mCaptureSession = cameraCaptureSession;
                    Log.i(TAG, "Video Session:" + mCaptureSession.toString());
                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
                    mCameraHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mCameraHandler);
                            } catch (CameraAccessException e) {
                                throw new RuntimeException("Can't visit camera");
                            }
                        }
                    });
                    mMediaRecorder.start();
                }
                @Override
                public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                }
            },mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void closeCameraCaptureSession() {
        if (mCaptureSession != null) {
            Log.i(TAG, "close Session:" + mCaptureSession.toString());
            mCaptureSession.close();
            mCaptureSession = null;
        }
    }
    public void startRecorder(){
        Log.d(TAG,"startRecorder");
        setupMediaRecorder();
    }

    public void stopRecorder(){
        Log.d(TAG,"stopRecorder");
        try {
            //camera record process,step6 取消持续捕获
            mCaptureSession.stopRepeating();
            //丢弃当前待处理和正在进行的所有捕获
            mCaptureSession.abortCaptures();
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //camera record process,step7 停止图像捕获 并且重启预览模式
        mMediaRecorder.stop();
        mMediaRecorder.reset();
        //可根据需要将视频设置为系统gallery可见。
        mCameraHandler.post(new FileUtils.VideoSaver(mContext, mVideoFile));
        startCaptureSession();
    }

    public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
        mSurfaceTexture = surfaceTexture;
    }
}

保存照片和保存视频相关操作在FileUtils类中代码如下所示:

class FileUtils {
    private static final String TAG = "FileUtils";

    //camera capture progress,step4 保存图片相关操作
    public static class ImageSaver implements Runnable {
        private final Image mImage;
        private final File mFile;
        Context mContext;

        ImageSaver(Context context,Image image, File file) {
            mContext = context;
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            Log.d(TAG,"take picture Image Run");
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DESCRIPTION, "This is an qr image");
            values.put(MediaStore.Images.Media.DISPLAY_NAME, mFile.getName());
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            values.put(MediaStore.Images.Media.TITLE, "Image.jpg");
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/");
            Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            ContentResolver resolver = mContext.getContentResolver();
            Uri insertUri = resolver.insert(external, values);
            OutputStream os = null;
            try {
                if (insertUri != null) {
                    os = resolver.openOutputStream(insertUri);
                }
                if (os != null) {
                    os.write(bytes);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                try {
                    if(os!=null) {
                        os.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static class VideoSaver implements Runnable {
        private final File mFile;
        Context mContext;

        VideoSaver(Context context,File file) {
            mContext = context;
            mFile = file;
        }

        private ContentValues getVideoContentValues(File paramFile,long paramLong) {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Video.Media.TITLE, paramFile.getName());
            values.put(MediaStore.Video.Media.DISPLAY_NAME, paramFile.getName());
            values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
            values.put(MediaStore.Video.Media.DATE_TAKEN, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATE_MODIFIED, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATE_ADDED, Long.valueOf(paramLong));
            values.put(MediaStore.Video.Media.DATA, paramFile.getAbsolutePath());
            values.put(MediaStore.Video.Media.SIZE, Long.valueOf(paramFile.length()));
            return values;
        }

        @Override
        public void run() {
            Log.d(TAG, "recorder video Run");
            ContentResolver localContentResolver = mContext.getContentResolver();
            ContentValues localContentValues = getVideoContentValues(mFile, System.currentTimeMillis());
            Uri localUri = localContentResolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, localContentValues);
            OutputStream os = null;
            FileInputStream fis = null;
            byte[] buf = new byte[1024];
            int len;
            try {
                if (localUri != null) {
                    fis = new FileInputStream(mFile);
                    os = localContentResolver.openOutputStream(localUri);
                }
                if (os != null) {
                    while ((len = fis.read(buf)) >= 0) {
                        os.write(buf, 0, len);
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if(os!=null) {
                        os.close();
                    }
                    if(fis!=null){
                        fis.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Activity UI相关代码如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = "CameraDemo";
    private ImageButton mTakePictureBtn;
    private Button mBtnVideoStart;
    private Button mBtnVideoStop;
    private CameraCoreManager manager;
    private TextureView mTextureView;
    Lock Mutex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        mTakePictureBtn = findViewById(R.id.camera_take_picture);
        mTakePictureBtn.setOnClickListener(this);
        mBtnVideoStart = findViewById(R.id.btn_video_start);
        mBtnVideoStart.setOnClickListener(this);
        mBtnVideoStop = findViewById(R.id.btn_video_stop);
        mBtnVideoStop.setOnClickListener(this);
        mTextureView = findViewById(R.id.texture_view);
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                manager.setSurfaceTexture(surface);
                manager.startPreview();
            }

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

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            }
        });
        manager = new CameraCoreManager(this,mTextureView);
        Log.d(TAG,"onCreate:init");
    }

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

    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        manager.stopPreview();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.camera_take_picture:
                Log.d(TAG,"takepicture");
                manager.captureStillPicture();
                break;
            case R.id.btn_video_start:
                manager.startRecorder();
                break;
            case R.id.btn_video_stop:
                manager.stopRecorder();
                break;
            default:

                break;
        }
    }
}

2.2 layout文件

这里涉及布局文件主要为activity_camera.xml,xml文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/gray"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="20dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent">

    <ImageButton
        android:id="@+id/camera_take_picture"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="@drawable/ic_camera" />
</LinearLayout>

<TextureView
    android:id="@+id/texture_view"
    android:layout_width="420dp"
    android:layout_height="360dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/btn_video_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="录相开始"
        app:layout_constraintTop_toBottomOf="@id/texture_view"
        app:layout_constraintLeft_toLeftOf="parent"/>
    <Button
        android:id="@+id/btn_video_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="录相结束"
        app:layout_constraintTop_toBottomOf="@id/texture_view"
        app:layout_constraintRight_toRightOf="parent"/>


</androidx.constraintlayout.widget.ConstraintLayout>

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值