说明: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() {@Overridepublic 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() {@Overridepublic void run() {try {mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {throw new RuntimeException("Can't visit camera");}}});mMediaRecorder.start();}@Overridepublic 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;}@Overridepublic 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 APIprivate 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() {@Overridepublic 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() {@Overridepublic void onOpened(CameraDevice camera) {//重写onOpened方法,最为关键mCameraOpenCloseLock.release();mCameraDevice = camera;startCaptureSession();}@Overridepublic void onDisconnected(CameraDevice camera) {mCameraOpenCloseLock.release();camera.close();mCameraDevice = null;}@Overridepublic 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() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request, TotalCaptureResult result) {super.onCaptureCompleted(session, request, result);}@Overridepublic 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 打开cameramCameraManager.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() {@Overridepublic 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();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {Log.e(TAG, "Failed to configure capture session");}@Overridepublic 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.BuildermCaptureRequestBuilder = 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 捕获静态图像,结束后执行onCaptureCompletedmCaptureSession.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项目需要考虑兼容问题,应该选择AACmMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264mMediaRecorder.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();//从获取录制视频需要的Surfacetry {//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() {@Overridepublic 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() {@Overridepublic void run() {try {mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mCameraHandler);} catch (CameraAccessException e) {throw new RuntimeException("Can't visit camera");}}});mMediaRecorder.start();}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;@Overrideprotected 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() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {manager.setSurfaceTexture(surface);manager.startPreview();}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}});manager = new CameraCoreManager(this,mTextureView);Log.d(TAG,"onCreate:init");}@Overridepublic void onResume() {super.onResume();}@Overridepublic void onPause() {super.onPause();}@Overrideprotected void onDestroy() {super.onDestroy();manager.stopPreview();}@Overridepublic 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"><LinearLayoutandroid: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"><ImageButtonandroid:id="@+id/camera_take_picture"android:layout_width="60dp"android:layout_height="60dp"android:background="@drawable/ic_camera" />
</LinearLayout><TextureViewandroid: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" /><Buttonandroid: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"/><Buttonandroid: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>

Android APP Camera2应用(04)录像保存视频流程相关推荐

  1. android app 自动登录,Android APP首次登录和之后自动登录流程

    Android APP首次登录和之后自动登录流程 Android APP首次登录和之后自动登录流程 App登陆保存数据流程 App因为要实现自动登陆功能,所以必然要保存一些凭据,所以比较复杂. App ...

  2. Android APP Camera2应用(03)拍照保存照片流程

    说明:camera子系统 系列文章针对Android10.0系统,主要针对 camera API2 + HAL3 框架进行解读. 1 拍照&保存照片流程简要解读 在完成预览操作之后,点击拍照按 ...

  3. Android 使用Camera2 实现拍照录像的功能

    职场小白迷上优美句子: 还是电影  <无问西东>中的台词,这句有点感人: 沈光耀的妈妈对沈光耀说:"当初你离家千里,来到这个地方读书,你父亲和我都没有反对过,因为,是我们想你,能 ...

  4. Android APP Camera2应用(02)预览流程

    说明:camera子系统 系列文章针对Android10.0系统,主要针对 camera API2 + HAL3 框架进行解读. 1 预览流程简要解读 @1 由 CameraManager.openC ...

  5. android发广播更新相册,安卓保存视频和图片之后相册不刷新的问题总结

    最近在做项目中遇到保存照片和视频,本地已经保存而在相册和项目中不能找到,这里做一个简单的总结 在本地保存之后需要吧文件发送到本地或者广播的方式刷新相册 1.照片发送到相册 //把文件插入到系统图库 / ...

  6. 【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)

    需要全部代码请点赞关注收藏后评论区留言私信~~~ 与传统的影视行业相比,诞生于移动互联网时代的短视频是个全新行业,它制作方便又容易传播,一出现就成为大街小巷的时髦潮流. 各行各业的人们均可通过短视频展 ...

  7. Android Studio App开发之使用摄像机录制视频和从视频库中选取视频的讲解及实战(附源码)

    运行有问题或需要源码请点赞关注收藏后评论区留言~~~ 一.使用摄像机录制视频 与音频类似,通过系统摄像机可以很方便的录制视频,只要指定摄像动作为MediaStore.ACTION_VIDEO_CAPT ...

  8. Android 中使用MediaRecorder进行录像详解(视频录制)

    简单的视频录制功能. package com.video; import java.io.IOException; import android.app.Activity; import androi ...

  9. 捷径app 保存视频_Android N App捷径

    捷径app 保存视频 Android N brought App Shortcuts for developers and users. In this tutorial, we'll be crea ...

最新文章

  1. cocos2dx-2.2.0的开始
  2. python异步io多文件_Python 异步 IO 性能又上一层楼
  3. MSP430杂谈--IIC通信
  4. Luogu P4139 上帝与集合的正确用法【扩展欧拉定理】By cellur925
  5. PreparedStatement设置时间
  6. HSQL转换成MapReduce过程
  7. 随机游走问题的神奇应用(一)
  8. HDU 3949 XOR (线性基第k小)题解
  9. react-tv-focusable
  10. 金融风控建模全流程(Python,收藏)
  11. 软件工程大作业:网上购物系统
  12. 部署Extmail邮件系统需注意几点
  13. Downloading https://ultralytics.com/assets/Arial.ttf to /data/..../.config/Ultralytics/Arial.ttf
  14. HashMap源码分析(深入理解HashMap)
  15. 计算机怎么进入用户模式,Win7系统怎么进入电脑安全模式?
  16. matlab中除号的有效字符,乘号(x)、除号(÷)、双引号()等字符实体的HTML代码...
  17. 【bzoj 2844】: albus就是要第一个出场
  18. weditor安装和使用
  19. 您的账户已被停用,请向系统管理员咨询 解决方案(亲测有效)
  20. bosch热水器教程_博世热水器维修方法 安装及保养方法

热门文章

  1. 11g r2 rac 11.2.0.2升级11.2.0.2.3 [PSU patch 12419353]
  2. wap2.0有关windows mobile模拟器测试环境的搭建
  3. 王者荣耀微信4区服务器,怎么查看自己的微信王者荣耀是哪个区?
  4. 物联网学习的误区之:我要做物联网工程师!
  5. bben计算机配置,震惊!世界上最小的电脑主机,只有U盘大小!
  6. NXP RT1064学习笔记(一)— 开发环境
  7. 微服务链路追踪-SkyWalking
  8. 阿里云EDAS如何查看出口IP
  9. 有谁喜欢玩msn里面的西洋跳棋?
  10. 计算机NTFS数据恢复,ntfs数据恢复