概述

Android框架支持设备的相机拍照和录像功能,你的应用可以直接调用系统的Camera应用来拍照或者录像(比如微信拍照),当然也可以利用Android系统提供的API开发一个Camera应用来实现相机拍照和录像功能(比如市面上流行的360相机)。此篇文章主要记录相机开发有关的基础知识,以及带着自己的理解翻译Camera官方文档,如有翻译不恰当支出,还请指出改正。当然我会开一个有关相机开发的一个系列,该系列主要内容包括如下:

  1. 相机基本预览拍照功能。
  2. 实现相机的Flash,Hdr,滤镜,前后摄像头切换功能。
  3. 实现16:9和4:3预览比例自由切换。
  4. 正方形拍照。
  5. 自动添加水印功能。
  6. 实现视频录像功能。
  7. 实现支持第三方应用请求拍照和录像功能。

注意事项

在你的应用程序能够在Android设备上使用相机之前,你应该考虑几个问题,那就是你的app打算如何使用相机拍照或者录像?

  • Camera需求的声明 - 使用相机功能对于你的应用程序来说是否很重要并且你不希望你的应用程序被安装在没有相机的机器上?如果是这样,那么你需要把 相机需求声明在配置文件里.
  • 快速拍照还是自定义相机 -你的应用程序该如何使用相机?你是否仅仅对捕捉一个快照或者一个视频剪辑, 或者你的应用程序希望提供一种使用相机的新的方式?获取快照或者视频,建议 使用已存在的系统相机应用。开发一个自定义相机请查看创建一个相机应用。
  • 存储 - 是否你的应用生成的图片和视频仅对你的应用可见,或者是用来分享,这样的话,其他应用程序例如相册或者其他的多媒体和社交app可以使用它们?你是否希望你的应用程序被卸载后,这些照片和视频仍然可用?保存图片和视频文件。

基础知识

Android系统提供API和Intent来支持自定义相机拍照和快速拍照,以下是有关的类:

  • Camera
    该类提供基础API来使用设备上的相机,且该类可以为你的应用提供拍照和录像相关的API。
  • SurfaceView
    该类用于显示相机的预览数据。
  • MediaRecorder
    该类提供相机录像相关的API。
  • Intent
    使用MediaStore.ACTION_IMAGE_CAPTURE 和MediaStore.ACTION_VIDEO_CAPTURE Intent action可以快速拍照或者录像。

Manifest权限申明

在开发设备相机之前,你需要在Manifest申请如下权限才可以使用相机。

Camera Permission - 你的应用必须申请相机权限才可以使用设备相机。

<uses-permission android:name="android.permission.CAMERA" />

注意:如果你使用Intent发送快速拍照请求,你的应用无需申请该权限。

Storage Permission - 如果你的应用需要保持照片或者视频到设备存储中,你必须在Manifest指定文件的写权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Audio Recording Permission - 你必须申请录音权限才能使用相机来录像.

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Location Permission - 当然如果你需要拍摄的照片记录地理位置,你同样需要申请如下权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

使用已存在的相机应用拍摄

你的应用可以用过发送一个Intent 到系统相机应用来实现一个快速拍照或者录像的功能。一个Camera intent可以通过已存在的相机应用来抓取一张照片或者一段视频剪辑,然后将它们返回给你的应用。这一部分主要演示如何通过发送intent来抓取一张照片或者视频。

使用camera intent拍照的流程如下:

  1. Compose a Camera Intent - 创建一个Intent请求用来拍照或者录像,有关的Intent类型如下:
    • MediaStore.ACTION_IMAGE_CAPTURE - 该Intent action 类型用于请求系统相机拍照。
    • MediaStore.ACTION_VIDEO_CAPTURE - 该Intent action 类型用于请求系统相机录像。
  2. Start the Camera Intent - 调用activity的startActivityForResult()方法来发送camera intent请求拍照或者录像,当发送camera intent 以后,当前应用会跳转到系统相机应用app界面,让用户可以拍照或者录像。
  3. Receive the Intent Result - 在你的应用中实现onActivityResult()回调方法去接收来自系统相机的拍摄结果。该方法在用户完成拍照或者录像以后有系统调用。

