Android 11 开发者预览版现已推出;快来测试并分享您的反馈吧

Camera API

Android 框架包含对各种相机和设备上可用相机功能的支持,使您能够在应用中捕获照片和视频。本文档将介绍一种捕获图像和视频的简单快捷方法,还会概述一种为用户打造自定义相机体验的高级方法。

注意:本页面所描述的 Camera 类已弃用。我们建议您使用更新的 camera2 类,后者能在 Android 5.0(API 级别 21)或更高版本上运行。请通过我们的博客了解关于 camera2 的更多信息,并观看此视频

请参阅下列相关资源:

注意事项

在让您的应用可以在 Android 设备上使用相机之前,您应该考虑关于您的应用打算如何使用此硬件功能的一些问题。

  • 相机要求 - 使用相机是否对于您的应用非常重要,以至于您不想让应用安装在没有相机的设备上?如果是,则您应在清单文件中声明相机需求
  • 快照或自定义相机 - 您的应用将如何使用相机?您是否只是对拍摄快照或视频剪辑感兴趣,抑或是您的应用会提供一种使用相机的新方法?如果要拍摄快照或制作视频剪辑,请考虑使用现有的相机应用。如果要开发自定义相机功能,请参阅本文的构建相机应用部分。
  • 前台 Service 要求 - 您的应用何时与相机交互?在 Android 9(API 级别 28)及更高版本中,在后台运行的应用无法访问相机。因此,您应该在应用于前台运行或作为前台服务的一部分时使用相机。
  • 存储 - 您应用生成的图像或视频是否打算只对您的应用可见,还是说可以共享给其他应用(例如 Gallery)或媒体和社交应用使用?您是否想让照片和视频在您的应用卸载后仍然可用?请查看保存媒体文件部分,了解如何实现这些选项。

基础知识

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" />
    
  • 存储权限 - 如果您的应用将图像或视频保存至设备的外部存储装置(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 实例来请求其访问权限(除非您使用 Intent 访问相机)。

如要访问主摄像头,请使用 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)及更高版本引入多窗口功能,您无法再假设预览的宽高比与您的 Activity 相同,即便调用 setDisplayOrientation() 也是如此。根据窗口尺寸和屏幕宽高比,你可能需要使用 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.release() 来释放 Camera 对象!如需了解有关如何释放相机的信息,请参阅释放相机

捕获视频

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

注意:从 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 添加到 IntentEXTRA_OUTPUT 中。

如需了解关于在 Android 设备上保存文件的更多信息,请参阅数据存储

相机功能

Android 支持多种相机功能,您可使用相机应用控制这些功能,如图片格式、闪光模式、对焦设置等等。本部分将列举常用相机功能,并简单介绍其使用方法。您可以使用“通过 Camera.Parameters 对象”访问和设置大多数相机功能。不过,有几个重要功能需要的不仅仅是在 Camera.Parameters 中进行简单设置。以下各部分将介绍这些功能:

如需了解关于如何使用通过 Camera.Parameters 控制的功能的一般信息,请参阅使用相机功能部分。如需了解关于如何使用通过相机参数对象控制的功能的详细信息,请点击下方功能列表中的链接,前往 API 参考文档。

表格 1. 按 Android API 级别从高到低排序的常用相机功能。

功能 API 级别 说明
人脸检测 14 识别照片中的人脸,并将其用于对焦、测光和白平衡
区域测光 14 在图像中指定一个或多个区域以计算白平衡
对焦区域 14 在图像中设置一个或多个区域以用于对焦
White Balance Lock 14 停止或开始自动白平衡调整
Exposure Lock 14 停止或开始自动曝光调整
Video Snapshot 14 在录制视频的同时拍摄照片(帧捕获)
延时视频 11 采用视频抽帧的方式录制延时视频
Multiple Cameras 9 支持设备上的多个摄像头,包括前置摄像头和后置摄像头
Focus Distance 9 报告相机和焦点对象之间的距离
Zoom 8 设置图像的缩放比例
Exposure Compensation 8 增加或减少曝光量
GPS Data 5 在图像中添加或省略地理位置数据
White Balance 5 设置白平衡模式,该模式会影响所捕获图像中的颜色值
Focus Mode 5 设置相机对自动,固定,微距或无穷远等物体对焦的方式
Scene Mode 5 对特定类型的摄影(例如夜间、海滩、雪景或烛光场景)应用预设模式
JPEG Quality 5 设置 JPEG 图像的压缩级别,这会提高或降低图像输出文件的质量和大小
Flash Mode 5 打开、关闭闪光灯,或使用自动设置
Color Effects 5 对所捕获的图像应用色彩效果,例如黑白,棕褐色调或负片。
Anti-Banding 5 减少由于 JPEG 压缩导致的色彩渐变带状效果
Picture Format 1 指定照片的文件格式
Picture Size 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 对象,用于在相机视野内指定区域和权重值,该值告知相机此区域在测光或焦点计算中应被赋予的重要性级别。

Camera.Area 对象中的 Rect 字段描述映射在 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() 调用此方法。不要试图在您相机应用主 Activity 的 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 的更多信息,请参阅捕获视频

Android Camera2VideoAndroid HcrViewfinder 示例进一步演示如何使用本页面中所述的 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

更多示例代码

如要下载关于 Camera2 Basic 的示例应用,请参阅 Android Camera2Basic 示例。如要下载关于 Camera2 Raw 的示例应用,请参阅 Android Camera2Raw 示例