Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 1

在上一章中,我们看了如何利用Android内置的相机应用为其他应用提供一个现成的照片拍摄组件。它提供标准界面给最终用户,对我们程序员而言又简单直接,不过它也因此缺乏灵活度。例如,如果我们想要我们的相机应用支持延时拍摄,使用内置应用就不好实现。

幸运的是,使用内置应用并不是访问摄像头的唯一途径。底层硬件的开放程度以及系统提供的访问方法,对我们和相机应用来说是一样的,我们可以在任意类型的应用中使用这些功能。

在这一章,我们将使用底层Camera类,创建一个拍照应用,以此来学习如何利用 Android 提供给我们的功能。为此我们将逐步创建一些不同的应用:

  • 简单的相机应用
  • 具有倒计时计时器的相机应用
  • 具备定时拍照功能的相机应用

Camera类的使用

我们使用Android的Camera类来访问设备上摄像头。我们使用它来采集图像,它的嵌套类Camera.Parameters来设置它的各种属性,如是否打开闪光灯,给白平衡设置一个合适的值
http://developer.android.com/reference/android/hardware/Camera.html

Camera权限

为了使用Camera类来拍摄照片,我们需要在AndroidManifest.xml文件中声明我们需要CAMERA权限。

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

预览Surface

在开始使用Camera之前,我们还需创建某类Surface供Camera绘制取景器或者预览图像。Surface是一个抽象类,代表一个可以绘制图形或者图像的区域。取得一个可绘制的Surface的简单方法是使用SurfaceView。SurfaceView是一个具体类,它在标准的View中提供Surface。

要在布局中指定SurfaceView,我们只需在任何常规布局XML中使用<SurfaceView />即可。这里是一个基本的布局,在LinearLayout中指定了一个SurfaceView,供Camera做预览。

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="fill_parent"      
    android:layout_height="fill_parent"  >  
    
    <SurfaceView android:id="@+id/CameraView" 
        android:layout_width="fill_parent"          
        android:layout_height="fill_parent">
    </SurfaceView> 
</LinearLayout> 

在我们的代码中,为了将SurfaceView和Camera联系起来,我们需要把SurfaceHolder加进来。SurfaceHolder类扮演了Surface监视器的角色,通过回调接口,我们知道Surface什么时候创建,销毁,或者改变。SurfaceView类提供了函数getHolder,我们可以通过它方便地取得其Surface的SurfaceHolder。

这是获取布局XML中声明的SurfaceView,及其SurfaceHolder的代码片段。并且,它还将此Surface设置为Push型Surface,其意思是它的绘制缓冲区是由外部维护的。在这个例子中,缓冲区由 Camera 类管理的,Camera预览需要push型Surface。 

SurfaceView cameraView = (CameraView) this.findViewById(R.id.CameraView);  
SurfaceHolder surfaceHolder = cameraView.getHolder();  
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 

另外,我们要在activity里实现SurfaceHolder.Callback接口。这样,我们的activity就能在Surface被创建,改变和被销毁时得到通知。为实现这个回调,我们加入下列方法。

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {}  
public void surfaceCreated(SurfaceHolder holder) {}  
public void surfaceDestroyed(SurfaceHolder holder) {} 

最后,我需要告知SurfaceHolder用此activity作为其Callback的实现类。 

surfaceHolder.addCallback(this);

现在,我们的activity看起来应该像这样。

package com.apress.proandroidmedia.ch2.snapshot;  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.SurfaceHolder;  
import android.view.SurfaceView;  

public class SnapShot extends Activity implements SurfaceHolder.Callback {  
    SurfaceView cameraView;  
    SurfaceHolder surfaceHolder;  

    @Override  public void onCreate(Bundle savedInstanceState)  {  
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        cameraView = (SurfaceView) this.findViewById(R.id.CameraView);
        surfaceHolder = cameraView.getHolder();
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceHolder.addCallback(this);
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    }
    public void surfaceCreated(SurfaceHolder holder) {
    }
    public void surfaceDestroyed(SurfaceHolder holder) {
    }
} 

相机实现

现在,activity和预览Surface都已经就绪,我们可以开始使用Camera对象了。当Surface被创建时,因SorfaceHolder.Callback的缘故,触发surfaceCreated方法的调用。在该函数中,我们通过调用Camera的静态函数open,取得一个Camera对象。 

Camera camera;
public void surfaceCreated(SurfaceHolder holder) {  
    camera = Camera.open();

接着,我们将Camera的预览设置到由回调函数传入的SurfaceHolder中显示。调用设置函数需要包含在try catch块中,因为它可能会抛出一个IOException。如果真有异常抛出,我们要释放Camera,否则我们占用摄像头硬件资源,会影响其他应用使用。

    try 
    {
        camera.setPreviewDisplay(holder);  
    }  
    catch (IOException exception)  
    {  
        camera.release();
    }

最后,我们启动Camera预览。

    camera.startPreview();
}

同样,在surfaceDestoryed中,我们也要释放Camera。我们首先调用stopPreview,确保完成所有清理工作。 

public void surfaceDestroyed(SurfaceHolder holder) {
   camera.stopPreview();
   camera.release();
}

运行这段代码,你可能会发现预览画面有些奇怪。预览图像逆时针旋转了90度,如图2-1所示。


图2-1.  摄像头预览,旋转了90度

