本文译自官方文档:https://developer.android.com/guide/topics/media/camera.html
Android框架层包含了对多种相机和相机特性的支持,可以让你在你的应用中拍照或录像。本文档主要讨论如何快速、简单的进行拍照和录像,同时也对如何开发复杂一些的相机应用做了简要介绍。
1 基础
Android框架层支持通过android.hardware.camera2
API 或 camera Intent来拍照和录像,以下是相关的类:
android.hardware.camera2
这个包提供了控制相机设备的主要API。
Camera
已过时的控制相机设备的API。
SurfaceView
这个类用来向用户展示实时的相机预览(live camera preview)。
MediaRecorder
这个类用来录像。
Intent
不需要直接操作相机设备,通过MediaStore.ACTION_IMAGE_CAPTURE
和MediaStore.ACTION_VIDEO_CAPTURE
这两个Intent就可以快速地进行拍照和录像。
2 清单文件声明
在开始使用相机API进行开发之前,首先要确保你的清单文件中已经声明了相应的权限和特性。
相机权限 —— 如果你的app要直接使用相机设备,则必须声明此权限。
<uses-permission android:name="android.permission.CAMERA" />
注意:如果是通过Intent来间接使用相机,则不需要声明此权限。
相机特性(Camera Features)
<uses-feature android:name="android.hardware.camera" />
如果在清单文件中声明了相机特性,那么Google Play会阻止你的app被安装在不支持相机的设备上。
存储权限 —— 如果你的app将相片和视频存储在外部存储设备(SD卡),则必须声明此权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
录音权限 —— 如果需要在录像的同时录音,则必须声明录音权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
获取位置信息权限 —— 如果想要给拍摄的照片加上位置信息,则必须声明此权限。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3 通过Intent使用已有的相机app来拍照和录像
在你的app中进行拍照和录像的一个简单方法是:通过Intent来调起一个已有的相机应用。有如下几步:
- 创建Intent
MediaStore.ACTION_IMAGE_CAPTURE
:通过已有的相机应用拍照。MediaStore.ACTION_VIDEO_CAPTURE
:通过已有的相机应用录像。
- 发送Intent:调用
startActivityForResult()
。 - 接收拍照或录像的结果:在
onActivityResult()
方法中接收拍照或录像的结果。
(1) 拍照intent
拍照Intent可以包含如下额外信息:
MediaStore.EXTRA_OUTPUT
:设置照片保存的路径(包含文件名),值为一个Uri对象。此项设置是可选的,但是强烈建议进行设置。如果不设置,则照片会以默认的名字保存在默认路径,通过调用onActivityResult()
方法中参数intent的getData()
方法,可以获得照片的保存路径(uri)。
以下为示例代码,其中getOutputMediaFileUri(...)
方法的实现见后面的存储媒体文件部分:
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private Uri fileUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// create Intent to take a picture and return control to the calling application
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
// start the image capture Intent
startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
(2) 录像intent
录像Intent可以包含如下额外信息:
MediaStore.EXTRA_OUTPUT
:同上,设置视频保存的路径(包含文件名),不再赘述。MediaStore.EXTRA_VIDEO_QUALITY
:设置视频的质量。取值从0到1,1代表最高的视频质量(和最大的文件尺寸)。MediaStore.EXTRA_DURATION_LIMIT
:限制视频的时长,以秒为单位。MediaStore.EXTRA_SIZE_LIMIT
:限制视频文件的大小,以字节为单位。
以下为示例代码,其中getOutputMediaFileUri(...)
方法的实现见后面的存储媒体文件部分:
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
private Uri fileUri;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//create new Intent
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO); // create a file to save the video
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1); // set the video image quality to high
// start the Video Capture Intent
startActivityForResult(intent, CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE);
}
(3) 接收拍照或录像的结果
为了接收拍照或录像的结果,你需要覆写Acitivity的onActivityResult(...)
方法,如下例所示:
private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
private static final int CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE = 200;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// Image captured and saved to fileUri specified in the Intent
Toast.makeText(this, "Image saved to:\n" + data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
// User cancelled the image capture
} else {
// Image capture failed, advise user
}
}
if (requestCode == CAPTURE_VIDEO_ACTIVITY_REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// Video captured and saved to fileUri specified in the Intent
Toast.makeText(this, "Video saved to:\n" + data.getData(), Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_CANCELED) {
// User cancelled the video capture
} else {
// Video capture failed, advise user
}
}
}
4 直接操作相机设备来拍照和录像
如果你想要定义自己的拍照和录像界面,或者需要更高级一些的功能,那么Intent方式就不能满足要求了——你需要直接操作相机设备来达到目的。
注意:下面的讲述以过时的
Camera
中的API为例。但如果你是开发一个新的app,不考虑向下兼容性,那么建议使用android.hardware.camera2
中的新API(仅支持API level 21以上,即Android 5.0以上的设备)。
步骤:
- 检测和访问相机:使用代码检测设备上是否有相机设备,如果有则打开相机设备。
- 创建预览视图:创建一个类用于展示相机预览画面,它需要继承
SurfaceView
类并实现SurfaceHolder
接口。 - 创建布局:创建一个布局以容纳预览视图以及用户操作界面。
- 设置画面采集的开关:为用户操作界面中的按钮设置监听,当点击按钮时就开始或停止采集画面。
- 画面采集与保存:采集静态图像或视频,并保存。
- 释放相机资源:在使用完相机资源之后,必须将其释放。
注意:在使用完相机资源之后一定要记得使用
Camera.release()
将其释放,否则之后的任何打开相机操作都将失败(无论是其他app还是当前app),这可能会造成错误和app闪退。
侦测相机设备
在使用相机之前,首先要检测当前设备上是否有相机设备:
/** 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 Level 9)以上,你可以使用Camera.getNumberOfCameras()
来获取当前设备上的相机数量。
访问相机
通过Camera.open()
来获取当前设备上主相机(一般就是后置相机)的实例,如下面代码所示。记得要捕获可能抛出的异常:
/** 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
}
在Android 2.3(API Level 9)以上的设备上,你可以使用Camera.open(int id)
来获得指定的相机。
获取相机的更多信息
在获得了相机实例之后,你可以通过调用其Camera.getParameters()
方法来获得此相机的更多信息。在API Level 9及以上的设备上,还可以使用Camera.getCameraInfo()
来查看此相机是前置相机还是后置相机,以及相机画面的方向。
创建预览视图
下面的代码说明了如何创建一个类用于展示相机预览画面。这个类是SurfaceView
的子类,因而可以实时显示来自相机的画面数据;实现了SurfaceHolder.Callback
接口,因而可以监听surface的创建和销毁。
/** 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());
}
}
}
如果你想使用setPreviewSize(...)
设置相机预览数据的宽高尺寸,那么就按照上面注释中的说明,在surfaceChanged(...)
方法中进行设置。但是要注意,相机预览的宽高尺寸必须是getSupportedPreviewSizes(...)
方法的返回值中有的,不能随意设置。
将预览视图放到布局中
下面的示例代码创建了一个简单的Acitivity布局,用来容纳预览视图。其中的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>
在绝大多数设备上,相机预览画面都是横屏(landscape)的,因此为了简单一点,我们也将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 Level 8)及以上,可以使用
setDisplayOrientation()
来改变相机预览画面的方向。但是,如果相机预览正在进行的话,需要先停止预览(调用Camera.stopPreview()
),然后改变相机预览画面的方向,最后再重新开始相机预览(调用Camera.startPreview()
)。
下面的代码展示了如何将预览视图()添加到Activity的布局中:
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()
来拍摄一张照片:
// 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) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
其中mPicture
是一个回调,其代码如下:
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: " + e.getMessage());
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());
}
}
};
录像
(1) 概述
录像使用的是MediaRecorder
。除了Camera.open()
和Camera.release()
之外,你还需要适时的调用Camera.lock()
和Camera.unlock()
,这样MediaRecorder
才能成功地访问到相机设备。
注意:从Android 4.0(API level 14)开始,
Camera.lock()
和Camera.unlock()
会被自动调用,不再需要你去手动调用。
录像有严格的步骤和顺序,如下所示:
1 打开相机
调用Camera.open()
来获得一个相机实例。
2 连接到预览视图
调用Camera.setPreviewDisplay()
以使Camera和SurfaceView建立关联。
3 开始相机预览
调用Camera.startPreview()
。
4 开始录像
下面的步骤必须严格按照顺来执行:
a.解锁相机:调用Camera.unlock()
。
b.配置MediaRecorder:按顺序调用MediaRecorder的下列方法。
1. setCamera()
2. setAudioSource():设置音频信号源, 使用MediaRecorder.AudioSource.CAMCORDER.
3. setVideoSource():设置视频信号源,使用MediaRecorder.VideoSource.CAMERA.
4. 设置视频输出格式与编码:
从Android 2.2(API Level 8)开始,直接调用MediaRecorder.setProfile(CamcorderProfile.get(...))即可。
对于Android 2.2以前的版本,调用setOutputFormat()、setAudioEncoder()和setVideoEncoder()来进行设置。
5. setOutputFile():设置输出文件
6. setPreviewDisplay():注意这是MediaRecorder的setPreviewDisplay()方法。
注意:必须按顺序调用以上方法,否则可能会出错。
c.准备MediaRecorder:调用MediaRecorder.prepare()
使上面的配置生效。
d.启动MediaRecorder:调用MediaRecorder.start()
。
5 停止录像
按照顺序调用如下方法:
a.停止MediaRecorder:调用MediaRecorder.stop()
。
b.重置MediaRecorder:调用MediaRecorder.reset()
,此操作是可选的。
c.释放MediaRecorder:调用MediaRecorder.release()
。
d.锁定相机:调用Camera.lock()
。
6 停止相机预览
调用Camera.stopPreview()
。
7 释放相机资源
调用Camera.release()
。
(2) 配置MediaRecorder
private boolean prepareVideoRecorder(){
mCamera = getCameraInstance();
mMediaRecorder = new MediaRecorder();
// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
// Step 4: Set output file
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// Step 5: Set the preview output
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.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;
}
除了上面提到的之外,MediaRecorder还有以下设置录像参数的方法:
setVideoEncodingBitRate()
setVideoSize()
setVideoFrameRate()
setAudioEncodingBitRate()
setAudioChannels()
setAudioSamplingRate()
(3) 启动和停止MediaRecorder
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
mMediaRecorder.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
mMediaRecorder.start();
// inform the user that recording has started
setCaptureButtonText("Stop");
isRecording = true;
} else {
// prepare didn't work
releaseMediaRecorder();
// inform user
}
}
}
}
);
释放相机资源
相机是被设备上众多app共享的资源,因此当不使用的时候一定要记得调用Camera.release()
将其释放,否则之后的任何打开相机操作都将失败(无论是其他app还是当前app),这可能会造成错误和app闪退。
public class CameraActivity extends Activity {
private Camera mCamera;
private SurfaceView mPreview;
private MediaRecorder mMediaRecorder;
...
@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 (mMediaRecorder != null) {
mMediaRecorder.reset(); // clear recorder configuration
mMediaRecorder.release(); // release the recorder object
mMediaRecorder = null;
mCamera.lock(); // lock camera for later use
}
}
private void releaseCamera(){
if (mCamera != null){
mCamera.release(); // release the camera for other applications
mCamera = null;
}
}
}
5 存储媒体文件
为了节省内部存储空间,诸如图片、视频之类的媒体文件最好是存储在手机的外部存储(sd卡)中。虽然存储在sd卡的任何位置都是可以的,但是建议使用如下两种标准的存储目录:
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
——这个方法会返回一个用于存储图片和视频的、标准的、共享的目录。所谓共享就是说,其他app可以轻易的发现、读取、修改和删除此目录下的文件。当你的app被用户卸载时,此目录下的文件并不会被删除。为了避免混乱,建议在此目录下创建一个子目录来存放你app中的图片和视频。Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
——此目录与你的app相关联,当你的app被用户卸载时,此目录中的文件也会被删除。此外,其他app也可以读取、修改和删除此目录下的文件。
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;
}
6 相机特性(Camera Features)
Android支持众多的相机特性,比如图片格式、闪光灯模式、对焦设置等等。下面我们将列出常用的相机特性,并简要的讨论如何使用它们。大部分相机特性都可以通过Camera.Parameters
来设置和使用,但也有几个重要的相机特性需要更多的操作,它们是:测光和对焦区域、面部侦测、时间推移视频。
关于如何使用由Camera.Parameters
控制的相机特性,参考下面的使用相机特性
章节。
表格1. 常用的相机特性(按API Level排序)
Feature | API Level | Description |
---|---|---|
Face Detection | 14 | 侦测图像中的人脸,并将其作为对焦、测光、白平衡的依据 |
Metering Areas | 14 | 在图像中指定一个或多个区域,用来计算白平衡 |
Focus Areas | 14 | 在图像中指定一个或多个区域,用来对焦 |
White Balance Lock | 14 | 停止或开始自动白平衡修正 |
Exposure Lock | 14 | 停止或开始自动曝光修正 |
Video Snapshot | 14 | 在录像时拍一张照片(frame grab) |
Time Lapse Video | 11 | 时间推移视频 |
Multiple Cameras | 9 | 支持设备上的多个相机 |
Focus Distance | 9 | 报告被摄物体与相机之间的距离 |
Zoom | 8 | 变焦(图像放大) |
Exposure Compensation | 8 | 增、减闪光灯曝光级别 |
GPS Data | 5 | 包含或忽略图片的地理位置信息 |
White Balance | 5 | 设置白平衡模式,将影响图片的色值(color values) |
Focus Mode | 5 | 设置对焦模式,比如automatic(自动对焦), fixed(固定焦距), macro , infinity(无限远对焦) |
Scene Mode | 5 | 设置场景模式,如夜晚、沙滩、烛光 |
JPEG Quality | 5 | 设置图片质量 |
Flash Mode | 5 | 设置闪光灯模式,如开启、关闭、自动 |
Color Effects | 5 | 为图片加上一种色调(color effect) |
Anti-Banding | 5 | 减轻JPEG图像中颜色过渡时出现的带状效果 |
Picture Format | 1 | 设置图片格式 |
Picture Size | 1 | 设置图片的尺寸(宽、高) |
注意,以上相机特性并非在所有的设备上都可用(取决于硬件差异和软件实现)。
检查相机特性是否可用
首先需要明白的是,并非所有设备都支持所有的相机特性。此外,不同设备对某一特性支持的程度也不同。
下面代码以对焦为例,展示了如何获取Camera.Parameters
对象,以及如何确定当前设备是否支持自动对焦:
// get Camera parameters
Camera.Parameters params = mCamera.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就会阻止你的app被安装到不支持这些特性的设备上。
使用相机特性
以自动对焦为例:
// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
上面的做法适用于几乎所有相机特性。此外,大部分的参数都可以在任何时候被改变。
测光和对焦区域(Metering and focus areas)
有时候自动对焦和测光并不能达到你想要的效果,这时不妨尝试手动指定测光和对焦区域。
下面代码展示了如何为相机指定两个测光区域:
// Create an instance of Camera
mCamera = getCameraInstance();
// set Camera parameters
Camera.Parameters params = mCamera.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);
}
mCamera.setParameters(params);
Camera.Area
的构造方法有两个参数,一个是Rect,用于指定区域,一个是权重,用于指定该区域的重要程度。
Camera.Area
的Rect参数描述了一个映射到2000*2000的单元网格中的矩形。坐标-1000,-1000
代表相机图像的左上角,坐标1000,1000
代表相机图像的右下角,如下图所示。
坐标系的边界始终与相机预览图像的边界对应,不会因为变焦而缩小或者扩大。同样,使用
Camera.setDisplayOrientation()
对相机预览图像进行旋转也不会影响此坐标系。
面部侦测
对于含有人像的照片,人的面部通常是最为重要的部分,应该被作为对焦和白平衡的依据。 Android 4.0(API Level 14)提供了对面部侦测的支持。
注意:当面部侦测运行时,
setWhiteBalance(String)
,setFocusAreas(List)
和setMeteringAreas(List)
都无效。
要在app中使用面部侦测需要遵循以下步骤:
- 检测当前设备是否支持面部侦测
- 为Camera对象设置面部侦测的监听器
- 在相机预览开始之后启动面部侦测
并非所有设备都支持面部侦测,你可以通过调用getMaxNumDetectedFaces()
来确定当前设备是否支持面部侦测。
为了在侦测到面部时得到通知,你需要创建一个Camera.FaceDetectionListener
对象,并将它设置给Camera对象:
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() );
}
}
}
mCamera.setFaceDetectionListener(new MyFaceDetectionListener());
检测设备是否支持面部侦测并启动面部侦测:
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();
}
}
在相机预览开始之后启动面部侦测,如下面代码所示:
...
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 (mHolder.getSurface() == null){
// preview surface does not exist
Log.d(TAG, "mHolder.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(mHolder);
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());
}
}
...
时间推移视频(Time lapse video)
时间推移视频,其实就是延时摄影视频,即每隔一段时间拍摄一张照片,然后用这些照片生成一个视频。
时间推移视频也是通过MediaRecorder
来录制的。要录制一个时间推移视频,你需要像上面的录像章节那样去配置MediaRecorder
、设置一个较低的帧率并设置视频的质量,就像下面这样:
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));
...
// Step 5.5: Set the video capture rate to a low number
mMediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds
配置完MediaRecorder之后,你就可以开始录制一个时间推移视频了,就像录制一个普通视频那样。