相机 API

Android 框架包含对设备上各种相机和相机功能的支持,使您能够在应用中捕获图片和视频。本文档讨论了一种快速简单的图像和视频捕获方法,并概述了一种为用户创建自定义相机体验的高级方法。

注意:此页面描述了已弃用的Camera 类。我们建议您使用 CameraX Jetpack 库,或者对于特定用例,使用 camera2 类。CameraX 和 Camera2 都适用于 Android 5.0(API 级别 21)及更高版本。

请参阅以下相关资源

注意事项

在启用您的应用程序以在 Android 设备上使用摄像头之前,您应该考虑一些关于您的应用程序打算如何使用此硬件功能的问题。

  • 摄像头要求 - 摄像头对于您的应用程序是否如此重要,以至于您不希望在没有摄像头的设备上安装您的应用程序?如果是这样,您应该在 清单中声明摄像头要求
  • 快速拍照或自定义摄像头 - 您的应用程序将如何使用摄像头?您是否只对拍摄快速照片或视频剪辑感兴趣,或者您的应用程序是否会提供一种新的使用摄像头的方式?对于快速拍摄照片或剪辑,请考虑 使用现有的摄像头应用程序。对于开发自定义摄像头功能,请查看 构建摄像头应用程序 部分。
  • 前台服务要求 - 您的应用程序何时与摄像头交互?在 Android 9(API 级别 28)及更高版本中,在后台运行的应用程序无法访问摄像头。因此,您应该在应用程序处于前台或作为 前台服务 的一部分时使用摄像头。
  • 存储 - 您的应用程序生成的图像或视频是否仅供您的应用程序查看,还是共享以便其他应用程序(如图库或其他媒体和社交应用程序)可以使用?即使您的应用程序被卸载,您是否希望图片和视频可用?查看 保存媒体文件 部分以了解如何实现这些选项。

基础知识

Android 框架通过 android.hardware.camera2 API 或摄像头 Intent 支持捕获图像和视频。以下是相关的类

android.hardware.camera2
此包是控制设备摄像头的主要 API。当您构建摄像头应用程序时,它可以用于拍摄照片或视频。
Camera
此类是用于控制设备摄像头的较旧的已弃用 API。
SurfaceView
此类用于向用户呈现实时摄像头预览。
MediaRecorder
此类用于录制来自摄像头的视频。
Intent
MediaStore.ACTION_IMAGE_CAPTUREMediaStore.ACTION_VIDEO_CAPTURE 的 Intent 操作类型可用于捕获图像或视频,而无需直接使用 Camera 对象。

清单声明

在使用 Camera API 开始开发应用程序之前,您应该确保您的清单具有适当的声明以允许使用摄像头硬件和其他相关功能。

  • 摄像头权限 - 您的应用程序必须请求使用设备摄像头的权限。
    <uses-permission android:name="android.permission.CAMERA" />
    

    注意:如果您正在 通过调用现有的摄像头应用程序 使用摄像头,则您的应用程序不需要请求此权限。

  • 摄像头功能 - 您的应用程序还必须声明使用摄像头功能,例如
    <uses-feature android:name="android.hardware.camera" />
    

    有关摄像头功能的列表,请参阅清单 功能参考

    将摄像头功能添加到您的清单会导致 Google Play 阻止您的应用程序安装到不包含摄像头或不支持您指定的摄像头功能的设备上。有关使用基于功能的过滤与 Google Play 的更多信息,请参阅 Google Play 和基于功能的过滤

    如果您的应用程序可以使用摄像头或摄像头功能以确保正常运行,但不需要它,则您应该在清单中通过包含 android:required 属性并将其设置为 false 来指定这一点。

    <uses-feature android:name="android.hardware.camera" android:required="false" />
    
  • 存储权限 - 如果您的应用程序的目标是 Android 10(API 级别 29)或更低版本并在清单中指定以下内容,则它可以将图像或视频保存到设备的外部存储(SD 卡)。
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  • 音频录制权限 - 对于录制带有视频捕获的音频,您的应用程序必须请求音频捕获权限。
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  • 位置权限 - 如果您的应用程序使用 GPS 位置信息标记图像,则必须请求 ACCESS_FINE_LOCATION 权限。请注意,如果您的应用程序的目标是 Android 5.0(API 级别 21)或更高版本,您还需要声明您的应用程序使用设备的 GPS

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />
    

    有关获取用户位置的更多信息,请参阅 位置策略

使用现有的摄像头应用程序

在您的应用程序中启用拍摄照片或视频的快速方法(无需大量额外代码)是使用 Intent 调用现有的 Android 摄像头应用程序。详细信息在培训课程 简单拍摄照片简单录制视频 中进行了描述。

构建摄像头应用程序

