Android Camera详解

本文译自官方文档:https://developer.android.com/guide/topics/media/camera.html

Android框架层包含了对多种相机和相机特性的支持,可以让你在你的应用中拍照或录像。本文档主要讨论如何快速、简单的进行拍照和录像,同时也对如何开发复杂一些的相机应用做了简要介绍。

1 基础

Android框架层支持通过android.hardware.camera2API 或 camera Intent来拍照和录像,以下是相关的类:

android.hardware.camera2

这个包提供了控制相机设备的主要API。

Camera

已过时的控制相机设备的API。

SurfaceView

这个类用来向用户展示实时的相机预览(live camera preview)。

MediaRecorder

这个类用来录像。

Intent

不需要直接操作相机设备,通过MediaStore.ACTION_IMAGE_CAPTUREMediaStore.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来调起一个已有的相机应用。有如下几步:

  1. 创建Intent
    • MediaStore.ACTION_IMAGE_CAPTURE:通过已有的相机应用拍照。
    • MediaStore.ACTION_VIDEO_CAPTURE:通过已有的相机应用录像。
  2. 发送Intent:调用startActivityForResult()
  3. 接收拍照或录像的结果:在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之后,你就可以开始录制一个时间推移视频了,就像录制一个普通视频那样。

发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/78517644