使用Intent拍照

使用camera intent拍照是一个快速的,最简单的方法。发送Intent拍照携带的外部数据extra的信息如下:

  • MediaStore.EXTRA_OUTPUT - 这个关键字用于创建一个Uri对象来指定一个路径和文件名保存照片。当然,这个设置是可选的,不过强烈推荐使用该方法来保存照片。如果你没有在指定该关键字的值,系统的camera应用会将照片以默认的名字保存在一个默认的地方,当你指定了该关键字的值,数据以Intent.getData()方法返回Uri对象。

接下来的实例代码将演示如何构建一个Intent用来拍照,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);
}

当调用startActivityForResult() 方法以后,用户可以看到系统相机的拍照界面。在用户拍照结束以后(或者取消拍照),系统相机会把照片数据返回给你的应用,当然你必须在自己的应用中实现onActivityResult()方法来接收照片数据。更多有关如何在自己的应用接收拍照结果的信息请参考接收相机返回的数据。

使用Intent录像

使用camera intent录像是一个快速的,最简单的方法。发送Intent录像携带的外部数据extra的信息如下:

  • MediaStore.EXTRA_OUTPUT - 该关键字和拍照使用的关键字一样,意思就是制定一个路径和文件名来构建一个U日对象来保存录像结果,同样录像结果会以Intent.getData()的方法返回Uri对象。
  • MediaStore.EXTRA_VIDEO_QUALITY - 该关键字用于指定拍摄的录像质量,参数0表示低质量,参数1表示高质量。
  • MediaStore.EXTRA_DURATION_LIMIT - 该关键之用于指定拍摄的录像的时间限制,单位是秒。
  • MediaStore.EXTRA_SIZE_LIMIT - 该关键字用于指定拍摄的录像文件大小限制,单位值byte。

接下来的实例代码将演示如何构建一个Intent用来录像,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);
}

当调用startActivityForResult() 方法以后,用户可以看到系统相机的拍照界面。在用户录像结束以后(或者取消录像),系统相机会把视频数据返回给你的应用,当然你必须在自己的应用中实现onActivityResult()方法来接收视频数据。更多有关如何在自己的应用接收拍照结果的信息请参考接收相机返回的数据。

接收相机返回的数据

一旦你发送了一个拍照或者录像的intent,你的应用必须去接收Intent的结果数据。这一小节将演示如何在本地应用中实现接收Intent的结果数据(照片或者视频数据)。

为了接收Intent的结果数据,你必须重写activity的onActivityResult()方法。接下来的代码将演示如何实现onActiviytResult()方法来接收照片或者视频数据。

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

一旦你的activity成功接收返回结果,相机拍摄的照片或者视频是一个指向你应用可以访问的路径。也就是getOutputMediaFileUri()方法返回的Uri文件地址。

【CSDN 废墟的树 Android Camera开发之基础知识篇

创建一个相机应用

创建一个独立自定义的相机app基本遵循如下步骤:

  • 检测和访问相机 - 首先代码检测该设备相机是否存在,如果存在才能请求访问设备相机.
  • 创建一个预览来显示相机图像 - 在你的布局中使用SurfaceView控件,然后在代码中继承SurfaceHolder.Callback接口并且实现接口中的方法来显示来自相机的图像信息。
  • 设置相机基本参数 - 根据需求设置相机预览尺寸,图片大小,预览方向,图片方向等。
  • 设置拍照录像监听 - 当用户按下按钮时调用Camera#takePicture或者MediaRecorder#start()来进行拍照或录像。
  • 文件保存 - 当拍照结束或者录像视频结束时,需要开启一个后台线程去保存图片或者视频文件。
  • 释放相机资源 - Camera硬件是一个共享资源,所以你必须小心的编写你的应用代码来管理相机资源。一般在Activity的生命周期的onResume中开机相机,在onPause中释放相机。

注意: 当你不在使用相机资源时,记得调用Camera#release方法来释放相机资源,否则其他应用甚至你自己的应用再次请求访问设备相机时会失败,并且crash。

检测相机硬件是否存在

