(第二讲)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);

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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui