Android Camera

原文地址:
http://developer.android.com/intl/zh-cn/guide/topics/media/camera.html#considerations

Android框架提供了对各种相机以及设备提供的相机功能的支持,让你可以在你的应用中获取照片和视频。本文档讨论了一种简单快速的获取照片和视频的方法,并描述了一种可以让你为你的客户创建自定义相机的先进方法。


Considerations

在允许你的应用程序使用相机之前,你应该考虑你的应用程序要用相机来干什么:

  • Camera Requirement  相机对你的应用程序很重要吗?你不希望你的应用程序安装在一个没有相机的设备上吗?如果是,那你应该在manifest中声明本应用需要相机
  • Quick Picture or Customized Camera  你的应用程序如何使用相机?你是想提供快速拍照功能和视频剪辑功能呢,还是要提供一个相机的新用法呢?如果是前者,你可以考虑使用现有的相机程序(看下面的Using Existing Camera Apps);如果是后者,你可以考虑开发一个相机应用(看下面的Building a Camera App)
  • Storage  应用程序生成的照片和视频是私有的(仅限于本应用程序使用)还是共享的(其他应用程序(如Gallery等多媒体应用和其他社交应用)也可以使用)?甚至在你的应用程序被卸载后,你是否希望照片和视频依旧可用?你可以参考下面的Saving Media Files完成这些功能

The Basics

Android框架通过camera2 API和camera Intent支持相机和视频功能。以下是相关介绍:

android.hardware.camera2
这个package是操作相机的主要API。当你开发相机应用时可以用它来获取照片和视频。

Camera
这个类已经过期。
SurfaceView
这个类是用来向用户提供实时预览的。

MediaRecorder
这个类可以从相机中记录视频。
Intent
你可以通过MediaStore.ACTION_IMAGE_CAPTURE和MediaStore.ACTION_VIDEO_CAPTURE获取照片和视频而不需要直接使用相机对象。

Manifest Declarations

在使用Camera API开发应用程序前,你应该确保在manifest中明确声明相机和其他相关功能的使用权限。

  • Camera Permission 应用程序必须请求这个权限以使用相机
       <uses-permission android:name="android.permission.CAMERA" />

              Note:如果你是通过Intent使用相机,那可以不请求该权限

  • Camera Features 应用程序必须声明使用相机功能,例如
    <uses-feature android:name="android.hardware.camera" />
    所有的相机功能,请看下面的Camera Feature。

    在manifest中声明相机功能,Google Play可以避免你的应用程序被安装在没有相机的设备以及那些不支持你所请求的相机功能的设备。更多关于Google Play的Feature-Based Filtering信息,请看这里(http://developer.android.com/intl/zh-cn/guide/topics/manifest/uses-feature-element.html#market-feature-filtering)
    如果你的应用程序可以使用相机或相机功能做相关的操作,但这些操作并不是必须的,你可以在manifest声明中加入android:requred 属性,并且设置为false:
    <uses-feature android:name="android.hardware.camera" android:required="false" />

  • Storage Permission 如果你的应用程序把照片和视频保存到外部储存中(SD Card)中,那你必须在manifest中声明以下权限:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  • Audio Recording Permission 为了在录制视频的时候也记录音频,你必须在manifest中请求以下权限:
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

  • Location Permission 如果你的照片需要标记上GPS位置信息,你必须在manifest中声明以下权限:
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    更多关于获取用户位置的内容请看这里(http://developer.android.com/guide/topics/location/strategies.html)

Using Existing Camera Apps

一种无需额外的代码就可以让你的应用程序可以拍照和拍视频的方法是使用Intent去启动Android设备现在的相机应用。Camera Intent通过Android自带的相机应用发出捕获照片或剪辑视频的请求,然后把控制交还你的应用程序。这一部分将会让你看到如何使用这种技术。

调用Camera Intent需要遵循以下步骤:

1. Compose a Camera Intent 你可以使用以下其中一个来创建你的Intent

  • MediaStore.ACTION_IMAGE_CAPTURE   从现在的相机应用中请求照片
  • MediaStore.ACTION_VIDEO_CAPTURE   从现在的相机应用中请求视频
2. Start the Camera Intent 使用startActivityForResult()方法执行刚拿到的Camera Intent。在执行方法后,设备自带的相机应用就会被启动,用户可以拍照或拍视频。
3. Receive the Intent Result 当用户拍完照片或视频(或者他们取消了操作),系统都会回调你应用程序中的onActivityResult()方法,你可以在这里取得相机设备返回的数据。

Image capture intent

使用Camera Intent可以使你的应用程序以最少的代码快速的获取照片。获取照片的Intent还可以包含以下额外信息:
  • MediaStore.EXTRA_OUTPUT 当你需要指定照片的保存位置时,你可以使用这个选项,你需要一个Uri对象指定保存照片的路径和照片的名字。这个选项是可选的,但我们强烈建议你使用。如果你不指定这个选项,照片会以默认的名字保存到默认的路径下,你可以通过返回的Intent的getData()方法取得它。
              下面的例子展示如何构建一个捕获照片的Intent,并执行它。例子用的getOutputMediaFileUri()用到了Saving Media Files中的示例代码。
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);
}
              当startActivityForResult()方法被执行时,用户会看到一个相机应用的界面。当用户完成了拍照的操作(或者取消了操作),界面会回到你的应用程序,你必须实现onActivityResult()方法,从该方法中取出Intent返回的结果,然后继续执行你的其他操作。更多关于如何取出返回数据的信息,请看下面的 Receiving camera intent result .

