Android多媒体开发 Pro Android Media 第二章 创建自定义相机应用 1
幸运的是,使用内置应用并不是访问摄像头的唯一途径。底层硬件的开放程度以及系统提供的访问方法,对我们和相机应用来说是一样的,我们可以在任意类型的应用中使用这些功能。
在这一章,我们将使用底层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、 闪光模式、 焦点模式、 场景模式和白平衡。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。