一些开发人员可能需要一个摄像头用户界面,该界面自定义为其应用程序的外观或提供特殊功能。编写您自己的拍照代码可以为您的用户提供更引人入胜的体验。

注意:以下指南适用于较旧的已弃用的 Camera API。对于新的或高级摄像头应用程序,建议使用较新的 android.hardware.camera2 API。

为您的应用程序创建自定义摄像头界面的常规步骤如下

  • 检测和访问摄像头 - 创建代码以检查摄像头是否存在并请求访问。
  • 创建预览类 - 创建一个扩展 SurfaceView 并实现 SurfaceHolder 接口的摄像头预览类。此类预览来自摄像头的实时图像。
  • 构建预览布局 - 获得摄像头预览类后,创建一个包含预览和您想要的用户界面控件的视图布局。
  • 设置捕获侦听器 - 连接界面控件的侦听器,以响应用户操作(例如按下按钮)启动图像或视频捕获。
  • 捕获和保存文件 - 设置捕获图片或视频并保存输出的代码。
  • 释放摄像头 - 使用摄像头后,您的应用程序必须正确释放它以供其他应用程序使用。

摄像头硬件是必须仔细管理的共享资源,因此您的应用程序不会与也可能想要使用它的其他应用程序发生冲突。以下部分讨论了如何检测摄像头硬件、如何请求访问摄像头、如何捕获图片或视频以及如何在应用程序完成使用后释放摄像头。

注意:请记住,在应用程序完成使用后,通过调用 Camera.release() 来释放 Camera 对象!如果您的应用程序没有正确释放摄像头,则所有后续尝试访问摄像头的操作(包括您自己的应用程序)都将失败,并可能导致您的应用程序或其他应用程序被关闭。

检测摄像头硬件

如果您的应用程序没有使用清单声明明确要求摄像头,则您应该检查运行时是否可用。要执行此检查,请使用 PackageManager.hasSystemFeature() 方法,如下面的示例代码所示

Kotlin

/** Check if this device has a camera */
private fun checkCameraHardware(context: Context): Boolean {
    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
        // this device has a camera
        return true
    } else {
        // no camera on this device
        return false
    }
}

Java

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

Android 设备可以有多个摄像头,例如用于摄影的后置摄像头和用于视频通话的前置摄像头。Android 2.3(API 级别 9)及更高版本允许您使用 Camera.getNumberOfCameras() 方法检查设备上可用的摄像头数量。

访问摄像头

如果您已确定您的应用程序正在运行的设备具有摄像头,则必须通过获取 Camera 的实例来请求访问它(除非您正在使用 意图访问摄像头)。

要访问主摄像头,请使用 Camera.open() 方法,并确保捕获任何异常,如下面的代码所示

Kotlin

/** A safe way to get an instance of the Camera object. */
fun getCameraInstance(): Camera? {
    return try {
        Camera.open() // attempt to get a Camera instance
    } catch (e: Exception) {
        // Camera is not available (in use or does not exist)
        null // returns null if camera is unavailable
    }
}

Java

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

注意:在使用 Camera.open() 时始终检查异常。如果摄像头正在使用或不存在,而未能检查异常将导致系统关闭您的应用程序。

在运行 Android 2.3(API 级别 9)或更高版本的设备上,您可以使用 Camera.open(int) 访问特定的摄像头。上面的示例代码将访问具有多个摄像头的设备上的第一个后置摄像头。

检查摄像头功能

获得对摄像头的访问权限后,您可以使用 Camera.getParameters() 方法并检查返回的 Camera.Parameters 对象以获取支持的功能,从而获取有关其功能的更多信息。当使用 API 级别 9 或更高版本时,请使用 Camera.getCameraInfo() 来确定摄像头是在设备的前面还是后面,以及图像的方向。

创建预览类

为了让用户有效地拍摄照片或视频,他们必须能够看到设备摄像头看到的内容。摄像头预览类是一个 SurfaceView,可以显示来自摄像头的实时图像数据,以便用户可以构图和捕获图片或视频。

以下示例代码演示了如何创建一个基本摄像头预览类,该类可以包含在 View 布局中。此类实现 SurfaceHolder.Callback 以捕获创建和销毁视图的回调事件,这些事件是分配摄像头预览输入所必需的。

Kotlin

/** A basic Camera preview class */
class CameraPreview(
        context: Context,
        private val mCamera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val mHolder: SurfaceHolder = holder.apply {
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        addCallback(this@CameraPreview)
        // deprecated setting, but required on Android versions prior to 3.0
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        mCamera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "Error setting camera preview: ${e.message}")
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.surface == null) {
            // preview surface does not exist
            return
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview()
        } catch (e: Exception) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        mCamera.apply {
            try {
                setPreviewDisplay(mHolder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "Error starting camera preview: ${e.message}")
            }
        }
    }
}

