Android自定义Camera

来源:互联网 发布:上海德语培训班知乎 编辑:程序博客网 时间:2024/06/05 18:05

Build A CAMERA(创建一个自定义的Camera)

一些开发人员需要一个(为应用程序定制或提供特殊功能)的相机用户界面(自定义相机)。创建一个定制的相机活动需要更多的代码,但它可以为你的用户提供更令人信服的体验。

 

为您的应用程序创建自定义相机接口的一般步骤如下:

1.        检测和访问摄像机-创建代码,以检查是否存在摄像头和允许访问。

2.        创建一个预览类,创建一个摄像机预览类继承SurfaceView实现SurfaceHolder接口。这类用于相机预览。

3.        建立一个预览布局,一旦你有相机预览类,创建一个视图布局,集成了你想要预览界面和用户界面控件。

4.        设置监听(为捕获),为像按钮一样的控件设置监听。

5.        捕获并保存文件-设置捕获图片或视频并保存到外部设备的代码。

6.        释放相机(重要)-在使用相机后,您的应用程序必须正确地释放它,供其他应用程序使用。

 

相机硬件是一个共享资源,必须小心管理,所以你的应用程序可能会与其他要使用它的其他应用程序发生冲突。

 

下面的章节将讨论如何检测相机的硬件,如何请求访问一个摄像头,如何捕捉照片或视频,以及如何释放相机:

记住:

当您的应用程序不在使用摄像头的时候,要通过Camera.release()方法释放相机对象。如果你的应用程序没有正确地释放相机,所有随后尝试访问相机的应用程序,包括那些你自己的应用程序,都将失败,可能会导致你或其他应用程序被关闭。

Camera.release()断开并释放相机的对象资源。只要不使用相机对象,就要释放。

 

 

1.        检测摄像机硬件

如果你的应用程序没有特别要求使用摄像头(在清单文件中声明Camera),你应该检查一下是否有一个摄像头在运行时可用。要执行此检查,使用PackageManager.hasSystemFeature()方法,如下面的代码示例所示:

  /**Check if this device has a camera */

private boolean checkCameraHardware(Contextcontext) {

   if(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){

       // this device has a camera

       return true;

    }else {

       // no camera on this device

       return false;

    }

}

 

安卓设备可以有多个摄像头,例如一个背对着相机的摄影和一个前置摄像头进行录像。Android 2.3(API Level 9)以及后面的版本允许你检查在设备上可使用的摄像头的个数。通过Camera.getNumberOfCameras()方法。

public static int getNumberOfCameras():返回此设备上可用的物理摄像头的数量。

 

2.        连接相机

如果你已经确定了你的应用程序运行的设备上有一个摄像头,你必须通过获取一个相机的实例来请求获得它(除非你使用的意图访问相机)。

 

为了访问私有摄像头,要使用Camera.open() open(int)方法,并且try catch所有异常,下面代码所示:

public static Cameraopen ()创建一个新的相机对象(连接第一个后置摄像头),如果设备没有一个后置摄像头,返回null。

 

/** A safe way to get an instance of theCamera 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()方法的时候,要检查异常,如果不检查异常:当摄像头正被其他应用使用,或者不存在摄像头,会导致你的应用程序shut down。在Android2.3(API Level9)的设备上或更高的版本上,你可以使用通过Camera.open(int)方法连接特定的摄像头。上面的代码返回第一个,后置摄像头(该设备上的,即使有多个摄像头)。

public static Cameraopen (int cameraId)创建一个新的相机对象访问特定的硬件相机。如果这个摄像头被其他应用程序打开,将会抛出一个运行时异常。参数:between0 and getNumberOfCameras()-1

 

u  使用完摄像头后必须调用release()方法释放相机,否则这个摄像头仍然被锁定,是无法被其他应用程序访问的。

 

u  对于一个特定的摄像头硬件,你的应用程序应该在同一时间只有一个活动着的摄像头对象。

 

u  其他方法的回调被传递给了thread(线程)的event loop(事件轮询器, 消息循环,使线程随时处理事件,但不退出)叫做open()。如果这个线程没有event loop,回调会被传递给主线程的event loop。如果没有主线程的event loop,回调不会传递。

 

警告:在某些设备上,这种方法可能需要很长的时间才能完成。最好是从一个工作者线程调用这个方法(可能使用AsyncTask)以避免阻塞UI线程的主要应用。(此方法最好在子线程中调用)。

 

摄像头禁用?

public boolean getCameraDisabled (ComponentNameadmin);

确定是否设备的摄像头已被禁用或被当前的管理员,如果指定,或所有管理员。

Admin:管理组件的名称。

 

3.        检查相机功能

一旦你获得一个摄像头,你可以得到进一步得到更多摄像头的信息,通过Camera.getParameters()方法可以得到摄像头的功能信息,检查返回的相机所支持功能的参数对象。使用API级别9或更高的时候,可以通过Camera.getCameraInfo()方法确定一个摄像头是否在设备的正面或背面,以及图像的方向。

publicCamera.Parameters getParameters ()返回此相机的当前设置。如果要修改返回的相机配置参数,就必须通过setParameters(Camera.Parameters)使新的相机参数生效。

 

public void setParameters (Camera.Parameters params):修改相机的设置。

Camera.Parameters params:相机参数。

 

²  Camera.Parameters params类(以后补充)

 

4.        创建预览类

为了使用户可以拍摄照片和视频,要使他们必须能够看到设备摄像头看到的场景。相机预览类是一个图形,可以实时显示来自摄像头的数据,所以用户可以组织和拍摄照片或视频。

下面的示例代码演示了如何创建一个基本的相机预览类,它包含了一个视图布局。这个类实现了SurfaceHolder.Callback接口。用于捕捉事件回调(创建和销毁界面),需要指定摄像预览输入。

 

 

/** A basic Camerapreview class */

