赞
踩
注意:本页介绍的 Camera 类已弃用。我们建议您使用 CameraX Jetpack 库或 camera2 类(适用于特定使用情形)。CameraX 和 Camera2 均适用于 Android 5.0(API 级别 21)及更高版本。
请参阅以下相关资源:
MediaPlayer 概览
数据和文件存储概览
在让您的应用可以在 Android 设备上使用相机之前,您应该考虑几个有关应用打算如何使用此硬件功能的问题。
相机要求 - 使用相机是否对您的应用非常重要,以至于您不想将应用安装在没有相机的设备上?如果是,请在清单中声明相机要求。
快速拍照或自定义相机 - 您的应用将如何使用相机?您是只对快速拍照或拍摄视频片段感兴趣,还是您的应用会提供一种使用相机的新方式?如果要快速拍照或拍摄视频片段,不妨考虑使用现有相机应用。如果要开发自定义的相机功能,请参阅构建相机应用部分。
前台服务要求 - 您的应用在什么情况下与相机互动?在 Android 9(API 级别 28)及更高版本中,在后台运行的应用无法访问相机。因此,您应该在应用于前台运行时或作为前台服务的一部分运行时使用相机。
存储 - 您的应用所生成的图片或视频是仅对您的应用可见,还是会共享给其他应用(例如,图库或其他媒体和社交应用)使用?您是否想让照片和视频在您的应用卸载后仍然可用?如需了解如何实现这些选项,请参阅保存媒体文件部分。
Android 框架支持通过 android.hardware.camera2 API 或相机 Intent 拍摄图片和视频。以下是相关的类:
android.hardware.camera2
此软件包是用于控制设备相机的主要 API。当您构建相机应用时,该软件包可用于拍摄照片或视频。
Camera
此类是用于控制设备相机的旧版 API,现已弃用。
SurfaceView
此类用于向用户呈现实时相机预览。
MediaRecorder
此类用于通过相机录制视频。
Intent
MediaStore.ACTION_IMAGE_CAPTURE 或 MediaStore.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 实例请求该相机的访问权限(除非您正在使用 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.open() 和 Camera.release() 调用外,您还必须管理 Camera.lock() 和 Camera.unlock() 调用,以允许 MediaRecorder 访问相机硬件。
注意:从 Android 4.0(API 级别 14)开始,系统将自动为您管理 Camera.lock() 和 Camera.unlock() 调用。
与使用设备相机拍照不同,拍摄视频需要非常特殊的调用顺序。您必须按照特定的顺序执行操作,这样才能成功地让应用做好准备,开始拍摄视频,具体步骤如下所述。
打开相机 - 使用 Camera.open() 获取相机对象实例。
连接预览 - 使用 Camera.setPreviewDisplay() 将 SurfaceView 连接到相机,做好实时相机图片预览准备。
开始预览 - 调用 Camera.startPreview() 以开始显示实时相机图片。
开始录制视频 - 为了成功录制视频,必须完成以下步骤:
解锁相机 - 通过调用 Camera.unlock() 解锁相机,以供 MediaRecorder 使用。
配置 MediaRecorder - 按以下顺序调用下面的 MediaRecorder 方法。如需了解详情,请参阅 MediaRecorder 参考文档。
setCamera() - 设置要用于拍摄视频的相机;请使用您应用当前的 Camera 实例。
setAudioSource() - 设置音频源;请使用 MediaRecorder.AudioSource.CAMCORDER。
setVideoSource() - 设置视频源;请使用 MediaRecorder.VideoSource.CAMERA。
设置视频的输出格式和编码。对于 Android 2.2(API 级别 8)及更高版本,请使用 MediaRecorder.setProfile 方法,并使用 CamcorderProfile.get() 获取配置文件实例。对于 Android 2.2 之前的版本,您必须设置视频的输出格式和编码参数:
setOutputFormat() - 设置输出格式;请指定默认设置或 MediaRecorder.OutputFormat.MPEG_4。
setAudioEncoder() - 设置声音编码类型;请指定默认设置或 MediaRecorder.AudioEncoder.AMR_NB。
setVideoEncoder() - 设置视频编码类型;请指定默认设置或 MediaRecorder.VideoEncoder.MPEG_4_SP。
setOutputFile() - 设置输出文件;请使用保存媒体文件部分中示例方法中的 getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()。
setPreviewDisplay() - 为您的应用指定 SurfaceView 预览布局元素。请使用您在连接预览部分中指定的同一对象。
注意:您必须按上述顺序调用上面的 MediaRecorder 配置方法,否则您的应用会遇到错误且视频录制将会失败。
准备 MediaRecorder - 调用 MediaRecorder.prepare() 以使用提供的配置设置准备 MediaRecorder。
启动 MediaRecorder - 通过调用 MediaRecorder.start() 开始录制视频。
停止录制视频 - 依次调用以下方法,以便成功完成视频录制:
停止 MediaRecorder - 通过调用 MediaRecorder.stop() 停止录制视频。
重置 MediaRecorder -(可选)通过调用 MediaRecorder.reset() 移除录制器的配置设置。
释放 MediaRecorder - 通过调用 MediaRecorder.release() 释放 MediaRecorder。
锁定相机 - 通过调用 Camera.lock() 锁定相机,以便将来的 MediaRecorder 会话可以使用相机。从 Android 4.0(API 级别 14)开始,除非 MediaRecorder.prepare() 调用失败,否则不需要进行此调用。
停止预览 - 在 Activity 使用完相机后,使用 Camera.stopPreview() 停止预览。
释放相机 - 通过调用 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 视频录制参数是给定的默认设置,但您可能需要针对自己的应用调整这些设置:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
启动和停止 MediaRecorder
使用 MediaRecorder 类开始和停止视频录制时,您必须按照特定顺序操作,如下所示。
使用 Camera.unlock() 解锁相机
配置 MediaRecorder,如上述代码示例所示
使用 MediaRecorder.start() 开始录制
录制视频
使用 MediaRecorder.stop() 停止录制
使用 MediaRecorder.release() 释放媒体录制器
使用 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) - 此方法会返回一个标准位置,用于保存与您的应用相关联的照片和视频。如果用户卸载了您的应用,保存在此位置中的所有文件都将遭到移除。系统没有对此位置中的文件采取任何强制的安全措施,因此其他应用可以读取、更改和删除它们。
以下示例代码演示了如何为媒体文件创建 File 或 Uri 位置,以便在使用 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 添加到 Intent 的 EXTRA_OUTPUT 中。
如需详细了解如何在 Android 设备上保存文件,请参阅数据存储。
Android 支持多种相机功能(例如,照片格式、闪光灯模式、对焦设置等),您可以使用相机应用控制这些功能。本部分列出了一些常用的相机功能,并简要讨论了如何使用它们。您可以使用 Camera.Parameters 对象访问和设置大多数相机功能。但有几项重要功能需要的不仅仅是在 Camera.Parameters 中进行简单设置。如需了解这些功能,请参阅以下部分:
区域测光和对焦
人脸检测
延时摄影视频
如需了解有关如何使用通过 Camera.Parameters 控制的功能的一般信息,请参阅使用相机功能部分。如需详细了解如何使用通过相机参数对象控制的功能,请点击下方功能列表中的链接,查看相关 API 参考文档。
表 1. 常用相机功能(按引入时所在 Android API 级别排序)。
注意:由于硬件差异和软件实现方式,并非所有设备都支持这些功能。如需了解如何检查运行您应用的设备上的功能可用性,请参阅检查功能可用性。
开始着手在 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 对象(用于指定相机视野范围内的区域)和一个 weight 值(用于告知相机在计算测光或对焦时应为此区域指定的重要性级别)。
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,请参阅拍摄视频。
Camera2Video 和 HdrViewfinder 示例进一步演示了如何使用本页介绍的 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 示例应用。
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2020-11-23。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。