测试所有摄像头
安卓CameraX:https://developer.android.com/media/grow/spatial-audio?hl=zh-cn
1、MainActivity.java
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载布局文件 activity_main.xml
setContentView(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() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
// 获取选中的显示项
String selected = cameraIdList.get(position);
// 查找对应的真实摄像头 ID
String 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 以获取其 cameraId
Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.from(info);
if (camera2CameraInfo.getCameraId().equals(cameraId)) {
matched.add(info);
}
}
return matched;
}).build();
// 创建预览用的 Preview 对象,并设置其 SurfaceProvider
Preview 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
html
<?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">
<Spinner
android:id="@+id/cameraSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
3、build.gradle.kts(app)
groovy
import com.android.build.gradle.internal.packaging.createDefaultDebugStore
plugins {
alias(libs.plugins.androidApplication)
}
android {
namespace = "com.mms.densenapplication"
compileSdk = 34
defaultConfig {
applicationId = "com.mms.densenapplication"
minSdk = 30
targetSdk = 34
versionCode = 1
versionName = "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 = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = 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
<?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" /> <!-- 摄像头权限 -->
<application
android: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-library
android:name="libcdsprpc.so"
android:required="true" />
<activity
android: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>