(第二讲)Android开发取摄像头流的基础(ImageAnalysis)

要用Android开发视频类的程序,首相要了解的是Camerx,先学会预览。学会从摄像机取视频流处理。

我给你 CameraX 最精简核心代码,复制直接用。

1. 权限(AndroidManifest.xml)

xml 复制代码
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera.any"/>

2. 布局(PreviewView)

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <!-- PreviewView 是 CameraX 推荐的预览组件,自动处理表面生命周期和缩放 -->
    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnSwitchCamera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="切换摄像头"
        android:layout_margin="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. 核心代码(Activity)

java 复制代码
package com.example.myapplication;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
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.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "CameraXPreview";
    private static final int REQUEST_CODE_PERMISSIONS = 10;
    private static final String[] REQUIRED_PERMISSIONS = new String[]{Manifest.permission.CAMERA};
    private PreviewView previewView;
    private ProcessCameraProvider cameraProvider;
    private CameraSelector cameraSelector;
    private ExecutorService cameraExecutor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        previewView = findViewById(R.id.previewView);
        Button btnSwitch = findViewById(R.id.btnSwitchCamera);

        // 初始化后台线程池用于相机操作
        cameraExecutor = Executors.newSingleThreadExecutor();

        // 默认使用后摄
        cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

        // 请求权限
        if (allPermissionsGranted()) {
            startCamera();
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }

        // 切换摄像头按钮逻辑
        btnSwitch.setOnClickListener(v -> switchCamera());
    }

    private void switchCamera() {
        if (cameraProvider == null) return;

        // 切换前后摄像头选择器
        if (cameraSelector.equals(CameraSelector.DEFAULT_BACK_CAMERA)) {
            cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;
        } else {
            cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
        }

        // 重新启动相机
        startCamera();
    }

    private void startCamera() {
        // 获取 CameraProvider 实例
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        cameraProviderFuture.addListener(() -> {
            try {
                cameraProvider = cameraProviderFuture.get();

                // 构建预览用例
                Preview preview = new Preview.Builder().build();

                // 将预览表面绑定到 PreviewView
                preview.setSurfaceProvider(previewView.getSurfaceProvider());

                // 解绑之前绑定的用例(防止重复绑定导致崩溃)
                cameraProvider.unbindAll();

                // 将预览用例绑定到生命周期
                Camera camera = cameraProvider.bindToLifecycle(
                        this, cameraSelector, preview);

            } catch (ExecutionException | InterruptedException e) {
                Log.e(TAG, "Use case binding failed", e);
            }
        }, ContextCompat.getMainExecutor(this));
    }

    private boolean allPermissionsGranted() {
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera();
            } else {
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 关闭执行器以释放资源
        cameraExecutor.shutdown();
    }
}

4. 依赖(build.gradle)

gradle 复制代码
def camerax = "1.3.0"
implementation "androidx.camera:camera-core:$camerax"
implementation "androidx.camera:camera-view:$camerax"
implementation "androidx.camera:camera-lifecycle:$camerax"

这里最关键的是理解

Preview preview = new Preview.Builder().build(); //相当于拿了一个视频线

preview.setSurfaceProvider(previewView.getSurfaceProvider()); //视频线连接到了显示器

CameraSelector selector = CameraSelector.DEFAULT_BACK_CAMERA;//启动了摄像机

provider.bindToLifecycle(this, selector, preview);//摄像机连接到了视频线

理解了上面,接下来我们加入ImageAnalysis

主要修改的是MainActivity.java