Java

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

如果要为摄像头预览设置特定大小,请在上面注释中提到的 surfaceChanged() 方法中设置。设置预览大小时,必须使用 getSupportedPreviewSizes() 中的值。不要setPreviewSize() 方法中设置任意值。

注意: 从 Android 7.0(API 级别 24)及更高版本引入 多窗口 功能后,即使调用了 setDisplayOrientation(),也不能再假设预览的纵横比与您的 Activity 相同。根据窗口大小和纵横比,您可能需要使用 letterbox 布局将宽的相机预览适配到纵向布局,反之亦然。

在布局中放置预览

相机预览类(例如上一节中显示的示例)必须与其他用于拍照或录制的用户界面控件一起放置在 Activity 的布局中。本节将向您展示如何构建用于预览的基本布局和 Activity。

以下布局代码提供了一个非常基本的视图,可用于显示相机预览。在此示例中,FrameLayout 元素旨在作为相机预览类的容器。使用这种布局类型是为了能够在实时相机预览图像上叠加其他图片信息或控件。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>

在大多数设备上,相机预览的默认方向是横向。此示例布局指定了水平(横向)布局,并且下面的代码将应用程序的方向固定为横向。为了简化相机预览的渲染,您应该将应用程序的预览 Activity 方向更改为横向,方法是在清单中添加以下内容。

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

注意: 相机预览不必处于横向模式。从 Android 2.2(API 级别 8)开始,您可以使用 setDisplayOrientation() 方法设置预览图像的旋转。为了在用户重新调整手机方向时更改预览方向,在预览类的 surfaceChanged() 方法中,首先使用 Camera.stopPreview() 停止预览,更改方向,然后使用 Camera.startPreview() 重新开始预览。

在相机视图的 Activity 中,将您的预览类添加到上面示例中所示的 FrameLayout 元素中。您的相机 Activity 还必须确保在暂停或关闭时释放相机。以下示例展示了如何修改相机 Activity 以附加 创建预览类 中显示的预览类。

Kotlin

class CameraActivity : Activity() {

    private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create an instance of Camera
        mCamera = getCameraInstance()

        mPreview = mCamera?.let {
            // Create our Preview view
            CameraPreview(this, it)
        }

        // Set the Preview view as the content of our activity.
        mPreview?.also {
            val preview: FrameLayout = findViewById(R.id.camera_preview)
            preview.addView(it)
        }
    }
}

Java

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }
}

注意: 上述示例中的 getCameraInstance() 方法指的是 访问相机 中显示的示例方法。

捕获图片

创建预览类和用于显示它的视图布局后,就可以开始使用您的应用程序捕获图像了。在您的应用程序代码中,您必须为用户界面控件设置监听器,以便通过拍照来响应用户操作。

要检索图片,请使用 Camera.takePicture() 方法。此方法接受三个参数,这些参数接收来自相机的数。为了以 JPEG 格式接收数据,您必须实现 Camera.PictureCallback 接口以接收图像数据并将其写入文件。以下代码显示了 Camera.PictureCallback 接口的基本实现,用于保存从相机接收到的图像。

Kotlin

private val mPicture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG, ("Error creating media file, check storage permissions"))
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "File not found: ${e.message}")
    } catch (e: IOException) {
        Log.d(TAG, "Error accessing file: ${e.message}")
    }
}

Java

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};

通过调用 Camera.takePicture() 方法触发捕获图像。以下示例代码展示了如何从按钮 View.OnClickListener 中调用此方法。

Kotlin

val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    // get an image from the camera
    mCamera?.takePicture(null, null, picture)
}

Java

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an image from the camera
            mCamera.takePicture(null, null, picture);
        }
    }
);

注意: 以下示例中的 mPicture 成员指的是上面的示例代码。

注意: 请记住,在应用程序完成使用 Camera 对象后,通过调用 Camera.release() 释放它!有关如何释放相机的更多信息,请参阅 释放相机

捕获视频

使用 Android 框架捕获视频需要仔细管理 Camera 对象并与 MediaRecorder 类协调。当使用 Camera 录制视频时,除了 Camera.open()Camera.release() 调用之外,您还必须管理 Camera.lock()Camera.unlock() 调用,以允许 MediaRecorder 访问相机硬件。

注意: 从 Android 4.0(API 级别 14)开始,Camera.lock()Camera.unlock() 调用会自动为您管理。

