相机 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 对象。

清单声明

在使用相机 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()后,您也不能再假设预览的纵横比与您的活动相同。根据窗口大小和纵横比,您可能需要使用 letterbox 布局将宽的相机预览适应纵向布局,反之亦然。

在布局中放置预览

相机预览类(例如上一节中显示的示例)必须与其他用户界面控件一起放置在活动的布局中,以便拍摄照片或视频。本节将向您展示如何为预览构建基本布局和活动。

以下布局代码提供了一个非常基本的视图,可用于显示相机预览。在此示例中,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 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() 重新开始预览。

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

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. 停止预览 - 当您的活动完成使用相机时,请使用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_OUTPUTIntent中。

有关在 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 示例应用