更改相机预览大小
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版本上的部分,在注释中做了声明。
package com.apress.proandroidmedia.ch2.snapshot;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.content.ContentValues;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore.Images.Media;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
public class SnapShot extends Activity implements OnClickListener,
SurfaceHolder.Callback, Camera.PictureCallback
{
SurfaceView cameraView;
SurfaceHolder surfaceHolder;
Camera camera;
@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);
cameraView.setFocusable(true);
cameraView.setFocusableInTouchMode(true);
cameraView.setClickable(true);
cameraView.setOnClickListener(this);
}
public void onClick(View v)
{
camera.takePicture(null, null, this);
}
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)
{
Toast t = Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT);
t.show();
}
catch (IOException e)
{
Toast t = Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT);
t.show();
}
camera.startPreview();
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
{
camera.startPreview();
}
public void surfaceCreated(SurfaceHolder holder)
{
camera = Camera.open();
try {
camera.setPreviewDisplay(holder);
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);
}
// 效果, 用于Android 2.0 及以上版本
List colorEffects = parameters.getSupportedColorEffects();
Iterator cei = colorEffects.iterator();
while (cei.hasNext()) {
String currentEffect = cei.next();
if (currentEffect.equals(Camera.Parameters.EFFECT_SOLARIZE))
{
parameters.setColorEffect(Camera.Parameters.EFFECT_SOLARIZE);
break;
}
}// End 效果, 用于Android 2.0 及以上版本
camera.setParameters(parameters);
}
catch (IOException exception)
{
camera.release();
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
camera.stopPreview();
camera.release();
}
} // End the Activity