Android API 指南 -- Camera API

3,505 阅读29分钟
原文链接: blog.csdn.net

Camera API

原文链接:developer.android.google.cn/guide/topic…

Android框架支持各种相机和设备上可用的相机功能,它们可以让你在应用程序中捕获图片、音频。本文讨论一种快捷、简单的方式来捕获图片和音频以及概述一种为用户创建自定义相机体验的高级方法。

提示:本文所描述的Camera类已弃用。我们推荐你使用更新的camera2类,camera2能在Android5.0(API 21)或以上版本正常运行。在我们的博客上阅读更多关于camera2的信息。

考虑事项(Considerations)

在Android设备上让你的应用启用相机之前,你应该考虑一些问题:关于你的应用要如何使用这些硬件功能。

  • 相机需求 - 是否相机的使用对于你的应用程序非常重要,以至于你不想让应用程序安装在没有相机的设备上?如果是,你应该在清单文件中声明相机需求。

  • 快速获取图片或自定义相机 - 你的应用将如何使用相机?你是否只是对快速捕获图片或视频剪辑感兴趣,抑或你想让你的应用提供一种更新颖的方式使用相机?为了获取快速捕获图片或剪辑,你可以考虑使用已存在的相机应用。如果是为了开发一个自定义相机功能,参看本文“Building a Camera App”部分。

  • 存储 - 在你的应用程序生成的图片或音频是否只是对你的应用可见,还是说可以共享给其它应用,比如Gallery、其他媒体和社交应用使用?你是否想让图片和音频在你的应用被卸载后仍然可用?查看“Saving Media Files”小节来看看如何实现这个选项。

基础(The basics)

Android框架支持通过android.hardware.camera2 API或相机Intent捕获图片和音频。这里是相关的类:

android.hardware.camera2

这个包是控制设备相机的主要API。当你要编译一个相机应用程序时,可以用它来拍照或录制视频。

Camera

这个类是旧版本控制设备相机的API,现在已弃用。

SurfaceView

这个类用于向用户呈现一个实时预览相机。

MediaRecorder

这个类用于从相机中录制视频。

Intent

意图动作类型 - MediaStore.ACTION_IMAGE_CAPTURE 或r MediaStore.ACTION_VIDEO_CAPTURE 可以在不用直接使用Camera对象的情况下捕获图片或视频。

清单文件声明(Manifest declarations)

在你的应用程序上开始使用Camera API开发之前,你应该确保应用程序的清单文件有允许使用相机硬件和其他相关功能的声明。

  • 相机权限 - 你的应用程序必须有使用设备相机的权限。
<uses-permission android:name="android.permission.CAMERA" />

提示:如果你是通过已存在的相机app来使用相机,你的应用程序就不需要声明这个权限。

  • 相机功能 - 你的应用程序必须声明相机功能的使用,例如:
<uses-feature android:name="android.hardware.camera" />

获取相机功能列表,参看清单文件Features Reference

清单文件添加相机功能的声明可以让Google Play防止让你的应用安装在不包含相机或不支持指定相机功能的设备上。获取更多关于使用Google Play过滤并使用基础功能的信息,参看Google Play and Feature-Based Filtering.

如果你的应用程序通过正确的操作使用相机或相机功能,但却没有获取到它,你应该在清单文件中通过包含的android:required 属性来指定,将其设置为false。

<uses-feature android:name="android.hardware.camera" android:required="false" />
  • 存储权限 - 如果你的应用程序保存图片或音频到设备的外部存储(SD Card),你必须在清单文件中指定这个权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 音频录制权限 - 通过视频的捕获来录制音频,应用程序必须获取音频捕获权限。
<uses-permission android:name="android.permission.RECORD_AUDIO" />
  • 定位权限 - 如果你的应用程序使用GPS的位置信息标记图片,你必须请求ACCESS_FINE_LOCATION权限。注意,如果你的app是针对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" />

使用已存在的相机app(Using existing camera apps)

一种在应用程序中不需要大量额外的代码就能开启拍照或录制视频的快捷方式就是使用Intent来获取已存在的相机应用。更多细节描述参看training lessons的 Taking Photos SimplyRecording Videos Simply.