与使用设备相机拍照不同,捕获视频需要非常特定的调用顺序。您必须按照特定的执行顺序才能成功准备并使用您的应用程序捕获视频,如下所述。

  1. 打开相机 - 使用 Camera.open() 获取相机对象的实例。
  2. 连接预览 - 通过使用 Camera.setPreviewDisplay()SurfaceView 连接到相机来准备实时相机图像预览。
  3. 开始预览 - 调用 Camera.startPreview() 以开始显示实时相机图像。
  4. 开始录制视频 - 必须按顺序完成以下步骤才能成功录制视频
    1. 解锁相机 - 通过调用 Camera.unlock() 解锁相机,以便 MediaRecorder 使用。
    2. 配置 MediaRecorder - 按以下顺序调用 MediaRecorder 方法。有关更多信息,请参阅 MediaRecorder 参考文档。
      1. setCamera() - 设置用于视频捕获的相机,使用应用程序当前的 Camera 实例。
      2. setAudioSource() - 设置音频源,使用 MediaRecorder.AudioSource.CAMCORDER
      3. setVideoSource() - 设置视频源,使用 MediaRecorder.VideoSource.CAMERA
      4. 设置视频输出格式和编码。对于 Android 2.2(API 级别 8)及更高版本,使用 MediaRecorder.setProfile 方法,并使用 CamcorderProfile.get() 获取配置文件实例。对于 Android 2.2 之前的版本,您必须设置视频输出格式和编码参数
        1. setOutputFormat() - 设置输出格式,指定默认设置或 MediaRecorder.OutputFormat.MPEG_4
        2. setAudioEncoder() - 设置声音编码类型,指定默认设置或 MediaRecorder.AudioEncoder.AMR_NB
        3. setVideoEncoder() - 设置视频编码类型,指定默认设置或 MediaRecorder.VideoEncoder.MPEG_4_SP
      5. setOutputFile() - 设置输出文件,使用 保存媒体文件 部分示例方法中的 getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()
      6. setPreviewDisplay() - 为您的应用程序指定 SurfaceView 预览布局元素。使用与连接预览中指定的相同对象。

      注意: 必须按此顺序调用这些 MediaRecorder 配置方法,否则您的应用程序将遇到错误,并且录制将失败。

    3. 准备 MediaRecorder - 通过调用 MediaRecorder.prepare() 使用提供的配置设置准备 MediaRecorder
    4. 启动 MediaRecorder - 通过调用 MediaRecorder.start() 开始录制视频。
  5. 停止录制视频 - 按顺序调用以下方法,以成功完成视频录制
    1. 停止 MediaRecorder - 通过调用 MediaRecorder.stop() 停止录制视频。
    2. 重置 MediaRecorder - 可选地,通过调用 MediaRecorder.reset() 从录制器中删除配置设置。
    3. 释放 MediaRecorder - 通过调用 MediaRecorder.release() 释放 MediaRecorder
    4. 锁定相机 - 锁定相机,以便将来 MediaRecorder 会话可以使用它,方法是调用 Camera.lock()。从 Android 4.0(API 级别 14)开始,除非 MediaRecorder.prepare() 调用失败,否则不需要此调用。
  6. 停止预览 - 当您的 Activity 完成使用相机后,使用 Camera.stopPreview() 停止预览。
  7. 释放相机 - 通过调用 Camera.release() 释放相机,以便其他应用程序可以使用它。

注意: 可以先不创建相机预览,然后跳过此过程的前几个步骤,来使用 MediaRecorder。但是,由于用户通常希望在开始录制之前看到预览,因此此处不讨论该过程。

提示:如果您的应用程序通常用于录制视频,请在开始预览之前将setRecordingHint(boolean)设置为true。此设置可以帮助缩短开始录制所需的时间。

配置 MediaRecorder

当使用MediaRecorder类录制视频时,必须按照特定顺序执行配置步骤,然后调用MediaRecorder.prepare()方法来检查和实现配置。以下示例代码演示了如何正确配置和准备MediaRecorder类以进行视频录制。

Kotlin

