Android多媒体开发 Pro Android(安卓)Media 第二章 创建自定义相机应用 2
更改相机预览大小
Camera.Parameters中另一个特别有用设置是设置预览大小。正如使用其他设置,我们首先要查询的参数对象,取得其支持列表。取得预览尺寸列表之后,我们遍历它,以确保在设置之前,我们想要设置的大小是相机支持的。
在本示例中,我们不设定精确的预定尺寸,而选择一个设备支持,最接近且不大于预定值的尺寸。图 2-4 显示了此示例的输出。
...public static final int LARGEST_WIDTH = 200;public static final int LARGEST_HEIGHT= 200;...
如同所有的 Camera.Parameters,我们在SurfaceCreated中,打开相机和设置其预览显示Surface之后,获取和设置他们的值。
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,说明我们没能找到符合我们需要的尺寸,又或者Camera只支持一个固定的预览大小,这两种情况,我们不做处理。如果他们的值不为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);
设置参数之后,剩下的就是收尾工作。
} catch (IOException exception) { camera.release(); }}
图2-4. 相机以小尺寸预览
捕获和保存图像
使用Camera类捕获图像,我们得调用takePicture方法。此方法接收三个或四个参数,都为回调函数。使用takePicture方法的最简单形式是所有参数都设为 null。但是在拍摄了图像之后,没法得到图像的引用。至少要实现其中的一个回调函数。最安全的是 Camera.PictureCallback.onPictureTaken。当图像采集压缩就绪后,它一定会被调用。为此,我们让我们的activity实现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 记录中存储的元数据,我们可以用一个新的 ContentValues 对象来更新,如第 1 章中所述。
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。为此,我们让预览屏幕变为可点击,在 onClick 方法中,我们进行拍摄。
我们让我们的activity实现 OnClickListener 并设置surfaceView 的 onClickListener 为activity。然后,通过setClickable(true),我们让SurfaceView成为可点击的。此外,我们需要使 SurfaceView 可获得焦点。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); }
其他Camera回调方法
除了Camera.PictureCallback,还有其他一些回调方法也值得提出来。
Camera.PreviewCallback: 定义了方法onPreviewFrame(byte[] data, Camera camera),当预览帧就绪时被调用。函数传入一字节数组,包含图像当前的像素值。Camera有三种使用此回调的方法。
setPreviewCallback(Camera.PreviewCallback): 使用此方法注册Camera.PreviewCallback,可以确保每当一个新的预览帧就绪调用onPreviewFrame,并显示在屏幕上 。传递给 onPreviewFrame 的数据字节数组,最可能是YUV格式。不幸的是,直到Android 2.2,才配备了 YUV 格式解码器 (YuvImage) ;在以前版本中,解码必须手工完成。
setOneShotPreviewCallback(Camera.PreviewCallback): 使用此方法注册 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和MERA_ERROR_SERVER_DIED.
Camera.OnZoomChangeListener: 定义了方法onZoomChange方法,当正在进行或完成"平滑缩放" (缓慢放大或缩小)时被调用。在 Android 2.2(API 级别 8)中介绍了这个类及其方法。
Camera.ShutterCallback: 定义 onShutter 方法,在拍摄图像时被调用。
代码整合
让我们完成整个示例。下面的代码要运行在Android 2.2 及以上版本,但做细小修改,它可以在 1.6 及更高版本上运行。需要运行在1.6版本上的部分,在注释中做了声明。
这应该涵盖了建立一个自定义的基于camera的应用程序的基本知识。接下来,让我们看看如何扩展此应用程序,实现内置相机应用程序中不存在的功能。
更多相关文章
- Android进阶——阿里Android开发手册学习笔记(一)
- AIDL --- Android中的远程接口[转]
- Android的icon设计指南
- Android菜鸟的成长笔记(4)——你真的理解了吗?
- Android研究之英特尔 Android* 开发人员指南上的对等应用详解
- android中基于网络和GPS的不同精度定位
- 一个现有Android工程作为组件加入到另一个Android工程最简便方法
- Android(安卓)仿淘宝选中商品不同尺寸的按钮组(一)
- Android深入学习之各种隐私权限判断和获取方法总结