public class CameraPreview extendsSurfaceView 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
//在改变surface前要先停止预览。
        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());
        }
    }
}


SurfaceHolder.Callback:

一个客户端可以实现这个接口从而接收到Surface的信息变化。
想让SurfaceHolder.CallBack生效,必须执行SurfaceHolder.addCallBack(SurfaceHolder.CallBack)方法。当使用一个SurfaceView,这个Surface只在surfaceCreated(SurfaceHolder)surfaceDestroyed(SurfaceHolder)之间可获得。这个Callback SurfaceHolder.addCallback方法中设置。 


abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height):一旦Surface的结构变化(格式或者尺寸大小的变化)都会导致这个方法的调用。你应该此时更新Surface表面的图像。这个方法在surfaceCreated(SurfaceHolder)后至少调用一次。


Holder: 该SurfaceHolder的surface发生改变了。
Format: Surface的新的像素格式。
Width:Surface的新宽度。
Height:Surface的新高度。


abstract void surfaceCreated(SurfaceHolder holder):Surface第一次创建后立即调用。在这个方法中写希望渲染surface的代码。注意:只有一个线程可以在一个Surface上draw(),所以你不应该在Surface上draw(),如果此时该Surface在另一个线程中渲染。
Holder:该SurfaceHolder的surface正在被创建。


abstract void surfaceDestroyed(SurfaceHolder holder):Surface马上被销毁前调用。执行这个回调后,你不应该尝试去访问这个surface。在执行这个方法后,如果你有一个渲染线程直接访问这个surface,你必须确保这个线程不再接触这个Surface。


public abstract void addCallback (SurfaceHolder.Callback callback):为该holder添加一个Callback回调,这个holder可以关联多个Callback.


如果你想为你的相机预览界面设置特定的大小,就在surfacechanged()方法中设置。设置预览大小的时候,你必须使用从getsupportedpreviewsizes()方法中获取的值。不要在setpreviewsize()设定任意的值。
public List<Camera.Size> getSupportedPreviewSizes ():获取所支持预览的大小。返回一个相机所支持的场景模式的集合,返回null表示不支持场景模式设置。


Camera.Size
相机尺寸
总结:
属性:public int height 图片的高度
 Public int width 图片的宽度
构造方法:
Camera.Size(int w, int h)


public void setPreviewSize (int width, int height):设置预览图片的尺寸。如果预览已经开始(启动了),应用程序应该在更改预览大小之前先停止预览。宽度和高度的边是基于摄像机的方向。也就是说,预览大小是在它被显示方向旋转之前的大小。因此,应用程序需要考虑的显示方向,同时设置预览大小。(这两个一起设置的时候要注意)例如,假设相机支持320x480 480x320的预览图片。应用程序需要一个3:2的预览比例。如果显示方向设置为0或180,预览大小应设置为480x320。如果显示方向设置为90或270,预览大小应设置为320x480。在设置图片大小和缩略图大小时,还应考虑显示方向。
Width: 图片的宽度,以像素为单位。
Height: 图片的宽度,以像素为单位。


public final void setDisplayOrientation (int degrees):设置顺时针旋转的预览显示的度数。这影响到预览框和snapshot后显示的图片。这个方法对垂直模式的应用有效。请注意:前置摄像头的预览显示在旋转之前水平翻转了,即
图像是反映沿摄像头传感器的中心垂直轴。所以用户可以看到自己像镜子一样。


这个方法不允许在摄像头预览的时候调用(setDisplayOrientation)


