安卓开发相机功能

发布于:2025-03-05 ⋅ 阅读:(14) ⋅ 点赞:(0)

相机功能

安卓中的相机调用功能也经历了很多的方案升级,目前可选的官方方案是CameraX、Camera2、Camera(废弃),还有一些第三方免费或者是付费的相机库。对于大多数开发者,建议使用 CameraX。

CameraX

CameraX 是 Android Jetpack 库的一部分,旨在简化相机应用的开发。它构建在 Camera2 API 之上,为开发者提供了更简洁的接口,相比于Camera和Camera2,有更好的设备兼容性。

  • 简单易用:相比 Camera2,CameraX 简化了相机操作,提供了更直观的 API,可以更快实现常见的相机功能。
  • 向后兼容性:CameraX 支持 Android 5.0 (API 21) 及以上版本,解决了 Camera2 在一些设备上的兼容性问题。
  • 生命周期感知:CameraX 会自动处理生命周期问题,例如当用户切换到后台时停止相机,回到前台时重新启动。
  • 内置扩展:CameraX 提供了诸如 HDR、夜间模式、美颜等功能,支持基于不同设备硬件的特性自动调整。不过这个需要看手机型号,很多手机并不支持。

CameraX拍照

  1. 项目依赖配置

在 build.gradle 文件中添加 CameraX 相关依赖:

def camerax_version = "1.2.0-alpha04" implementation "androidx.camera:camera-core:$camerax_version"//核心库 implementation "androidx.camera:camera-camera2:$camerax_version"//基于 Camera2 的实现模块 implementation "androidx.camera:camera-lifecycle:$camerax_version"//自动管理相机的生命周期 implementation "androidx.camera:camera-view:1.0.0-alpha31"//显示相机预览的 UI 组件 implementation "androidx.camera:camera-extensions:1.0.0-alpha31"//额外的高级相机功能如 HDR 和美颜

  1. 权限配置(AndroidManifest.xml)

运行相机需要对相机权限做出声明:

<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" android:required="false" />

android:required="false":这意味着相机功能并不是应用的必需条件。如果设备没有相机,应用仍然可以安装和运行。如果是true,而设备本身没有相机,那应用就无法正常运行。

  1. 布局文件(activity_main.xml)

<!-- CameraX 预览控件 --> <androidx.camera.view.PreviewView android:id="@+id/previewView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toTopOf="@+id/captureButton" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> <!-- 拍照按钮 --> <Button android:id="@+id/captureButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="拍照" app:layout_constraintTop_toBottomOf="@+id/previewView" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" />

  1. Activity中

package com.example.cameraxdemo; 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.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.camera.core.Camera; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; 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 com.google.common.util.concurrent.ListenableFuture; import java.io.File; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { private static final int REQUEST_CODE_PERMISSIONS = 10; private static final String[] REQUIRED_PERMISSIONS = new String[]{Manifest.permission.CAMERA}; private PreviewView previewView; private ImageCapture imageCapture; private ExecutorService cameraExecutor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); previewView = findViewById(R.id.previewView); Button captureButton = findViewById(R.id.captureButton); // 请求相机权限,如果有权限直接启动相机 if (allPermissionsGranted()) { startCamera(); } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS); } // 点击拍照按钮时执行拍照 captureButton.setOnClickListener(view -> takePhoto()); cameraExecutor = Executors.newSingleThreadExecutor(); } // 初始化相机 private void startCamera() { ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // 获取 CameraProvider ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); // 创建预览 Preview preview = new Preview.Builder().build(); // 创建 ImageCapture,用于拍照 imageCapture = new ImageCapture.Builder().build(); // 选择后置摄像头 CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; // 将预览与 PreviewView 绑定 preview.setSurfaceProvider(previewView.getSurfaceProvider()); // 绑定预览和 ImageCapture 到相机生命周期 cameraProvider.unbindAll(); Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture); } catch (ExecutionException | InterruptedException e) { Log.e("CameraXDemo", "Error starting camera: ", e); } }, ContextCompat.getMainExecutor(this)); } // 拍照逻辑 private void takePhoto() { if (imageCapture == null) { return; } // 创建保存文件 File photoFile = new File(getExternalFilesDir(null), System.currentTimeMillis() + ".jpg"); // 设置拍照输出选项 ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); // 执行拍照 imageCapture.takePicture(outputFileOptions, cameraExecutor, new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { runOnUiThread(() -> Toast.makeText(MainActivity.this, "Photo saved: " + photoFile.getAbsolutePath(), Toast.LENGTH_SHORT).show()); } @Override public void onError(@NonNull ImageCaptureException exception) { Log.e("CameraXDemo", "Photo capture failed: " + exception.getMessage(), exception); } }); } // 检查是否已经获得所有权限 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) { 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(); } }