一般情况,我们会在运行代码时检测该设备是否有相机硬件,如果有相机硬件,才进一步去访问相机,如下是检测相机硬件是否存在是代码示例:

/** 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 设备可以有多个相机硬件,现在一般手机都是前后两个camera,因此我们在Android2.3以后也可以使用Camera#getNumberOfCameras()方法来获得当前设备camera个数来判断相机硬件是否存在。

访问相机设备

如果在你的应用中已经检测到了该设备有相机,那么你必须得到一个Camera类的实例才能访问相机(除非你使用Intent快速访问相机)。

为了访问相机基本功能,可以使用Camera#open()方法来获得一个Camera的实例,示例代码如下:

/** 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()方法时总是要去捕获一个异常,以免打开相机设备失败导致整个应用crash。在Android2.3以及更高api上,你可以使用Camera#open(int)来打开指定的相机。以上代码示例总是默认打开后置camera,一般情况参数为0表示打开后置camera,参数为1表示打开后置camera。

获取相机的属性特性

一旦你可以成功访问相机设备,你可以使用Camera#getParameters()方法来获取相机参数信息,可以根据
返回值 Camera.Parameters 类来查看当前camea支持哪些参数设置等。当使用API 9或者更高时,你可以使用Camera.getCameraInfo()静态方法来获取前后camera的ID,以及camera数据流的方向和是否能禁止拍照快门声音标记。示例代码如下:

/**
     * get current camera info
     *
     * @param cameraId current camera id
     * @return camera info
     */
    public static Camera.CameraInfo getCameraInfo(int cameraId) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);
        return cameraInfo;
    }

创建一个预览类

为了有效的拍照或者录像,我们必须在屏幕上能看到相机的预览。一个相机预览类是由SurfaceView控件来实时显示来自camera的预览数据,如此我们才能看到每一帧数据和捕获图片或者视频。

如下示例代码演示了如何创建一个基本的camera预览类以及如何布局。该类继承SurfaceView.Callback接口类,并且需要实现里面的接口方法以便监听SurfaceView控件的创建以及销毁事件的回调,在回调方法中关联相机预览显示。

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

预览添加到布局中

我们需要创建一个布局来加载camera预览类,在该例子中,FrameLayout作为camera 预览类的父容器。示例代码如下:

<?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>

在更多的设备上,camera 预览默认的方向是横屏的,故在该例子中布局指定水平方向以及固定该应用为横屏显示。为了简便渲染camera预览,你应该在manifest配置文件中指定CameraActivity的方向为横屏。

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

注意:“Camera的预览并不一定是横屏的,在Android 2.2之后,可以使用setDisplayOrientation()方法设置预览的方向。为了改变预览方向,请在surfaceChanged()方法中显示调用Camera#stopPreview()来停止预览,改变方向以后再次调用Camera#stratPreview()启动预览。

首先在activity中创建CameraPreview预览,然后将camera 预览添加到布局FrameLayout中。你必须确保在camera activity 的onPause或者应用退出时去释放camera资源。如下示例代码演示怎么将CameraPreview预览加载到布局中去显示。

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(id.camera_preview);
        preview.addView(mPreview);
    }
}

注意: 以上示例中的getCameraInstance() 方法在 访问相机设备 小节已经讲到。

拍照

一旦你创建了camera preview并且加载到布局中可以实时显示预览画面了,此时就可以进行拍照了。在代码中你应该实现一个监听回调来捕获用户拍照的行为。代码中调用Camera#takePciture()方法来进行拍照。该方法接受三个参数,第一个参数ShutterCallback响应快门的接口,第二个参数PictureCallback接收raw格式的图片数据,第三个参数PictureCallback接收jpeg格式的图片数据。为了保存图片数据,你需要分别实现以上三个接口。此处我们暂且实现第三个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()方法触发拍照,以下代码演示在button点击监听事件中调用拍照方法。

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

录像

