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