写点什么

Android 平台 Camera 开发实践指南,【大牛疯狂教学】

用户头像
Android架构
关注
发布于: 5 小时前

&& (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);//设置 surfaceHoldercamera.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() {@Overridepublic 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() {@Overridepublic


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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 会监听正在录制的视频,然后我们 可以在它的回调方法里处理。


@Overridepublic 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 实践指南

  • [Android Camera2 官方视频](


)


  • [Android Camera2 官方文档](


)


  • [Android 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() {@Overridepublic void onOpened(@NonNull CameraDevice cameraDevice) {//获取 CameraDevicemcameraDevice = cameraDevice;}


@Overridepublic void onDisconnected(@NonNull CameraDevice cameraDevice) {//关闭 CameraDevicecameraDevice.close();


}


@Overridepublic void onError(@NonNull CameraDevice cameraDevice, int error) {//关闭 CameraDevicecameraDevice.close();}};

2.2 关闭相机

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


//关闭 CameraDevicecameraDevice.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() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);}


@Overridepublic 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() {


@Overridepublic 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() {


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


@Overridepublic 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;}


//构建用来拍照的 CaptureRequestfinal 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() {@Overridepublic 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);//相机恢复正常的预览状态

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android平台Camera开发实践指南,【大牛疯狂教学】