参考资料
- https://developer.android.google.cn/codelabs/camerax-getting-started?hl=zh-cn#0
- https://developer.android.google.cn/training/camerax/video-capture?hl=zh-cn
- //https://blog.csdn.net/teolih/article/details/120423971
对应视频及项目代码
【项目代码】https://gitee.com/hellosunshine/camerax_android_java.git
步骤(java版本)
基本功能
- 预览界面
- 拍摄图片
- 录制视频
- 分析图片
创建项目
创建活动
启动
<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>
CameraX、LifeCycle添加依赖
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
//CameraX添加依赖
def camerax_version = "1.1.0-alpha08"
def lifecycle_version = "2.5.1"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.3.0-alpha07"
implementation 'androidx.camera:camera-video:'
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
}
布局添加
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/takeVideoBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/startRecord"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/takePhotoBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".9" />
<Button
android:id="@+id/takePhotoBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/takePhoto"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/takeVideoBtn"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".9" />
</androidx.constraintlayout.widget.ConstraintLayout>
添加相机、读写存储权限
<!-- 拍照权限 -->
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
绑定控件 & 添加监听 & 获取权限
package com.example.cameraxjavaactivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
//按钮
Button takePhotoButton;
Button takeVideoButton;
//预览
PreviewView previewView;
//权限
private static final String[] REQUIRE_PERMISSIONS = new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
public static final int REQUEST_CODE_PERMISSIONS = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//绑定控件
takePhotoButton = findViewById(R.id.takePhotoBtn);
takeVideoButton = findViewById(R.id.takeVideoBtn);
previewView = findViewById(R.id.preview_view);
takePhotoButton.setOnClickListener(v -> takePhoto());
takeVideoButton.setOnClickListener(v -> takeVideo());
//获取权限
if (havePermissions()) {
initCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRE_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == REQUEST_CODE_PERMISSIONS) {
initCamera();
} else {
finish();
}
}
//判断权限是否获取
private boolean havePermissions() {
for (String permission : REQUIRE_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
//初始化Camera
private void initCamera() {
}
//拍照
private void takePhoto() {
}
//录像
private void takeVideo() {
}
}
运行没有报错,继续下步骤
初始化相机
实现拍照功能
- 定义imageCapture和processCameraProviderListenableFuture
//capture
ImageCapture imageCapture;
ListenableFuture<ProcessCameraProvider> processCameraProviderListenableFuture;
- 初始化相机预览
//初始化Camera
private void initCamera() {
///实例化(可以设置许多属性)
imageCapture = new ImageCapture.Builder().build();
processCameraProviderListenableFuture = ProcessCameraProvider.getInstance(this);
processCameraProviderListenableFuture.addListener(() -> {
try {
//配置预览(https://developer.android.google.cn/training/camerax/preview?hl=zh-cn)
previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
Preview preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
//绑定到生命周期
ProcessCameraProvider processCameraProvider = processCameraProviderListenableFuture.get();
processCameraProvider.unbindAll();
processCameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(this));
}
- 运行代码,看到预览界面有了,继续下步骤
- 实现按下拍照,并存储至相册
//拍照
private void takePhoto() {
if (imageCapture != null) {
//ContentValues
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.SIMPLIFIED_CHINESE).format(System.currentTimeMillis())
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/CameraXImage");
}
//图片输出
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(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Log.i("CameraXTest", Objects.requireNonNull(outputFileResults.getSavedUri()).toString());
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.e("CameraXTest", exception.toString());
}
});
}
}
- 运行后点击拍照可以看到照片被保存到手机相册
实现录像功能
定义videoCapture、recording
VideoCapture videoCapture;
Recording recording;
- 构建实例
Recorder recorder = new Recorder.Builder().build();
videoCapture = VideoCapture.withOutput(recorder);
添加videoCapture文章来源:https://www.toymoban.com/news/detail-728284.html
processCameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture, videoCapture);
//processCameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageCapture);
- 录像
//录像
private void takeVideo() {
if (videoCapture != null) {
takeVideoButton.setEnabled(false);
if (recording != null) {
recording.stop();
recording = null;
return;
}
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.SIMPLIFIED_CHINESE).format(System.currentTimeMillis()) + ".mp4";
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "Movies/CameraX-Video");
}
MediaStoreOutputOptions mediaStoreOutputOptions = new MediaStoreOutputOptions
.Builder(getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
.setContentValues(contentValues)
.build();
Recorder recorder = (Recorder) videoCapture.getOutput();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, REQUIRE_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
return;
}
recording = recorder.prepareRecording(this, mediaStoreOutputOptions)
.withAudioEnabled()
.start(ContextCompat.getMainExecutor(this), videoRecordEvent -> {
if (videoRecordEvent instanceof VideoRecordEvent.Start) {
takeVideoButton.setText(getString(R.string.stopRecord));
takeVideoButton.setEnabled(true);
} else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
if (((VideoRecordEvent.Finalize) videoRecordEvent).hasError()) {
if (recording != null) {
recording.close();
recording = null;
}
} else {
String msg = "视频为" + ((VideoRecordEvent.Finalize) videoRecordEvent).getOutputResults().getOutputUri();
Log.i("CameraXTest", msg);
}
takeVideoButton.setEnabled(true);
takeVideoButton.setText(getString(R.string.startRecord));
}
});
}
}
- 点击录像后录制,录制后点击停止录像,相册会出现一段视频
相机扩展
https://developer.android.google.cn/training/camerax/extensions-api?hl=zh-cn
本人设备不支持文章来源地址https://www.toymoban.com/news/detail-728284.html
图片分析
- 添加imageAnalysis
//executor & imageAnalysis
private ExecutorService cameraExecutor;
private ImageAnalysis imageAnalysis;
- 实例化
在绑定生命周期前面执行
//图片分析
@SuppressLint("UnsafeOptInUsageError")
private void initImageAnalysis() {
imageAnalysis = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(cameraExecutor, imageProxy -> {
int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees();
Log.i("CameraXTest", String.valueOf(Objects.requireNonNull(imageProxy.getImage()).getTimestamp()));
Log.i("CameraXTest", String.valueOf(rotationDegrees));
imageProxy.close();
});
}
- 修改绑定生命周期
processCameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, imageAnalysis, preview, imageCapture, videoCapture);
- 运行,查看日志
其他配置
- 闪光灯模式
imageCapture = new ImageCapture.Builder()
//控制闪光灯模式 FLASH_MODE_ON(拍照时,闪光灯会亮起)
.setFlashMode(ImageCapture.FLASH_MODE_ON)
.build();
- 旋转(屏幕方向与最后拍出的图片方向)
///旋转
//orientation为北为0,顺时针度数0-360
//Surface.ROTATION_270将拍摄好的图片顺时针旋转270度
private void setOrientationEventListener() {
OrientationEventListener orientationEventListener = new OrientationEventListener(this) {
@Override
public void onOrientationChanged(int orientation) {
int rotation;
if (orientation >= 45 && orientation < 135) {
rotation = Surface.ROTATION_270;
} else if (orientation >= 135 && orientation < 225) {
rotation = Surface.ROTATION_180;
} else if (orientation >= 225 && orientation < 315) {
rotation = Surface.ROTATION_90;
} else {
rotation = Surface.ROTATION_0;
}
Log.i("CameraXTest", String.valueOf(rotation));
Log.i("CameraXTest", String.valueOf(orientation));
imageCapture.setTargetRotation(rotation);
}
};
orientationEventListener.enable();
}
- 裁剪矩形
//剪裁矩形(拍摄之后,对图片进行裁剪)
ViewPort viewPort = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
viewPort = new ViewPort.Builder(
new Rational(100, 100), getDisplay().getRotation()
).build();
} else {
viewPort = new ViewPort.Builder(
new Rational(100, 100), Surface.ROTATION_0
).build();
}
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
.addUseCase(preview)
.addUseCase(imageAnalysis)
.addUseCase(imageCapture)
.addUseCase(videoCapture)
.setViewPort(viewPort)
.build();
processCameraProvider.unbindAll();
processCameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, useCaseGroup);
控制相机输入
到了这里,关于【Android】一步步实现手机拍照、录像及存储至相册(CameraX)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!