在本课程中,我们将讨论如何直接使用框架 API 控制相机硬件。
注意:本页面引用的是已弃用的 Camera 类。我们建议使用 CameraX,或者针对特定用例使用 Camera2。CameraX 和 Camera2 都支持 Android 5.0(API 级别 21)及更高版本。
直接控制设备相机所需的代码量远多于从现有相机应用请求图片或视频。但是,如果您想构建一个专业的相机应用,或者将相机功能完全集成到您的应用 UI 中,本课程将向您展示如何操作。
参考以下相关资源
打开 Camera 对象
获取 Camera
对象的实例是直接控制相机的第一个步骤。与 Android 自己的 Camera 应用一样,建议的相机访问方式是在从 onCreate()
启动的单独线程上打开 Camera
。这种方法是个好主意,因为它可能需要一些时间,并且可能会使 UI 线程变慢。在更基本的实现中,打开相机可以推迟到 onResume()
方法中,以便于代码复用并保持控制流的简单。
调用 Camera.open()
如果相机已被其他应用使用,会抛出异常,因此我们将其包装在 try
块中。
Kotlin
private fun safeCameraOpen(id: Int): Boolean { return try { releaseCameraAndPreview() mCamera = Camera.open(id) true } catch (e: Exception) { Log.e(getString(R.string.app_name), "failed to open Camera") e.printStackTrace() false } } private fun releaseCameraAndPreview() { preview?.setCamera(null) mCamera?.also { camera -> camera.release() mCamera = null } }
Java
private boolean safeCameraOpen(int id) { boolean qOpened = false; try { releaseCameraAndPreview(); camera = Camera.open(id); qOpened = (camera != null); } catch (Exception e) { Log.e(getString(R.string.app_name), "failed to open Camera"); e.printStackTrace(); } return qOpened; } private void releaseCameraAndPreview() { preview.setCamera(null); if (camera != null) { camera.release(); camera = null; } }
从 API 级别 9 开始,相机框架支持多个相机。如果您使用旧版 API 并且在调用 open()
时不带参数,您将获得第一个后置摄像头。
创建相机预览
拍照通常需要用户在按下快门前看到拍摄对象的预览。为此,您可以使用 SurfaceView
来绘制相机传感器捕获到的预览。
预览类
要开始显示预览,您需要预览类。预览需要实现 android.view.SurfaceHolder.Callback
接口,该接口用于将图像数据从相机硬件传递到应用。
Kotlin
class Preview( context: Context, val surfaceView: SurfaceView = SurfaceView(context) ) : ViewGroup(context), SurfaceHolder.Callback { var mHolder: SurfaceHolder = surfaceView.holder.apply { addCallback(this@Preview) setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS) } ... }
Java
class Preview extends ViewGroup implements SurfaceHolder.Callback { SurfaceView surfaceView; SurfaceHolder holder; Preview(Context context) { super(context); surfaceView = new SurfaceView(context); addView(surfaceView); // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. holder = surfaceView.getHolder(); holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } ... }
在开始实时图像预览之前,必须将预览类传递给 Camera
对象,如下一节所示。
设置并启动预览
相机实例及其相关预览必须按特定顺序创建,其中相机对象优先。在下面的代码片段中,相机初始化过程被封装起来,以便在用户更改相机时,setCamera()
方法会调用 Camera.startPreview()
。预览也必须在预览类的 surfaceChanged()
回调方法中重新启动。
Kotlin
fun setCamera(camera: Camera?) { if (mCamera == camera) { return } stopPreviewAndFreeCamera() mCamera = camera mCamera?.apply { mSupportedPreviewSizes = parameters.supportedPreviewSizes requestLayout() try { setPreviewDisplay(holder) } catch (e: IOException) { e.printStackTrace() } // Important: Call startPreview() to start updating the preview // surface. Preview must be started before you can take a picture. startPreview() } }
Java
public void setCamera(Camera camera) { if (mCamera == camera) { return; } stopPreviewAndFreeCamera(); mCamera = camera; if (mCamera != null) { List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes(); supportedPreviewSizes = localSizes; requestLayout(); try { mCamera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } // Important: Call startPreview() to start updating the preview // surface. Preview must be started before you can take a picture. mCamera.startPreview(); } }
修改相机设置
相机设置会改变相机拍照的方式,从变焦级别到曝光补偿。本示例只更改预览尺寸;更多内容请参阅相机应用的源代码。
Kotlin
override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) { mCamera?.apply { // Now that the size is known, set up the camera parameters and begin // the preview. parameters?.also { params -> params.setPreviewSize(previewSize.width, previewSize.height) requestLayout() parameters = params } // Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. startPreview() } }
Java
@Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // Now that the size is known, set up the camera parameters and begin // the preview. Camera.Parameters parameters = mCamera.getParameters(); parameters.setPreviewSize(previewSize.width, previewSize.height); requestLayout(); mCamera.setParameters(parameters); // Important: Call startPreview() to start updating the preview surface. // Preview must be started before you can take a picture. mCamera.startPreview(); }
设置预览方向
大多数相机应用都会将显示锁定在横向模式,因为这是相机传感器的自然方向。此设置不会阻止您拍摄纵向模式的照片,因为设备的方向会记录在 EXIF 标头中。setCameraDisplayOrientation()
方法允许您更改预览的显示方式,而不会影响图像的记录方式。但是,在 API 级别 14 之前的 Android 版本中,您必须在更改方向之前停止预览,然后再重新启动它。
拍照
预览启动后,使用 Camera.takePicture()
方法拍照。您可以创建 Camera.PictureCallback
和 Camera.ShutterCallback
对象,并将它们传递给 Camera.takePicture()
。
如果您想连续抓取图像,可以创建一个实现 onPreviewFrame()
的 Camera.PreviewCallback
。对于介于两者之间的情况,您可以只捕获选定的预览帧,或者设置一个延迟操作来调用 takePicture()
。
重新启动预览
拍照后,您必须重新启动预览,然后用户才能再次拍照。在本示例中,通过重载快门按钮来完成重新启动。
Kotlin
fun onClick(v: View) { previewState = if (previewState == K_STATE_FROZEN) { camera?.startPreview() K_STATE_PREVIEW } else { camera?.takePicture(null, rawCallback, null) K_STATE_BUSY } shutterBtnConfig() }
Java
@Override public void onClick(View v) { switch(previewState) { case K_STATE_FROZEN: camera.startPreview(); previewState = K_STATE_PREVIEW; break; default: camera.takePicture( null, rawCallback, null); previewState = K_STATE_BUSY; } // switch shutterBtnConfig(); }
停止预览并释放相机
当您的应用使用完相机后,是时候进行清理了。特别是,您必须释放 Camera
对象,否则您可能会导致其他应用崩溃,包括您自己应用的新实例。
您应该何时停止预览并释放相机?当您的预览 Surface 被销毁时,这是一个很好的提示,表明是时候停止预览并释放相机了,如 Preview
类中的这些方法所示。
Kotlin
override fun surfaceDestroyed(holder: SurfaceHolder) { // Surface will be destroyed when we return, so stop the preview. // Call stopPreview() to stop updating the preview surface. mCamera?.stopPreview() } /** * When this function returns, mCamera will be null. */ private fun stopPreviewAndFreeCamera() { mCamera?.apply { // Call stopPreview() to stop updating the preview surface. stopPreview() // Important: Call release() to release the camera for use by other // applications. Applications should release the camera immediately // during onPause() and re-open() it during onResume()). release() mCamera = null } }
Java
@Override public void surfaceDestroyed(SurfaceHolder holder) { // Surface will be destroyed when we return, so stop the preview. if (mCamera != null) { // Call stopPreview() to stop updating the preview surface. mCamera.stopPreview(); } } /** * When this function returns, mCamera will be null. */ private void stopPreviewAndFreeCamera() { if (mCamera != null) { // Call stopPreview() to stop updating the preview surface. mCamera.stopPreview(); // Important: Call release() to release the camera for use by other // applications. Applications should release the camera immediately // during onPause() and re-open() it during onResume()). mCamera.release(); mCamera = null; } }
在本课程的前面部分,此过程也是 setCamera()
方法的一部分,因此初始化相机总是从停止预览开始。