• 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方法,当捕获图像时立刻调用它。

      

  • 相关阅读:
    Ubuntu 14.04 设置静态IP
    Spring MVC 入门就这一篇
    一站式轻量级框架 Spring
    深入 Web 请求过程
    使用 Docker Compose 搭建 Nexus 依赖私服及使用配置
    Spring Cloud Alibaba 与 Dubbo 的完美融合
    使用 Spring Cloud Alibaba Nacos Config 作为配置中心
    使用 Spring Cloud Alibaba Nacos 实现服务注册与发现
    Spring Boot 监听 Redis Key 失效事件实现定时任务
    使用 Spring Boot Admin 监控服务
  • 原文地址:https://www.cnblogs.com/ZSS-Android/p/3927706.html
Copyright © 2020-2023  润新知