Android Jetpack架构组件(五)CameraX视频录制

添加Gradle依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Use the most recent version of CameraX, currently that is alpha04
def camerax_core_version = "1.0.0-beta03"
def camerax_version = "1.0.0-alpha10"
implementation "androidx.camera:camera-core:${camerax_core_version}"
implementation "androidx.camera:camera-camera2:${camerax_core_version}"
// If you want to use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"
// If you want to use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"

//视频播放组件
implementation 'com.google.android.exoplayer:exoplayer-core:2.10.4'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.4'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.10.4'

implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'

权限声明

1
2
3
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private static final String[] PERMISSIONS = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO};
private ArrayList<String> deniedPermission = new ArrayList<>();

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_CODE);
...
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_CODE) {
deniedPermission.clear();
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
int result = grantResults[i];
if (result != PackageManager.PERMISSION_GRANTED) {
deniedPermission.add(permission);
}
}

if (deniedPermission.isEmpty()) {
//Toast.makeText(CaptureActivity.this, "all permission allowed", Toast.LENGTH_SHORT).show();
initCamera();
} else {
new AlertDialog.Builder(this)
.setMessage(getString(R.string.capture_permission_message))
.setNegativeButton(getString(R.string.capture_permission_no), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
CameraXCaptureActivity.this.finish();
}
})
.setPositiveButton(getString(R.string.capture_permission_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String[] denied = new String[deniedPermission.size()];
ActivityCompat.requestPermissions(CameraXCaptureActivity.this, deniedPermission.toArray(denied), PERMISSION_CODE);
}
}).create().show();
}
}
}

配置CameraX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void initCamera() {

executor = ContextCompat.getMainExecutor(this);

cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
// No errors need to be handled for this Future.
// This should never be reached.
}
}, executor);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@SuppressLint("RestrictedApi")
private void bindPreview(ProcessCameraProvider cameraProvider) {
CameraX.unbindAll();
cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
Preview preview = new Preview.Builder()
.setCameraSelector(cameraSelector) //前后摄像头
.setTargetAspectRatio(AspectRatio.RATIO_16_9) //宽高比
.setTargetRotation(rotation) //旋转角度
//.setTargetResolution(resolution) //分辨率
.build();

imageCapture = new ImageCapture.Builder()
.setCameraSelector(cameraSelector)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(rotation)
//.setTargetResolution(resolution)
.build();

videoCapture = new VideoCaptureConfig.Builder()
.setCameraSelector(cameraSelector)
.setTargetAspectRatio(AspectRatio.RATIO_16_9)
.setTargetRotation(rotation)
//.setTargetResolution(resolution)
//视频帧率
.setVideoFrameRate(25)
//bit率
.setBitRate(10440).build();

//Caused by: java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0. May be attempting to bind too many use cases.
//cameraSelector与videoCapture不能同时绑定
//Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, videoCapture, preview);

//Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, preview);
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture, preview);

CameraInfo cameraInfo = camera.getCameraInfo();

preview.setSurfaceProvider(mBinding.previewView.createSurfaceProvider(cameraInfo));
}

若报错:IllegalArgumentException: No supported surface combination is found,如下:

1
2
java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0.  May be attempting to bind too many use cases.
at androidx.camera.camera2.impl.Camera2DeviceSurfaceManager.getSuggestedResolutions(Camera2DeviceSurfaceManager.java:176)

解决:cameraSelector与videoCapture不能同时绑定

参考:https://github.com/android/camera-samples/issues/38
参考:https://stackoverflow.com/questions/57126429/an-error-occurred-by-camerax-bindtolifecycle

拍照和录像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_camerax_capture);

mBinding.recordView.setOnRecordListener(new RecordView.onRecordListener() {

@Override
public void onClick() {
takingPicture = true;
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), System.currentTimeMillis() + ".jpeg");
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(file).build();
mBinding.captureTips.setVisibility(View.INVISIBLE);
imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
String msg = "图片保存成功: " + file.getAbsolutePath();
Toast.makeText(CameraXCaptureActivity.this, msg, Toast.LENGTH_SHORT).show();
Log.i(CommonConstants.TAG, msg);
onFileSaved(file);
}

@Override
public void onError(@NonNull ImageCaptureException exception) {
String msg = "图片保存失败: " + exception.getMessage();
showErrorToast(msg);
Log.e(CommonConstants.TAG, msg);
}
});
}

@SuppressLint("RestrictedApi")
@Override
public void onLongClick() {
takingPicture = false;
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), System.currentTimeMillis() + ".mp4");
videoCapture.startRecording(file, executor, new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull File file) {
onFileSaved(file);
}

@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
String msg = "图片保存失败: " + message;
showErrorToast(msg);
Log.e(CommonConstants.TAG, msg);
}
});
}

@SuppressLint("RestrictedApi")
@Override
public void onFinish() {
videoCapture.stopRecording();
}
});

}

保存和预览

1
2
3
4
5
6
private void onFileSaved(File file) {
outputFilePath = file.getAbsolutePath();
String mimeType = takingPicture ? "image/jpeg" : "video/mp4";
MediaScannerConnection.scanFile(this, new String[]{outputFilePath}, new String[]{mimeType}, null);
CameraXPreviewActivity.startActivityForResult(this, outputFilePath, !takingPicture, "完成");
}

预览照片和录像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void previewImage(String previewUrl) {
mPreviewBinding.photoView.setVisibility(View.VISIBLE);
GlideApp.with(this).load(previewUrl).into(mPreviewBinding.photoView);
}

private void previewVideo(String previewUrl) {
mPreviewBinding.playerView.setVisibility(View.VISIBLE);
player = ExoPlayerFactory.newSimpleInstance(this, new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl());

Uri uri = null;
File file = new File(previewUrl);
if (file.exists()) {
DataSpec dataSpec = new DataSpec(Uri.fromFile(file));
FileDataSource fileDataSource = new FileDataSource();
try {
fileDataSource.open(dataSpec);
uri = fileDataSource.getUri();
} catch (FileDataSource.FileDataSourceException e) {
e.printStackTrace();
}
} else {
uri = Uri.parse(previewUrl);
}

ProgressiveMediaSource.Factory factory = new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(this, Util.getUserAgent(this, getPackageName())));
ProgressiveMediaSource mediaSource = factory.createMediaSource(uri);
player.prepare(mediaSource);
player.setPlayWhenReady(true);
mPreviewBinding.playerView.setPlayer(player);
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :