Android 自定义camera

根据官方教程,翻译而来。

创建一个Camera App:

  & 一般步骤

  • 检查并访问Camera  —— 写代码检查是否存在cameras并请求使用
  • 创建一个Preview类 —— 创建一个preview类,继承自SurfaceView 并实现SurfaceHolder 接口。这个类是用来预览在拍照时的影像
  • 创建一个Preview布局 —— 接下去创建一个跟Preview类对应的布局文件,里面可以放一些你想要的交互控件
  • 为Capture(拍照)设置监听(Listener) —— 为你的交互控件设置监听,比如按下一个Button
  • 拍照并保存文件 —— 为拍照或录像写代码,并保存到输出流(output)
  • 释放Camera —— 在使用完后,必须要合理地释放,以供其他应用使用。

注意:要及时地释放Camera资源,通过调用Camera.release() 来实现。否则其他应用,包括你自己的应用,如果想要使用Camera,都会被关闭。

 

* 检查(detecting)并访问(access)Camera:

  如果应用没有在Manifest文件里声明要使用Camera硬件,那就应该在runtime时检查Camera是否可用。通过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;

    }

}

  可能会有多个摄像头,在API9之后,可以通过Camera.getNumberOfCameras()得到有几个可用的摄像头。

* 访问Cameras:

  确定有设备之后,要通过得到一个Camera实例来访问它。(除非使用intent 来访问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

}

 

注意:要用try/catch Camera.open()这个方法,防止其他应用在使用Camera,而导致本应用被系统关闭。在API Level 9以上,可以通过Camera.open(int)来打开特定的摄像头。

* 检查Camera特征

  可以通过Camera.getParameters()方法来得到更多的关于它的功能。在API Level 9及以上版本中,使用Camera.getCameraInfo()来确定这个摄像头是前面的还是后面的,以及这个图像的朝向(orientation)。

 

* 创建一个预览类(preview class)

  下面的代码演示了如何创建一个基本的可以包含在一个View 布局里的预览图,这个类实现了SurfaceHolder.Callback接口,来捕获创建/删除这个view的回调事件,这些事件是被安排Camera预览所需要的。

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



        // 把这个监听添加进去,这样可以监听到创建和销毁的事件了
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()里使用合意值。注:supportedPreviewSize()是在camera.getParameters()后再得到的。

* 把预览图放在一个布局里

  一个Preview类,要放在一个布局里,这个布局还要包括其他用来控制拍照等行为的交互界面。这部分展示如何为预览图构建一个布局以及Activity。在这个例子里,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>

  在多数的设备上,默认的朝向是横向的。这个例子的布局指定一个水平的布局,下面的代码固定应用的朝向是横向的。在Manifest里如下指定,就可以简单地让应用保持横向。

<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:Preview没有必要一定是Landscape(横向)模式。从API Level 8开始,可以使用setDiaplayOrientation()方法来设置预览图的旋转。如果要改变朝向,在surfaceChanged()方法里,先用Camera.stopPreview()方法stop这个Preview,改变朝向,然后再用Camera.startPreview()来开始Preview。

  在你的CameraActivity中,把Preview类加入到这个Framelayout中。你的CameraActivity必须要保证在停止或关闭时释放这个camera。下面代码说明一切。

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

    }

}

 

* 拍照

  一旦设置好Preview,就可以使用它去拍照了。在应用代码里,必须要为你的交互界面设置拍照的响应。

  为了得到图,要使用Camera.takePicture()方法。这个方法带了三个参数,用来接收来自Camera的数据。为了接收JPEG格式的数据,必须要实现Camera.PictureCallback接口,来接收图像数据,并写入到一个文件。下例展示了一个基本的实现,用来保存来自Camera的图片。

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

        }

    }

};

“getOutputMediaFile(MEDIA_TYPE_IMAGE)”这个方法及这个常量会在之后“保存文件”一节中说到

  调用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);

        }

    }

);

 

* 释放Camera

  当停止使用Camera时要及时把它释放掉。在Activity.onPause()方法中也要释放。通过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;

        }

    }

}

 

* 保存文件

  Media文件应该被保存到设备的外部存储目录(SD Card),以此来节约系统空间。有很多可以存放文件的地方,但作为开发者,有两个标准的目录可以使用:

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES这个方法返回一个标准的,分享的,并被推荐的目录,用来存放图片和Video。如果被用户卸载了,文件也会存在。为了防止与用户已存在的文件冲突,你应该再创建一个子目录用来存放自己应用的图片。如下面的例子。这个方法在API Level 8以上可以使用,更早的设备,可以查看其他方法。
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES,这个方法返回一个标准的用来存放你的应用的图片和Video的地方。如果应用被卸载,这里的文件也会被卸载。其他应用也可以操作这里的文件。

  如下代码展示了如何创建一个File或者一个Uri,用来保存文件。适用于通过Intent或者自己构建的应用。

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;

}

 

* Camera Features 

  可以设置很多的特性,比如图片格式,闪光模式,焦点,还有其他。本节列出几个常用的特性,简单介绍如何使用它们。很多的特性可以通过访问Camera.Parameters对象来得到。但还是有一些重要的特性需要更多的设置。概括为以下几个部分:

  • 计量和焦点区域
  • 面部识别
  • 定时摄影

  关于如何使用这些由Camera.Parameters对象控制的特性,可以查看"使用Camera特性"一节。从API Level1到14,有很多的特性,便并不是所有的都可以被设备使用,使用前要先检查一下。

  & 检测特性是否可用

  要明确使用哪些特性,以及是哪个版本的,之后可以在代码里检查设备硬件是否支持这个特性,如果不行的话,要合理地处理。

  可以通过得到一个Camera Parameters对象还检查特性是否可用,以及相应的方法。下例演示如何得到一个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

}

 

  有些功能是要在Manifest里进行声明,比如闪光和自动对焦。可以在Manifest里的“Features Reference”中查到。

& 使用特性

  从camera中getParameters();然后进行setXXX();之后再setParameters()进Camera。

重要:有些参数的设置,可能需要先stop preview,变换preview size ,然后再重启preview。从4.0开始之后就不用再重启preview了。

  上文提到的三个部分,需要写一些更多的代码,下面会说到。

& Metering和对焦区域

跟其他的调用方法差不多

& 面部识别

  大多数包括人的照片里,脸部很重要,拍照时应该被焦点或者白平衡。4.0提供了API来确定脸部,并利用面部识别来捕捉照片。

注意:当使用面部识别时,setWhiteBalance(String),setFocusAreas(List)以及setMeteringAreas(List)就没有用了。

使用这个通常需要几个步骤:

    • 检查这个功能是是否被设备支持
    • 创建一个面部监测的监听
    • 把这个监听添加到Camera对象里
    • 在preview之后开始面部监测

面部识别不被所有设备支持,通过调用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()

  应用应该在每次开始(或重启)Camera 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();

    }

}

 

  必须在每次打开(或重启)preview时,开启面部监测。把上面这个方法添加到你的Preview类里的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()之后调用这个方法。不要尝试在mainActivity 的onCreate()方法里启动面部识别。

你可能感兴趣的:(android)