Camera视频录像不仅涉及到Camera类还用到了MediaRecorder类。当你使用Camera录像时,你应该调用Camera#lock()和Camera#unlock()来管理camera硬件,允许MediaRecorder进程访问caemra硬件。你应该在camera和MediaRecorder关联之前调用Camera#unlock()来解锁camera,允许MediaRecorder访问Camera,在释放MediaRecorder资源以后调用Camera#lock()来锁定camera以保证camera硬件资源的共享性。

注意:在Android4.0以后,系统会自动管理Camera#unlock()以及Camera#lock(),无需用户自己管理。

不像拍照流程,启动录像流程需要一个指定调用顺序,如下是详细的步骤流程:

  1. Open Camera - 使用Camera.open()静态方法来获得camera对象实例。
  2. Connect Preview - 使用Camera.setPreviewDiaplay()方法将相机的预览画面显示在SurfaceView控件上。
  3. Start Preview - 使用Camera.startPreview()方法 开始启动预览画面.
  4. Start Recording Video - 必须完成以下步骤才能正常开始正常录音:
    a. Unlock the Camera - 调用Camera.unlock()方法解锁caemra,使得MediaRecorder进程能访问Camera硬件。
    b. Configure MediaRecorder - 在这一步,分别调用MediaRecorder类中如下方法来配置MediaRecorder:

    1.setCamera() - 设置camera用于录像。
    2.setAudioSource() - 设置录像音频来源, 使用麦克风 MediaRecorder.AudioSource.CAMCORDER作为音频来源.
    3.setVideoSource() - 设置录像视频来源, 使用Camera MediaRecorder.VideoSource.CAMERA作为视频来源.
    4.设置视频的输出格式和编码格式。 对于Android2.2或者更高版本使用 MediaRecorder.setProfile方法即可,使用方法CamcorderProfile.get()来获得一个配置信息。
    5.setOutputFile() - 设置视频输出保存到文件的路径。
    6.setPreviewDisplay() - 为你的MediaRecorder指定预览显示.使用第2步一样的参数即可。
    

    注意:在这一步,你必须调用MediaRecorder类中的以上方法来配置MediaRecorder,否则你的应用将无法正常录像并且报错。

    c. Prepare MediaRecorder - 在配置完MediaRecorder参数之后调用MediaRecorder.prepare()方法来准备MediaRecorder.
    d. Start MediaRecorder - 调用MediaRecorder.start()方法启动录像.

  5. Stop Recording Video - 当你结束录像时调用如下方法:

    a. Stop MediaRecorder - 首先调用 MediaRecorder.stop()方法停止多媒体录像。
    b. Reset MediaRecorder - 调用MediaRecorder.reset()方法重置多媒体状态,调用该方法之后之前的所有MediaRecorder configuration将被移除,你如果还想再次录像,需要再次配置多媒体参数.
    c. Release MediaRecorder - 调用 MediaRecorder.release()方法释放多媒体资源.
    d. Lock the Camera - 调用Camera.lock()方法来给Camera硬件加锁. 在Android4.0及以后无需调用该方法,除非在调用MediaRecorder.prepare()失败时,才需要再次调用该方法。

  6. Stop the Preview - 当你的Activity已经不再使用camera时,调用Camera.stopPreview()方法来停止预览。

  7. Release Camera - 当不再使用Camera时,调用Camera.release()方法来释放camera,以便其他应用可以使用camera资源。

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

由于录像默认设置了很多参数,无需用户太关心更细节的参数设置,但是如果需要在你的应用中修改这些默认参数设置,你可以使用如下方法来修改默认参数:

  1. setVideoEncodingBitRate() 设置视频编码的字节率,在prepare()方法之前调用
  2. setVideoSize() 设置视频尺寸大小,在setVideoSource()和 setOutFormat()之后 prepare()之前调用。
  3. setVideoFrameRate() 设置视频帧率,在setVideoSource()和 setOutFormat()之后 prepare()之前调用。
  4. setAudioEncodingBitRate() 设置音频编码的字节率,在prepare()方法之前调用
  5. setAudioChannels() 设置音频的频道数目,在prepare()方法之前调用 参数一般1/2
  6. setAudioSamplingRate() 设置音频采样率。

停止和启动MediaRecorder