private fun prepareVideoRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    mCamera?.let { camera ->
        // Step 1: Unlock and set camera to MediaRecorder
        camera?.unlock()

        mediaRecorder?.run {
            setCamera(camera)

            // Step 2: Set sources
            setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
            setVideoSource(MediaRecorder.VideoSource.CAMERA)

            // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
            setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

            // Step 4: Set output file
            setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

            // Step 5: Set the preview output
            setPreviewDisplay(mPreview?.holder?.surface)

            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
            setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)


            // Step 6: Prepare configured MediaRecorder
            return try {
                prepare()
                true
            } catch (e: IllegalStateException) {
                Log.d(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            } catch (e: IOException) {
                Log.d(TAG, "IOException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            }
        }

    }
    return false
}

Java

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

在 Android 2.2(API 级别 8)之前,必须直接设置输出格式和编码格式参数,而不是使用CamcorderProfile。以下代码演示了这种方法。

Kotlin

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder?.apply {
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    }

Java

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

以下MediaRecorder的视频录制参数具有默认设置,但是,您可能希望根据您的应用程序调整这些设置。

启动和停止 MediaRecorder

在使用MediaRecorder类启动和停止视频录制时,必须按照以下列出的特定顺序进行操作。

  1. 使用Camera.unlock()解锁相机。
  2. 如上文代码示例所示配置MediaRecorder
  3. 使用MediaRecorder.start()开始录制。
  4. 录制视频。
  5. 使用MediaRecorder.stop()停止录制。
  6. 使用MediaRecorder.release()释放媒体录制器。
  7. 使用Camera.lock()锁定相机。

以下示例代码演示了如何将按钮连接起来,以使用相机和MediaRecorder类正确启动和停止视频录制。

注意:完成视频录制后,请勿释放相机,否则您的预览将停止。

Kotlin

var isRecording = false
val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    if (isRecording) {
        // stop recording and release camera
        mediaRecorder?.stop() // stop the recording
        releaseMediaRecorder() // release the MediaRecorder object
        mCamera?.lock() // take camera access back from MediaRecorder

        // inform the user that recording has stopped
        setCaptureButtonText("Capture")
        isRecording = false
    } else {
        // initialize video camera
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared,
            // now you can start recording
            mediaRecorder?.start()

            // inform the user that recording has started
            setCaptureButtonText("Stop")
            isRecording = true
        } else {
            // prepare didn't work, release the camera
            releaseMediaRecorder()
            // inform user
        }
    }
}

Java

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

注意:在上面的示例中,prepareVideoRecorder()方法指的是配置 MediaRecorder中显示的示例代码。此方法负责锁定相机,配置和准备MediaRecorder实例。

释放相机

相机是设备上应用程序共享的资源。您的应用程序在获取Camera实例后可以使用相机,并且必须特别注意在应用程序停止使用相机时以及应用程序暂停时(Activity.onPause())释放相机对象。如果您的应用程序未正确释放相机,则所有后续尝试访问相机(包括您自己的应用程序)都将失败,并可能导致您的应用程序或其他应用程序关闭。

要释放Camera对象的实例,请使用Camera.release()方法,如下面的示例代码所示。

Kotlin

class CameraActivity : Activity() {
    private var mCamera: Camera?
    private var preview: SurfaceView?
    private var mediaRecorder: MediaRecorder?

    override fun onPause() {
        super.onPause()
        releaseMediaRecorder() // if you are using MediaRecorder, release it first
        releaseCamera() // release the camera immediately on pause event
    }

    private fun releaseMediaRecorder() {
        mediaRecorder?.reset() // clear recorder configuration
        mediaRecorder?.release() // release the recorder object
        mediaRecorder = null
        mCamera?.lock() // lock camera for later use
    }

    private fun releaseCamera() {
        mCamera?.release() // release the camera for other applications
        mCamera = null
    }
}

Java

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView preview;
    private MediaRecorder mediaRecorder;

    ...

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();   // clear recorder configuration
            mediaRecorder.release(); // release the recorder object
            mediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}

警告:如果您的应用程序未正确释放相机,则所有后续尝试访问相机(包括您自己的应用程序)都将失败,并可能导致您的应用程序或其他应用程序关闭。

保存媒体文件

用户创建的媒体文件(如图片和视频)应保存到设备的外部存储目录(SD 卡),以节省系统空间并允许用户在无需设备的情况下访问这些文件。设备上保存媒体文件的目录位置有很多,但是作为开发人员,您只需要考虑两个标准位置。

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - 此方法返回保存图片和视频的标准、共享和推荐位置。此目录是共享(公共)的,因此其他应用程序可以轻松发现、读取、更改和删除在此位置保存的文件。如果用户卸载您的应用程序,则保存到此位置的媒体文件将不会被删除。为避免干扰用户现有的图片和视频,您应该在此目录中为您的应用程序的媒体文件创建一个子目录,如下面的代码示例所示。此方法在 Android 2.2(API 级别 8)中可用,有关早期 API 版本中的等效调用,请参阅保存共享文件
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 此方法返回与您的应用程序关联的保存图片和视频的标准位置。如果卸载您的应用程序,则此位置中的任何文件都将被删除。此位置的文件没有强制执行安全性,其他应用程序可能会读取、更改和删除它们。

以下示例代码演示了如何为媒体文件创建FileUri位置,这些位置可以在使用Intent调用设备的相机或作为构建相机应用程序的一部分时使用。

Kotlin

val MEDIA_TYPE_IMAGE = 1
val MEDIA_TYPE_VIDEO = 2

/** Create a file Uri for saving an image or video */
private fun getOutputMediaFileUri(type: Int): Uri {
    return Uri.fromFile(getOutputMediaFile(type))
}

