測試所有攝像頭
安卓CameraX:https://developer.android.com/media/grow/spatial-audio?hl=zh-cn
1、MainActivity.java
// 定義包名
package com.mms.densenapplication;// 引入 AppCompatActivity,支持兼容性更強的 Activity
import androidx.appcompat.app.AppCompatActivity;// 引入上下文和生命周期相關的類
import android.content.Context;
import android.os.Bundle;// 引入 CameraX 的預覽控件
import androidx.camera.view.PreviewView;// Java 的集合類
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.HashMap;// 下拉選擇框相關類
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.AdapterView;// View 相關類
import android.view.View;// Camera2 API 相關類
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;// CameraX 核心組件
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.Preview;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.lifecycle.ProcessCameraProvider;// 異步執行與線程池
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;// 工具類
import android.util.Size;
import android.util.Log;import androidx.core.content.ContextCompat;// CameraX 與 Camera2 的互操作類
import androidx.camera.camera2.interop.Camera2CameraInfo;public class MainActivity extends AppCompatActivity {// 攝像頭預覽組件private PreviewView previewView;// 攝像頭選擇下拉框private Spinner cameraSpinner;// 用于顯示在下拉框中的攝像頭列表(帶說明文字)private List<String> cameraIdList = new ArrayList<>();// 用于保存顯示名和真實 cameraId 的映射關系private Map<String, String> cameraIdMap = new HashMap<>();// Camera2 管理器private CameraManager cameraManager;// CameraX 的生命周期相機提供器private ProcessCameraProvider cameraProvider;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 加載布局文件 activity_main.xmlsetContentView(R.layout.activity_main);// 獲取布局中的預覽視圖和攝像頭選擇下拉框控件previewView = findViewById(R.id.previewView);cameraSpinner = findViewById(R.id.cameraSpinner);// 獲取系統攝像頭服務cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);try {// 遍歷系統中所有攝像頭for (String id : cameraManager.getCameraIdList()) {// 獲取該攝像頭的屬性CameraCharacteristics cc = cameraManager.getCameraCharacteristics(id);// 獲取攝像頭朝向(前置/后置)Integer facing = cc.get(CameraCharacteristics.LENS_FACING);// 生成顯示用的攝像頭名稱String name = "Camera ID: " + id;if (facing != null) {if (facing == CameraCharacteristics.LENS_FACING_BACK) name += " (BACK)";else if (facing == CameraCharacteristics.LENS_FACING_FRONT) name += " (FRONT)";else name += " (EXTERNAL)";}// 添加到顯示列表和 ID 映射表中cameraIdList.add(name);cameraIdMap.put(name, id);}} catch (CameraAccessException e) {e.printStackTrace();}// 設置下拉框的適配器ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, cameraIdList);cameraSpinner.setAdapter(adapter);// 當用戶選擇某個攝像頭時觸發cameraSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {@Overridepublic void onItemSelected(AdapterView<?> parent, View view, int position, long id) {// 獲取選中的顯示項String selected = cameraIdList.get(position);// 查找對應的真實攝像頭 IDString realCameraId = cameraIdMap.get(selected);// 調用綁定攝像頭的方法if (realCameraId != null) {bindCamera(realCameraId);}}@Override public void onNothingSelected(AdapterView<?> parent) {}});// 異步獲取 ProcessCameraProvider 實例(生命周期感知)ProcessCameraProvider.getInstance(this).addListener(() -> {try {cameraProvider = ProcessCameraProvider.getInstance(this).get();} catch (ExecutionException | InterruptedException e) {e.printStackTrace();}}, ContextCompat.getMainExecutor(this));}/*** 綁定指定 ID 的攝像頭,并顯示預覽及處理圖像幀*/private void bindCamera(String cameraId) {if (cameraProvider == null) return;// 解綁之前所有的攝像頭使用cameraProvider.unbindAll();// 構建 CameraSelector,通過 cameraId 過濾匹配的攝像頭CameraSelector cameraSelector = new CameraSelector.Builder().addCameraFilter(cameras -> {List<CameraInfo> matched = new ArrayList<>();for (CameraInfo info : cameras) {// 將 CameraInfo 轉換為 Camera2CameraInfo 以獲取其 cameraIdCamera2CameraInfo camera2CameraInfo = Camera2CameraInfo.from(info);if (camera2CameraInfo.getCameraId().equals(cameraId)) {matched.add(info);}}return matched;}).build();// 創建預覽用的 Preview 對象,并設置其 SurfaceProviderPreview preview = new Preview.Builder().build();preview.setSurfaceProvider(previewView.getSurfaceProvider());// 創建圖像分析器,并設置分辨率與策略ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().setTargetResolution(new Size(224, 224)) // 設置目標分辨率.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 丟棄舊幀.build();// 設置分析器線程和回調(這里只打印幀大小)imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), image -> {Log.d("CameraDebug", "Frame: " + image.getWidth() + "x" + image.getHeight());image.close(); // 一定要關閉,否則會卡住});// 將預覽和分析器綁定到生命周期cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis);}
}
2、layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Spinnerandroid:id="@+id/cameraSpinner"android:layout_width="match_parent"android:layout_height="wrap_content" /><androidx.camera.view.PreviewViewandroid:id="@+id/previewView"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"/>
</LinearLayout>
3、build.gradle.kts(app)
import com.android.build.gradle.internal.packaging.createDefaultDebugStoreplugins {alias(libs.plugins.androidApplication)
}android {namespace = "com.mms.densenapplication"compileSdk = 34defaultConfig {applicationId = "com.mms.densenapplication"minSdk = 30targetSdk = 34versionCode = 1versionName = "1.0"testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"ndk {
// abiFilters += listOf("armeabi-v7a", "arm64-v8a")abiFilters += listOf("arm64-v8a")}externalNativeBuild {cmake {cppFlags("")arguments("-DANDROID_STL=c++_shared")}}}signingConfigs{getByName("debug") {storeFile = file("${project.rootDir}/platform_2.0.jks")storePassword = "android"keyAlias = "androiddebugkey"keyPassword = "android"}}buildTypes {release {isMinifyEnabled = falseproguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}externalNativeBuild {cmake {path = file("src/main/cpp/CMakeLists.txt")version = "3.22.1"}}buildFeatures {viewBinding = true}aaptOptions {noCompress("dat", "zip", "bin", "")}
}dependencies {implementation(libs.appcompat)implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.0")implementation(libs.material)implementation(libs.constraintlayout)testImplementation(libs.junit)androidTestImplementation(libs.ext.junit)androidTestImplementation(libs.espresso.core)implementation("androidx.camera:camera-camera2:1.3.0") // 下面三個是攝像頭相關implementation("androidx.camera:camera-lifecycle:1.3.0")implementation("androidx.camera:camera-view:1.3.0")
}
4、AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:sharedUserId="android.uid.system"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.CAMERA" /> <!-- 攝像頭權限 --><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.DensenApplication"tools:targetApi="31"><uses-native-libraryandroid:name="libcdsprpc.so"android:required="true" /><activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>