Video capture intent

使用Camera Intent可以使你的应用程序以最少的代码快速的获取视频。获取视频的Intent还可以包含以下额外信息:
  • MediaStore.EXTRA_OUTPUT 当你需要指定视频的保存位置时,你可以使用这个选项,你需要一个Uri对象指定保存视频的路径和照片的名字。这个选项是可选的,但我们强烈建议你使用。如果你不指定这个选项,视频会以默认的名字保存到默认的路径下,你可以通过返回的Intent的getData()方法取得它。
  • MediaStore.EXTRA_VIDEO_QUALITY 这个值可以是0,这时视频质量是最差的,并且视频文件所占内存最小;也可以是1,这时视频质量最好,但视频文件所占内存最大。
  • MediaStore.EXTRA_DURATION_LIMIT  可以通过设定这个值限制视频的长度(单位是秒)
  • MediaStore.EXTRA_SIZE_LIMIT    可以通过设定这个值限制视频文件的大小(单位是byte)
          下面的例子展示如何构建一个捕获视频的Intent,并执行它。例子用的getOutputMediaFileUri()用到了Saving Media Files中的示例代码。
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);
}

当startActivityForResult()方法被执行时,用户会看到一个相机应用的界面。当用户完成了拍视频的操作(或者取消了操作),界面会回到你的应用程序,你必须实现onActivityResult()方法,从该方法中取出Intent返回的结果,然后继续执行你的其他操作。更多关于如何取出返回数据的信息,请看下面的。

Receiving camera intent result

一旦你构建并执行了Camera Intent,你的应用程序必须要可以接收Intent返回的结果。本节将会让你看到如何怎么拦截Camera Intent的回调,然后你的应用程序就可以对照片或视频进行后面的操作。

为了接收Camera Intent返回的结果,你的必须在执行Intent的Activity中重载onActivityResult()方法。下面的示例会为你展示如何在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
        }
    }
}

当你拿到成功的结果时,你的应用程序就可以访问指定的位置去使用照片或视频。

Building a Camera App

有时候开发者可能需要一个自定义的或者是提供特殊功能的相机界面。创建一个自定义的camera activity比使用Intent需要更多的代码,但它会让你的应用程序看上去更吸引人。

Note:下面的指导是基于旧的,过时的Camera API的,对于新的相机应用,我们推荐使用新的android.hardware.camera2 API。
下面是创建自定义相机界面的常规步骤:
  • Detect and Access Camera 检查相机是否存在,并请求访问相机
  • Create a Preview Class 创建一个预览相机内容的类,这个类扩展了SurfaceView和实现了SurfaceHolder接口。这个类可以生成相机当前拍摄内容的预览。
  • Build a Preview Layout 当你有了上面的预览类后,你就可以创建视图层,视图层应该包括预览界面和你希望有的操作控件。
  • Setup Listeners for Capture 连接监听器,响应用户操作,例如按下按钮捕获照片或视频。
  • Capture and Save Files 实现捕获照片和视频的代码,并保存输出。
  • Release the Camera 使用完相机后,你必须释放它,以便其他应用程序可以使用相机。