/** Create a File for saving an image or video */
private fun getOutputMediaFile(type: Int): File? {
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    val mediaStorageDir = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp"
    )
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    mediaStorageDir.apply {
        if (!exists()) {
            if (!mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory")
                return null
            }
        }
    }

    // Create a media file name
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return when (type) {
        MEDIA_TYPE_IMAGE -> {
            File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")
        }
        MEDIA_TYPE_VIDEO -> {
            File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")
        }
        else -> null
    }
}

Java

public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
      return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES), "MyCameraApp");
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "IMG_"+ timeStamp + ".jpg");
    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "VID_"+ timeStamp + ".mp4");
    } else {
        return null;
    }

    return mediaFile;
}

注意:Environment.getExternalStoragePublicDirectory()在 Android 2.2(API 级别 8)或更高版本中可用。如果您针对的是运行早期 Android 版本的设备,请改用Environment.getExternalStorageDirectory()。有关更多信息,请参阅保存共享文件

要使 URI 支持工作配置文件,请首先将文件 URI 转换为内容 URI。然后,将内容 URI 添加到EXTRA_OUTPUT中,该内容 URI 是Intent的一部分。

有关在 Android 设备上保存文件的更多信息,请参阅数据存储

相机功能

Android 支持您可以使用相机应用程序控制的各种相机功能,例如图片格式、闪光灯模式、对焦设置等等。本节列出了常见的相机功能,并简要讨论了如何使用它们。大多数相机功能都可以通过Camera.Parameters对象进行访问和设置。但是,有一些重要的功能需要比Camera.Parameters中的简单设置更复杂。以下各节将介绍这些功能。

有关如何使用通过Camera.Parameters控制的功能的常规信息,请查看使用相机功能部分。有关如何使用通过相机参数对象控制的功能的更详细信息,请按照以下功能列表中的链接跳转到 API 参考文档。

表 1. 按引入它们的 Android API 级别排序的常见相机功能。

功能 API 级别 描述
人脸检测 14 识别图片中的人脸,并将其用于对焦、测光和白平衡。
测光区域 14 指定图像中一个或多个用于计算白平衡的区域。
对焦区域 14 设置图像中一个或多个用于对焦的区域。
白平衡锁定 14 停止或启动自动白平衡调整。
曝光锁定 14 停止或启动自动曝光调整。
视频快照 14 在拍摄视频时拍摄照片(帧抓取)。
延时视频 11 以设定的延迟录制帧以录制延时视频。
多个摄像头 9 支持设备上的多个摄像头,包括前置摄像头和后置摄像头。
对焦距离 9 报告相机与似乎处于对焦状态的对象之间的距离。
缩放 8 设置图像放大倍数。
曝光补偿 8 增加或减少光照曝光级别。
GPS 数据 5 在图像中包含或省略地理位置数据。
白平衡 5 设置白平衡模式,这会影响捕获图像中的颜色值。
对焦模式 5 设置相机如何对焦于某个主体,例如自动、固定、微距或无限远。
场景模式 5 为特定类型的摄影场景(例如夜景、沙滩、雪景或烛光场景)应用预设模式。
JPEG 质量 5 设置 JPEG 图像的压缩级别,这会增加或减少图像输出文件质量和大小。
闪光灯模式 5 打开或关闭闪光灯,或使用自动设置。
色彩效果 5 对捕获的图像应用色彩效果,例如黑白、棕褐色或负片。
抗频闪 5 减少由于 JPEG 压缩导致的颜色渐变中的频闪效果。
图片格式 1 指定图片的文件格式。
图片大小 1 指定保存的图片的像素尺寸。

注意:由于硬件差异和软件实现,并非所有设备都支持这些功能。有关检查您的应用程序正在运行的设备上功能可用性的信息,请参阅检查功能可用性

检查功能可用性

在开始在 Android 设备上使用相机功能时,首先要了解的是,并非所有设备都支持所有相机功能。此外,支持特定功能的设备可能以不同的级别或不同的选项支持它们。因此,在开发相机应用程序时,您需要做出部分决策,即决定要支持哪些相机功能以及支持到什么程度。做出该决定后,您应该计划在相机应用程序中包含代码,以检查设备硬件是否支持这些功能,并在功能不可用时优雅地失败。

您可以通过获取相机参数对象的实例并检查相关方法来检查相机功能的可用性。以下代码示例显示了如何获取Camera.Parameters对象并检查相机是否支持自动对焦功能。

Kotlin

val params: Camera.Parameters? = camera?.parameters
val focusModes: List<String>? = params?.supportedFocusModes
if (focusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO) == true) {
    // Autofocus mode is supported
}

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}

您可以将上述技术应用于大多数相机功能。 Camera.Parameters 对象提供 getSupported...()is...Supported()getMax...() 方法来确定某个功能是否(以及在多大程度上)受支持。

