既然已经建立了活动及预览Surface,现在我们准备好开始使用实际的Camera对象。
当创建Surface时,由于SurfaceHolder.Callback的存在,它将在代码中的触发调用surfaceCreated方法。此时可以通过调用Camera类上的静态方法open获得Camera对象。
Camera camera;
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
随后,我们想要将预览显示设置为正在使用的SurfaceHolder,它通过回调提供给我们的方法。需要将该方法包装在try...catch块中,因为它可能会抛出IOException。如果发生了这种情况,那么我们会希望释放该Camera对象;否则,它将绑定摄像头的硬件资源,使其不能用于其他应用程序。
try
{
camera.setPreviewDisplay(holder);
}
catch (IOException exception)
{
camera.release();
}
最后,启动摄像头预览。
camera.startPreview();
}
相应地,在surfaceDestroyed中也需要释放该Camera对象。我们将首先调用stopPreview,以确保应该释放的资源都被清理。
public void surfaceDestroyed(SurfaceHolder holder) {
camera.stopPreview();
camera.release();
}
运行这段代码,您可能会发现预览有些奇怪。它会逆时针旋转预览图像90°,如图2-1所示。
产生这种旋转的原因是Camera对象假定方向是水平或横向模式。修正旋转的最简单方法是使活动以横向模式显示。为此,可以在活动的onCreate方法中添加以下代码。
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
现在摄像头预览将会正确地显示,如图2-2所示。但是,我们的应用程序现在被限定在横向模式。
1. 设置Camera对象的参数
前面提及,Camera类有一个嵌套的Camera.Parameters类。这个类有一系列重要的属性或设置,可以用来改变Camera对象运作的方式。其中一个现在能够帮助我们的参数可用来处理在预览时遇到的旋转/横向问题。
可以对Camera对象使用的Parameters做如下修改:
Camera.Parameters parameters = camera.getParameters();
parameters.set("some parameter", "some value");
// 或者
parameters.set("some parameter", some_int);
camera.setParameters(parameters);
此处有两个不同的通用Parameters.set方法。第一个方法的参数名称和值都采用字符串,而第二个方法的参数名称为字符串,但是值为整数。
应该在创建Camera对象和指定它的预览Surface之后立即在surfaceCreated方法中设置Parameters。
以下代码展示了如何使用Parameters请求Camera对象采用纵向方向而非横向方向。
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.2及以上版本取消注释
//parameters.setRotation(90);
} else {
//这是一个众所周知但未文档化的特性
parameters.set("orientation", "landscape");
//对于Android 2.2及以上版本
//camera.setDisplayOrientation(0);
//对于Android 2.2及以上版本取消注释
//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 Level 5(2.0版)和更高版本上可用,它实际上并不执行任何旋转;相反,它会告诉Camera对象在EXIF数据中指定该图像应该旋转90°显示。如果没有包含该信息,那么在其他应用程序中查看该图像时,它可能会侧面显示。
注意:以上所示的通过使用Camera.Parameters修改Camera对象旋转的方法用于Android 2.1和更早的版本。在Android 2.2中引入了Camera类的一个新方法setDisplayOrientation (int degrees)。该方法接受一个整数,表示图像应该旋转的度数。有效的度数为0、90、180和270。
大多数可以或应该修改的参数都有与它们相关联的特定方法。如同我们所看到的setRotation方法一样,这些方法遵循Java的获取器和设置器设计模式。例如,可以使用setFlashMode(Camera.Parameters.FLASH_MODE_AUTO)来设置Camera对象的闪光灯模式,同时可以使用getFlashMode()获得它的当前值,而无须使用通用的Parameters.set方法。
从Android 2.0开始,存在一个可用于展示的有趣参数,使用该参数可以修改颜色效果。对应的获取器和设置器方法是getColorEffect和setColorEffect。同时还存在一个getSupportedColorEffects方法,它返回一个String对象的列表,对应特定设备上所支持的各种效果。事实上,这种方法对于所有具有获取器和设置器方法的参数都存在,用于在使用某个功能之前确保所请求的功能是可用的。
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);
在上述代码中,首先查询Camera.Parameters对象,以通过getSupportedColorEffects方法查看所支持的效果列表。然后,使用迭代器循环查询该效果列表,并判断其中是否有一个效果能够匹配我们想要的效果,在当前情况下是Camera.Parameters.EFFECT_SOLARIZE。如果该效果出现在列表中,那么它是获得支持的,我们可以继续操作,在Camera.Parameters对象上调用setColorEffect,并传入EFFECT_SOLARIZE常量。图2-3显示了使用中的Camera. Parameters.EFFECT_SOLARIZE效果。
其他可能的效果也以常量的形式在Camera.Parameters类中列出。
● EFFECT_NONE
● EFFECT_MONO
● EFFECT_NEGATIVE
● EFFECT_SOLARIZE
● EFFECT_SEPIA
● EFFECT_POSTERIZE
● EFFECT_WHITEBOARD
● EFFECT_BLACKBOARD
● EFFECT_AQUA
还存在用于抗条带(antibanding)、闪光灯模式(flash mode)、聚焦模式(focus mode),情景模式(scene mode)及白平衡(white balance)等参数的类似常量。
2. 更改摄像头预览大小
另一个在Camera.Parameters中特别有用的设置是能够设置预览大小。与其他的设置一样,首先将查询参数对象并获得所支持的值。在获得所支持的大小列表之后,就可以在设置之前通过遍历它来确保所想要的大小是否获得支持。
在这个示例中,我们不是指定一个精确的大小,而是选择接近但不超过一对常量的大小。图2-4展示了这个示例的输出。
...
public static final int LARGEST_WIDTH = 200;
public static final int LARGEST_HEIGHT= 200;
...
与所有的Camera.Parameters一样,在已经打开Camera对象并设置它的预览显示Surface之后,就可以在surfaceCreated中获取和设置它们。
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
try {
camera.setPreviewDisplay(holder);
Camera.Parameters parameters = camera.getParameters();
我们将采用以下两个变量来记录小于但最接近上述常量的值。
int bestWidth = 0;
int bestHeight = 0;
然后,就可以获得设备所支持的所有大小的列表。这将返回一个Camera.Size对象的列表,可以对其进行循环遍历。
List<Camera.Size> previewSizes = parameters.
getSupportedPreviewSizes();
if (previewSizes.size() > 1)
{
Iterator<Camera.Size> cei = previewSizes.iterator();
while (cei.hasNext())
{
Camera.Size aSize = cei.next();
如果该列表中的当前大小大于保存的最佳大小,并且小于或等于LARGEST_WIDTH和LARGEST_HEIGHT常量,那么将在bestWidth和bestHeight变量中保存这个高度和宽度并继续检查。
Log.v("SNAPSHOT","Checking " + aSize.width + " x "
+ aSize.height);
if (aSize.width > bestWidth && aSize.width <= LARGEST_WIDTH
&& aSize.height > bestHeight
&& aSize.height <= LARGEST_HEIGHT) {
// 迄今为止,它是最大的大小,且不超过屏幕尺寸
bestWidth = aSize.width;
bestHeight = aSize.height;
}
}
在遍历完所有支持的大小之后,必须确保获得了所需要的值。如果bestHeight和bestWidth变量等于0,那么没有发现任何与我们的需要相匹配的大小,或者只存在一种支持的大小,从而不应采取任何操作。反之,如果它们有值,那么将使用bestWidth和bestHeight变量调用Camera.Parameters对象上的setPreviewSize方法。
另外,还需要告诉摄像头预览SurfaceView对象(即cameraView)以该大小进行显示。如果不这么做,那么SurfaceView不会改变大小,且来自摄像头的预览图像会扭曲或质量非常低。
if (bestHeight != 0 && bestWidth != 0) {
Log.v("SNAPSHOT", "Using " + bestWidth + " x " + bestHeight);
parameters.setPreviewSize(bestWidth, bestHeight);
cameraView.setLayoutParams(new LinearLayout.LayoutParams(
bestWidth, bestHeight));
}
}
camera.setParameters(parameters);
在设置该参数之后,剩余的工作就是关闭surfaceCreated方法。
} catch (IOException exception) {
camera.release();
}
}
3. 捕获和保存图像
要采用Camera类捕获图像,必须调用takePicture方法。该方法接受3个或4个参数,所有这些参数都是回调方法。takePicture方法的最简单形式是将所有的参数都设置为null。尽管能够捕获照片,但是不能获得它的引用。因此,至少应该实现一种回调方法。一种最安全的回调方法是Camera.PictureCallback.onPictureTaken。它确保会被调用,并且在压缩图像时被调用。为了利用该方法,我们将在活动中实现Camera.PictureCallback,并添加一个onPictureTaken方法。
public class SnapShot extends Activity implements
SurfaceHolder.Callback, Camera.PictureCallback {
public void onPictureTaken(byte[] data, Camera camera) {
}
该onPictureTaken方法有两个参数:第一个是实际的JPEG图像数据的字节数组,第二个是捕获该图像的Camera对象的引用。
由于给定了实际的JPEG数据,因此为了保存它,只需要将其写入磁盘的某个位置。正如我们已经知道的那样,可以利用MediaStore指定它的位置和元数据。
当执行onPictureTaken方法时,可以调用Camera对象上的startPreview。当调用takePicture方法时预览已经自动暂停,并且这个方法会告诉我们,现在可以安全地重新启动它。
public void onPictureTaken(byte[] data, Camera camera) {
Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_
CONTENT_URI, new ContentValues());
try {
OutputStream imageFileOS = getContentResolver().
openOutputStream(imageFileUri);
imageFileOS.write(data);
imageFileOS.flush();
imageFileOS.close();
}catch (FileNotFoundException e) {
} catch (IOException e) {
}
camera.startPreview();
}
上述的代码片段向MediaStore中插入了一条新记录,并返回一个URI。然后,利用这个URI可以获得一个OutputStream,用于写入JPEG数据。这将在MediaStore指定的位置中创建一个文件,并将它链接到新的记录。
如果后面想要更新存储在MediaStore记录中的元数据,那么如同第1章所描述的一样,可以利用一个新的ContentValues对象对记录进行更新。
ContentValues contentValues = new ContentValues(3);
contentValues.put(Media.DISPLAY_NAME, "This is a test title");
contentValues.put(Media.DESCRIPTION, "This is a test description");
getContentResolver().update(imageFileUri,contentValues,null,null);
最后,必须实际调用Camera.takePicture。为此,需要设置预览屏幕为“可单击(clickable)”,同时在onClick方法中完成照相。
在活动中将实现一个OnClickListener,并设置SurfaceView的onClickListener为活动本身。然后,使用setClickable(true)设置SurfaceView为“可单击”。另外,需要设置SurfaceView为“可聚焦(focusable)”。默认情况下SurfaceView不可聚焦,因此必须使用setFocusable(true)对它进行显式的设置。同样,当处于“触摸模式”时,通常会禁用焦点,所以必须使用setFocusInTouchMode(true)对其进行显式的设置,使这种情况不会发生。
public class SnapShot extends Activity implements OnClickListener,
SurfaceHolder.Callback, Camera.PictureCallback {
...
public void onCreate(Bundle savedInstanceState) {
...
cameraView.setFocusable(true);
cameraView.setFocusableInTouchMode(true);
cameraView.setClickable(true);
cameraView.setOnClickListener(this);
}
public void onClick(View v) {
camera.takePicture(null, null, null, this);
}
4. 其他的Camera回调方法
除了Camera.PictureCallback之外,还有其他一些值得提及的回调方法。
● Camera.PreviewCallback:定义了onPreviewFrame(byte[] data, Camera camera) 方法,当存在预览帧(preview frame)时调用该方法。可以传入保存当前图像像素的字节数组。在Camera对象上,有3种不同的方式使用这个回调:
• setPreviewCallback(Camera.PreviewCallback):使用此方法注册一个Camera. PreviewCallback,这将确保在屏幕上显示一个新的预览帧时调用onPreviewFrame方法。传递到onPreviewFrame方法中的数据字节数组最有可能采用YUV格式。但是,Android 2.2是第一个包含了YUV格式解码器(YuvImage)的版本;在以前的版本中,必须手动完成解码。
• setOneShotPreviewCallback(Camera.PreviewCallback):利用Camera对象上的这个方法注册Camera.PreviewCallback,从而当下一幅预览图像可用时调用一次onPreviewFrame。同样,传递到onPreviewFrame方法的预览图像数据最有可能采用YUV格式。可以通过使用ImageFormat中的常量检查Camera. getParameters(). getPreviewFormat()返回的结果来确定这一点。
• setPreviewCallbackWithBuffer(Camera.PreviewCallback):在Android 2.2中引入了该方法,其与setPreviewCallback的工作方式相同,但要求指定一个字节数组作为缓冲区,用于预览图像数据。这是为了能够更好地管理处理预览图像时使用的内存。
● Camera.AutoFocusCallback:定义了onAutoFocus方法,当完成一个自动聚焦活动时调用它。通过传入此回调接口的一个实例,在调用Camera对象上的autoFocus方法时会触发自动聚焦。
● Camera.ErrorCallback:定义了onError方法,当发生一个Camera错误时调用它。有两个常量可用于与传入的错误代码进行比较:CAMERA_ERROR_UNKNOWN和CAMERA_ERROR_SERVER_DIED。
● Camera.OnZoomChangeListener:定义了onZoomChange方法,当正在进行或完成“平滑缩放”(慢慢缩小或放大)时调用它。在Android 2.2 (API Level 8)中引入了这个类和方法。
● Camera.ShutterCallback:定义了onShutter方法,当捕获图像时立刻调用它。