当使用MediaRecorder类启动和停止录像时,你必须按照如下步骤来操作:

  1. 调用Camera.unlock()解锁camera。
  2. 按照上一节配置MediaRecorder参数。
  3. 调用MediaRecorder.start()启动录像。
  4. 调用MediaRecorder.stop()停止录像。
  5. 调用MediaRecorder.release()释放多媒体资源。
  6. 调用Camera.lock()锁定camrea硬件资源。

如下代码示例演示在button的点击事件中去启动和停止视频录像操作:

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

注意: 当完成一段视频录像时,不要马上去释放camera资源或者停止当前预览,因为有可能用户会再次启动录像操作。在上面示例中,prepareVideoRecorder() 方法定义在 MediaRecorder参数配置.

释放caemra资源

在一台设备上,相机作为一个共享的资源。在你的应用中必须确保在使用完相机以后去释放camera的资源。一般建议在Activity.onResume()方法中打开相机,在Activity.onPause()方法中去是让相机资源。如果你的应用在退出以后还没有释放camera资源,包括自己的应用在内的其他应用再次去启动相机将会失败。

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;

    ...

    @Override
    protected void onResume() {
        super.onResume();
        mCamera = Camera.open();//open the camera for the application
    }

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

保存多媒体文件

拍照或者录像生成的多媒体文件需要保存到手机存储中目录中(SD Card),所以在应用中必须有往手机中写文件的权限。一般可以有多种本地路径来保存多媒体文件,但是主要有如下两种常用的路径:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - 该方法返回一个标准的外部存储路径去保存照片和视频。这个路径是公共的,所以其他应用也可以访问,修改,删除该路径下的照片和视频,如果你的应用被卸载了,媒体文件依然存在本地储存中. 为了避免和其他多媒体文件混淆,你应该在公共目录下创建一个子目录来保存你自己应用中的多媒体数据。

  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 该方法返回一个标准的,唯独当前应用自己可见的路径去保存照片和视频。如果该应用被卸载,在该目录下的所有多媒体数据将会被移除.但是有一个好处就是其他应用无法去访问,修改,删除该路径下的文件。

如下示例代码演示如何创建一个路径用来保存照片和视频:

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

相机特征描述

Android支持一组相机功能来控制你的相机应用,比如:生成的照片格式,闪光灯模式,对焦设置等等。这里罗列出通用的相机功能并且显示怎么使用它们来辅助我们拍照。有关相机更多的参数设置,可以直接阅读Camera.Parameters类。一下列表罗列出通用的相机参数:

参数API等级描述
Face Detection14识别画面中的人脸,用于对焦,测量,白平衡
Metering Areas14在画面中指定一个或者多个区域用来计算白平衡
Focus Areas14在画面中指定一个或者多个区域用来对焦
White Balance Lock14停止或者启动自动白平衡调节
Exposure Lock14停止或者启动自动曝光调节
Video Snapshot14支持视频录像时拍照
Time Lapse Video11支持延时录像
Multiple Cameras9一台设备支持多个camera,包括前置和后置camera
Focus Distance9支持焦距调节
Zoom8支持缩放
GPS Data5支持往照片中写地理位置数据
White Balance5设置白平衡模式,该参数会影响照片颜色效果
Focus Mode5设置对焦模式,比如:自动对焦,固定对焦,微距,远距
Scene Mode5设置场景模式,比如:夜景,沙滩,雪景,烛光等场景
JPEG Quality5设置生成的jepg图片质量等级
Flash Mode5闪光灯模式,on,off,auto
Color Effects5支持滤镜效果
Anti-Banding5反带效应(防止闪烁),参数有off,auto,50hz,60hz
Picture Format1图片格式,默认图片格式为jpeg
Picture Size1图片尺寸,指定一个大小的尺寸用于保存图片大小

注意:以上列表中的功能并是不在所有的Android设备上都支持,因此你在使用以上参数时需要去检测当前Android设备是否支持该参数,然后再去使用它们来辅助相机拍照。

检测相机特性是否可用

首先你要知道,并不是所有android设备都支持全部的camera特性功能,因此在应用总使用camera特性功能需要先检测是否支持,然后在去使用。否则你使用了不支持的camera特性功能将会报错。