如果你想让相机的图像显示在同一个方向上显示,可以使用以下代码:
public static void setCameraDisplayOrientation(Activity activity,
         int cameraId, android.hardware.Camera camera) {
     android.hardware.Camera.CameraInfo info =
             new android.hardware.Camera.CameraInfo();
     android.hardware.Camera.getCameraInfo(cameraId, info);
     int rotation = activity.getWindowManager().getDefaultDisplay()
             .getRotation();
     int degrees = 0;
     switch (rotation) {
         case Surface.ROTATION_0: degrees = 0; break;
         case Surface.ROTATION_90: degrees = 90; break;
         case Surface.ROTATION_180: degrees = 180; break;
         case Surface.ROTATION_270: degrees = 270; break;
     }


     int result;
     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
         result = (info.orientation + degrees) % 360;
         result = (360 - result) % 360;  // compensate the mirror
     } else {  // back-facing
         result = (info.orientation - degrees + 360) % 360;
     }
     camera.setDisplayOrientation(result);
 }


从API14开始,这种方法可以在预览时主动被调用。
int degrees:图片将顺时针旋转的角度。有效值分别为0、90、180和270。起始位置为0(景观)




public final void setPreviewDisplay (SurfaceHolder holder):用于设置实时预览的界面。
不论是surface还是surface texture 必须能够预览,并且预览界面可以用来拍照。同一个surface界面可以重新设定而不会损害surface。设置一个预览界面会重置所有预览界面的结构,通过setPreviewTexture(SurfaceTexture)方法实现。
当这个方法调用的时候,SurfaceHolder必须已经包含了一个surface。如果你使用SurfaceView,在调用setPreviewDisplay()或starting preview()之前,你必须先注册一个SurfaceHolder.Callback接口和调用addCallback(SurfaceHolder.Callback)方法,并且等待surfaceCreated(SurfaceHolder)的执行。
这个方法必须在startPreview()方法之前调用。唯一的例外是,在调用startPreview()方法之前,如果没有设置预览界面,(或者预览界面设置为空),这个方法会调用一次(传入一个非空参数)来设置预览界面。预览界面正在运行的时候,预览界面无法做改动。




Camera布局:
例如前面举的一个例子(相机预览类)。需要一个布局,来进行拍照和录像。本节将向您展示如何构建预览的基本布局和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>


在大多数设备,相机预览的默认方向是垂直的。此示例布局指定一个横向(纵向)的布局,下面的代码应用程序landscape的方向性。要使摄像头的预览方向为垂直方向,你应该在清单文件中增加以下代码。
<activity android:name=".CameraActivity"
          android:label="@string/app_name"
          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->
          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>


注意:一个摄像头预览模式不必一定是垂直模式。在Android 2.2(API Level 8开始),你可以使用setDisplayOrientation()方法设置预览图像旋转。要想改变预览方向。在预览类的surfaceChanged()方法中,首先要通过Camera.stopPreview()方法停止预览。然后通过Camera.startPreview()方法重启预览。把你自定义的预览类添加到上面FrameLayout元素中。你的相机Activity必须确定当Activity执行pause(暂停)和shut down(关闭)的时候,它已经释放了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);
    }
}
在上面的例子中的getCameraInstance()方法指的是在Accessing cameras中。




捕获图片:
一旦你已经建立了一个预览类和一个视图布局来显示它,你准备开始用您的应用程序捕捉图像。在您的应用程序代码中,您必须为用户界面控件设置侦听器来回应用户的拍照操作。要获取一张图片,使用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() 。下面的代码展示了如何在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);
        }
    }
);


注意:在应用程序使用完摄像头后要及时通过Camera.release()方法释放摄像头。


捕获录像
使用Android框架的视频采集需要对相机对象和MediaRecorder类两者进行精心的协调管理。当使用摄像头录像,你必须管理Camera.lock()和Camera.unlock()方法让MediaRecorder可以访问相机硬件。除了 Camera.open() 和 Camera.release()需要调用之外。


public final void lock ():重新锁上的摄像头,以防止其他进程访问它。相机对象默认是锁定的,除非调用unlock()方法。通常用reconnect()代替。自从API级别14,相机会自动锁定在应用程序的start()中。应用程序可以使用相机,当启动后(start)。记录启动或停止后无需再调用此方法。如果你不录制视频,你可能不需要这种方法。


public final void unlock ():打开相机允许另一个进程访问它。通常,相机被锁定在的活动着的摄像机对象,直到release()被调用。允许进程间快速切换,你可以调用这个方法来暂时释放相机,让另一个应用来使用;一旦其他进程完成后你可以调用reconnect()方法取回相机。
这个方法必须在setCamera(Camera)方法之前调用。执行start()方法之后就不能调用了。
如果你不录制视频,你可能不需要这种方法。


注意:从安卓4.0(API Level 14)之后,Camera.lock()和Camera.unlock()由你自己管理。


不像使用摄像头来拍照,录像需要一个非常特别的调用顺序。你必须遵循一个特定的执行顺序,为了使您的应用程序成功地准备和捕捉视频。正如下面所说。

0 0