欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android Camera2 预览功能实现

程序员文章站 2022-05-11 14:17:20
1. 概述 最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能。网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camera2 API 所取代。 全新的 Camera2 在 Camera 的基础上进行了改造,大幅提升了 A ......

1. 概述

最近在做一些关于人脸识别的项目,需要用到 android 相机的预览功能。网上查阅相关资料后,发现 android 5.0 及以后的版本中,原有的 camera api 已经被 camera2 api 所取代。

全新的 camera2 在 camera 的基础上进行了改造,大幅提升了 android 系统的拍照功能。它通过以下几个类与方法来实现相机预览时的工作过程:

  • cameramanager :摄像头管理器,主要用于检测系统摄像头、打开系统摄像头等;
  • cameradevice : 用于描述系统摄像头,可用于关闭相机、创建相机会话、发送拍照请求等;
  • cameracharacteristics :用于描述摄像头所支持的各种特性;
  • cameracapturesession :当程序需要预览、拍照时,都需要先通过 cameracapturesession 来实现。该会话通过调用方法 setrepeatingrequest() 实现预览;
  • camerarequest :代表一次捕获请求,用于描述捕获图片的各种参数设置;
  • camerarequest.builder :负责生成 camerarequest 对象。

2. 相机预览

下面通过源码来讲解如何使用 camera2 来实现相机的预览功能。

2.1 相机权限设置

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

2.2 app 布局

  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<framelayout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    tools:context=".mainactivity">
</framelayout>
  • fragment_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".camerafragment">

    <com.lightweh.camera2preview.autofittextureview
        android:id="@+id/textureview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centervertical="true"
        android:layout_centerhorizontal="true" />

</relativelayout>

2.3 相机自定义view

public class autofittextureview extends textureview {

    private int mratiowidth = 0;
    private int mratioheight = 0;

    public autofittextureview(context context) {
        this(context, null);
    }

    public autofittextureview(context context, attributeset attrs) {
        this(context, attrs, 0);
    }

    public autofittextureview(context context, attributeset attrs, int defstyle) {
        super(context, attrs, defstyle);
    }

    public void setaspectratio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new illegalargumentexception("size cannot be negative.");
        }
        mratiowidth = width;
        mratioheight = height;
        requestlayout();
    }

    @override
    protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
        super.onmeasure(widthmeasurespec, heightmeasurespec);
        int width = measurespec.getsize(widthmeasurespec);
        int height = measurespec.getsize(heightmeasurespec);
        if (0 == mratiowidth || 0 == mratioheight) {
            setmeasureddimension(width, height);
        } else {
            if (width < height * mratiowidth / mratioheight) {
                setmeasureddimension(width, width * mratioheight / mratiowidth);
            } else {
                setmeasureddimension(height * mratiowidth / mratioheight, height);
            }
        }
    }
}

2.4 动态申请相机权限

public class mainactivity extends appcompatactivity {

    private static final int request_permission = 1;

    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        setcontentview(r.layout.activity_main);

        if (haspermission()) {
            if (null == savedinstancestate) {
                setfragment();
            }
        } else {
            requestpermission();
        }
    }

    @override
    public void onrequestpermissionsresult(int requestcode, string permissions[], int[] grantresults) {

        if (requestcode == request_permission) {
            if (grantresults.length == 1 && grantresults[0] == packagemanager.permission_granted) {
                setfragment();
            } else {
                requestpermission();
            }
        } else {
            super.onrequestpermissionsresult(requestcode, permissions, grantresults);
        }
    }
    // 权限判断,当系统版本大于23时,才有必要判断是否获取权限
    private boolean haspermission() {
        if (build.version.sdk_int >= build.version_codes.m) {
            return checkselfpermission(manifest.permission.camera) == packagemanager.permission_granted;
        } else {
            return true;
        }
    }
    // 请求相机权限
    private void requestpermission() {
        if (build.version.sdk_int >= build.version_codes.m) {
            if (shouldshowrequestpermissionrationale(manifest.permission.camera)) {
                toast.maketext(mainactivity.this, "camera permission are required for this demo", toast.length_long).show();
            }
            requestpermissions(new string[]{manifest.permission.camera}, request_permission);
        }
    }
    // 启动相机fragment
    private void setfragment() {
        getsupportfragmentmanager()
                .begintransaction()
                .replace(r.id.container, camerafragment.newinstance())
                .commitnowallowingstateloss();
    }
}

2.5 开启相机预览

首先,在onresume()中,我们需要开启一个 handlerthread,然后利用该线程的 looper 对象构建一个 handler 用于相机回调。

@override
public void onresume() {
    super.onresume();
    startbackgroundthread();

    // when the screen is turned off and turned back on, the surfacetexture is 
    // already available, and "onsurfacetextureavailable" will not be called. in 
    // that case, we can open a camera and start preview from here (otherwise, we 
    // wait until the surface is ready in the surfacetexturelistener).
    if (mtextureview.isavailable()) {
        opencamera(mtextureview.getwidth(), mtextureview.getheight());
    } else {
        mtextureview.setsurfacetexturelistener(msurfacetexturelistener);
    }
}
private void startbackgroundthread() {
    mbackgroundthread = new handlerthread("camerabackground");
    mbackgroundthread.start();
    mbackgroundhandler = new handler(mbackgroundthread.getlooper());
}

同时,在 onpause() 中有对应的 handlerthread 关闭方法。

当屏幕关闭后重新开启,surfacetexture 已经就绪,此时不会触发 onsurfacetextureavailable 回调。因此,我们判断 mtextureview 如果可用,则直接打开相机,否则等待 surfacetexture 回调就绪后再开启相机。

private void opencamera(int width, int height) {
    if (contextcompat.checkselfpermission(getactivity(), manifest.permission.camera)
            != packagemanager.permission_granted) {
        return;
    }
    setupcameraoutputs(width, height);
    configuretransform(width, height);
    activity activity = getactivity();
    cameramanager manager = (cameramanager) activity.getsystemservice(context.camera_service);
    try {
        if (!mcameraopencloselock.tryacquire(2500, timeunit.milliseconds)) {
            throw new runtimeexception("time out waiting to lock camera opening.");
        }
        manager.opencamera(mcameraid, mstatecallback, mbackgroundhandler);
    } catch (cameraaccessexception e) {
        e.printstacktrace();
    } catch (interruptedexception e) {
        throw new runtimeexception("interrupted while trying to lock camera opening.", e);
    }
}

开启相机时,我们首先判断是否具备相机权限,然后调用 setupcameraoutputs 函数对相机参数进行设置(包括指定摄像头、相机预览方向以及预览尺寸的设定等),接下来调用 configuretransform 函数对预览图片的大小和方向进行调整,最后获取 cameramanager 对象开启相机。因为相机有可能会被其他进程同时访问,所以在开启相机时需要加锁。

private final cameradevice.statecallback mstatecallback = new cameradevice.statecallback() {

    @override
    public void onopened(@nonnull cameradevice cameradevice) {
        mcameraopencloselock.release();
        mcameradevice = cameradevice;
        createcamerapreviewsession();
    }

    @override
    public void ondisconnected(@nonnull cameradevice cameradevice) {
        mcameraopencloselock.release();
        cameradevice.close();
        mcameradevice = null;
    }

    @override
    public void onerror(@nonnull cameradevice cameradevice, int error) {
        mcameraopencloselock.release();
        cameradevice.close();
        mcameradevice = null;
        activity activity = getactivity();
        if (null != activity) {
            activity.finish();
        }
    }
};

相机开启时还会指定相机的状态变化回调函数 mstatecallback,如果相机成功开启,则开始创建相机预览会话。

private void createcamerapreviewsession() {
    try {
        // 获取 texture 实例
        surfacetexture texture = mtextureview.getsurfacetexture();
        assert texture != null;

        // 设置 textureview 缓冲区大小
        texture.setdefaultbuffersize(mpreviewsize.getwidth(), mpreviewsize.getheight());

        // 获取 surface 显示预览数据
        surface surface = new surface(texture);

        // 构建适合相机预览的请求
        mpreviewrequestbuilder = mcameradevice.createcapturerequest(cameradevice.template_preview);
        
        // 设置 surface 作为预览数据的显示界面
        mpreviewrequestbuilder.addtarget(surface);

        // 创建相机捕获会话用于预览
        mcameradevice.createcapturesession(arrays.aslist(surface),
                new cameracapturesession.statecallback() {

                    @override
                    public void onconfigured(@nonnull cameracapturesession cameracapturesession) {
                        // 如果相机关闭则返回
                        if (null == mcameradevice) {
                            return;
                        }

                        // 如果会话准备好则开启预览
                        mcapturesession = cameracapturesession;
                        try {
                            // 自动对焦
                          mpreviewrequestbuilder.set(capturerequest.control_af_mode,
                                 capturerequest.control_af_mode_continuous_picture);

                            mpreviewrequest = mpreviewrequestbuilder.build();
                            // 设置反复捕获数据的请求,预览界面一直显示画面
                            mcapturesession.setrepeatingrequest(mpreviewrequest,
                                    null, mbackgroundhandler);
                        } catch (cameraaccessexception e) {
                            e.printstacktrace();
                        }
                    }

                    @override
                    public void onconfigurefailed(
                            @nonnull cameracapturesession cameracapturesession) {
                        showtoast("failed");
                    }
                }, null
        );
    } catch (cameraaccessexception e) {
        e.printstacktrace();
    }
}

以上便是 camera2 api 实现相机预览的主要过程。

3. demo 源码

github:camera2preview

4. 参考

  • https://github.com/googlesamples/android-camera2basic