如果您的应用程序需要某些相机功能才能正常运行,则可以通过在应用程序清单中添加内容来要求这些功能。当您声明使用特定相机功能(例如闪光灯和自动对焦)时,Google Play 会限制您的应用程序安装在不支持这些功能的设备上。有关可在应用清单中声明的相机功能列表,请参阅清单 功能参考

使用相机功能

大多数相机功能都是使用 Camera.Parameters 对象激活和控制的。您可以通过首先获取 Camera 对象的实例,调用 getParameters() 方法,更改返回的参数对象,然后将其设置回相机对象来获得此对象,如下面的示例代码所示

Kotlin

val params: Camera.Parameters? = camera?.parameters
params?.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera?.parameters = params

Java

// get Camera parameters
Camera.Parameters params = camera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);

此技术适用于几乎所有相机功能,并且在获取 Camera 对象的实例后,几乎可以随时更改大多数参数。参数更改通常会立即在应用程序的相机预览中显示给用户。在软件方面,参数更改可能需要几个帧才能真正生效,因为相机硬件会处理新的指令,然后发送更新的图像数据。

重要提示:某些相机功能无法随意更改。特别是,更改相机预览的大小或方向需要先停止预览,更改预览大小,然后重新启动预览。从 Android 4.0(API 级别 14)开始,可以在不重新启动预览的情况下更改预览方向。

其他相机功能需要更多代码才能实现,包括

  • 测光和对焦区域
  • 人脸检测
  • 延时视频

以下各部分提供了有关如何实现这些功能的简要概述。

测光和对焦区域

在某些摄影场景中,自动对焦和测光可能无法产生预期的结果。从 Android 4.0(API 级别 14)开始,您的相机应用程序可以提供其他控件,允许您的应用或用户指定图像中用于确定对焦或光照水平设置的区域,并将这些值传递给相机硬件以用于捕获图像或视频。

测光和对焦区域的工作方式与其他相机功能非常相似,因为您可以通过 Camera.Parameters 对象中的方法来控制它们。以下代码演示了为 Camera 的实例设置两个测光区域

Kotlin

// Create an instance of Camera
camera = getCameraInstance()

// set Camera parameters
val params: Camera.Parameters? = camera?.parameters

params?.apply {
    if (maxNumMeteringAreas > 0) { // check that metering areas are supported
        meteringAreas = ArrayList<Camera.Area>().apply {
            val areaRect1 = Rect(-100, -100, 100, 100) // specify an area in center of image
            add(Camera.Area(areaRect1, 600)) // set weight to 60%
            val areaRect2 = Rect(800, -1000, 1000, -800) // specify an area in upper right of image
            add(Camera.Area(areaRect2, 400)) // set weight to 40%
        }
    }
    camera?.parameters = this
}

Java

// Create an instance of Camera
camera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = camera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of image
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of image
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

camera.setParameters(params);

Camera.Area 对象包含两个数据参数:一个 Rect 对象,用于指定相机视野内的区域,以及一个权重值,用于告诉相机在测光或对焦计算中应给予此区域多大的重要程度。

Rect 字段在 Camera.Area 对象中描述了一个映射在 2000 x 2000 单位网格上的矩形形状。坐标 -1000、-1000 表示相机图像的左上角,坐标 1000、1000 表示相机图像的右下角,如下面的插图所示。

图 1. 红线说明了在相机预览中指定 Camera.Area 的坐标系。蓝色框显示了具有 Rect 值 333、333、667、667 的相机区域的位置和形状。

此坐标系的边界始终对应于相机预览中可见图像的外边缘,并且不会随着缩放级别而缩小或扩大。类似地,使用 Camera.setDisplayOrientation() 旋转图像预览不会重新映射坐标系。

人脸检测

对于包含人物的照片,人脸通常是照片中最重要的部分,在捕获图像时应使用人脸来确定对焦和白平衡。Android 4.0(API 级别 14)框架提供了用于识别面部并使用面部识别技术计算图片设置的 API。

注意:在面部检测功能运行期间,setWhiteBalance(String)setFocusAreas(List<Camera.Area>)setMeteringAreas(List<Camera.Area>) 将不起作用。

在您的相机应用程序中使用面部检测功能需要几个通用步骤

  • 检查设备上是否支持面部检测
  • 创建面部检测监听器
  • 将面部检测监听器添加到您的相机对象
  • 在预览之后(以及每次重新启动预览之后)启动面部检测

并非所有设备都支持面部检测功能。您可以通过调用 getMaxNumDetectedFaces() 来检查此功能是否受支持。此检查的示例显示在下面的 startFaceDetection() 示例方法中。

为了接收面部检测事件的通知并做出响应,您的相机应用程序必须为面部检测事件设置监听器。为此,您必须创建一个实现 Camera.FaceDetectionListener 接口的监听器类,如下面的示例代码所示。

Kotlin

internal class MyFaceDetectionListener : Camera.FaceDetectionListener {

    override fun onFaceDetection(faces: Array<Camera.Face>, camera: Camera) {
        if (faces.isNotEmpty()) {
            Log.d("FaceDetection", ("face detected: ${faces.size}" +
                    " Face 1 Location X: ${faces[0].rect.centerX()}" +
                    "Y: ${faces[0].rect.centerY()}"))
        }
    }
}

Java

class MyFaceDetectionListener implements Camera.FaceDetectionListener {

    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}

创建此类后,您将其设置为应用程序的 Camera 对象,如下面的示例代码所示

Kotlin

camera?.setFaceDetectionListener(MyFaceDetectionListener())

Java

camera.setFaceDetectionListener(new MyFaceDetectionListener());

每次启动(或重新启动)相机预览时,您的应用程序都必须启动面部检测功能。创建一个用于启动面部检测的方法,以便您可以根据需要调用它,如下面的示例代码所示。

Kotlin

fun startFaceDetection() {
    // Try starting Face Detection
    val params = mCamera?.parameters
    // start face detection only *after* preview has started

    params?.apply {
        if (maxNumDetectedFaces > 0) {
            // camera supports face detection, so can start it:
            mCamera?.startFaceDetection()
        }
    }
}

Java

public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}

每次启动(或重新启动)相机预览时,您都必须启动面部检测。如果您使用 创建预览类 中所示的预览类,请将您的 startFaceDetection() 方法添加到预览类中的 surfaceCreated()surfaceChanged() 方法中,如下面的示例代码所示。

Kotlin

override fun surfaceCreated(holder: SurfaceHolder) {
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // start face detection feature
    } catch (e: IOException) {
        Log.d(TAG, "Error setting camera preview: ${e.message}")
    }
}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    if (holder.surface == null) {
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null")
        return
    }
    try {
        mCamera.stopPreview()
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: ${e.message}")
    }
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // re-start face detection feature
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: ${e.message}")
    }
}

Java

public void surfaceCreated(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // start face detection feature

    } catch (IOException e) {
        Log.d(TAG, "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

    if (holder.getSurface() == null){
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null");
        return;
    }

    try {
        mCamera.stopPreview();

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
    }

    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // re-start face detection feature

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

注意:请记住,在调用 startPreview()之后调用此方法。不要尝试在相机应用主活动的 onCreate() 方法中启动面部检测,因为此时您的应用程序的执行过程中预览不可用。

延时视频

延时摄影视频允许用户创建将间隔几秒或几分钟拍摄的照片组合在一起的视频剪辑。此功能使用 MediaRecorder 来录制延时序列的图像。

要使用 MediaRecorder 录制延时摄影视频,您必须像录制普通视频一样配置录制器对象,将捕获的每秒帧数设置为较低数字,并使用其中一个延时摄影质量设置,如下面的代码示例所示。

Kotlin

mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH))
mediaRecorder.setCaptureRate(0.1) // capture a frame every 10 seconds

Java

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));
...
// Step 5.5: Set the video capture rate to a low number
mediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds

这些设置必须作为 MediaRecorder 的更大配置过程的一部分进行。有关完整的配置代码示例,请参阅 配置 MediaRecorder。配置完成后,您可以像录制普通视频剪辑一样启动视频录制。有关配置和运行 MediaRecorder 的更多信息,请参阅 捕获视频

Camera2VideoHdrViewfinder 示例进一步演示了本页介绍的 API 的用法。

需要权限的相机字段

在 Android 10(API 级别 29)或更高版本上运行的应用必须具有 CAMERA 权限才能访问 getCameraCharacteristics() 方法返回的以下字段的值

  • LENS_POSE_ROTATION
  • LENS_POSE_TRANSLATION
  • LENS_INTRINSIC_CALIBRATION
  • LENS_RADIAL_DISTORTION
  • LENS_POSE_REFERENCE
  • LENS_DISTORTION
  • LENS_INFO_HYPERFOCAL_DISTANCE
  • LENS_INFO_MINIMUM_FOCUS_DISTANCE
  • SENSOR_REFERENCE_ILLUMINANT1
  • SENSOR_REFERENCE_ILLUMINANT2
  • SENSOR_CALIBRATION_TRANSFORM1
  • SENSOR_CALIBRATION_TRANSFORM2
  • SENSOR_COLOR_TRANSFORM1
  • SENSOR_COLOR_TRANSFORM2
  • SENSOR_FORWARD_MATRIX1
  • SENSOR_FORWARD_MATRIX2

其他示例代码

要下载示例应用,请参阅 Camera2Basic 示例官方 CameraX 示例应用