使用MediaStore,遵循分区存储:

// 创建 ContentValues ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, System.currentTimeMillis() + ".jpg"); contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/CameraXDemo"); // 设置输出选项 ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues).build(); // 拍照 imageCapture.takePicture(outputFileOptions, cameraExecutor, new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { Uri savedUri = outputFileResults.getSavedUri(); if (savedUri != null) { runOnUiThread(() -> Toast.makeText(CameraActivity.this, "Photo saved to MediaStore: " + savedUri, Toast.LENGTH_SHORT).show()); } else { Log.e(TAG, "Image not saved properly."); } } @Override public void onError(@NonNull ImageCaptureException exception) { Log.e(TAG, "Photo capture failed: " + exception.getMessage(), exception); } });

切换前后置摄像头

主要思路是重新绑定摄像头,在重新绑定之前切换前后摄像头参数即可:

public class MainActivity extends AppCompatActivity { private PreviewView previewView; private ImageCapture imageCapture; private ProcessCameraProvider cameraProvider; private CameraSelector cameraSelector; private boolean isFrontCamera = false; // 默认后置摄像头 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); previewView = findViewById(R.id.previewView); // 启动相机 startCamera(); // 切换摄像头按钮点击事件 findViewById(R.id.switchCameraButton).setOnClickListener(v -> { isFrontCamera = !isFrontCamera; switchCamera(); }); } private void startCamera() { // 获取 CameraProvider ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { cameraProvider = cameraProviderFuture.get(); // 默认使用后置摄像头 cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; bindCameraUseCases(); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } }, ContextCompat.getMainExecutor(this)); } private void bindCameraUseCases() { // 创建预览 Preview preview = new Preview.Builder().build(); // 将 Preview 连接到 PreviewView preview.setSurfaceProvider(previewView.getSurfaceProvider()); // 拍照设置 imageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build(); // 解绑之前的所有用例 cameraProvider.unbindAll(); // 绑定预览和拍照功能到相机 cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview, imageCapture); } private void switchCamera() { // 切换前后置摄像头 if (isFrontCamera) { cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA; } else { cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; } // 重新绑定摄像头 bindCameraUseCases(); } // 拍照方法 private void takePicture() { if (imageCapture != null) { ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "my_image_" + System.currentTimeMillis() + ".jpg"); contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues).build(); imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(ImageCapture.OutputFileResults outputFileResults) { Uri savedUri = outputFileResults.getSavedUri(); Log.d("CameraXApp", "Image saved: " + savedUri); } @Override public void onError(ImageCaptureException exception) { Log.e("CameraXApp", "Error saving image: " + exception.getMessage()); } }); } } }

CameraX视频拍摄

  1. 项目依赖配置

除了CameraX的基础依赖库,还需要新增下面的库:

implementation "androidx.camera:camera-video:$camerax_version" // 视频录制相关库

  1. 权限配置

录制视频,除了需要获取相机权限,还需要额外添加录音权限

<uses-permission android:name="android.permission.RECORD_AUDIO" />

