控制摄像头

在本课中,我们将讨论如何使用框架 API 直接控制摄像头硬件。

注意:此页面指的是 Camera 类,该类已弃用。我们建议使用 CameraX 或针对特定用例使用 Camera2。CameraX 和 Camera2 均支持 Android 5.0(API 级别 21)及更高版本。

直接控制设备摄像头比请求现有摄像头应用程序的图片或视频需要更多的代码。但是,如果您想构建一个专门的摄像头应用程序或将其完全集成到您的应用程序 UI 中,本课程将向您展示如何操作。

参考以下相关资源

打开相机对象

获取 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();
    }
}

修改相机设置

相机设置会改变相机拍摄照片的方式,从变焦级别到曝光补偿。此示例仅更改预览大小;有关更多内容,请参阅 Camera 应用程序的源代码。

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.PictureCallbackCamera.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 对象,否则可能会导致其他应用程序崩溃,包括您自己应用程序的新实例。

您应该何时停止预览并释放相机?好吧,预览界面被销毁是一个很好的提示,表明是时候停止预览并释放相机了,如 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() 方法的一部分,因此初始化相机始终以停止预览开始。