相机硬件是一个被小心管理的共享资源,所以你的应用程序不会和其他也想使要相机的应用程序发生冲突。接下来的部分我们将讨论如何检测相机硬件,如何请求访问相机,如何捕获照片或视频,如何释放相机。

Caution:当你的应用程序使用完相机后,记得调用Camera.release()释放Camera对象。如果你没有释放相机,所有后来试图访问相机的应用程序,包括你的应用程序,都会失败,甚至会引起程序的奔溃。

Detecting camera hardware

如果你没在manifest中声明需要使用Camera,那你应该在运行时检测相机是否可用。你可以使用PackageManager.hasSystemFeature()方法完成检测。示例如下:
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()方法取得设备上可用的摄像头的数量。

Accessing cameras

如果你已经知道程序所运行的设备上有可用的摄像头,那么你可以通过取得Camera的一个实例去访问它。(除非你是用Intent访问相机)
为了取得主摄像头,你可以使用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
}
Caution:当使用Camera.open()时需要经常的检测异常。当camera正在被使用或者不存在时,如果你没有检测异常,系统会导致你的程序崩溃。

在Android 2.3(API Level 9)及以上版本的设备中,你可以通过Camera.open(int)访问指定的摄像头。当设备中有多个摄像头时,上面示例中的代码会访问第一个后置摄像头。

Checking camera features

一旦取得对Camera的访问,你可以调用Camera.getParameters()方法并查看该方法返回的Camera.Parameters对象,取得该相机能力的更多信息。在Android 2.3(API Level 9)及以上版本的设备中,可以调用Camera.getCameraInfo()判断当前访问的摄像头是前置摄像头还是后置摄像头,以及照片的方向信息。

Creating a preview class

当用户拍照或拍视频的时候,他们理所应当的要看到当前摄像头拍摄的画面。预览类是一个SurfaceView,它可以展示摄像头传过来的实时图像数据,这时候用户可以构图然后捕获照片或视频。