发生旋转的原因是Camera假定方向是水平或横向的。纠正旋转最简单的方法是让我们的activity以横向模式出现。为此,我们在activity的onCreate方法中增加下面的代码。

    @Override  
    public void onCreate(Bundle savedInstanceState)
   {
       super.onCreate(savedInstanceState);
       setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

现在,我们的相机预览正常显示了,如图2-2. 不幸的是,我们的应用停留在了横向模式


图2-2.  摄像头横向预览

设置摄像头参数

前面提到过,Camera类有一个嵌套类Camera.Parameters。这个类有一系列重要的属性或者设置可以用来改变Camera的运行。其中一个能马上帮助我们解决预览的旋转、横向问题。

相机使用的参数可以像下面这样修改: 

Camera.Parameters parameters = camera.getParameters();

parameters.set("some parameter", "some value");
// 或者
parameters.set("some parameter", some_int);

camera.setParameters(parameters);

有两种不同的通用的 Parameters.set 方法。第一个采用采用字符串作为参数名称和值,第二个采用字符串作名称但值是一个整数。

参数的设置位于surfaceCreated方法中,放在Camera创建并指定其预览Surface之后。这里演示了我们如何使用参数来请求相机纵向而非横向显示。

public void surfaceCreated(SurfaceHolder holder)
{
    camera = Camera.open();
    try {
        Camera.Parameters parameters = camera.getParameters();
        if (this.getResources().getConfiguration().orientation 
            !=  Configuration.ORIENTATION_LANDSCAPE)
       {
            // 这是一个没有文档说明,但是广为人知的功能
            parameters.set("orientation", "portrait");
          
            // Android 2.2 及以上版本
            //camera.setDisplayOrientation(90);

            // Android 2.0及以上,可取消注释
            //parameters.setRotation(90);
        }
        else 
        {
             // 这是一个没有文档说明,但是广为人知的功能
             parameters.set("orientation", "landscape"); 

             // Android 2.2 及以上版本
             //camera.setDisplayOrientation(0); 

             // Android 2.0及以上,可取消注释
             //parameters.setRotation(0);
        }
      
      camera.setParameters(parameters);
      camera.setPreviewDisplay(holder);  
    }
    catch (IOException exception)
    {
        camera.release();
        Log.v(LOGTAG,exception.getMessage());
    }
    camera.startPreview();
}

上述代码首先检查该设备的配置 (通过调用 Context.getResources().getConfiguration()) 来看看当前的方向是什么。如果不是横向,它将设置 Camera.Parameters 的 "orientation" 为 "portrait"。此外,调用 Camera.Parameters 的 setRotation 方法,传入参数90度。这个方法,包含在 API 级别 5 (版本 2.0)及以上版本,它实际没有做任何旋转 ;而是告诉 Camera,在 EXIF 数据中指定图像在显示时,旋转 90 度。如果不写这一行,在其他应用程序中查看此图像时,它可能会横向显示。

注意: 使用Camera.Parameter来修改Camera旋转属性的方法,针对的是Android 2.1 及以下版本。在 Android 2.2 版本,Camera类引入了一个新方法:setDisplayOrientation(int degrees)。这种方法接受一个整数做参数,表示图像应旋转的度数。有效的度数只有0、 90、 180、 270。

大多数可以或者应当修改的参数,都有特定的方法与之关联。我们可以从 setRotation 方法看到,它们遵循的 Java getter 和 setter 设计模式。例如,设置相机的闪光模式可以用getFlashMode (Camera.Parameters.FLASH_MODE_AUTO),获取当前值可以用 getFlashMode(),而不必通过通用Parameters.set 方法来完成。

从 Android 2.0开始,一个有趣的属性允许我们更换颜色效果。现在我们用它来做演示。它的Getter 和 setter 是 getColorEffect 和 setColorEffect。还有一个 getSupportedColorEffects 方法,返回一个字符串对象列表,表示指定设备支持的各种颜色效果。事实上,所有具有 getter 和 setter方法的属性都有跟这个类似的方法,用于确保在使用该功能之前,该功能可用。 

Camera.Parameters parameters = camera.getParameters();
List<String> colorEffects = parameters.getSupportedColorEffects();
Iterator<String> cei = colorEffects.iterator();


while (cei.hasNext())
{
    String currentEffect = cei.next();
    Log.v("SNAPSHOT","Checking " + currentEffect);


    if (currentEffect.equals(Camera.Parameters.EFFECT_SOLARIZE))
    {
        Log.v("SNAPSHOT","Using SOLARIZE");
        parameters.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE);
        break;
    }
}
Log.v("SNAPSHOT","Using Effect: " + parameters.getColorEffect());
camera.setParameters(parameters);

在上述代码中,我们首先通过 getSupportedColorEffect 方法查询 Camera.Parameters 对象支持哪些效果。然后我们使用一个迭代器来遍历效果列表,检查其中是否有我们想要的效果,这个例子中我们要的是 Camera.Parameters.EFFECT_SOLARIZE。如果它出现在列表中,那么表示 Camera 是支持它的。我们可以继续向前,调用 Camera.Parameters 对象的setColorEffect方法,传入曝光过度参数。图 2-3 显示 Camera.Parameters.EFFECT_SOLARIZE 生效。


图 2-3. 摄像头过度曝光预览图像

其他可能的效果也作为常量列在 Camera.Parameter 类中:

  • EFFECT_NONE 
  • EFFECT_MONO
  • EFFECT_NEGATIVE
  • EFFECT_SOLARIZE
  • EFFECT_SEPIA
  • EFFECT_POSTERIZE
  • EFFECT_WHITEBOARD
  • EFFECT_BLACKBOARD
  • EFFECT_AQUA

还存在类似常量, 分别用于antibanding、 闪光模式、 焦点模式、 场景模式和白平衡。


Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 1,,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。