• 我们在autoFocus功能分析一文中分析了如何启动自动对焦,也在App层focus or face detection界面显示分析中阐述了界面是如何更新的,但是在二者之间有重要的一环还缺失,就是自动对焦成功的是如何被应用捕捉并相应...

    我们在autoFocus功能分析一文中分析了如何启动自动对焦,也在App层focus or face detection界面显示分析中阐述了界面是如何更新的,但是在二者之间有重要的一环还缺失,就是自动对焦成功的是如何被应用捕捉并相应的呢?本文就对此简单分析。
    首先,在自动对焦开始的同时,应用就设定了回调监听类的实例。

    @Override

        public void autoFocus() {

            mFocusStartTime = System.currentTimeMillis();

            mCameraDevice.autoFocus(mAutoFocusCallback);

            setCameraState(FOCUSING);

        }

    当相机对焦完成,就会触发监听类中的回调函数。

    private final class AutoFocusCallback

               implements android.hardware.Camera.AutoFocusCallback {

           public void onAutoFocus(

                   boolean focused, android.hardware.Camera camera) {

               if (mPausing) return;

     

               mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;

               Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");

               setCameraState(IDLE);

               mFocusManager.onAutoFocus(focused);

           }

    }

    进一步调用FocusManager.java的onAutoFocus函数。

    public void onAutoFocus(boolean focused) {

            if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {

                // Take the picture no matter focus succeeds or fails. No need

                // to play the AF sound if we're about to play the shutter

                // sound.

                if (focused) {

                    mState = STATE_SUCCESS;

                } else {

                    mState = STATE_FAIL;

                }

                updateFocusUI();

                capture();

            } else if (mState == STATE_FOCUSING) {

                // This happens when (1) user is half-pressing the focus key or

                // (2) touch focus is triggered. Play the focus tone. Do not

                // take the picture now.

                if (focused) {

                    mState = STATE_SUCCESS;

                    mListener.playSound(CameraSound.FOCUS_COMPLETE);

                } else {

                    mState = STATE_FAIL;

                }

                updateFocusUI();

                // If this is triggered by touch focus, cancel focus after a

                // while.

                if (mFocusArea != null) {

                    mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);

                }

            } else if (mState == STATE_IDLE) {

                // User has released the focus key before focus completes.

                // Do nothing.

            }

    }     

     

    在此函数中会根据是根据何种方式启动的自动对焦而拍照或显示自动对焦完成。

     

    1.   Ontouch对焦:对焦成功后会跟新界面并且保持3秒

    private static final int RESET_TOUCH_FOCUS_DELAY = 3000;

     

    private class MainHandler extends Handler {

            @Override

            public void handleMessage(Message msg) {

                switch (msg.what) {

                    case RESET_TOUCH_FOCUS: {

                        cancelAutoFocus();

                        mListener.startFaceDetection();

                        break;

                    }

                }

            }

        }

    2.       如果是shutterdown对焦成功后会更新界面,然后拍照,拍照完成后重置对焦。

    FocusManager.java

    private void capture() {

            if (mListener.capture()) {

                mState = STATE_IDLE;

                mHandler.removeMessages(RESET_TOUCH_FOCUS);

            }

        }

     

     Camera.java

     @Override

        public boolean capture() {

            // If we are already in the middle of taking a snapshot then ignore.

            if (mCameraState == SNAPSHOT_IN_PROGRESS || mCameraDevice == null) {

                return false;

            }

            mCaptureStartTime = System.currentTimeMillis();

            mPostViewPictureCallbackTime = 0;

            mJpegImageData = null;

     

            // Set rotation and gps data.

            Util.setRotationParameter(mParameters, mCameraId, sOrientation);

            Location loc = mLocationManager.getCurrentLocation();

            Util.setGpsParameters(mParameters, loc);

            mCameraDevice.setParameters(mParameters);

     

            mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback,

                   mPostViewPictureCallback, new JpegPictureCallback(loc));

            mFaceDetectionStarted = false;

            setCameraState(SNAPSHOT_IN_PROGRESS);

            return true;

        }

     

     

    private final class ShutterCallback

                implements android.hardware.Camera.ShutterCallback {

            public void onShutter() {

                mShutterCallbackTime = System.currentTimeMillis();

                mShutterLag = mShutterCallbackTime - mCaptureStartTime;

                Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");

                mFocusManager.onShutter();

            }

        }

     FocusManager.java

    public void onShutter() {

            resetTouchFocus();

            updateFocusUI();

        }

     

      public void resetTouchFocus() {

            if (!mInitialized) return;

     

            // Put focus indicator to the center.

            RelativeLayout.LayoutParams p =

                    (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout.getLayoutParams();

            int[] rules = p.getRules();

            rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE;

            p.setMargins(0, 0, 0, 0);

     

            mFocusArea = null;

            mMeteringArea = null;

        }

        }

          

    展开全文
  • Android相机开发那些坑Android开发实践:掌握Camera的预览方向和拍照方向Android 开发之解决相机预览上下颠倒问题【腾讯优测干货分享】Android 相机预览方向及其适配探索关于Android Camera几点须知在Android相机...

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/53350551 文章出自:薛瑄的博客

    在Android相机开发实际开发过程中遇到了不少问题,在网上找了这些资料,但是感觉如果没有经历过Android相机开发开发,直接看这些还是有点太抽象,建议参考一些代码来学习下面的内容

    由于之前没有接触过Android相机开发,所以在整个开发过程中踩了不少坑,费了不少时间和精力。这篇文章总结了Android相机开发的相关知识、流程,以及容易遇到的坑,希望能帮助今后可能会接触Android相机开发的朋友快速上手,节省时间,少走弯路。

    一.Android中开发相机应用的两种方式

    Android系统提供了两种使用手机相机资源实现拍摄功能的方法,

    • 一种是直接通过Intent调用系统相机组件,这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。
    • 另一种是使用相机API来定制自定义相机,这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。

    这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的

    二.相机API中关键类解析

    通过相机API实现拍摄功能涉及以下几个关键类和接口:

    2.1、SurfaceView:用于绘制相机预览图像的类,展现实时的预览图像。

    • 普通的view以及派生类都是共享同一个surface的,所有的绘制都必须在UI线程中进行。 surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。

    • surfaceview是一种比较特殊的view,它并不与其他普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同时处理其他交互逻辑,因此对view的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。

    2.2、SurfaceHolder:控制surface的一个抽象接口

    它能够控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview通过getHolder()方法获得surfaceholder 实例,通过后者管理监听surface 的状态。

    2.3、SurfaceHolder.Callback接口:负责监听surface状态变化的接口,

    有三个方法:

    • surfaceCreated(SurfaceHolder holder):在surface创建后立即被调用。在开发自定义相机时,可以通过重载这个函数调用camera.open()、camera.setPreviewDisplay(),来实现获取相机资源、连接camera和surface等操作。

    • surfaceChanged(SurfaceHolder holder, int format, int width, int height):在surface发生format或size变化时调用。在开发自定义相机时,可以通过重载这个函数调用camera.startPreview来开启相机预览,使得camera预览帧数据可以传递给surface,从而实时显示相机预览图像。

    • surfaceDestroyed(SurfaceHolder holder):在surface销毁之前被调用。在开发自定义相机时,可以通过重载这个函数调用camera.stopPreview(),camera.release()来实现停止相机预览及释放相机资源等操作。

    2.4、Camera:最主要的类,用于管理和操作camera资源。

    它提供了完整的相机底层接口,支持相机资源切换,设置预览/拍摄尺寸,设定光圈、曝光、聚焦等相关参数,获取预览/拍摄帧数据等功能,主要方法有以下这些:

    • open():获取camera实例。

    • setPreviewDisplay(SurfaceHolder):通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。

    • setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。

    • startPreview():开始预览,将camera底层硬件传来的预览帧数据显示在绑定的surface上。

    • stopPreview():停止预览,关闭camra底层的帧数据传递以及surface上的绘制。

    • release():释放Camera实例

    • takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。

    三.自定义相机的开发过程

    定制一个自定义相机应用,通常需要完成以下步骤,其流程图如图1所示:

    • 检测并访问相机资源 检查手机是否存在相机资源,如果存在,请求访问相机资源。
    • 创建预览类 创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。此类能够显示相机的实时预览图像。
    • 建立预览布局 有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起。
    • 设置拍照监听器 给用户界面控件绑定监听器,使其能响应用户操作(如按下按钮), 开始拍照过程。
    • 拍照并保存文件 将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片。
    • 释放相机资源 相机是一个共享资源,必须对其生命周期进行细心的管理。当相机使用完毕后,应用程序必须正确地将其释放,以免其它程序访问使用时,发生冲突。
      这里写图片描述

    四. 开发过程遇到的一些坑

    下面再讲讲我在开发自定义相机时踩过的一些坑:

    1.相机预览方向适配问题的产生

    1.1、相机的安装方向如何获取?

    Android官方提供orientation这个属性:表示相机采集的图像顺时针旋转orientation度后才能与到设备自然方向一致。

    假设设备是竖屏显示。后置相机传感器是横屏安装的。

    这里写图片描述

    你面向屏幕时,如果后置相机传感器所采集的图像的上边是在设备自然方向的右边,则后置相机的orientation是90。

    如果前置相机传感器所采集的图像的上边是在设备自然方向的右边,则前置相机的orientation是270。

    这个值,不同的设备有所差异,但大多数都是这样的值。

    1.2、Activity设为竖屏时,SurfaceView预览图像为什么是逆时针旋转90度?

    说明这个问题之前,先介绍下Android手机上几个方向的概念:

    屏幕坐标:在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向。

    自然方向(natrual orientation)

    每个设备都有一个自然方向,手机和平板的自然方向不同。Android:screenOrientation的默认值unspecified即为自然方向。
    关于orientation的两个常见值是这样定义的:
    landscape(横屏):the display is wider than it is tall,正常拿着设备的时候,宽比高长,这是平板的自然方向。
    portrait(竖屏):the display is taller than it is wide,正常拿设备的时候,宽比高短,这是手机的自然方向。

    图像传感器(Image Sensor)方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,如下图2所示,坐标原点位于手机横放时的左上角,即与横屏应用的屏幕X方向一致。换句话说,与竖屏应用的屏幕X方向呈90度角。

    这里写图片描述

    相机的预览方向:将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,与图像传感器方向一致。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器方向一致。

    下面是Camera.setDisplayOrientation的注释文档:

    /**
     * Set the clockwise rotation of preview display in degrees. This affects
     * the preview frames and the picture displayed after snapshot. This method
     * is useful for portrait mode applications. Note that preview display of
     * front-facing cameras is flipped horizontally before the rotation, that
     * is, the image is reflected along the central vertical axis of the camera
     * sensor. So the users can see themselves as looking into a mirror.
     *
     * <p>This does not affect the order of byte array passed in {@link
     * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
     * method is not allowed to be called during preview.     
     */
    
    public native final void setDisplayOrientation(int degrees);

    注释中的第二段,描述了这个API修改的仅仅是Camera的预览方向而已,并不会影响到PreviewCallback回调、生成的JPEG图片和录像文件的方向,这些数据的方向依然会跟图像Sensor的方向一致。

    注意:设置预览方向并不会改变拍出照片的方向

    • 对于横屏(这个activity设置的是横屏显示)来说,由于屏幕方向和预览方向一致,预览图像和看到的实物方向一致。
    • 对于竖屏(这个activity设置的是竖屏显示),屏幕方向和预览方向垂直,所以会出现颠倒90度现象,无论怎么旋转手机,显示在UI预览界面的画面与人眼看到的实物始终成90度(UI预览界面逆时针转了90度)。为了得到一致的预览画面,需要将相机的预览方向旋转90(setDisplayOrientation(90)),保持与屏幕方向一致,

    如图3所示(红色箭头表示相机预览的x轴方向,蓝色箭头表示屏幕的x轴方向)。

    这里写图片描述

    看到这里,相信你肯定有疑问,按照这样旋转的话,看到的UI预览图应该是顺时针旋转90度才对啊,但为什么看到的是逆时针旋转90度

    举例

    下图是MI3手机的屏幕“自然”方向和后置相机的图像传感器方向。

    后置相机的orientation是90

    这里写图片描述

    1、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系逆时针旋转90度,才能显示到屏幕的坐标系上,
    2、于是看到的图像逆时针旋转了90度,因此需要顺时针旋转90度(setDisplayOrientation(90)),才能与手机的自然方向保持一致。

    这里写图片描述

    1.3、 前置摄像头的镜像效果

    Android相机硬件有个特殊设定,就是对于前置摄像头,

    • 在预览图像是真实场景的镜像

    • 拍出的照片和真实场景一样。

    以MI3为例

    通过程序调用取到的后置相机的orientation是90,前置相机的orientation是270。

    orientation可以理解为图像传感器方向顺时针旋转到屏幕自然方向的角度。

    下图所示是MI3手机的后置相机和前置相机对准同一个小人,后置前置相机采集到的图像、前置相机预览的图像。

    后置

    对于后置相机,相机预览的图像和相机采集到的图像是一样的。只需要旋转后置相机orientation度,即90度即可和屏幕方向保持一致;

    前置

    对于前置相机来说,相机预览的图像和相机采集到的图像是镜像关系, 注意下图小人头发在前置采集的图像和预览图像中头发是相反的。

    因此在MI3手机上做竖屏应用时,

    采集的图像:顺时针旋转270度后,与屏幕方向一致

    预览的图像:顺时针旋转90度后,与屏幕方向一致.(因为底层相机对采集的图像做了镜像处理,所以只需转90度)

    这里写图片描述

    1 、图像传感器获得到图像后,就知道了这幅图像每个坐标的像素值,但是要显示到屏幕上,就要按照屏幕的坐标系来显示,于是就需要将图像传感器的坐标系顺时针旋转90度,才能显示到屏幕的坐标系上,于是看到的图像顺时针旋转了90度

    2、也就是说前置摄像头获取到的图像(未经镜像处理)需要顺时针旋转270,才能和手机的自然方向一致(上图第二行)

    3、但是在预览的时候,做了镜像处理后,只需要顺时针旋转90度,就能和手机的自然方向一致。(如上图第三行):

    下图显示了,屏幕的坐标系和前置摄像头的坐标系

    这里写图片描述

    下图简单显示了,横屏和竖屏的情况

    这里写图片描述

    此外,由于拍摄图像并没有做水平翻转,所以对于前置摄像头拍出来的照片,用户会发现跟预览时所见的是左右翻转的。可以根据自己的需求进行处理。

    1.4、官方推荐的相机预览方向适配做法

    通过orientation属性的含义可以知道,我们可以用它和应用的方向来做相机预览方向的适配,下面代码是官方网站推荐的。

    但并不是所有手机的orientation值都靠谱,比如VIVO V1手机第一次获取后置相机的CameraInfo的orientation值是90,而当执行了mCamera = Camera.open();之后再获取CameraInfo的orientation值就是0,而且以后获取的都是 0 ,除非重启手机。无论是这款手机上的哪个应用,只要执行了一次Camera.open()之后,其他所有程序中获取CameraInfo的orientation都是是0。

    因此按照此方法做适配不能解决所有手机上的问题。

     public static void setCameraDisplayOrientation(Activity activity,
         int cameraId, android.hardware.Camera camera) {
         android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
         android.hardware.Camera.getCameraInfo(cameraId, info);
         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
         int degrees = 0;
         switch (rotation) {
             case Surface.ROTATION_0: degrees = 0; break;
             case Surface.ROTATION_90: degrees = 90; break;
             case Surface.ROTATION_180: degrees = 180; break;
             case Surface.ROTATION_270: degrees = 270; break;
         }
    
         int result;
         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
             result = (info.orientation + degrees) % 360;
             result = (360 - result) % 360;  // compensate the mirror
         } else {  // back-facing
             result = (info.orientation - degrees + 360) % 360;
         }
         camera.setDisplayOrientation(result);
     }

    2、Camera的拍照方向

    当你点击拍照按钮,得到的图片方向不一定与画面中预览的方向一致,这是因为拍摄的照片是将图像Sensor采集到的数据直接存储到SDCard上的,因此,Camera的拍照方向与上述的Camera的图像Sensor方向一致。

    为了演示这个问题,我用手机的Camera对同一个场景拍了两张照片,第一张是横着拿手机拍的,第二张是竖着拿手机拍的。然后用在电脑上打开得到的图片(实际场景中的杯子是竖着的),效果如下所示:

    这里写图片描述

    由此可见,如果横向拿手机拍照,由于正好与Camera的拍照方向一致,因此得到的照片是“正确”的;而竖着拿手机拍照的话,Camera的图像Sensor依然以上面描述的角度在采集图像并存储到SDCard上,所以得到的图片就是右图这样的,因为竖着拿手机正好与图像Sensor的方向相差了90度。由此,大家应该明白了为什么我们用手机拍出的照片经常需要旋转90度才能看到“正确”的画面了吧?

    3. SurfaceView预览图像、拍摄照片拉伸变形

    说明这个问题之前,同样先说一下几个跟相机有关的尺寸。

    • SurfaceView尺寸:即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里surfaceview显示的预览图像暂且称作手机预览图像。

    • Previewsize:相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。

    • Picturesize:相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。图4说明了以上几种图像及照片之间的关系。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍

    这里写图片描述

    下面说下我在开发过程中遇到的三种拉伸变形现象:

    1. 手机预览画面中物体被拉伸变形。
    2. 拍摄照片中物体被拉伸变形。
    3. 点击拍照瞬间,手机预览画面会停顿下,此时的图像是拉伸变形的,然后预览画面恢复后图像又正常了。

    现象1的原因是SurfaceView和Previewsize的长宽比率不一致。因为手机预览视图的图像是由相机预览图像根据SurfaceView大小缩放得来的,当长宽比不一致时必然会导致图像变形。

    后两个现象的原因则是Previewsize和Picturesize的长宽比率不一致所致,查了相关的资料,发现其具体原因跟某些手机相机硬件的底层实现有关。

    总之为了避免以上几种变形现象的发生,在开发时最好将SurfaceView、PreviewSize、PictureSize三个尺寸保证长宽比例一致。具体实现可以先通过camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获得相机硬件支持的所有预览和拍摄尺寸,然后在里面筛选出和SurfaceView的长宽比一致并且大小合适的尺寸,通过camera.setPrameters来更新设置。注意:市场上手机相机硬件支持的尺寸一般都是主流的4:3或者16:9,所以SurfaceView尺寸不能太奇葩,最好也设置成这样的长宽比。

    4. 各种crash

    这里写图片描述

    这里写图片描述

    这里写图片描述

    前两个Crash的原因是:相机硬件在聚焦和拍照前必须要保证已经连接到surface,并且开启相机预览,surface有收到预览数据。如果在还没有执行camera. setPreviewDisplay或者未调用camera. startPreview之前,就调用camera.autofocus或camera.takepicture,就会出现这个运行时异常。

    对应到自定义相机的代码中,要注意在拍照按钮事件响应中执行camera.autofocus或camera.takepicture前,一定要检验camera有没有设置预览Surfaceview并开启了相机预览。这里有个方法可以判断预览状态:Camera.setPreviewCallback是预览帧数据的回调函数,它会在SurfaceView收到相机的预览帧数据时被调用,因此在里面可以设置是否允许对焦和拍照的标志位。

    这里写图片描述

    还有一点要注意,camera.takePicture()在执行过程中会执行camera.stopPreview来获取拍摄帧数据,表现为预览画面卡住,而如果此时用户点击了按钮的话,也就是调用camera.takepicture,也会出现上面的crash,因此在开发时,可能还需要屏蔽拍照按钮的连续点击。
    第三个crash则涉及图像的裁剪,由于要支持1:1或者4:3尺寸镜头,所以会需要对预览视图进行裁剪,由于是竖屏应用,所以裁剪区域的坐标系跟相机传感器方向是成90度角的,表现在裁剪里就是,屏幕上的x方向,对应在拍摄图像上是高度方向,而屏幕上的y方向,对应到拍摄图像上则是宽度方向。因此在计算时要一定注意坐标系的转换以及越界保护。

    5. 锁屏下相机资源的释放问题

    为了节省手机电量,不浪费相机资源,在开发的自定义相机里,如果预览图像已不需要显示,如按Home键盘切换后台或者锁屏后,此时就应该关闭预览并把相机资源释放掉。

    参考官方API文档,

    • 当surfaceView变成可见时,会创建surface并触发surfaceHolder.callback接口中surfaceCreated回调函数。

    • 而surfaceview变成不可见时,则会销毁surface,并触发surfacedestroyed回调函数。

    我们可以在对应的回调函数里,处理相机的相关操作,如连接surface、开启/关闭预览。 至于相机资源释放,则可以放在Acticity的onpause里执行。相应的,要重新恢复预览图像时,可以把相机资源申请和初始化放在Acticity的onResume里执行,然后通过创建surfaceview,将camera和surface相连并开启预览。
    这里写图片描述

    但是在开发过程中发现,对于按HOME键切后台场景,程序可以正常运行。对于锁屏场景,则在重新申请相机资源时会发生crash,说相机资源访问失败。那么原因是什么呢?我在代码里增加了调试log, 检查了代码的执行顺序,结果如下:

    在自定义相机页面按HOME键时的执行流程:

    • 程序运行->按HOME键
      Activity调用的顺序是onPause->onStop
      SurfaceView调用了surfaceDestroyed方法
      然后再切回程序
      Activity调用的顺序是onRestart->onStart->onResume
      SurfaceView调用了surfaceCreated->surfaceChanged方法

    • 而对于锁屏,其执行流程则是:
      Activity只调用onPause方法
      解锁后Activity调用onResume方法
      SurfaceView中surfaceholder.callback的所有方法都没有执行

      问题找到了,由于锁屏时,callback的回调方法没有执行,导致相机和预览的连接还没有断开,相机资源就被释放了,所以导致在重新申请相机资源时,系统报crash。根据上面的文档,推测是锁屏下系统并没有改变surfaceview的可见性,于是我尝试在onPause和onResume时通过手动设置surfaceview的visibile属性,结果发现可以正常触发回调函数了。由于在切后台或者锁屏时,用户本来就应该看不到surfaceview,因此这种手动更改surfaceview的可见性的方法,并不会对用户的体验造成影响。

    这里写图片描述

    参考:

    Android相机开发那些坑

    Android开发实践:掌握Camera的预览方向和拍照方向

    Android 开发之解决相机预览上下颠倒问题

    【腾讯优测干货分享】Android 相机预览方向及其适配探索

    关于Android Camera几点须知

    关注我的公众号,轻松了解和学习更多技术
    这里写图片描述

    展开全文
  • 在Camera中,autofocus是个很重要的知识点,一般有移动对焦,点击聚焦和长按聚焦,这里我们以展锐平台相机的点击聚焦为例,分析下聚焦在app层的流程。 1、聚焦触发 点击屏幕事件的触发 PhotoModule.java的...

    在Camera中,autofocus是个很重要的知识点,一般有移动对焦,点击聚焦和长按聚焦,这里我们以展锐平台相机的点击聚焦为例,分析下聚焦在app层的流程。

    1、聚焦触发

    点击屏幕事件的触发
    PhotoModule.java的onSingleTapUp中

     public void onSingleTapUp(View view, int x, int y) {
     	......
     	//调用FocusManager的singletap
     	mFocusManager.onSingleTapUp(x, y);
     }
    

    FocusOverlayManager.java的onSingleTapUp中

    public void onSingleTapUp(int x, int y) {
             
            lastSingleTap.set(x,y);
            //如果当前已经是定焦的,取消定焦,重新对焦
            if(isAFLock) {
                sendCancleMsg(false);
                mListener.setAELock(false);
                isAFLock = false;
            }
    
            //没有初始化或者拍照前的聚焦的状态,直接返回
            if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) { 
                return;
            }
            //让用户可以取消掉之前未消失的触摸聚焦
            if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
                    mState == STATE_SUCCESS || mState == STATE_FAIL)) { 
                cancelAutoFocus();
            }
            if (mPreviewRect.width() == 0 || mPreviewRect.height() == 0) {
                return;
            }
         
            // Initialize variables.
            if (mAutoChasingSupported && mAutoChasingEnable){
                boolean isChasing = isAutoChasing();
                if (isChasing) {
                    initializeAutoChasingRegion(0,0);
                    Log.d(TAG,"set chasing Region x = 0 , y = 0");
                    mListener.setAutoChasingParameters();
                }
            }
    
            //初始化聚焦的区域
            // Initialize mFocusArea.
            if (mFocusAreaSupported) {
                initializeFocusAreas(x, y);
                mFocusRing.setFocusLocation(x, y);
            }
            // Initialize mMeteringArea.
            if (mMeteringAreaSupported) {
                initializeMeteringAreas(x, y);
            }
    
            // Log manual tap to focus.
            mTouchCoordinate = new TouchCoordinate(x, y, mPreviewRect.width(), mPreviewRect.height());
            mTouchTime = System.currentTimeMillis();
    
            //停止人脸识别,因为要进行识别聚焦和测量area
            mListener.stopFaceDetection();
    
            //设置聚焦区域&测量区域
            mListener.setFocusParameters();
    
            
            if (mFocusAreaSupported) {
                autoFocus();//开始对焦
            } else {  // Just show the indicator in all other cases.
                // Reset the metering area in 4 seconds.
                mHandler.removeMessages(RESET_TOUCH_FOCUS);
                mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY_MILLIS);
            }
        }
    

    接着看FocusOverlayManager这里的autoFocus()方法

    private void autoFocus(int focusingState) {
            //监听autofocus,这里autofocus的实现是PhotoModule中
            mListener.autoFocus();
            //当前focus的状态设置成正在focus
            mState = focusingState;
            //更新正在对焦的对焦框ui
            updateFocusUI();
            mHandler.removeMessages(RESET_TOUCH_FOCUS);
            initializeCameraSound();
        }
    

    在PhotoMudule.java中的autoFocus()中

    @Override
        public void autoFocus() {
            if (mCameraDevice == null) {
                return;
            }
            mNeedCancelAutoFocus = true;
            mFocusStartTime = System.currentTimeMillis();
            //设置Camera的回调聚焦
            mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
            //设置相机的状态为FOCUSING
            setCameraState(FOCUSING);
        }
    

    2、聚焦完成回调

    接着看上层对于聚焦的回调处理,在PhotoModule中有内部类AutoFocusCallback

    private final class AutoFocusCallback implements CameraAFCallback {
            @Override
            public void onAutoFocus(boolean focused, CameraProxy camera) {
                //如果是暂停状态,不聚焦
                if (mPaused) {
                    return;
                }
                //算出当前到聚焦开始的时间差
                mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
                //设置Camera状态为IDLE
                setCameraState(IDLE);
                //调用FocusManager中的onAutoFocus
                mFocusManager.onAutoFocus(focused, false);
            }
        }
    

    接着去看聚焦完成的具体处理
    在FocusManager的onAutoFocus中

    public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
            //正在进行聚焦,拍照动作必须再聚焦完之后
            if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
                // 无论聚焦成功还是失败,都会拍照。如果要进行拍照发声,就无需AF发声了
                if (focused) {
                    //聚焦成功
                    mState = STATE_SUCCESS;
                } else {
                    //聚焦失败
                    mState = STATE_FAIL;
                }
    
                //更新聚焦完成的ui以及各种状态的设置
                updateFocusUI();
                //拍照,mState的状态变为STATE_IDLE
                capture();
    
                //如果是聚焦中的状态
            } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
                //此状态的发生分为两种,half-pressing按压聚焦或者触摸聚焦被触发,这个时候不要发生拍照动作
                if (focused) {
                    mState = STATE_SUCCESS;
                } else {
                    mState = STATE_FAIL;
                }
                //如果是触摸聚焦,需要延迟一下取消掉聚焦
                if (mFocusArea != null) {
                    mFocusLocked = true;
                    if(!isAFLock) {
                        mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY_MILLIS);
                    }
                }
                //更新聚焦完成的ui
                if(!isAFLock) {
                    updateFocusUI();
                    autoChase();
                } else {
                    mFocusRing.setAELockColor(true);
                }
                if (shutterButtonPressed) {
                    // Lock AE & AWB so users can half-press shutter and recompose.
                    lockAeAwbIfNeeded();
                }
            } else if (mState == STATE_IDLE) {
                // User has released the focus key before focus completes.
                // Do nothing.
            }
        }
    

    3、取消聚焦

    在FocusManager()中的cancleAutofocus()

    private void cancelAutoFocus() {
            //清除聚焦区域
            clearAfArea();
            mMeteringArea = null;
            //监听cancelAutoFocus,在PhotoModule中实现
            mListener.cancelAutoFocus();
            //重置聚焦,聚焦框恢复到屏幕中间
            resetTouchFocus();
            //改变聚焦状态
            mState = STATE_IDLE;
            mFocusLocked = false;
            //移除msg
            mHandler.removeMessages(RESET_TOUCH_FOCUS);
        }
    

    在PhotoModule的cancelAutoFocus()中

    public void cancelAutoFocus() {
            if (mCameraDevice == null) {
                return;
            }
            //取消底层聚焦
            mCameraDevice.cancelAutoFocus();
            //设置相机状态
            setCameraState(IDLE);
            if (mFace != null && mFace
                    && isCameraIdle() && !isHdr() ) {
                //开始人脸检测
                startFaceDetection();
            }
            setCameraParameters(UPDATE_PARAM_PREFERENCE);
        }
    

    以上就是app层聚焦的大致流程

    4、更新聚焦框ui

    前面有好几处有更新ui的操作,我们来看一下具体做了些什么,其实就是对于聚焦框的改变与展示。
    在FucusManager的updateFocusUI中:

    public void updateFocusUI() {
            //没有初始化完成
            if (!mInitialized) {
                // Show only focus indicator or face indicator.
                return;
            }
            //聚焦开始,mFocusRing为聚焦框,可自定义
            if (mState == STATE_IDLE) {
                if (mFocusArea != null) {
                    mFocusRing.startActiveFocus();
                }
                //正在聚焦
            } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
                if (mFocusArea == null) {
                    mFocusRing.centerFocusLocation();
                }
                mFocusRing.startActiveFocus();
            } else {
                //聚焦成功
                if (mState == STATE_SUCCESS) {
                    //拍照
                    if (mTouchListener != null) {
                        mTouchListener.touchCapture();
                    }
                    //聚焦完成显示的对焦框
                    mFocusRing.startActiveFocusedFocus();
                    //聚焦成功的声音
                    if (mFocusArea != null) {
                        if (mAppController.isPlaySoundEnable()) {
                            mCameraSound.play(MediaActionSound.FOCUS_COMPLETE);
                        }
                    } else {
                        mFocusRing.centerFocusLocation();
                    }
                    //聚焦失败
                } else if (mState == STATE_FAIL) {
                    if (mTouchListener != null) {
                        mTouchListener.touchCapture();
                    }
                    mFocusRing.startActiveFocusedFocus();
                }
            }
        }
    

    5、相机状态和聚焦状态

    聚焦过程中,相机状态的改变和聚焦状态的改变

    相机状态:

    在PhotoModule中设置相机状态:

    protected void setCameraState(int state) {
            mCameraState = state;
            switch (state) {
            case PREVIEW_STOPPED:
            case SNAPSHOT_IN_PROGRESS:
            case SWITCHING_CAMERA:
                // TODO: Tell app UI to disable swipe
                break;
            case PhotoController.IDLE:
                // TODO: Tell app UI to enable swipe
                break;
            }
        }
    

    聚焦状态:

    STATE_IDLE:空闲状态

    capture():空闲状态可以执行拍照
    onAutoFocus(boolean focused):用户再聚焦完成前放掉了聚焦按钮,所以不做任何事。
    onPreviewStarted()onPreviewStopped():状态都变为空闲
    cancelAutoFocus():取消聚焦,状态变为空闲
    updateFocusUI():如果空闲&有聚焦区域,显示聚焦框
    

    STATE_FOCUSING:正在对焦

    onShutterUp():如果是focusmode为自动聚焦,状态为正在聚焦,则取消掉聚焦
    capture():如果是正在聚焦状态,将状态改为聚焦完拍照状态
    onAutoFocus(boolean focused, boolean shutterButtonPressed):判断focused,为true变为聚焦成功状态,为false变为聚焦失败状态
    onSingleTapUp(int x, int y):如果正在聚焦&之前有手动聚焦了,则取消掉之前的聚焦
    autoFocus():状态变为聚焦状态
    updateFocusUI():显示聚焦框
    

    STATE_FOCUSING_SNAP_ON_FINISH:聚焦完拍照

    onShutterUp():状态不是聚焦完拍照,可以设置FocusParameters
    onAutoFocus(boolean focused, boolean shutterButtonPressed):判断focused,为true变为聚焦成功状态,为false变为聚焦失败状态,更新聚焦框UI
    onSingleTapUp(int x, int y):直接不继续操作下去
    updateFocusUI():显示聚焦框
    

    STATE_SUCCESS:聚焦成功

    onShutterDown():如果是自动聚焦mode,并且不为聚焦成功状态,执行聚焦
    onShutterUp(): 如果是自动聚焦mode,并且为聚焦成功状态,执行取消聚焦
    capture():拍照
    onSingleTapUp(int x, int y):让用户可以取消掉之前未消失的触摸聚焦
    updateFocusUI():聚焦框显示成功
    

    STATE_FAIL:聚焦失败

    onShutterDown():如果是自动聚焦mode,并且不为聚焦失败状态,执行聚焦
    onShutterUp(): 如果是自动聚焦mode,并且为聚焦失败状态,执行取消聚焦
    capture():拍照
    onSingleTapUp(int x, int y):让用户可以取消掉之前未消失的触摸聚焦
    updateFocusUI():聚焦框显示失败
    

    关于底层的实现,主要是聚焦命令的下发(开始聚焦和结束聚焦),以及聚焦完成的回调

    //开始聚焦
    mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);//mCameraDevice为CameraProxy的引用,mAutoFocusCallback为聚焦完成的回调
    
    //取消聚焦
    mCameraDevice.cancelAutoFocus();
    

    CameraProxy为CameraAgent的内部类
    具体实现又在AndroidCamera2AgentImpl中,有兴趣的朋友可以自行查看源码

    展开全文
  • java.lang.RuntimeException: autoFocus failed Call autoFocus when preview is inactive (state = 1)

    java.lang.RuntimeException: autoFocus failed

    在使用华为手机测试自定义相机时,一进入自定义相机界面就崩溃,异常log信息如上。其他测试机没这个问题。

    代码如下:

    private void doStartPreview() {
            try {
                if (mCamera != null) {
                    width = mSurfaceView.getMeasuredWidth();
                    height = mSurfaceView.getMeasuredHeight();
                    Log.e(TAG, "宽高:" + width + "," + height + ",mCamera:" + mCamera);
                    mParams = mCamera.getParameters();
    
                    if (screenProp == 0) {
                        screenProp = (float) height / (float) width;
                        Log.e(TAG, "screenProp:" + screenProp + "," + height + width);
                    }
    
                    previewSize = CameraParamUtil.getInstance().getPreviewSize(mParams
                            .getSupportedPreviewSizes(), 1000, screenProp);
                    pictureSize = CameraParamUtil.getInstance().getPictureSize(mParams
                            .getSupportedPictureSizes(), 1200, screenProp);
    
                    mParams.setPreviewSize(previewSize.width, previewSize.height);
                    mParams.setPictureSize(pictureSize.width, pictureSize.height);
                    Log.e(TAG, "previewSize:" + previewSize.width + previewSize.height + ",pictureSize:" + pictureSize.width + pictureSize.height);
                    if (CameraParamUtil.getInstance().isSupportedFocusMode(
                            mParams.getSupportedFocusModes(),
                            Camera.Parameters.FOCUS_MODE_AUTO)) {
                        mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                    }
                    if (CameraParamUtil.getInstance().isSupportedPictureFormats(mParams.getSupportedPictureFormats(),
                            ImageFormat.JPEG)) {
                        mParams.setPictureFormat(ImageFormat.JPEG);
                        mParams.setJpegQuality(100);
                    }
    
                    mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦
                    mCamera.setParameters(mParams);
                    mCamera.setPreviewDisplay(mSurfaceHolder);
                    mCamera.setDisplayOrientation(cameraAngle);//浏览角度
    //                mCamera.setPreviewCallback(this); //每一帧回调。这个要注释掉,否则会出现Camera is being used after Camera.release() was called异常
                    mCamera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            camera.cancelAutoFocus();
                        }
                    });
    		mCamera.startPreview();//开始预览
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    还没有预览就进行对焦是不行的。把startPreview放在autoFocus前面。

    修改如下:

     // 自动对焦
     //                mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//注释掉
                    mCamera.setParameters(mParams);
                    mCamera.setPreviewDisplay(mSurfaceHolder);
                    mCamera.setDisplayOrientation(cameraAngle);//浏览角度
     //                mCamera.setPreviewCallback(this); //每一帧回调。这个要注释掉,否则会出现Camera is being used after Camera.release() was called异常
                    mCamera.startPreview();//开始预览
                    //自动对焦。要放在startPreview后面,否则可能报自动对焦失败异常,还没预览就进行对焦是不行的。
                    mCamera.autoFocus(new Camera.AutoFocusCallback() {
                        @Override
                        public void onAutoFocus(boolean success, Camera camera) {
                            camera.cancelAutoFocus();
                        }
                    });

    运行正常。

    但是如果点击了拍照按钮,preview停止了活动,如果点击“确定”和“取消”按钮之外的区域,页面出现异常。当然这个也是华为手机出现的问题,其他测试机没有出现。

    查看log:

    Call autoFocus when preview is inactive (state = 1)
    preview停止活动时调用了autoFocus方法。异常代码如下:
     @Override
        public boolean onTouchEvent(MotionEvent event) {
            //对焦
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    camera.cancelAutoFocus();
                }
            });
            return super.onTouchEvent(event);
        }

    点击屏幕时调用autoFocus方法,自动对焦。

    那就在preview活动时再调用autoFocus。但是怎么判断preview是活动的呢?目前只有mCamera.stopPreview()、mCamera.startPreview()方法表明是否是活动的,没找到其他可以判断是否活动的方法。后来想了想,设置一个boolean值isPreviewActive判断是否活动。当mCamera.stopPreview()时isPreviewActive=false;当mCamera.startPreview()时isPreviewActive=true。

    代码如下:

        private boolean isPreviewActive=true;//preview是否是活动的。防止preview是inactive时去调用对焦产生异常。
       
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if(isPreviewActive) {//preview活动时才能调用自动对焦功能
                //对焦
                mCamera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {
                        camera.cancelAutoFocus();
                    }
                });
            }
            return super.onTouchEvent(event);
        }
    问题解决。
    展开全文
  • 为什么把自动聚焦放在前面呢?因为前文...我这里就总结下,我的自动聚焦的实现。 手机华为U9200, android4.0.3, 预览的参数:myParameters.setPreviewSize

         为什么把自动聚焦放在前面呢?因为前文http://blog.csdn.net/yanzi1225627/article/details/7738736已经实现了拍照功能,且网上拍照的源码例子很多,自动聚焦很多人写的很简单,但结果发现不中。我这里就总结下,我的自动聚焦的实现。

    手机华为U9200,

    android4.0.3,

    预览的参数:myParameters.setPreviewSize(1280, 720)

    图片参数:myParameters.setPictureSize(2048, 1152); //1280, 720

    图片最终尺寸:宽600 * 高800

    关于setPreviewSize和setPictureSize的问题可以参照我以前的文章http://blog.csdn.net/yanzi1225627/article/details/7738736

     

             参照http://www.cnblogs.com/liuan/archive/2012/01/10/2318300.html,上面的思路是弄了一个定时器来进行自动聚焦,但发现根本不中,一按拍照程序就挂掉。而且由于定时器一直在重复循环,相机就在那聚焦来聚焦去,也就是一下清晰一下又模糊了。后来我把程序改成,当拍照后把定时器关闭掉发现还是不中。经反复实验,终于成功。南无阿弥陀佛,本着我为人人,人人为我。将源码贴在此处:

    第一,布局文件。里面有一个surfaceview和三个按钮,分别是预览、拍照、保存。

     

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
    
    <SurfaceView
        android:id="@+id/mySurfaceView"  
        android:layout_width="fill_parent"
        android:background="#D1EEEE"
        android:layout_height="800px"
        android:gravity="center" />
    
    <LinearLayout
        android:id="@+id/LinearLayout01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:paddingTop="20dip"    
        android:orientation="horizontal" >
    
        <Button
            android:id="@+id/btnPreview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            
            android:text="预览" />
    
        <Button
            android:id="@+id/btnPhoto"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="拍照" />
    
        <Button
            android:id="@+id/btnSave"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="保存" />
    
    </LinearLayout>
    
    </LinearLayout>

    第2,源程序:

    package yan.guoqi.testphoto;
    
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.Timer;
    import java.util.TimerTask;
    
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.AlertDialog.Builder;
    import android.content.DialogInterface;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.PixelFormat;
    import android.hardware.Camera;
    import android.hardware.Camera.PictureCallback;
    import android.hardware.Camera.ShutterCallback;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.Window;
    import android.view.WindowManager;
    import android.widget.Button;
    import android.widget.Toast;
    
    public class TestPhotoActivity extends Activity implements SurfaceHolder.Callback{
        /** Called when the activity is first created. */
    	private static final  String TAG = "yan:";
    	SurfaceView mySurfaceView = null;
    	SurfaceHolder mySurfaceHolder = null;
    	Button btnPreview = null;
    	Button btnPhoto = null;
    	Button btnSave = null;
    	Camera myCamera = null;
    	Camera.Parameters myParameters;
    	boolean isView = false;
    	Bitmap bm;
    	String savePath = "/mnt/sdcard/testPhoto/";
    	int cntSave = 0;
    	private Camera.AutoFocusCallback mAutoFocusCallback;
    	//private Camera.PreviewCallback mPreviewCallback;
    	private Timer mTimer;
    	private TimerTask mTimerTask;
        @Override
        public void onCreate(Bundle savedInstanceState) {
        	super.onCreate(savedInstanceState);
        	//璁剧疆鍏ㄥ睆鏃犳爣棰?    	requestWindowFeature(Window.FEATURE_NO_TITLE);
        	int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
        	Window myWindow = this.getWindow();
        	myWindow.setFlags(flag, flag);
        	setContentView(R.layout.main); //璁剧疆甯冨眬
        	mySurfaceView = (SurfaceView)findViewById(R.id.mySurfaceView);
        	mySurfaceView.setZOrderOnTop(true);
        	mySurfaceHolder = mySurfaceView.getHolder();
        	mySurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        	btnPreview = (Button)findViewById(R.id.btnPreview);
        	btnPhoto = (Button)findViewById(R.id.btnPhoto);
        	btnSave = (Button)findViewById(R.id.btnSave);
        	if(!isFolderExist(savePath)) //濡傛灉鍒涘缓鏂囦欢澶瑰け璐?    	{
        		AlertDialog.Builder alertDialog = new Builder(TestPhotoActivity.this);
        		alertDialog.setTitle("閿欒");
        		alertDialog.setMessage("鍥剧墖淇濆瓨鏂囦欢澶瑰垱寤哄け璐ワ紒");
        		alertDialog.setPositiveButton("纭畾", new DialogInterface.OnClickListener() {
    				
    				public void onClick(DialogInterface dialog, int which) {
    					// TODO Auto-generated method stub
    					dialog.dismiss();
    					TestPhotoActivity.this.finish();
    				}
    			});
        		alertDialog.show();
        	}
        	else
        		Toast.makeText(TestPhotoActivity.this,
        				"鎮ㄧ殑鐓х墖灏嗕繚瀛樺湪锛? + savePath,
        				Toast.LENGTH_SHORT).show();
        	mySurfaceHolder.addCallback(this);
        	mySurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        	mAutoFocusCallback = new Camera.AutoFocusCallback() {
    			
    			public void onAutoFocus(boolean success, Camera camera) {
    				// TODO Auto-generated method stub
    				if(success){
    					myCamera.setOneShotPreviewCallback(null);
    		    		Toast.makeText(TestPhotoActivity.this,
    		    				"鑷姩鑱氱劍鎴愬姛锛?,
    		    				Toast.LENGTH_SHORT).show();
    				}
    					
    			}
    		}; 
    		mTimer = new Timer();
    		mTimerTask = new CameraTimerTask();
    		//mTimer.schedule(mTimerTask, 0, 500);
    		
        	btnPreview.setOnClickListener(new BtnListener());
        	btnPhoto.setOnClickListener(new BtnListener());
        	btnSave.setOnClickListener(new BtnListener());
        	
        }
        ShutterCallback myShutterCallback = new ShutterCallback() {
    		
    		public void onShutter() {
    			// TODO Auto-generated method stub
    			
    		}
    	};
    	PictureCallback myRawCallback = new PictureCallback() {
    		
    		public void onPictureTaken(byte[] data, Camera camera) {
    			// TODO Auto-generated method stub
    			
    		}
    	};
    	PictureCallback myjpegCalback = new PictureCallback() {
    		
    		public void onPictureTaken(byte[] data, Camera camera) {
    			// TODO Auto-generated method stub
    			Log.i(TAG, "onPictureTaken........");
    			bm = BitmapFactory.decodeByteArray(data, 0, data.length);
    			isView = false;
    			myCamera.stopPreview();
    			myCamera.release();
    			myCamera = null;
    			isView = false;
    			
    		}
    	};
        class BtnListener implements OnClickListener{
    
    		public void onClick(View v) {
    			// TODO Auto-generated method stub
    			switch(v.getId()){
    			case R.id.btnPreview:
    				Toast.makeText(TestPhotoActivity.this,
    						"鎮ㄦ寜浜嗛瑙堟寜閽?,
    						Toast.LENGTH_SHORT).show();
    				initCamera();
    				break;
    			case R.id.btnPhoto:
    				if(isView && myCamera!=null){	
    										
    					myCamera.takePicture(myShutterCallback, myRawCallback, myjpegCalback);
    				}
    				else
    					Toast.makeText(TestPhotoActivity.this, "璇峰厛棰勮鐒跺悗鎷嶇収锛?, Toast.LENGTH_SHORT).show();				
    				break;
    			case R.id.btnSave:
                      if(bm == null)
                      {
                    	  Toast.makeText(TestPhotoActivity.this, "璇锋媿鎽勬垚鍔熷悗鍐嶄繚瀛橈紒锛侊紒", Toast.LENGTH_SHORT).show();
                    	  return;
                      }
          			int b =0, c=1;
        			File fTest = new File(savePath + b + c + ".JPG");
        			while(fTest.exists()){
        				if(c==9){
        					b++;
        					c = 0;
        				}
        				else
        					c++;
        				if(b==9){
        					b = 0;
        					Toast.makeText(TestPhotoActivity.this, "姝ゅ織鎰胯€呮牱鏈暟鐩凡瓒呰繃100锛?, 
        							Toast.LENGTH_SHORT).show();
        				}
        				fTest = new File(savePath + b + c + ".JPG");
        			}
        			try {
    					FileOutputStream fout = new FileOutputStream(fTest);
    					BufferedOutputStream bos = new BufferedOutputStream(fout);
    					Bitmap mBitmap = Bitmap.createScaledBitmap(bm, 600, 800, false);
    					mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    					//bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    					bos.flush();
    					bos.close();
    					cntSave++;
    					Toast.makeText(TestPhotoActivity.this, "鎮ㄦ媿鐨勭"+cntSave+"寮爌icture淇濆瓨鎴愬姛锛佺紪鍙凤細"+ b + c,
    							Toast.LENGTH_SHORT).show();
    					
    				}  catch (IOException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    					Toast.makeText(TestPhotoActivity.this,
    							"淇濆瓨澶辫触",
    							Toast.LENGTH_SHORT).show();
    				}
        			
                    	  
    				break;
    				default:									
    				
    			}			
    		}    	
        }
        
    	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    		// TODO Auto-generated method stub
    		
    	}
    
    	public void surfaceCreated(SurfaceHolder arg0) {
    		// TODO Auto-generated method stub
    		
    	}
    
    	public void surfaceDestroyed(SurfaceHolder arg0) {
    		// TODO Auto-generated method stub
    		
    		
    	}
    	
    	//鍒濆鍖栨憚鍍忓ご
    	public void initCamera()
    	{
    		if(myCamera == null && !isView)
    		{
    			myCamera = Camera.open();
    			Log.i(TAG, "camera.open");
    		}
    		if(myCamera != null && !isView) {
    			try {
    				
    					myParameters = myCamera.getParameters();
    					myParameters.setPictureFormat(PixelFormat.JPEG);
    					myParameters.setPreviewSize(1280, 720);
    					//myParameters.setFocusMode("auto");
    					
    					myParameters.setPictureSize(2048, 1152); //1280, 720
    					
    					myParameters.set("rotation", 90);
    					myCamera.setDisplayOrientation(90);
    					myCamera.setParameters(myParameters);
    					myCamera.setPreviewDisplay(mySurfaceHolder);
    					myCamera.startPreview();
    					isView = true;
    					myCamera.autoFocus(mAutoFocusCallback);
    			
    			} catch (Exception e) {
    				// TODO: handle exception
    				e.printStackTrace();
    				Toast.makeText(TestPhotoActivity.this, "鍒濆鍖栫浉鏈洪敊璇?,
    						Toast.LENGTH_SHORT).show();
    			}
    		}
    		
    	}
    	class CameraTimerTask extends TimerTask{
    
    		@Override
    		public void run() {
    			// TODO Auto-generated method stub
    			if(myCamera != null)
    			{
    				myCamera.autoFocus(mAutoFocusCallback);
    			}
    			
    		}
    		
    	}
    	//鍒ゆ柇鏂囦欢澶规槸鍚﹀瓨鍦紝濡傛灉涓嶅瓨鍦ㄥ垯鍒涘缓涓€涓?		public boolean isFolderExist(String folderPath){
    			boolean result = false;
    			File f = new File(folderPath);
    			if(!f.exists()){
    				if(f.mkdir()){
    					result = true;
    				}
    				else
    					result = false;
    			}
    			else
    				result = true;
    			
    			return result;
    		}
    }
    

    自动聚焦部分,核心的代码有,构造函数里的

    private Camera.AutoFocusCallback mAutoFocusCallback;
    mAutoFocusCallback = new Camera.AutoFocusCallback() {
       
       public void onAutoFocus(boolean success, Camera camera) {
        // TODO Auto-generated method stub
        if(success){
         myCamera.setOneShotPreviewCallback(null);
            Toast.makeText(TestPhotoActivity.this,
              "自动聚焦成功" , Toast.LENGTH_SHORT).show();
        }
         
       }
      };
    和initCamera里的myCamera.autoFocus(mAutoFocusCallback);调用!

     

    第三,AndroidManifest文件,红线部分是添加的对摄像头操作、sd存储的权限许可。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="yan.guoqi.testphoto"
        android:versionCode="1"
        android:versionName="1.0" >
        <uses-sdk android:minSdkVersion="15" />
        <!-- 鍦╯d鍗′腑鍒涘缓鍜屽垹闄ゆ枃浠剁殑鏉冮檺 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
        <!-- 鍦╯d鍗′腑鍐欏叆鏁版嵁鐨勬潈闄?-->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <!-- 浣跨敤鎷嶇収鐨勬潈闄?-->
        <uses-permission android:name="android.permission.CAMERA" />

        <uses-feature android:name="android.hardware.camera" />

        <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
            <activity
                android:name=".TestPhotoActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />

                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>

    </manifest>

     


       上两张效果图,来看下摄像头相同的previewSize、pictureSize下,自动对焦前后的差别:



    效果差别不是一般的大啊!

    1,上面的源程序里mTimer、mTimerTask等这些是我第一种思路,用定时器来控制聚焦,结果是失败的。碰到这些,大家略过去就行了。

    2,这里控制聚焦的核心实现时,在initCamera函数里,执行myCamera.autoFocus(mAutoFocusCallback);自动聚焦这句话!在构造函数里定义了一个mAutoFocusCallback的回调函数变量。还可以在按下拍照按钮后判断当前是否自动聚焦成功,如果聚焦成功再拍照!mAutoFocusCallback里的if(success)就是标示自动聚焦成功

    3,网上有很多,只是简单的在构造函数里写一句myCamera.setFocus(null),我真怀疑,这不是坑爹么?反正我这样是没弄成功。

    4,程序里的乱码部分是文字,由于从linux下拷出来就成乱码了,大家不用太关心,都是些提示性的话。

    5,另外还要交代一下,自动聚焦这句话一定要在摄像头正常预览的时候调用,否则是没有意义的,程序也会挂掉。

    6,我核心的参考文章:

    http://www.cnblogs.com/liuan/archive/2012/01/10/2318300.html

    http://www.cnblogs.com/skyseraph/archive/2012/03/26/2418665.html

    向他们表示感谢! 

    遗留的问题:

    我第一次按下预览按钮后,会进行自动聚焦,并提示自动聚焦成功。如果此时执行拍照--保存--再次预览,则自动聚焦是成功的。 如果我预览后,调整了和拍摄物的距离这时想再次自动聚焦,即连续两次按下预览按钮,怎么第二次就不自动聚焦了呢???这是不是和android摄像头内置的属性有关系?

    源码下载链接:http://download.csdn.net/detail/yanzi1225627/4538626

                                                                                                                                                                                                              ----------------------作者yanzi1225627 转载请注明 2012.8.31

    展开全文
  • android 自定义相机

    2016-04-14 19:31:36
    方式: 调用Camera API 自定义相机 调用系统相机 由于需求不同,所以选择的方案固然也不同,至于第二种调用系统相机,这里就不过多讲解了,使用Intent对象设置一个Action动作即可,跳转时使用...
  • 最近使用测试机(华为,4.4系统)调用系统相机进行拍照时,点击拍照完成,手机又反回了拍照页面,此时应该setResult上一个页面,但是上一个页面的onActivityResult没有被调用,大概有些小米手机也会这样吧,记录下...
  • 1.在应用程序中自定义相机拍照的大体步骤如下:  1.检测和访问相机:检测设备是否支持拍照,然后发出访问设备相机请求。  2.创建一个预览类:创建一个继承自类SurfaceView和实现接口SurfaceHolder接口的相机预览类...
  • 这个代码几乎涉及到了摄像头开发的所有方面,(除了PreviewCallback,这块东西我会结合android摄像头自动识别人脸/火灾来谈),且力求精简,是杂家的心血阿!相对之前改进之处有: 1,精简。只有一个ImageButton...
  • 项目地址https://github.com/979451341/OrcTest我们说说实现这个项目已实现的功能,能够截图手机界面的某一块,将这个某一块图片的Bitmap传给tess-two的代码来获取扫描结果我这里在贴出tess-two这个专为Android而...
  • 蛰伏半月有余,一直在准备期末考试,期间抽空研究了一些Android的源代码,现在我就把在这其中的一些收获分享给大家。 今天想分享给大家的是二维码扫描。说起二维码,大家一定不会陌生,尤其是微信火了以后,在我们的...
  • Android调用系统相机和自定义相机实例 本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显示出来,该例子也会涉及到Android加载大图片时候的处理(避免OOM),还有简要提...
  • Android相机开发实战

    2017-05-24 16:49:20
    开源分享二(Android相机开发实战教程) 上篇博文给大家分享了两个非常实用的项目功能模块,不知道大伙感觉如何?有木有一种臭袜子味扑鼻,酸爽的赶脚!!!贱笑贱笑了~ ~ OK!不扯淡了,言归正传。本文将主要为...
  • Android 4.0 平台特性

    2015-08-11 11:00:32
    在下面我们将讨论的所有新特性和API中,因为它将 Android 3.x 版本中广泛使用的API和全息图像主题带给了小屏幕设备,因此我们说 Android 4.0 是一次重要的平台发布版。作为一名开发者,现在你拥有了单一的平台和统一...
  • android的camera

    2015-08-13 19:58:12
    Android手机关于Camera的使用,一是拍照,二是摄像,由于Android提供了强大的组件功能,为此对于在Android手机系统上进行Camera的开发,我们可以使用两类方法:一是借助Intent和MediaStroe调用系统Camera ...
  • 布局文件是这样的: android:name="android.hardware.camera" android:required="false" /> android:name="android.ha
  • 上一篇文章OpenCV4Android开发实录(1):移植OpenCV3.3.0库到Android Studio大概介绍了下OpenCV库的基本情况,阐述了将OpenCV库移植到Android Studio项目中的具体步骤。本文将在此文的基础上,通过对OpenCV框架中的...
  • Android相机对焦问题

    2017-02-20 16:35:55
    对焦不上的问题 android camera
  • 其实偷拍与偷录实现方式是一样的,都是使用到的WindowManager来绘制桌面控件的原理。那我就不多说了… 一、首先我们需要一个SurfaceView: xmlns:android="http://schemas.android.com/apk/res/android" ...
  • Android手机配置不一样,手机像素高低也有不同,有的手机还不支持自动对焦 环境影响也特别大,亮光,反光、粉尘、灰尘、距离都会导致识别不佳 A4纸张打印的标签二维码,本来打印就不是特别清晰,有些像素点,不一定...
1 2 3 4 5 ... 20
收藏数 438
精华内容 175