Jetpack支持库CameraX使用入门——相机预览

概览

CameraX是一个Jetpack支持库,用来帮助开发者简化相机应用的开发和适配工作,它将一系列旧的Camera和新的Camera2的相机API进行统一适配,将两套设计迥异的相机API进行适配设计,并且作为androidX并作为Jetpack的一部分再发布,用来简化开发者开发相机功能时的大量重复适配的工作。最低可以适配到API Level 21。
由于加入到了Jetpack组件当中,它也增加了一部分功能用来绑定到LifecycleOwner等API来适配到生命周期接口的能力,使得开发者无需再重复编写生命周期相关的模板代码。

用例组件

CameraX内置了几个常用的用例,基类是UserCase
,从UserCase中派生出了以下三种使用场景:

  • 预览Preview :被用于显示预览,显示时会使用Surface展示预览,因此还有一个自定义View名为PreviewView来内部选择性使用SurfaceView或者TextureView来展现Surface的预览内容。
  • 图像分析ImageAnalyzeImageAnalyze 提供了一个接口ImageAnalysis.Analyzer用于在预览的同时可以获取图像缓冲区数据用于分析,比如机器学习、计算机视觉与图形学处理等等。
  • 图片拍摄ImageCaptureImageCapture提供了一个接口并在发生图片保存动作时进行回调。

首次使用

我们需要对build.gradle文件进行一定的修改:

我们用到了Kotlin语言,我们需要引入Kotlin语言包,
语言版本:

ext.kotlin_version = '1.5.21'
ext.java_version = JavaVersion.VERSION_1_8

添加Kotlin语言标准库和android支持:

dependencies {
    // Kotlin lang
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
}

我们需要引入CameraX相关的包

dependencies {
    // CameraX 核心库
    def camerax_version = '1.0.1'
    implementation "androidx.camera:camera-core:$camerax_version"

    // CameraX Camera2 扩展包
    implementation "androidx.camera:camera-camera2:$camerax_version"

    // CameraX Lifecycle 生命周期框架支持
    implementation "androidx.camera:camera-lifecycle:$camerax_version"

    // CameraX View 用于预览的View
    implementation 'androidx.camera:camera-view:1.0.0-alpha27'
}

预览用例

仅仅对于相机的预览来说,我们用到的类组件由以下几个类即可,不需要使用到Camera类。

Step 1:

首先我们先布局一个可预览的PreviewView。这个View是已经在androidx.camera中预置的自定义View:

<FrameLayout
    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>

由于这个View已经包含了SurfaceView或TextureView,我们无需再考虑处理这部分的逻辑。

Step 2:

我们通过ProcessCameraProvider.getInstance(this)得到一个线程池的一个可等待对象,这个类型是ListenableFuture<ProcessCameraProvider>

lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
cameraProviderFuture = ProcessCameraProvider.getInstance(this)

然后在这个延迟等待对象中执行代码,并且运行在主线程Looper的Handler中。

cameraProviderFuture.addListener({
}, ContextCompat.getMainExecutor(this))

Step 3:

我们获取ProcessCameraProvider,用于获取Camera并且绑定Surface到PreviewView上。

cameraProviderFuture.addListener({
   val cameraProvider = cameraProviderFuture.get()
   //在这里绑定Preview
   bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))

bindPreview的函数定义如下:

fun bindPreview(cameraProvider: ProcessCameraProvider) {
    var preview: Preview = Preview.Builder().build()
    var cameraSelector =
       CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
    val previewView = findViewById<PreviewView>(R.id.previewView)
    preview.setSurfaceProvider(previewView.surfaceProvider)

    cameraProvider.bindToLifecycle(this, cameraSelector, preview)
}

拆解一下这个函数每一句话的作用:

构建一个Preview用例
var preview: Preview = Preview.Builder().build()
选择摄像头

CameraSelector.LENS_FACING_BACK表示请求的镜头朝向是后置的,
CameraSelector.LENS_FACING_BACK表示请求的镜头朝向是前置的。

var cameraSelector = 
       CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
绑定PreviewView

Preview要显示预览内容必须将其与PreviewView中的SurfaceView或Texture的Surface绑定。
Preview.setSurfaceProvider(SurfaceProvider) 方法承担了这个最主要的工作。

val previewView = findViewById<PreviewView>(R.id.previewView)
preview.setSurfaceProvider(previewView.surfaceProvider)
将相机对象绑定到生命周期接口上

最后一步通过ProcessCameraProviderPreviewView预览、Preview用例、CameraSelector相机选择对象统合并且绑定。

cameraProvider.bindToLifecycle(this, cameraSelector, preview)

ProcessCameraProvider.bindToLifecycle()的最后一个参数是可变长度参数,可以传入多个UseCase的实现类,比如同时传入Preview的用例和ImageCapture的用例,实现预览并拍照的功能。如果加入ImageAnalyze还可以对缓冲区进行分析和其他操作。

一个DemoActivity的代码

这样就可以通过CameraX简化并且自动化选择相机参数显示相机预览,代码十分精简:

class DemoActivity : AppCompatActivity() {
    private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_demo)

        cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            //在这里绑定Preview
            bindPreview(cameraProvider)
        }, ContextCompat.getMainExecutor(this))
    }

    fun bindPreview(cameraProvider: ProcessCameraProvider) {
        var preview: Preview = Preview.Builder().build()
        var cameraSelector: CameraSelector =
            CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
        val previewView = findViewById<PreviewView>(R.id.previewView)
        preview.setSurfaceProvider(previewView.surfaceProvider)
        cameraProvider.bindToLifecycle(this, cameraSelector, preview)
    }
}

然而其内部判断是SurfaceView还是TextureView呢?PreviewView类中给出了一段代码

    static boolean shouldUseTextureView(@NonNull SurfaceRequest surfaceRequest,
            @NonNull final ImplementationMode implementationMode) {
        // TODO(b/159127402): use TextureView if target rotation is not display rotation.
        boolean isLegacyDevice = surfaceRequest.getCamera().getCameraInfoInternal()
                .getImplementationType().equals(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
        boolean hasSurfaceViewQuirk = DeviceQuirks.get(SurfaceViewStretchedQuirk.class) != null;
        if (surfaceRequest.isRGBA8888Required() || Build.VERSION.SDK_INT <= 24 || isLegacyDevice
                || hasSurfaceViewQuirk) {
            // Force to use TextureView when the device is running android 7.0 and below, legacy
            // level, RGBA8888 is required or SurfaceView has quirks.
            return true;
        }
        switch (implementationMode) {
            case COMPATIBLE:
                return true;
            case PERFORMANCE:
                return false;
            default:
                throw new IllegalArgumentException(
                        "Invalid implementation mode: " + implementationMode);
        }
    }

看得出来其给出了4个条件,只要符合其一就使用TextureView,并且通过可以通过手动设定模式来强制选择。

我们看一下这四个条件:

  • 1、如果这个设备是较老的设备,只要判定在Android 7.0以下;
  • 2、如果摄像头支持级别是旧的级别;
  • 3、如果摄像头请求要求的色彩空间是RGB8888时;
  • 4、设备厂商的兼容性问题

除此以外都使用SurfaceView。因此我们可以看出来,Android团队是多么偏爱SurfaceView。

当对PreviewView选择了特定实现模式是COMPATIBLE时,也是使用TextureView,选择实现模式是PERFORMANCE时,使用SurfaceView,并且使得以上4个条件失效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值