在应用中可以通过得到camera 的参数 parameters类,然后通过该类中的一些方法来检测当前设备是否支持camea特性功能。如下代码示例演示了如何获得一个Camera.Parameters对象且检测camera是否支持自动对焦特性:

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

使用相机的特性功能

大部分Android 相机特性功能都可以通过 Camera.Parameters类来控制。首先你可以获得一个Camera实例,然后调用Camera.getParameters()方法的返回值来得到Caemra.Parameters实例,之后就可以通过Parameters.setxxx()系列方法来设置一些参数使用相机的一些特性功能。以下是实例代码:

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

相机的所有参数都可以通过类似以上方法来设置,一般在打开相机成功以后就可以设置相机的基本参数。

注意:相机的一些特性不能再任意时刻改变,比如改变预览的尺寸和方向时,首先需要停止preview,修改预览尺寸之后再次重启preview。自从Android4.0以后修改预览方向以后无需 再次重启preview。

相机的其他功能介绍如下:

  • Metering and focus areas
  • Face detection
  • Time lapse video

接下来就介绍相机的以上三个功能在代码中如何使用

测光和对焦区域

在某些摄像情景中,自动调焦和测光可能不能达到设计结果。从Android4.0(API Level 14)开始,你的Camera应用程序能够提供另外的控制允许应用程序或用户指定图像中特定区域用于进行调焦或光线级别的设置,并且把这些值传递给Camera硬件用于采集图片或视频。

测光和调焦区域的工作与其他Camera功能非常类似,你可以通过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这块指定区域应该给予的测光或调焦计算的重要性等级。
在Camera.Area对象中的Rect字段,代表了一个被映射成2000x2000单元格的矩形。坐标(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角,如下图所示:

这里写图片描述

图中的红线说明了在Camera预览窗口中给Camera.Area指定的坐标系统。用Rect的值是(333,333,667,667)蓝色框显示了摄像区域的位置和形状。

这个坐标系统的边框总是对应着Camera预览窗口中所显示的图像的外边缘,并且不会使用缩放级别来缩小或放大。类似的,使用Camera.setDisplayOrientation()方法来选择图像的预览,不会重新映射坐标系统。

人脸识别

对于包含人的图片,通常人脸是图片的最重要的部分,并且在采集图像时,应该使用调焦和白平衡来进行检测。Android4.0(API Level 14)框架提供了用于识别人脸和使用人脸识别技术来计算图片设置的API。

注意:当人脸识别在运行时,setWhiteBalance(String), setFocusAreas(List) 和setMeteringAreas(List)方法都无效。

在你的应用中使用人脸识别技术一般需要如下几步:

  1. 检测当前设备是否支持人脸识别。
  2. 创建一个人脸识别监听回调。
  3. 将监听回调关联到camera 对象中去。
  4. 在启动preview预览之后去启动人脸识别。(每次重启preview之后都有去启动一个人脸识别)

人脸识别功能并是是在所有设备上都支持。你应当调用getMaxNumDetectedFaces()方法来检测当前设备是否支持人脸识别技术,只有当以上方法返回值大于0时,你才可以去调用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());

你的应用必须在每次重启相机preview时候去启动一个人脸检测。创建一个启动人脸识别的方法,在你需要的时候调用它。示例代码如下:

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

你必须在每次启动(或重启)Camera预览窗口时都要启动面部识别。如果你使用前文“创建预览类”中的预览类,就要把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());
    }
}

注意:在调用startPreview()方法之后,要记住调用这个方法。不要试图在你的Camera应用程序的主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参数配置。一旦配置完成,你就可以把它当做普通的视频剪辑来录制视频了。关于配置和运行MediaRecorder对象的更多信息,请看前文的 录像.

总结

到此Android Camera开发的基础知识已经有个大概的了解,当然以上是根据自己开发相机的经验以及翻译的Camra api官方文档,翻译存在很多不足之处,如有发现还恳请支出。后续文章会继续讲解怎么去实现一个拥有基本完善功能的相机项目。

【CSDN 废墟的树 Android Camera开发之基础知识篇

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