编译相机APP(Building a camera app)

一些开发者可能需要自定义相机的用户界面或提供特殊的功能。编写你自己的拍照代码可以为你的用户提供一种更吸引人的体验。

注意:下面的指南是比较旧且已弃用的Camera API。推荐使用更新的API android.hardware.camera2 来实现更新颖、先进的相机应用。

给应用程序创建自定义相机界面的总体步骤如下:

  • 检查和访问相机 - 创建代码来检查相机是否存在和获取访问权限。

  • 创建一个预览类- 创建一个继承SurfaceView并实现SurfaceHolder接口的相机预览类。这个类呈现相机实时获取的图片。

  • 编写一个预览布局 - 一旦有了相机预览类,创建一个包含预览界面和你想要的用户界面控件的视图布局。

  • 给捕获事件设置监听器 - 关联监听器和用于开始捕获图片或视频的界面控件,以便响应用户的动作,如按下按钮事件。

  • 捕获并保存文件 - 创建捕获图片或音频并保存文件的代码。

  • 释放相机 - 在使用完相机之后,你的应用程序必须正确地释放它以便其他应用的使用。

相机硬件是一个共享的资源,所以必须小心管理以便你的应用程序不会与其它可能也需要用到相机的应用程序冲突。接下来的部分讨论如何检查相机硬件,如何请求相机访问权限,如何捕获图片或音频以及当你的应用使用完相机后要如何释放相机资源。

注意:当你的应用使用完相机时,记得调用 Camera.release() 释放相机对象!如果你的应用程序没有正确地释放相机,所有后面访问相机的请求,包括你的应用程序,将会访问失败并且可能导致你或其它应用程序挂掉。

检查相机硬件(Detecting camera hardware)

如果你的应用程序没有特别要求使用清单文件声明照相机,你应该在运行时检查是否相机可用。要实现这个检查,可以如下示例代码使用PackageManager.hasSystemFeature() 方法:

/** 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() 方法检查设备可用的摄像头数量。

访问相机(Accessing cameras)

如果你已经决定在运行你应用程序的设备上要有相机功能,你必须通过获取Camera实例来请求访问权限(除非你使用意图访问已存在的相机应用)。

为了访问主摄像头,可以如下代码所示使用 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
}

注意:当使用 Camera.open()时,要一直检测异常。如果相机正在使用或不存在可能会导致系统down掉你的应用程序,如果没有检测异常的话。

在Android 2.3(API 9)或更高版本的设备上,你可以通过调用 Camera.open(int)访问指定摄像头。如上代码所示就是访问超过一个摄像头设备的第一个摄像头–后置摄像头。

检查相机功能(Checking camera features)

一旦你获取相机的访问权,你就可以使用 Camera.getParameters() 方法检查 Camera.Parameters返回的对象来获取关于相机所支持的功能信息。当使用API级别是 9或更高时,使用Camera.getCameraInfo() 来决定设备的相机是前置还是后置,以及图片的方向。

创建预览类(Creating a preview class)

为了让用户更高效地拍照或拍视频,他们必须能够看到设备的相机所捕获的东西。预览相机类是一个SurfaceView类,它可以显示来自相机的实时图片数据,所以用户可以一帧帧捕获图片或视频。

下面的示例代码展示如何创建一个包含视图布局的基础相机预览类。这个类实现了SurfaceHolder.Callback接口以便捕获视图布局创建或销毁时的回调事件,这些都是实现相机预览所需要做的事。

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

如果你想为预览相机设置指定的尺寸,可以如上述代码所示在surfaceChanged() 方法中设置。当设置预览视图尺寸时,你必须使用来自 getSupportedPreviewSizes()的值。不要在 setPreviewSize() 方法中设置任意的值。

注意:随着Android 7.0(API 24)或更高版本介绍的多窗口特性,即使你调用了 setDisplayOrientation(),你无法再跟activity一样设置预览屏幕宽高比。根据窗口尺寸和屏幕宽高比,你可能得使用信箱布局将普遍的预览相机适配到纵向布局或水平布局上。

在布局中放置预览视图(Placing preview in a layout)

一个相机预览类,如前面示例所示,必须随同用户拍照或拍视频的界面控件放置在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>

提示:预览相机不一定要是横向水平模式。从Android2.2(API 8)开始,你就可以使用setDisplayOrientation() 方法设置预览图片的旋转。为了在用户重定向手机时改变预览方向,在你预览类的surfaceChanged() 方法中,先通过 Camera.stopPreview() 改变方向来停止预览,然后使用 Camera.startPreview()再次开始预览。

将activity中的相机预览视图添加到上述示例的FrameLayout节点中。你activity中的相机也必须确保在相机暂停或停掉时释放资源。以下的示例展示如何修改Cameraactivity来添加如上文所创建的预览类中。

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() 方法,引用上文的getCameraInstance() 方法。

捕获图片(Capturing pictures)

一旦已经编译好预览类和展示它的视图布局,你就可以用你的应用准备开始捕获图片。在应用程序的代码中,你必须给用户界面的控件设置监听来响应用户的拍照事件。

使用 Camera.takePicture() 方法来获取一个图片。该方法携带三个来自相机的参数。为了接受JPEG格式数据,你必须实现 Camera.PictureCallback 接口来获取图片数据并将图片数据写入一个文件中。下面的代码展示使用 Camera.PictureCallback 接口保存相机所获取图片的基本实现。

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());
        }
    }
};

通过调用 Camera.takePicture() 方法触发图片的捕获。下面示例代码展示如何用一个按钮的View.OnClickListener调用这个方法。

// 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变量为上文示例代码中的引用。

注意:当你的应用完成对相机的使用时,记得通过调用 Camera.release() 释放相机对象。获取关于如何释放相机的信息,参看“释放相机(Releasing the camera)”小节。

捕获视频(Capturing videos)

使用Android框架捕获视频需要注意Camera对象与MediaRecorder类的协调管理。当使用Camera录制视频时,你必须管理 Camera.lock() 和 Camera.unlock() 的调用来让 MediaRecorder 访问相机硬件,还有 Camera.open() 和 Camera.release() 的调用。

提示:从Android4.0(API 14)开始, 系统主动帮你管理Camera.lock() 和Camera.unlock() 的调用。

捕获视频需要一个非常特定的调用顺序,这点不像拍照。你必须跟着指定的顺序执行来使应用程序成功地准备和捕获视频,细节如下:

1.打开相机 - 使用 Camera.open() 获取相机对象实例。

2.连接预览- 通过使用 Camera.setPreviewDisplay()连接一SurfaceView到相机上,然后准备实时摄像图片的预览。

3.开始预览 - 调用 Camera.startPreview() 开始显示实时摄像图片。

4.开始录制视频 - 如下步骤必须完成才能成功录制视频。

  • a.解锁相机 - 通过调用Camera.unlock()解锁相机以供给MediaRecorder使用。

  • b.配置MediaRecorder - 按以下顺序调用MediaRecorder的方法。获取更多信息,参看MediaRecorder的引用文档。

    • 1.setCamera() - 使用应用当前的Camera实例来设置相机以便捕获视频。

    • 2.setAudioSource() - 使用 MediaRecorder.AudioSource.CAMCORDER设置音频资源。

    • 3.setVideoSource()-使用 MediaRecorder.VideoSource.CAMERA设置视频资源。

    • 4.设置视频的输出格式和编码。对于Android 2.2(API Level 8)
      及更高版本,请使用MediaRecorder.setProfile方法,并使用CamcorderProfile.get()获取配置文件实例。对于2.2之前的Android版本,你必须设置视频输出格式和编码参数。

      • i. setOutputFormat() - Set the output format, specify the default setting or MediaRecorder.OutputFormat.MPEG_4.
      • ii.setAudioEncoder() - Set the sound encoding type, specify the default setting or MediaRecorder.AudioEncoder.AMR_NB.
      • iii.setVideoEncoder() - Set the video encoding type, specify the default setting or MediaRecorder.VideoEncoder.MPEG_4_SP.
    • 5.setOutputFile() - 使用下文“保存媒体文件(Saving Media Files)”小节的方法:getOutputMediaFile(MEDIA_TYPE_VIDEO).toString() 设置输出文件。

    • 6.setPreviewDisplay() - 为应用程序指定SurfaceView预览布局节点。使用与上述步骤“2.连接预览”相同的SurfaceView对象。

      注意:你必须按顺序调用这些MediaRecorder的配置方法,否则应用程序将会报错并在录制视频时出现异常错误。

  • c. 准备MediaReocorder - 使用上述所设置的配置并通过调用MediaRecorder.prepare()准备MediaRecorder。

  • d. 开始MediaRecorder - 调用 MediaRecorder.start().开始录制视频。

5.停止录制视频 - 按顺序调用以下方法以便成功完成视频的录制:

  • a. 停止MediaRecorder- 停止录制视频。

  • b. 重置MediaRecorder - 可选,通过调用 MediaRecorder.reset()从录制器中删除配置设置。

  • c. 释放MediaRecorder - 通过调用 MediaRecorder.release()释放MediaRecorder。

  • d. 锁住相机 - 锁住相机以便未来一段时间MediaRecorder可以通过调用Camera.loak()使用它。从Android4.0(API 14)开始,这个调用就不是必要的,除非MediaRecorder.prepare()的调用失败。

6.停止预览- 当activity已经结束对相机的使用时,通过 Camera.stopPreview()停止预览。

7.释放相机 - 通过调用 Camera.release()释放相机以便其它应用程序可以使用它。

Note:可能会出现在没有创建预览相机以及跳过前几个步骤就使用了MediaRecorder的情况。但是,用户一般都是在开始录制之前就能看到预览视图的,所以这个情况不在这里讨论。

Tip:如果你的应用通常用于录制视频,请在开始预览之前将setRecordingHint(boolean)设置为true。这个设置可以帮你减少开始录制所需的时间。

配置MediaRecorder(Configuring MediaRecorder)

当使用MediaRecorder类录制视频时,你必须按指定的顺序实现配置步骤,然后调用MediaRecorder.prepare() 方法检查并实现配置。下面的示例代码展示如何正确的配置和准备MediaRecordder类来录制视频:

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;
}

在Android2.2(API 8)之前,你必须直接设置输出格式和编码格式参数来替代使用 CamcorderProfile.。下面的代码展示这个方法:

// Step 3: Set output format and encoding (for versions prior to API Level 8)
    mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

以下录制视频参数可以设置MediaRecorder的默认参数,不过,你可以根据你的应用来调整这些参数。

  • setVideoEncodingBitRate()

  • setVideoSize()

  • setVideoFrameRate()

  • setAudioEncodingBitRate()

  • setAudioChannels()

  • setAudioSamplingRate()

开始和停止MediaRecorder(Starting and stopping MediaRecorder)

当使用MediaRecorder类开始和停止录制视频时,你必须按指定顺序执行,如下列表。

1.使用Camera.unlock()解锁相机。

2.如上示例所示配置MediaRecorder

3.使用MediaRecorder.start()开始录制

4.录制视频

5.使用MediaRecorder.stop()停止录制

6.使用MediaRecorder.release()释放媒体录制器

7.使用Camera.lock()给相机上锁

下面的示例代码展示如何编写一个按钮并使用相机和MediaRecorder类来正确地开始和停止视频的录制。

Note:当完成视频的录制时,不要释放相机资源,否则预览视图将被停止。

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, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

提示:在上面的示例中, prepareVideoRecorder() 方法引用“配置MediaRecorder(Configuring MediaRecorder)”小节的示例代码。这个方法要注意锁住相机并配置和准备MediaRecorder实例。

释放相机(Releasing the camera)

相机是设备上所有应用程序共享的资源。应用程序可以在获取相机实例后使用相机,当你的应用一旦暂停并停止使用它时,你必须特别注意释放相机对象。如果你的应用没有正确地释放相机,所有后来对相机访问的请求,包括你的应用,将访问失败,并且可能导致应用程序挂掉。

为了释放相机对象实例,使用Camera.release()方法,如下所示:

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;
        }
    }
}

保存媒体文件(Saving media files)

用户创建诸如图片和视频的媒体文件应该保存到设备的外部储存目录上(SD卡)以便节省系统空间,并且要让用户能够访问这些文件。设备上保存媒体文件的目录位置有多种可能。但是,作为一个开发者,你应该考虑的只有两种标准的位置。

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - 这个方法返回标准、共享和值得推荐的位置来存放图片和视频。这个目录是共享的,所以其他应用程序可以很容易的找到,并读取、改变和删除保存在这个位置的文件。如果你的应用被用户卸载,这个保存媒体文件的位置不会被删除。为了避免干扰用户已存在的图片和视频,你应该创建一个子目录来存储你的应用所创建的图片和视频,示例代码如下所示。这个方法在Android 2.2.中可用。获取更早API版本相关的调用,参看“保存共享文件(Saving Shared Files)”的小节。

  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 这个方法返回一个标准位置来保存与你的应用相关联图片和视频。如果你的应用被卸载,保存在这个位置的文件都将被删除。这个位置的文件强的安全性,其他应用都可以读取,更改和删除它们。

下面的示例代码展示如何给媒体文件创建一个File或Uri位置。

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() 替代。获取更多信息,参看“保存共享文件(Saving Shared Files)”小节。

获取更多关于保存文件到Android设备的信息,参看Data Storage

相机功能(Camera features)

Android支持多种相机功能,你可以使用你的相机应用控制这些功能,如图片格式,闪光灯模式,对焦设置等等。这节列出常见的相机功能,并简单概述如何使用它们。大多数相机功能可以访问和通过使用 Camera.Parameters 对象的设置来使用。不过,有几个重要的功能需要比Camera.Parameters.简单设置有更多要求。这些功能如下:

  • 计量和对焦区域

  • 脸部识别

  • 延时摄影

获取关于如何通过 Camera.Parameters使用可控制的功能信息,参看“使用相机功能(Using camera features)”小节。获取更多关于如何通过相机参数对象使用可控制的功能信息,参看下面的API引用文档

表1.按Android API的级别排列常见相机功能。

功能 API 级别 描述
Face Detection 14 Identify human faces within a picture and use them for focus, metering and white balance
Metering Areas 14 Specify one or more areas within an image for calculating white balance
Focus Areas 14 Set one or more areas within an image to use for focus
White Balance Lock 14 Stop or start automatic white balance adjustments
Exposure Lock 14 Stop or start automatic exposure adjustments
Video Snapshot 14 Take a picture while shooting video (frame grab)
Time Lapse Video 11 Record frames with set delays to record a time lapse video
Multiple Cameras 9 Support for more than one camera on a device, including front-facing and back-facing cameras
Focus Distance 9 Reports distances between the camera and objects that appear to be in focus
Zoom 8 Set image magnification
Exposure Compensation 8 Increase or decrease the light exposure level
GPS Data 5 Include or omit geographic location data with the image
White Balance 5 Set the white balance mode, which affects color values in the captured image
Focus Mode 5 Set how the camera focuses on a subject such as automatic, fixed, macro or infinity
Scene Mode 5 Apply a preset mode for specific types of photography situations such as night, beach, snow or candlelight scenes
JPEG Quality 5 Set the compression level for a JPEG image, which increases or decreases image output file quality and size
Flash Mode 5 Turn flash on, off, or use automatic setting
Color Effects 5 Apply a color effect to the captured image such as black and white, sepia tone or negative
Anti-Banding 5 Reduces the effect of banding in color gradients due to JPEG compression
Picture Format 1 Specify the file format for the picture
Picture Size 1 Specify the pixel dimensions of the saved picture

提示:由于每个设备硬件和软件实现的不同,不是所有设备都支持这些功能。获取检查运行你应用程序的设备的可用功能,参看“检查可用功能(Checking feature availability)”。

检查可用功能(Checking feature availability)

当打算在Android设备上使用相机功能时,首先要明白不是所有设备都支持所有的相机功能的。此外,支持特定功能的设备可能支持不同级别或不同选项。因此,当你开发照相机应用程序时,支持什么相机功能,以及什么级别是你的决定过程的一部分。在确认这个决定之后,你应该规划你相机应用的代码,让其检查设备硬件是否支持那些功能,如果功能是不可能用的时候要友好的提示失败。

你可以通过获取相机参数对象实例检查可用的相机功能,并检查相关的方法。下面的代码展示如何获取一个 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会限制你的应用安装在不支持这些功能的设备上。获取应用可以在清单文件声明的相机功能,参看清单文件Features Reference

使用相机功能(Using camera features)

大多数相机功能是可用的,并且可以使用 Camera.Parameters 对象来控制。首先通过获取Camera对象实例,并调用 getParameters() 方法来获取这个对象,然后更改这个返回的参数对象并将其设置到相机对象中,如下代码所示:

// 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);

这个方法可以作用在几乎所有的相机功能上,并且大多数参数可以在你获取Camera对象实例后的任意时刻更改。在应用程序的相机预览中,用户通常可以立即看到参数的变化。在软件方面,参数变化可能需要几个帧才能生效,因为相机硬件要处理新指令,再发送更新的图像数据。

重要:一些相机功能不能任意更改。特别的是,更改相机预览的尺寸或方向首先需要你停止预览,然后更改预览尺寸,再重启预览。从Android 4.0(API 14)开始,预览方向可以在不需要重启预览视图的情况下更改。

其它需要更多代码实现的相机功能,包括:

  • 计量和对焦区域

  • 脸部识别

  • 延时摄影

下面这节概述如何快速实现这些功能。

计量和焦点区域(Metering and focus areas)

在一些拍摄场景,自动对焦和光线的计量可能没有得到预期的效果。从Android4.0(API 14)开始,你的相机应用可以提供另外的控件,让你的应用或用户指定图像中的区域来确定对焦或灯光水平设置,并将这些值传递到相机硬件,用于捕获图像或视频.。

计量的区域和对焦工作非常类似其它的相机功能,都是通过Camera.Parameters 对象来控制的。下面的示例代码给相机实例设置两处灯光计量区域:

// 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代表相机图片右下角,如下图文所示:

这里写图片描述
图1.红线表示在相机预览中为Camera.Area指定坐标系统。蓝色方框展示使用值为333,333,667,667的Rect的相机区域位置和形状。

该坐标系的边界总是与相机预览中可见的图像的外边缘相一致,且不会随缩放级别而缩小或扩展。同样,使用 Camera.setDisplayOrientation() 旋转预览的图片,不会重新映射坐标系统。

脸部识别(Face detection)

图片包含人时,脸部常常是图片最重要的部分,当捕获一个图片时,这些脸部应该被用来决定图片的焦点和白平衡。Android 4.0(API 14)框架提供APIs来识别脸部并使用脸部识别技术计算图片的设置。

Note:当脸部识别功能正在运行时, setWhiteBalance(String), setFocusAreas(List<Camera.Area>) 和setMeteringAreas(List<Camera.Area>) 是没有作用的。

在你的相机应用中使用脸部识别功能,大概需要的几个步骤:

  • 检测设备是否支持脸部识别功能

  • 创建脸部识别监听器

  • 添加脸部识别监听器到你的相机对象上

  • 在开启预览后(以及重启预览视图后)开始脸部识别

脸部识别功能不是所有设备都支持的。你可以通过调用getMaxNumDetectedFaces()检测设备是否支持该功能。在startFaceDetection() 的示例方法中展示了如何检查该功能事是否可用。

你的相机应用必须设置一个脸部识别事件监听器来通知和响应脸部的识别。为了做到这一点,你必须创建一个实现 Camera.FaceDetectionListener 接口的监听类,代码如下:

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对象中,代码如下:

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();
    }
}

如果你使用“创建预览类”小节所创建的预览类,你需要在这个预览类中将 startFaceDetection() 方法添加到surfaceCreated() 和 surfaceChanged() 方法中,代码如下:

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());
    }
}

Note:记得在调用 startPreview()后调用 startFaceDetection() 方法。不要试图在你的相机应用主Activity的onCreate方法中开启脸部处理,因为这个时刻预览还是不可用的。

延时视频

延时视频允许用户结合几秒或几分钟拍摄的图片来创建视频剪辑。这个功能使用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一个更大的配置程序来做。获取完整的配置代码示例,参看本文“配置MediaRecorder(Configuraing MediaRecorder)”小节。一旦完成配置,就可以开始视频的录制。获取更多关于配置和运行MediaRecorder的信息,参看本文“捕获视频(Capturing vidoes)”小节。