阅读 789

Android进阶4:Android的Camera相机开发

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

Android系统提供了两种使用手机相机资源实现拍摄功能的方法, : - 一种是直接通过Intent调用系统相机组件.这种方法快速方便,适用于直接获得照片的场景,如上传相册,微博、朋友圈发照片等。 : - 一种是使用相机API来定制自定义相机.这种方法适用于需要定制相机界面或者开发特殊相机功能的场景,如需要对照片做裁剪、滤镜处理,添加贴纸,表情,地点标签等。这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的。

1.1使用Intent调用相机相机组件

如果你的App的需求只是调用摄像头拍照并拿到照片,老司机的建议是别自己实现拍照模块,这里面坑多水深。你完全可以使用Intent来调用系统相机或第三方具备拍照功能的App来拍照并获取返回照片数据。 创建一个Intent,指定两个拍摄类型之一

  • MediaStore.ACTION_IMAGE_CAPTURE 拍摄照片;
  • MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频;

Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);
复制代码

通用流程startActivityForResult()onActivityResult()就不表述了。说说拍摄照片的Intent参数吧。

首先是设置拍摄后返回数据的地址:

intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);
复制代码

MediaStore.EXTRA_OUTPUT 参数用于指定拍摄完成后的照片/视频的储存路径。你可以使用Android默认的储存照片目录来保存:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)
复制代码
public class MainActivity extends Activity {

    private ImageView show_iv;

