Android 平台 Camera 开发实践指南,【大牛疯狂教学】
&& (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
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 都是通过创建请求会话的方式进行调用的,具体说来:
调用 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法创建 CaptureRequest,调用
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 拍照
拍照具体来说分为三步:
对焦
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();}}}};
拍照
我们定义了一个 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");}}
取消对焦
拍完照片后,我们还要解锁相机焦点,让相机恢复到预览状态。
try {//重置自动对焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);//相机恢复正常的预览状态
评论