  1. Activity中

public class VideoRecordActivity extends AppCompatActivity { private static final String TAG = "VideoRecordActivity"; private PreviewView previewView;// 预览摄像头捕获内容的视图 private ExecutorService cameraExecutor;// 用于处理相机操作的后台线程 private boolean isRecording; // 记录否正在录制的状态 private VideoCapture<Recorder> videoCapture; //捕获视频的核心组件 // private Recording recording; private ProcessCameraProvider cameraProvider;// 相机的生命周期管理组件 private CameraSelector cameraSelector;// 前置或后置摄像头 private Recording recording;//当前正在进行的录制实例 private ImageView ivRecord; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_record); previewView = findViewById(R.id.preview); //拍照 ivRecord = findViewById(R.id.tv_take); ivRecord.setOnClickListener(view -> { // 如果当前正在录制,点击按钮停止录制,否则就是开始录制 if (isRecording) { stopRecording(); } else { startRecording(); } }); // 创建单线程后台线程池 cameraExecutor = Executors.newSingleThreadExecutor(); startCamera(); requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 11); } // 启动相机预览 private void startCamera() { ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); // 异步获取摄像头的命周期管理器实例 cameraProviderFuture.addListener(() -> { try { // 获取摄像头生命周期管理器实例 cameraProvider = cameraProviderFuture.get(); // 创建预览实例 Preview preview = new Preview.Builder().build(); // 将预览内容绑定到 PreviewView 上 preview.setSurfaceProvider(previewView.getSurfaceProvider()); // 视频录制用例 Recorder recorder = new Recorder.Builder() .setQualitySelector(QualitySelector.from(Quality.HD)) // 设置录制质量为 HD .build(); // 创建 VideoCapture 用例,这个一个获取视频的核心组件 videoCapture = VideoCapture.withOutput(recorder); // 默认使用后置摄像头 cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; //解绑所有之前的摄像头用例 cameraProvider.unbindAll(); // 将预览和视频录制绑定到生命周期 cameraProvider.bindToLifecycle(VideoRecordActivity.this, cameraSelector, preview, videoCapture); } catch (ExecutionException | InterruptedException e) { Log.e(TAG, "Error starting camera", e); } }, ContextCompat.getMainExecutor(this));// 在主线程执行 } private void startRecording() { if (isRecording) { Toast.makeText(this, "Recording is already in progress", Toast.LENGTH_SHORT).show(); return; } // 创建保存视频的 ContentValues,指定文件名和文件类型 ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "video_" + System.currentTimeMillis()); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4"); // 使用 MediaStoreOutputOptions 指定输出位置 MediaStoreOutputOptions options = new MediaStoreOutputOptions.Builder(getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI) .setContentValues(contentValues).build(); // 准备录制前检查录音权限 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "请先获取录音权限", Toast.LENGTH_SHORT).show(); return; } // 准备录制视频,并启用音频录制 PendingRecording pendingRecording = videoCapture.getOutput() .prepareRecording(VideoRecordActivity.this, options) .withAudioEnabled(); // 如果需要音频录制,调用该方法 // 启动录制,并且设定录制回调 //回调中的videoRecordEvent会有下面几种状态: //VideoRecordEvent.Start:录制开始。 //VideoRecordEvent.Pause:录制暂停。 //VideoRecordEvent.Resume:录制恢复。 //VideoRecordEvent.Finalize:录制完成(停止或失败)。 //VideoRecordEvent.Status:录制状态更新(持续获取统计信息)。 recording = pendingRecording.start(ContextCompat.getMainExecutor(this), videoRecordEvent -> { Log.d(TAG, "Recording videoRecordEvent " + videoRecordEvent); Log.d(TAG, "Recording videoRecordEvent getRecordingStats" + videoRecordEvent.getRecordingStats()); if (videoRecordEvent instanceof VideoRecordEvent.Start) { Log.d(TAG, "Recording started"); isRecording = true; refreshUI(videoRecordEvent); } else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) { Log.d(TAG, "Recording finalized"); Toast.makeText(this, "已保存", Toast.LENGTH_SHORT).show(); isRecording = false; refreshUI(videoRecordEvent); } }); } private void stopRecording() { if (recording != null && isRecording) { recording.stop(); // 停止录制 recording = null; } } //更新UI private void refreshUI(VideoRecordEvent videoRecordEvent) { if (videoRecordEvent instanceof VideoRecordEvent.Start) { //开始录制,把相关的ui换成录制的效果 ivRecord.setImageResource(R.mipmap.icon_stop_record); } else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) { //结束录制 ivRecord.setImageResource(R.mipmap.icon_record); }else{ //这里可以自行扩展其他的状态 } } @Override protected void onDestroy() { super.onDestroy(); cameraExecutor.shutdown(); } }


网站公告

今日签到

点亮在社区的每一天
去签到