1, Foreword
From Camera1 to Camera2, and now CameraX in Jetpack, Google has been committed to making it easier for Android application developers to use camera related API s and realize camera related application functions more simply and quickly; Let's learn how to use CameraX to take photos and record videos;
2, CameraX overview
CameraX is a component in Android Jetpack, which aims to simplify the application development related to cameras. However, it should be noted that cameraX is at least compatible with Android 5.0(API 21);CameraX takes advantage of the functions of Camera2, but takes a simpler way with life cycle awareness and based on use case. It also solves the problem of device compatibility.
3, CameraX use
Add dependency:
In the Module's build Add CameraX related dependencies to gradle
dependencies { ..... def camerax_version = "1.0.0-beta07" // CameraX core library using camera2 implementation implementation "androidx.camera:camera-camera2:$camerax_version" // CameraX Lifecycle Library implementation "androidx.camera:camera-lifecycle:$camerax_version" // CameraX View class implementation "androidx.camera:camera-view:1.0.0-alpha14" }
At the same time, CameraX needs some methods belonging to java8, so we need to set the compilation options accordingly. In build At the end of the android block in the gradle file, after buildTypes, add the following:
android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
Add related permissions
Add camera permission. Here, because we also need to realize photo shooting and video recording, in addition to camera permission, we also need recording permission and stored read-write permission;
<uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Camera preview
For camera preview, we must have a View to display the camera image. When using Camera1 or Camera2, we usually use SurfaceView to display the camera image, and CameraX provides PreviewView
View as a camera screen
So we add a PreviewView to the layout
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.camera.view.PreviewView android:id="@+id/preview_activity_video_recode" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
And use the API related to Preview to Preview the camera picture
@SuppressLint("RestrictedApi") private void preview() { ListenableFuture<ProcessCameraProvider> listenableFuture = ProcessCameraProvider.getInstance(mActivity); try { mProcessCameraProvider = listenableFuture.get(); mProcessCameraProvider = listenableFuture.get(); mPreview = new Preview.Builder() .setTargetResolution(new Size(640, 480)) .build(); mPreview.setSurfaceProvider(mPreviewView.createSurfaceProvider()); ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .build(); imageAnalysis .setAnalyzer(ContextCompat.getMainExecutor(mActivity), new ImageAnalysis.Analyzer() { @Override public void analyze(@NonNull ImageProxy image) { Log.e(TAG, "analyze: " + image); image.close(); } } ); mProcessCameraProvider.bindToLifecycle((LifecycleOwner) mActivity, CameraSelector.DEFAULT_BACK_CAMERA, mPreview, imageAnalysis ); } catch (Exception e) { e.printStackTrace(); } }
While realizing the preview of the camera picture, we also add an ImageAnalysis image analysis use case. ImageAnalysis sets an ImageAnalysis Analyzer: this use case is used to analyze the data of each frame, and each frame will be called back to the analyze(ImageProxy image) method
We can get the image data of each frame from IamgeProxy;
Note that after using IamgeProxy in the callback method, you must call IamgeProxy Close() method is closed, otherwise the analyze(ImageProxy image) method will only call back once. This is a pit. Be sure to close it;
Take photos
Use the relevant API s of IamgeCapture to realize the photo shooting function
private void takePhoto() { ImageCapture imageCapture = new ImageCapture.Builder() //Optimizing capture speed may reduce picture quality .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) //Set aspect ratio .setTargetResolution(new Size(640, 480)) //Sets the initial rotation angle .build(); String dirPath = getExternalFilesDir("").getAbsolutePath() + File.separator + "TestRecode"; File dirFile = new File(dirPath); if (!dirFile.exists()) { boolean mkdir = dirFile.mkdir(); Log.e(TAG, "takePhoto: mkdir: " + mkdir); } File file = new File(dirFile, System.currentTimeMillis() + ".jpg"); if (!file.exists()) { try { boolean newFile = file.createNewFile(); Log.e(TAG, "takePhoto: newFile: " + newFile); } catch (IOException e) { e.printStackTrace(); } } ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build(); mProcessCameraProvider.bindToLifecycle((LifecycleOwner) mActivity, CameraSelector.DEFAULT_BACK_CAMERA, imageCapture ); imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(mActivity), new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { Log.e(TAG, "onImageSaved: " + outputFileResults); Uri savedUri = outputFileResults.getSavedUri(); Log.e(TAG, "onImageSaved: " + savedUri); if (savedUri == null) { savedUri = Uri.fromFile(file); } Toast.makeText(mActivity, "Successful shooting", Toast.LENGTH_SHORT).show(); } @Override public void onError(@NonNull ImageCaptureException exception) { Log.e(TAG, "onError: " + exception); } }); }
record video
Use the API related to VideoCapture to realize the function of video recording
@SuppressLint("RestrictedApi") private void startRecorder() { mVideoCapture = new VideoCapture.Builder() .build(); String dirPath = getExternalFilesDir("").getAbsolutePath() + File.separator + "TestRecode"; File dirFile = new File(dirPath); if (!dirFile.exists()) { boolean mkdir = dirFile.mkdir(); Log.e(TAG, "startRecorder: mkdir: " + mkdir); } File file = new File(dirFile, System.currentTimeMillis() + ".mp4"); if (!file.exists()) { try { boolean newFile = file.createNewFile(); Log.e(TAG, "startRecorder: newFile: " + newFile); } catch (IOException e) { e.printStackTrace(); } } mProcessCameraProvider.bindToLifecycle( this, CameraSelector.DEFAULT_BACK_CAMERA, mPreview, mVideoCapture); mVideoCapture.startRecording(file, ContextCompat.getMainExecutor(mActivity), new VideoCapture.OnVideoSavedCallback() { @Override public void onVideoSaved(@NonNull File file) { Log.e(TAG, "onVideoSaved: " + file); Toast.makeText(mActivity, "End of recording", Toast.LENGTH_SHORT).show(); } @Override public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { Log.e(TAG, "onError: " + videoCaptureError + "," + message); Toast.makeText(mActivity, "Recording error: code: " + videoCaptureError + "," + message, Toast.LENGTH_SHORT).show(); mIsRecording = false; } } ); } @SuppressLint("RestrictedApi") private void stopRecorder() { if (mVideoCapture != null) { mVideoCapture.stopRecording(); } }
At the beginning, call back to onError. videoCaptureError is 2 and message is MediaMuxer creation failed!, Baidu also didn't find a solution. Later, it was found that the video File was not created successfully, so pay attention to check whether the video File was created successfully;
Of course, don't forget to dynamically apply for relevant permissions in the program;
The above is to use CameraX to realize the functions of camera preview, photographing and video recording;
Demo source address: https://github.com/maqing-programmer/CameraXDemo