    private String mFilePath = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        show_iv = (ImageView) findViewById(R.id.show_iv);
    }

    public void onClick(View view) {
        Intent intent = new Intent();
        // 1. 直接调用系统相机 没有返回值
//       intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
//       startActivity(intent);
        // 2 调用系统相机 有返回值 但是返回值是 缩略图
//       intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
//       startActivityForResult(intent, 100);
        // 3 .返回原图
         mFilePath =
         Environment.getExternalStorageDirectory().getAbsolutePath() +
         "/picture.png";
         intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
         Uri uri = Uri.fromFile(new File(mFilePath));
        //  指定路径
         intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
         startActivityForResult(intent, 300);

        // 4. 打开系统相册
//      intent.setAction(Intent.ACTION_PICK);
//      intent.setType("image/*");
//      startActivityForResult(intent, 500);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);
        //   返回缩略图
        if (requestCode == 100) {
            if (data != null) {
                Bitmap bitmap = (Bitmap) data.getExtras().get("data");
                if (bitmap != null) {
                    show_iv.setImageBitmap(bitmap);
                }
            }
        }
        // 原图
        if (requestCode == 300) {
            FileInputStream inputStream = null;
            try {
                inputStream = new FileInputStream(mFilePath);
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                if (bitmap != null) {
                    show_iv.setImageBitmap(rotateBitmap(bitmap));
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        // 相册
        if (requestCode == 500) {
            Uri uri = data.getData();
            try {
                Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
                show_iv.setImageBitmap(bitmap);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    // 三星机型拍照的时候照片会旋转90度 所以需要转回来
    public Bitmap rotateBitmap(Bitmap bitmap) {
        Matrix matrix = new Matrix();
        matrix.postRotate(90);
        Bitmap map = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return map;
    }

}

复制代码

注意事项:

相机的权限问题和Android7.0以上的拍照返回图片文件Uri问题,参考Android踩坑日记(一):android7.0动态相机权限

1.2 使用相机API进行相机开发

1.2.1 相机API关键类解析

Camera: 最主要的类,用于管理和操作camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览、拍摄尺寸,设定光圈,曝光,聚焦等相关参数,获取预览,拍摄帧数据等功能,主要一下方法:

  • open():获取camera实例。
 / **
     *创建一个新的Camera对象来访问特定的硬件摄像机。如果
     *相同的相机被其他应用程序打开,这会抛出一个
     * RuntimeException。
     *
     * <p>完成使用相机后,您必须致电{@link #release()},
     *否则它将保持锁定状态并对其他应用程序不可用。
     *
     * <p>您的应用程序一次只能有一个Camera对象处于活动状态
     *用于特定的硬件摄像机。
     *
     * <p>来自其他方法的回调被传递给调用open()的线程。如果这个线程没有事件循      * 环loop,那么
     *回调传递到主应用程序事件循环loop。如果有
     *没有主应用程序事件循环,回调不会传递。
     *
     * <p class =“caution”> <b>注意:</ b>
     * 在某些设备上,此方法可能会发生
     * 需要很长时间才能完成。最好从一个工作线程
     *(可能使用{@link android.os.AsyncTask})调用这个方法,来避免
     *阻塞主应用程序UI线程。
     *
     * @参数cameraId硬件摄像头访问,介于0和
     * {@link #getNumberOfCameras()} - 1。
     * @返回一个新的Camera对象,连接,锁定并准备使用。
     *如果打开相机失败,则引发RuntimeException(例如,如果
     *相机正在被其他进程或设备策略管理器使用
     *禁用相机)
    public static Camera open(int cameraId) {
        return new Camera(cameraId);
    }

   /**
      *创建一个新的摄像头对象,以访问摄像头上的第一个后置摄像头
      *设备。 如果设备没有后置摄像头,则返回
      * 空值。
      * @see #open(int)
     */
    public static Camera open() {
        int numberOfCameras = getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                return new Camera(i);
            }
        }
        return null;
    }


复制代码
  • setPreviewDisplay(SurfaceHolder):绑定绘制预览图像的surface。surface是指向屏幕窗口原始图像缓冲区(raw buffer)的一个句柄,通过它可以获得这块屏幕上对应的canvas,进而完成在屏幕上绘制View的工作。通过surfaceHolder可以将Camera和surface连接起来,当camera和surface连接后,camera获得的预览帧数据就可以通过surface显示在屏幕上了。

/ **
     *设置{@link Surface}用于实时预览。
     *预览时需要surface or surface texture,并且
     *预览对拍照是必要的。相同的surface可以毫无影响的重新设置
     *设置预览surface将取消设置任何通过{@link#setPreviewTexture}设置预览surface texture
     *
     * <p>此时,当这个方法被调用的时候,{@link SurfaceHolder}必须已经包含一个surface
     *如果您使用{@link android.view.SurfaceView},
     *您需要注册{@link SurfaceHolder.Callback}
     *通过调用{@link SurfaceHolder#addCallback(SurfaceHolder.Callback)}并等待
     * {@link SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)}并且在调用
     *调用setPreviewDisplay()或开始预览。
     *
     * <p>必须在{@link #startPreview()}之前调用此方法。
     *有一个异常Exception的情况是,在调用startPreview()之前,如果预览surafce未设置(或设置为空)
     *,则可以调用此方法一次 ,使用非空参数设置预览surface(这允许相机设置和表面创建并行发生,节省时间。)
     * 预览运行时,预览surface可能不会改变。
     *
     * @param 持有者包含放置预览的Surface,
     *或null删除预览表面
     *如果方法失败,则引发IOException(例如,如果表面
     *不可用或不适用)。
     * /

 public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
        if (holder != null) {
            setPreviewSurface(holder.getSurface());
        } else {
            setPreviewSurface((Surface)null);
        }
    }
复制代码
  • setPrameters设置相机参数,包括前后摄像头,闪光灯模式、聚焦模式、预览和拍照尺寸等。
/**
      *更改此相机服务的设置。
     *
      * @参数params用于此Camera服务的参数
      *如果任何参数无效或不受支持,则引发RuntimeException。
      * @see #getParameters()
     */
    public void setParameters(Parameters params) {
        // If using preview allocations, don't allow preview size changes
        if (mUsingPreviewAllocation) {
            Size newPreviewSize = params.getPreviewSize();
            Size currentPreviewSize = getParameters().getPreviewSize();
            if (newPreviewSize.width != currentPreviewSize.width ||
                    newPreviewSize.height != currentPreviewSize.height) {
                throw new IllegalStateException("Cannot change preview size" +
                        " while a preview allocation is configured.");
            }
        }

        native_setParameters(params.flatten());
    }

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

 /**
      *开始捕捉和绘制预览帧到屏幕上。
      *预览不会实际开始,直到提供surface
      *使用{@link #setPreviewDisplay(SurfaceHolder)}或
      * {@link #setPreviewTexture(SurfaceTexture)}。
     *
      * <p>如果通过{@link #setPreviewCallback(Camera.PreviewCallback)},
      * {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}或
      * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)}设置了回调callback
      *,那么回调的{@ Camera.PreviewCallback#onPreviewFrame(byte [],Camera)}
      *在预览数据可用时将被调用。即预览时onPreviewFrame时一直被回调的
     */
    public native final void startPreview();
复制代码
  • takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg):这个是实现相机拍照的主要方法,包含了三个回调参数。shutter是快门按下时的回调,raw是获取拍照原始数据的回调,jpeg是获取经过压缩成jpg格式的图像数据的回调。

 / **
     *触发异步图像捕捉。相机服务将启动
     *随着图像捕捉的进行,对应用程序进行一系列回调。
     *拍摄图像后发生快门回调。这可以使用
     *触发声音让用户知道图像已被捕捉。该
     *当原始图像数据可用时发生原始回调(注意:数据
     *如果没有可用的原始图像回调缓冲区,则它将为空
     *原始图像回调缓冲区不足以容纳原始图像)。
     * postview回调时发生缩放,完全处理的postview
     *图像可用(注意:并非所有硬件都支持此功能)。 jpeg
     *当压缩图像可用时发生回调。如果
     *应用程序不需要特定的回调,可以传递null
     *而不是回调方法。
     *
     * <p>此方法仅在预览处于活动状态时有效(之后
     * {@link #startPreview()})。预览将在图像后停止
     *采取;调用者必须再次调用{@link #startPreview()}
     *重新开始预览或拍摄更多照片。这不应该被称为之间
     * {@link android.media.MediaRecorder#start()}和
     * {@link android.media.MediaRecorder#stop()}。
     *
     * <p>调用此方法后,您不得调用{@link #startPreview()}
     *或拍摄另一张照片,直到JPEG回调已返回。
     *
     * @参数快门图像捕捉时刻的回调,或为空
     * @参数为原始(未压缩)图像数据的回调,或为空
     * postview图像数据的@param postview回调,可能为空
     * @param jpeg JPEG图像数据的回调,或者为null
     * /
    public final void takePicture(ShutterCallback shutter, PictureCallback raw,
            PictureCallback postview, PictureCallback jpeg) {
        mShutterCallback = shutter;
        mRawImageCallback = raw;
        mPostviewCallback = postview;
        mJpegCallback = jpeg;

        // If callback is not set, do not send me callbacks.
        int msgType = 0;
        if (mShutterCallback != null) {
            msgType |= CAMERA_MSG_SHUTTER;
        }
        if (mRawImageCallback != null) {
            msgType |= CAMERA_MSG_RAW_IMAGE;
        }
        if (mPostviewCallback != null) {
            msgType |= CAMERA_MSG_POSTVIEW_FRAME;
        }
        if (mJpegCallback != null) {
            msgType |= CAMERA_MSG_COMPRESSED_IMAGE;
        }

        native_takePicture(msgType);
        mFaceDetectionRunning = false;
    }
复制代码
  • stopPreview():停止预览,关闭camra底层的帧数据传递以及surface上的绘制。
  /**
     * 关闭camra底层的帧数据传递以及surface上的绘制, and
     * 重置相机,为了以后调用 {@link #startPreview()} 
     */
    public final void stopPreview() {
        _stopPreview();
        mFaceDetectionRunning = false;

        mShutterCallback = null;
        mRawImageCallback = null;
        mPostviewCallback = null;
        mJpegCallback = null;
        synchronized (mAutoFocusCallbackLock) {
            mAutoFocusCallback = null;
        }
        mAutoFocusMoveCallback = null;
    }

    private native final void _stopPreview();
复制代码
  • release():释放Camera实例

    /**
     * Disconnects and releases the Camera object resources.
     *
     * <p>You must call this as soon as you're done with the Camera object.</p>
     */
    public final void release() {
        native_release();
        mFaceDetectionRunning = false;
    }
    
    private native final void native_release();
复制代码

SurfaceView:这个之前写过文章

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

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

  • 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()来实现停止相机预览及释放相机资源等操作

1.2.2 相机开发流程

在 Android Manifest.xml 中声明相机权限

开发第一步是在 Android Manifest.xml 文件中声明使用相机的权限:

<uses-permission android:name="android.permission.CAMERA" />
复制代码

有些同学在开发时忘了声明权限,运行时应用可能会崩溃掉。另外也要增加以下两个特性声明:

<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
复制代码

required属性是说明这个特性是否必须满足。比方说示例的设置就是要求必须拥有相机设备但可以没有自动对焦功能。

这两个声明是可选的,它们用于应用商店(GooglePlay)过滤不支持相机和不支持自动对焦的设备。

另外在保存照片时需要写入储存器的权限,也需要加上读写储存器的权限声明:

<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
复制代码

2. 打开相机设备

在市面上销售的手机/平板等消费产品基本标配两个摄像头。如华为P9,更有前置双摄像头。讲真,我很好奇开发双摄像头的App是怎样的体验。在打开相机设备前,先获取当前设备有多少个相机设备。如果你的开发需求里包含切换前后摄像头功能,可以获取摄像头数量来判断是否存在后置摄像头。

int cameras = Camera.getNumberOfCameras();

复制代码

这个接口返回值为摄像头的数量:非负整数。对应地,摄像头序号为: cameras - 1。例如在拥有前后摄像头的手机设备上,其返回结果是2,则第一个摄像头的cameraId是0,通常对应手机背后那个大摄像头;第二个摄像头的cameraId是1,通常对应着手机的前置自拍摄像头

相机是一个硬件设备资源,在使用设备资源前需要将它打开,可以通过接口**Camera.open(cameraId)**来打开。参考以下代码:

public static Camera openCamera(int cameraId) {
    try{
        return Camera.open(cameraId);
    }catch(Exception e) {
        return null;
    }
}
复制代码

注意

打开相机设备可能会失败,你一定要检查打开操作是否成功。打开失败的可能原因有两种: : 一是安装App的设备上根本没有摄像头,例如某些平板或特殊Android设备; 二是cameraId对应的摄像头正被使用,可能某个App正在后台使用它录制视频。

3. 配置相机参数

在打开相机设备后,你将获得一个Camera对象,并独占相机设备资源。 通过**Camera.getParameters()**接口可以获取当前相机设备的默认配置参数。下面列举一些我能理解的参数:

闪光灯配置参数,可以通过Parameters.getFlashMode()接口获取当前相机的闪光灯配置参数:

  • Camera.Parameters.FLASH_MODE_AUTO 自动模式,当光线较暗时自动打开闪光灯;
  • Camera.Parameters.FLASH_MODE_OFF 关闭闪光灯;
  • Camera.Parameters.FLASH_MODE_ON 拍照时闪光灯;
  • Camera.Parameters.FLASH_MODE_RED_EYE 闪光灯参数,防红眼模式。

对焦模式配置参数,可以通过Parameters.getFocusMode()接口获:

  • Camera.Parameters.FOCUS_MODE_AUTO 自动对焦模式,摄影小白专用模式;
  • Camera.Parameters.FOCUS_MODE_FIXED 固定焦距模式,拍摄老司机模式;
  • Camera.Parameters.FOCUS_MODE_EDOF 景深模式,文艺女青年最喜欢的模式;
  • Camera.Parameters.FOCUS_MODE_INFINITY 远景模式,拍风景大场面的模式;
  • Camera.Parameters.FOCUS_MODE_MACRO 微焦模式,拍摄小花小草小蚂蚁专用模式;

场景模式配置参数,可以通过Parameters.getSceneMode()接口获取:

  • Camera.Parameters.SCENE_MODE_BARCODE 扫描条码场景,NextQRCode项目会判断并设置为这个场景;
  • Camera.Parameters.SCENE_MODE_ACTION 动作场景,就是抓拍跑得飞快的运动员、汽车等场景用的;
  • Camera.Parameters.SCENE_MODE_AUTO 自动选择场景;
  • Camera.Parameters.SCENE_MODE_HDR 高动态对比度场景,通常用于拍摄晚霞等明暗分明的照片;
  • Camera.Parameters.SCENE_MODE_NIGHT 夜间场景;

4. 设置相机预览方向

相机预览图需要设置正确的预览方向才能正常地显示预览画面,否则预览画面会被挤压得很惨。 在通常情况下,如果我们需要知道设备的屏幕方向,可以通过Resources.Configuration.orientation来获取。Android屏幕方向有“竖屏”和“横屏”两种,对应的值分别是ORIENTATION_PORTRAIT和ORIENTATION_LANDSCAPE。但相机设备的方向却有些特别,设置预览方向的接口Camera.setDisplayOrientaion(int)的参数是以角度为单位的,而且只能是0,90,180,270其中之一,``默认为0,是指手机的左侧为摄像头顶部画面。记得只能是[0、90、180、270]其中之一,输入其它角度数值会报错`。

如果你想让相机跟随设备的方向,预览界面顶部一直保持正上方,以下代码供参考:

public static void followScreenOrientation(Context context, Camera camera){
    final int orientation = context.getResources().getConfiguration().orientation;
    if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
        camera.setDisplayOrientation(180);
    }else if(orientation == Configuration.ORIENTATION_PORTRAIT) {
        camera.setDisplayOrientation(90);
    }
}
复制代码

5. 预览View与拍照

我们一般使用SurfaceView作为相机预览View,你也可以使用Texture。在SurfaceView中获取得SurfaceHolder,并通过setPreviewDisplay()接口设置预览。在设置预览View后,一定要记得以下两点:

  • 调用startPreview()方法启动预览,否则预览View不会显示任何内容;
  • 拍照操作需要在startPreview()方法执行之后调用;
  • 每次拍照后,预览View会停止预览。所以连续拍照,需要重新调用startPreview()来恢复预览;

Camera接受一个SurfaceHolder接口,这个接口可以通过SurfaceHolder.Callback获得。我们可以通过继承SurfaceView来实现相机预览效果。在NextQRCode项目中,实现了LiveCameraView类,它内部已实现了相机预览所需要的处理过程,很简洁的类,以下是它的全部源码:

ublic class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback {
    private final static String TAG = LiveCameraView.class.getSimpleName();
    private Camera mCamera;
    private SurfaceHolder mSurfaceHolder;

    public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mSurfaceHolder = this.getHolder();
        mSurfaceHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d(TAG, "Start preview display[SURFACE-CREATED]");
        startPreviewDisplay(holder);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (mSurfaceHolder.getSurface() == null){
            return;
        }
        Cameras.followScreenOrientation(getContext(), mCamera);
        Log.d(TAG, "Restart preview display[SURFACE-CHANGED]");
        stopPreviewDisplay();
        startPreviewDisplay(mSurfaceHolder);
    }

    public void setCamera(Camera camera) {
        mCamera = camera;
        final Camera.Parameters params = mCamera.getParameters();
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
    }

    private void startPreviewDisplay(SurfaceHolder holder){
        checkCamera();
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.e(TAG, "Error while START preview for camera", e);
        }
    }

    private void stopPreviewDisplay(){
        checkCamera();
        try {
            mCamera.stopPreview();
        } catch (Exception e){
            Log.e(TAG, "Error while STOP preview for camera", e);
        }
    }

    private void checkCamera(){
        if(mCamera == null) {
            throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]");
        stopPreviewDisplay();
    }
}


复制代码

从上面代码可以看出LiveCameraView的核心代码是SurfaceHolder.Callback的回调:在创建/销毁时启动/停止预览动作。在LiveCameraView类中,我们利用了View的生命周期回调来实现自动管理预览生命周期控制:

  • 当SurfaceView被创建后自动开启预览;
  • 当SurfaceView被销毁时关闭预览;
  • 当SurfaceView尺寸被改变时重置预览;

预览View需要注意预览输出画面的尺寸。相机输出画面只支持部分尺寸。关于尺寸部分,后面再更新

在启用预览View后,就可以通过Camera.takePicture()方法拍摄一张照片,返回的照片数据通过Callback接口获取takePicture()接口可以获取三个类型的照片:

  • 第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效;
  • 第二个,PictureCallback接口,返回未经压缩的RAW类型照片;
  • 第三个,PictureCallback接口,返回经过压缩的JPEG类型照片;

我们使用第三个参数,JPEG类型的照片的图片精度即可满足识别二维码的需求。在NextQRCode项目中,ZXing识别二维码的数据格式为Bitmap,通过BitmapFactory可以很方便方便地将byte数组转换成Bitmap。


public abstract class BitmapCallback implements Camera.PictureCallback {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
    }
    public abstract void onPictureTaken(Bitmap bitmap);
}

复制代码

6. 释放相机设备

在打开一个相机设备后,意味着你的App就独占了这个设备,其它App将无法使用它。因此在你不需要相机设备时,记得调用**release()方法释放设备,再使用时可以重新打开,这并不需要多大的成本。可以选择在stopPreview()**后即释放相机设备。

附加工具性代码实现

  • 1,判断手机设备是否有相机设备
public static boolean hasCameraDevice(Context ctx) {
    return ctx.getPackageManager()
            .hasSystemFeature(PackageManager.FEATURE_CAMERA);
}

复制代码
  • 2 - 判断是否支持自动对焦
public static boolean isAutoFocusSupported(Camera.Parameters params) {
   List<String> modes = params.getSupportedFocusModes();
   return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
}
复制代码

参考链接

关注下面的标签,发现更多相似文章
评论