下面的示例代码为你展示如何构建一个包含一个View层的预览类。这个预览类实现了SurfaceHolder.Callback接口,这样它就可以捕获创建和删除View时的回调事件,这是预览的输入所必须的。
/** 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()方法中对应的备注那里修改。设置预览界面大小的时候,你必须用getSupportedPreviewSized()放回的值修改。不要在setPreviewSize()方法中设定随意的值。

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>
在很多设备中,相机预览界面的默认方向是横向的。示例布局中指定了水平(横向)布局并且下面的代码使得应用程序的界面固定为横屏。为了时预览界面的绘制变得简单,你应该在manifest中加入以下代码,使应用程序中的Preview 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>
Note:相机预览界面不一定要是横向的。从Android 2.2(API Level 8)开始,你可以使用setDisplayOrientation()方法旋转预览图片。为了在用户改变手机方向的同时改变预览界面的方向,可以在预览类的surfaceChanged()方法中,首先调用Camera.stopPreview()停止预览界面,改变方向,然后调用Camera.startPreview()重启预览界面。


在相机界面所属的Activity中把预览类加入到上面布局中的FrameLayout。同时,当不在使用相机时,确保在Activity中释放相机。下面的示例将为你展示如何在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);
    }
}
Note:上面示例中的getCameraInstance()方法请看Accessing cameras。

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

下面的示例展示了如何在Button的View.OnClickListener方法中调用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);
        }
    }
);
(这句是我加的:三个参数是 ShutterCallbackRawCallback,PictureCallback,分别是响应快门,raw数据,JPEG数据

Note:上面示例的mPicture是再上一个示例的PictureCallback

Caution:在应用程序拍完照后记得要调用Camera.release()方法释放Camera对象。更多信息请看Releasing the camera。

Capturing videos

使用Android 框架捕获视频要求小心管理Camera对象,并和MediaRecorder类协调工作。除了Camera.open()和Camera.release()方法外,你还得管理Camera.lock()和Camera.unlock()方法,使得MediaRecorder可以访问相机硬件。

Note:从Android 4.0(API level 14)开始,Camera.lock()和Camera.unlock()方法是自动管理的。


不像拍照片,捕获视频需要特别的调用顺序。你必须遵循一个特定的执行顺序,使得你的应用程序可以成功地准备并捕获视频,下面是详细说明:

  • Open Camera调用Camera.open()方法取得camera对象的一个实例
  • Connect Preview 调用Camera.setPreviewDisplay()方法使camera连接上SurfaceView,准备好相机画面的实时预览
  • Start Preview 调用Camera.startPreview()方法开始显示相机实时画面
  • Start Recording Video 为了成功捕获视频,接下来的步骤必须全部完成(分步骤很多,注意层次关系):
         a.Unlock the Camera 调用Camera.unlock()解锁Camera,这样MediaRecorder就可以使用Camera了

         b.Configure 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()方法取得Profile的一个实例。在2.2以下的版本,你必须设置输出格式和编码参数:

                                        i. setOutputFormat() 设置输出格式,可以指定为默认设置或MediaRecorder.OutputFormat.MPEG_4

                                        ii. setAudioEncoder() 设置声音编码类型,可以指定为默认设置或MediaRecorder.AudioEncoder.AMR_NB

                                        iii. setVideoEncoder() 设置视频的编码类型,可以指定为默认设置或MediaRecorder.VideoEncoder.MPEG_4_SP

                   5. setOutputFile() 设置输出文件,你可以使用Saving Media Files小节示例中的getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()方法

                   6.setPreviewDisplay() 在应用程序中指定SurfaceView预览布局元素,使用上面Connect Preview中的同一个对象。

                       Caution:你必须按顺序调用MediaRecorder的这些配置方法,否则你的程序会出错并且捕获视频失败。

          c. Prepare MediaRecorder 调用MediaRecorder.prepare()方法提交上面的配置设定

          d. Start MediaRecorder 调用MediaRecorder.start()方法开始录制视频。

  • Stop Recording Video 按顺序调用一下方法,完成视频录制。
         a. Stop MediaRecorder 调用MediaRecorder.stop()方法停止视频录制

         b. Reset MediaRecorder (这一步是可选的)调用MediaRecorder.reset()方法移除之前的配置设定

          c. Release MediaRecorder 调用MediaRecorder.release()释放MediaRecorder

          d. Lock the Camera 锁定相机以便MediaRecorder将来可以通过Camera.lock()方法继续使用相机。从Android 4.0(API level 14)开始,调用MediaRecorder.prepare()失败时才需要调用该方法。

  • Stop the Preview 当程序不再使用相机的时候,调用Camera.stopPreview()方法关闭预览。
  • Release Camera 调用Camera.release()方法释放相机,使得其他应用程序可以使用相机。
         Note:你可以跳过前面几个步骤,直接使用MediaRecorder而不创建预览界面。但用户更希望记录视频时可以先预览,所以这里不深入讨论。

         Tip:如果你的应用程序通常是用来拍摄视频的,你可以在启动预览前调用setRecordingHint(true)。这个设定可以减少开始录制前的等待时间。

Configuring MediaRecorder

在使用MediaRecorder类录制视频时,你必须按照特定的步骤配置它,然后调用MediaRecorder.prepare()方法检查并完成配置。下面的示例将为你展示如何为录制视频配置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;
}
在Android 2.2(API level 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()
  • setViderSize()
  • setVideoFrameRate()
  • setAudioEncodingBitRate()
  • setAudioChannels()
  • setAudioSamplingRate()

Starting and stopping MediaRecorder

使用MediaRecorder开始和停止录制视频时,你必须遵从下面列出的步骤:

  1. 调用Camera.unlock()解锁camera
  2. 像上面示例一样配置MediaRecorder
  3. 调用MediaRecorder.start()开始视频录制
  4. 录制声音
  5. 调用MediaRecorder.stop()停止视频录制
  6. 调用MediaRecorder.release()解除视频录制
  7. 使用Camera.lock()锁定camera
下面的示例将为你展示如何通过Button使用camera和MediaRecorder类开始和停止视频录制

Note:完成录制后先别释放camera,否则你的预览界面会停止

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
                }
            }
        }
    }
);
Note:上面示例中的prepareVideoRecorder()方法可以在Configuring MediaRecorder示例中找到,这个方法锁定camera,配置并准备MediaRecorder实例。

Releasing the camera

Camera是共享资源,取得Camera实例后,你的应用程序就可以使用它。要注意的是,在程序不再使用它或者当程序处于Paused(Activity,onPause())时,必须释放Camera。如果你没有释放相机,所有后来试图访问相机的应用程序,包括你的应用程序,都会失败,甚至会引起程序的奔溃。

你可以调用Camera.release()释放Camera对象的实例,下面是示例:

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;
        }
    }
}
Caution如果你没有释放相机,所有后来试图访问相机的应用程序,包括你的应用程序,都会失败,甚至会引起程序的奔溃。

Saving Media Files

应用程序产生的多媒体文件(照片/视频)应该储存到外部储存器目录(SD Card)中,这样可以保留系统空间,并且用户不通过设备也可以访问这些文件。设备中有多个可以存储多媒体文件的目录位置,但作为开发者你只需要考虑以下两种标准的位置:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 该方法返回一个标准的共享的推荐的储存照片和视频的内存目录。因为这个目录是共享的,所以其他应用程序可以很容易的发现,读取,修改,删除存储在该目录里的多媒体文件。当你的应用程序被用户卸载后,存储在该目录下的多媒体文件不会被删除。为了避免与用户现有的照片和视频发生冲突,你应该在这个目录下为你的应用程序创建一个子目录,就像下面示例展示的那样。下面的方法在Android 2.2(API Level 8)可用,其他版本中对应的方法请看Saving Shared Files。
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) 这个方法返回一个和你应用程序相关联的标准的存储照片和视频的内存目录。当你的应用程序被卸载后,存储在这里的多媒体文件也会被删除。但存储在这个目录下的多媒体文件并不是安全的,其他应用程序也可以读取,修改,删除它们。
下面的示例将为你展示如何为多媒体文件创建一个文件或Uri,上面提到的用Intent启动相机和Building a Camera 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;
}
Note:Environment.getExternalStoragePublicDirectory()在Android 2.2(API Level 8)及以上版本可用,如果你应用的目标设备是更早期的版本,你可以使用Environment.getExternalStorageDirectory()代替。更多详细信息请看Saving Shared Files。

更多在Android设备上存储文件的信息请看Data Storage.

Camera Features

Android为你的相机应用提供了一系列的相机功能,例如图片格式,闪光灯模式,对焦等。本节将会列出常用的相机功能,并简单的讨论如何使用它们。大部分功能可以通过Camera.Parameters对象简单的设置。但这里有几个重要的功能需要更复杂的设置,这些功能包括以下几点:

  • Metering and focus areas
  • Face detection
  • Time lapse video
如何通过Camera.Parameters使用相机的功能,请回到上面的Using camera features。下面列表给出相机常用功能,更多相关信息请看相应的API文档。

Feature API Level Description
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

Note:由于硬件的不同和软件的实现等原因,并不是所有的设备都支持这些功能的。更多关于检测相机功能可用性的信息请看下面的Checking feature availability。

Checking feature availability

在使用相机功能前,你必须了解到不是所有的设备都支持所有的功能的。此外,支持特殊功能的设备会在不同级别或不同的选项上支持这些功能。因此,你的考虑清楚你的应用程序需要用到哪些相机功能以及在哪种程度上使用它们。考虑好这些后,你就可以在代码中检测设备是否支持这些功能,并妥善处理设备不支持这些功能的情况。

你可以通过取得Camera Parameter对象的一个实例并调用相关函数来检测相机功能的可用性。下面的示例代码将为你展示如何取得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对象提供了getSupportedXXX(),isXXXSupported()以及getMaxXXX()等方法判断相机是否支持某个功能。

如果应用程序为了正常运行而必须用到某些相机功能,你可以在manifest中请求这些功能。当你声明这些特定的功能,如闪光灯和自动对焦,Google Play会限制你的应用程序,使得程序不会被安装在不能提供这些功能的设备上。更多可以在manifest中声明的功能,请查看Features Reference.

Using camera features

大部分相机功能可以通过Camera.Parameters对象激活和使用。首先你得取得Camera对象的一个实例,然后调用它的getParameters()方法,修改Parameter对象并把它重新放回Camera对象中,下面的示例将为你展示:
// 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对象的实例后,你可以在任意的时刻修改Parameter。修改Parameter后用户一般可以马上在预览界面看到变化。在软件层次,Parameter的修改需要经过若干个框架直到Camera硬件执行新的指令并发送新的图片数据才能生效。

Important:一些相机功能不能随意改变。特别是改变预览界面的大小或方向时,你得先停止预览界面,然后改变大小,最后重新开启预览界面。从Android 4.0(API Level 14)开始,改变预览界面的方向不需要重启。

其他的功能需要更多的代码才能实现,包括:

  • Metering and focus areas
  • Face detection
  • Time lapse video
在下面的章节中我们会给出实现这些功能的概述。

Metering and focus areas

在一些拍摄场景中,自动对焦和测光可能不会产生理想的效果。从Android 4.0(API Level 14)开始,你的相机应用可以提供一些额外的操作,以允许应用和用户指定图片上的特定区域进行对焦和光线等级设定,这些设定会传递给相机硬件用于照片的捕获和视频的录制。

测光和对焦区域的工作和其他相机功能非常相似,你可以通过Camera.Parameters对象的方法控制它们。下面将为你展示如何通过Camera对象的一个实例设置两个测光区域。

// 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画面内的一个区域和一个权重值,这些参数会告诉相机这个区域应该给予什么级别的测光和焦点计算。

Camera.Area对象把画面映射为2000X2000个单元格的矩形区域。坐标点<-1000,-1000>代表画面左上角的顶点,坐标点<1000,1000>代表画面右下角的顶点,如下图所示:

Android Camera_第1张图片

红色线是Camera.Area的坐标系,蓝色框显示值为(333,333,667,667)的Rect对象的位置和形状。

坐标系的边界总是与预览界面中可见图片的边界相契合且不会大规模的缩放。类似地,使用Camera.setDisplayOrientation()旋转预览界面时不会重新映射坐标系。

Face detection

在有人物的照片中,人脸通常是照片最重要的部分,且在拍照时应该根据人脸来确定焦点和白平衡。Android 4.0(API Level 14)框架提供了人脸识别APIs,并会根据人脸识别技术计算照片参数。

Note:当人脸识别功能启动后,setWhiteBalance(String),setFocusAreas(List)和setMeteringAreas(List)不会产生效果。

使用人脸识别功能的一般步骤:

  • 检测设备是否支持人脸识别功能
  • 创建人脸识别监听器
  • 把人脸识别监听器加入到Camera对象中
  • 启动预览界面后再启动人脸识别功能
并不是所有设备都支持人脸设备功能的,你可以用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();
    }
}
每次启动(或重新启动)预览界面时,都必须启动人脸识别功能。如果你使用的是上面Creating a preview class的预览类,你可以把上面的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()方法。请不要尝试在main Activity的onCreate()方法中启动人脸识别功能,因为这个时候预览界面还没有启动。

Time lapse video

(Time lapse video这个不知道怎么翻译,大部分翻译为延时拍摄,但实际使用中,是你拍了很久才会生成1秒的视频,最后结果相当于把一段视频以几倍速播放)

延时拍摄允许用户把图片合并成一段几秒或几分钟的视频。这个功能需要MediaRecorder记录图片的一个时间序列。

为了使用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完整的配置过程请看Configuring MediaRecorder。配置完成后,你可以像拍摄普通视频那样开始拍摄。更多关于MediaRecorder的配置和使用信息,请看Capturing videos.

你可能感兴趣的:(Camera)