zoukankan      html  css  js  c++  java
  • Android CameraX 打开摄像头预览

    目标很简单,用CameraX打开摄像头预览,实时显示在界面上。看看CameraX有没有Google说的那么好用。先按最简单的来,把预览显示出来。

    引入依赖

    模块gradle的一些配置,使用的Android SDK版本为31,启用了databinding

    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    
    android {
        compileSdkVersion 31
        buildToolsVersion "31.0.0"
        defaultConfig {
            minSdkVersion 21
            targetSdkVersion 31
        }
        dataBinding {
            enabled = true
        }
    }
    

    引入CameraX依赖(CameraX 核心库是用camera2实现的),目前主要用1.1.0-alpha11版本

    dependencies {
        implementation "androidx.camera:camera-core:1.1.0-alpha11"
        implementation "androidx.camera:camera-camera2:1.1.0-alpha11"
        implementation "androidx.camera:camera-lifecycle:1.1.0-alpha11"
        implementation "androidx.camera:camera-view:1.0.0-alpha31"
        implementation "androidx.camera:camera-extensions:1.0.0-alpha31"
    }
    

    使用1.0.2版本的CameraX核心库会报错,找不到getOrCreateInstance方法。

    ??? bug "NoSuchMethodError getOrCreateInstance"

    ```log
    CrashHandler: In thread: Thread[main,5,main]
        UncaughtException detected: java.lang.NoSuchMethodError: No static method getOrCreateInstance(Landroid/content/Context;)Lcom/google/common/util/concurrent/ListenableFuture; in class Landroidx/camera/core/CameraX; or its super classes (declaration of 'androidx.camera.core.CameraX' appears in /data/app/com.rustfisher.tutorial2020-1/base.apk)
        at androidx.camera.lifecycle.ProcessCameraProvider.getInstance(ProcessCameraProvider.java:149)
        at com.rustfisher.tutorial2020.camera.SimplePreviewXAct.onCreate(SimplePreviewXAct.java:36)
        at android.app.Activity.performCreate(Activity.java:6161)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1112)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2507)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2640)
        at android.app.ActivityThread.access$800(ActivityThread.java:182)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1493)
        at android.os.Handler.dispatchMessage(Handler.java:111)
        at android.os.Looper.loop(Looper.java:194)
        at android.app.ActivityThread.main(ActivityThread.java:5682)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:963)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:758)
    ```
    

    权限

    需要动态申请android.permission.CAMERA权限

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

    本文略过动态申请权限的地方

    界面

    CameraX为开发者贴心地准备了androidx.camera.view.PreviewView

    把它放在一个FrameLayout里,如下的act_simple_preivew_x.layout

    <?xml version="1.0" encoding="utf-8"?>
    <layout>
        <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <androidx.camera.view.PreviewView
                android:id="@+id/previewView"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
    
        </FrameLayout>
    </layout>
    

    开启预览

    在activity中开启相机预览

    // SimplePreviewXAct.java
    import android.os.Bundle;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.camera.core.Camera;
    import androidx.camera.core.CameraSelector;
    import androidx.camera.core.Preview;
    import androidx.camera.lifecycle.ProcessCameraProvider;
    import androidx.core.content.ContextCompat;
    import androidx.databinding.DataBindingUtil;
    import androidx.lifecycle.LifecycleOwner;
    
    import com.google.common.util.concurrent.ListenableFuture;
    // import com.rustfisher.tutorial2020.R;
    // import com.rustfisher.tutorial2020.databinding.ActSimplePreivewXBinding;
    
    import java.util.concurrent.ExecutionException;
    
    public class SimplePreviewXAct extends AppCompatActivity {
    
        private ActSimplePreivewXBinding mBinding;
        private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.act_simple_preivew_x);
            cameraProviderFuture = ProcessCameraProvider.getInstance(this);
            cameraProviderFuture.addListener(() -> {
                try {
                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    bindPreview(cameraProvider);
                } catch (ExecutionException | InterruptedException e) {
                    // 这里不用处理
                }
            }, ContextCompat.getMainExecutor(this));
        }
    
        void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
            Preview preview = new Preview.Builder().build();
    
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build();
    
            preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());
    
            Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);
        }
    }
    

    注意我们这里使用的是androidx.appcompat.app.AppCompatActivity

    为了获得ProcessCameraProvider,用ProcessCameraProvider.getInstance方法拿到一个cameraProviderFuture
    cameraProviderFuture完成后取出ProcessCameraProvidercameraProvider)。

    要开启预览,通过Preview.Builder构建一个Preview。用CameraSelector来选择后置摄像头。
    PreviewSurfaceProvider由layout中的androidx.camera.view.PreviewView提供。

    cameraProvider.bindToLifecycle绑定上后,启动摄像头预览

    运行测试

    运行到手机上,打开这个Activity就可以看到摄像头预览。图像宽高比正常,没有拉伸现象。

    • 荣耀 EMUI 3.1 Lite,Android 5.1 运行正常
    • Redmi 9A,MIUI 12.5.1稳定版,Android 10 运行正常
    • 一加5,H2OS 10.0.3,Android 10 运行正常

    增加开关

    在layout里加2个按钮,控制相机开关

    <?xml version="1.0" encoding="utf-8"?>
    <layout>
    
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimaryDark"
                android:gravity="center"
                android:orientation="horizontal"
                android:padding="4dp">
    
                <Button
                    android:id="@+id/start"
                    style="@style/NormalBtn"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="打开" />
    
                <Button
                    android:id="@+id/end"
                    style="@style/NormalBtn"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="12dp"
                    android:text="关闭" />
            </LinearLayout>
    
            <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/container"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
                <androidx.camera.view.PreviewView
                    android:id="@+id/previewView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </FrameLayout>
    
        </LinearLayout>
    </layout>
    

    根layout换成LinearLayout

    修改bindPreview方法,先检查传入的ProcessCameraProvider是否为空

    private void bindPreview(ProcessCameraProvider cameraProvider) {
        if (cameraProvider == null) {
            Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();
            return;
        }
        Preview preview = new Preview.Builder().build();
    
        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();
    
        preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());
    
        Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);
    }
    

    修改后的activity部分代码

    import android.os.Bundle;
    import android.widget.Toast;
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.camera.core.Camera;
    import androidx.camera.core.CameraSelector;
    import androidx.camera.core.Preview;
    import androidx.camera.lifecycle.ProcessCameraProvider;
    import androidx.core.content.ContextCompat;
    import androidx.databinding.DataBindingUtil;
    
    import com.google.common.util.concurrent.ListenableFuture;
    // import com.rustfisher.tutorial2020.R;
    // import com.rustfisher.tutorial2020.databinding.ActSimplePreivewXBinding;
    
    import java.util.concurrent.ExecutionException;
    
    public class SimplePreviewXAct extends AppCompatActivity {
    
        private ActSimplePreivewXBinding mBinding;
        private ListenableFuture<ProcessCameraProvider> mCameraProviderFuture;
        private ProcessCameraProvider mCameraProvider;
        private boolean mRunning = false;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mBinding = DataBindingUtil.setContentView(this, R.layout.act_simple_preivew_x);
            mCameraProviderFuture = ProcessCameraProvider.getInstance(this);
            mCameraProviderFuture.addListener(() -> {
                try {
                    mCameraProvider = mCameraProviderFuture.get();
                } catch (ExecutionException | InterruptedException e) {
                    // 这里不用处理
                }
            }, ContextCompat.getMainExecutor(this));
            mBinding.start.setOnClickListener(v -> {
                if (mCameraProvider != null && !mRunning) {
                    bindPreview(mCameraProvider);
                }
            });
            mBinding.end.setOnClickListener(v -> {
                mCameraProvider.unbindAll();
                mRunning = false;
            });
        }
    
        private void bindPreview(ProcessCameraProvider cameraProvider) {
            if (cameraProvider == null) {
                Toast.makeText(getApplicationContext(), "没获取到相机", Toast.LENGTH_SHORT).show();
                return;
            }
            Preview preview = new Preview.Builder().build();
    
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build();
    
            preview.setSurfaceProvider(mBinding.previewView.getSurfaceProvider());
    
            Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview);
            mRunning = true;
        }
    }
    

    拿到mCameraProvider后不要立刻绑定生命周期。

    如果要开启预览,则调用bindPreview(mCameraProvider)。记录一下现在相机已经开启预览mRunning = true

    如果要停止预览,则解绑生命周期mCameraProvider.unbindAll()。这个方法需要在主线程调用。

    运行起来后,可以用按钮来控制相机预览的开关。相比之前,PreviewView的高度变小了一点(让了点位置给按钮)。
    但视频宽高比例正常,没有被拉伸。默认的配置下,还有自动对焦的功能。

    小结

    从简单的打开相机预览来看,CameraX简化了开发者的工作。提供了PreviewView,开发者不需要自定义SurfaceView或者TextureView。实时预览中,相机能够自动对焦。本文用的是1.1.0-alpha11,而CameraX还在发展之中。

    参考

    原文链接CameraX 打开摄像头预览

    一个软件工程师的记录
  • 相关阅读:
    关于sifari兼容性的一个问题
    HTML标签的应用(新手)
    HTML标签的应用(新手)
    未完成的开锁动画演示
    HTML标签的应用(新手)
    HTML新手向
    C++
    STL之set
    C++输入输出
    提升一下逼格
  • 原文地址:https://www.cnblogs.com/rustfisher/p/15670052.html
Copyright © 2011-2022 走看看