要用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);
我们运行后,能看到后台不停的打印