java 复制代码
package com.example.myapplication;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

   private static final String TAG = "CameraXPreview";
   private static final int REQUEST_CODE_PERMISSIONS = 10;
   private static final String[] REQUIRED_PERMISSIONS = new String[]{Manifest.permission.CAMERA};
   private PreviewView previewView;
   private ProcessCameraProvider cameraProvider;
   private CameraSelector cameraSelector;
   private ExecutorService cameraExecutor;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       EdgeToEdge.enable(this);
       setContentView(R.layout.activity_main);
       previewView = findViewById(R.id.previewView);
       Button btnSwitch = findViewById(R.id.btnSwitchCamera);

       // 初始化后台线程池用于相机操作
       cameraExecutor = Executors.newSingleThreadExecutor();

       // 默认使用后摄
       cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

       // 请求权限
       if (allPermissionsGranted()) {
           startCamera();
       } else {
           ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
       }

       // 切换摄像头按钮逻辑
       btnSwitch.setOnClickListener(v -> switchCamera());
   }

   private void switchCamera() {
       if (cameraProvider == null) return;

       // 切换前后摄像头选择器
       if (cameraSelector.equals(CameraSelector.DEFAULT_BACK_CAMERA)) {
           cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;
       } else {
           cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
       }

       // 重新启动相机
       startCamera();
   }

   private void startCamera() {
       // 获取 CameraProvider 实例
       ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

       cameraProviderFuture.addListener(() -> {
           try {
               cameraProvider = cameraProviderFuture.get();

               // 构建预览用例
               Preview preview = new Preview.Builder().build();

               // 将预览表面绑定到 PreviewView
               preview.setSurfaceProvider(previewView.getSurfaceProvider());

               //创建ImageAnalysis
               ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                       .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
                       .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                       .build();

               // 设置分析器,使用后台线程执行
               imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new ImageAnalysis.Analyzer() {
                   @Override
                   public void analyze(@NonNull ImageProxy imageProxy) {
                       int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
                       // 在此处插入图像处理逻辑,例如二维码识别、物体检测等
                       // processImage(imageProxy.getImage(), rotationDegrees);

                       Log.d(TAG, "Analyzed frame with rotation1: " + rotationDegrees);

                       // 【重要】处理完成后必须关闭 imageProxy,否则后续帧无法获取
                       imageProxy.close();
                   }
               });

               // 解绑之前绑定的用例(防止重复绑定导致崩溃)
               cameraProvider.unbindAll();

               // 将预览用例绑定到生命周期
               Camera camera = cameraProvider.bindToLifecycle(
                       this, cameraSelector, preview,imageAnalysis);

           } catch (ExecutionException | InterruptedException e) {
               Log.e(TAG, "Use case binding failed", e);
           }
       }, ContextCompat.getMainExecutor(this));
   }

   private boolean allPermissionsGranted() {
       for (String permission : REQUIRED_PERMISSIONS) {
           if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
               return false;
           }
       }
       return true;
   }


   @Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
       super.onRequestPermissionsResult(requestCode, permissions, grantResults);
       if (requestCode == REQUEST_CODE_PERMISSIONS) {
           if (allPermissionsGranted()) {
               startCamera();
           } else {
               Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
               finish();
           }
       }
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
       // 关闭执行器以释放资源
       cameraExecutor.shutdown();
   }
}

注意,我们创建了一个ImageAnalyzed,并且bingToLifecycle中绑定了两路,一路预览,一路视频分析

cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis);

我们运行后,能看到后台不停的打印

相关推荐
敲代码的瓦龙2 小时前
操作系统?Android与Linux!!!
android·linux·运维
愚公搬代码3 小时前
【愚公系列】《移动端AI应用开发》017-Android端应用开发(网络通信与API集成)
android·人工智能
say_fall3 小时前
可编程中断控制器8259A工作方式超详细解析
android·开发语言·学习·硬件架构·硬件工程
甜瓜看代码4 小时前
SystemUI 启动与组成机制
android·源码·源码阅读
黄林晴5 小时前
Kotlin 2.4.0 正式稳定!Android 升级、Compose、KMP 全变化详解
android·kotlin
恋猫de小郭6 小时前
Android 官方给 Compose 搞了个不需要 UI 环境的 Composable
android·前端·flutter
珊瑚里的鱼8 小时前
C++的强制类型转换
android·开发语言·c++
问心无愧05138 小时前
ctf show web入门102
android·java·前端·笔记
Kapaseker9 小时前
Kotlin 相等的奥义
android·kotlin