2.1.3 实现Camera对象

    既然已经建立了活动季玉兰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方法,当捕获图像时立刻调用它。

  

 

posted on 2014-08-21 17:30  宁静致远,一览众山小  阅读(446)  评论(0编辑  收藏  举报

导航