Android CameraX Picture And Video Capture Complete Code Tutorial
This tutorial explains how to use Android CameraX to develop a full working camera app, including picture / video capture and writing media files to system DCIM folder, with the help of the new CameraX widget CameraView. It's Android 10 compatible.
App running Gif:
We all know how complicated and frustrating Android Camera2 was. The good news is we now have CameraX, though still in beta phase, but fully workable.
With CameraX and CameraView, we can write way less code than Camera2.
1. Add Dependencies And CompileOptions
Let's open your module/app level build.gradle
file and add dependencies and compileOptions:
(File build.gradle)
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.haoc.cameraxfullcodedemo"
minSdkVersion 23
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//TODO: add these lines
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// TODO: add these dependencies
// CameraX core library using the camera2 implementation
def camerax_version = "1.0.0-beta08"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha15"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha15"
}
Make sure to check the official Android CameraX page for the latest version number.
Click "Sync Now" to get them.
2. Add Camera Permissions
Now add camera permissions to AndroidManifest.xml
before the application
tag:
(File AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.haoc.cameraxfullcodedemo">
<!--TODO: add camera permissions here-->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3. Add CameraView To Layout
Add CameraView to activity_main.xml. Here CameraView is preview. The layout has another 5 buttons:
File activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.camera.view.CameraView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_above="@id/btnLens"/>
<Button
android:id="@+id/btnClose"
android:layout_height="wrap_content"
android:layout_width="96dp"
android:text="CLOSE"
android:layout_alignParentRight="true"
android:layout_above="@id/btnPhoto"/>
<Button
android:id="@+id/btnLens"
android:layout_height="wrap_content"
android:layout_width="96dp"
android:text="Lens"
android:layout_above="@id/btnVideo"
android:layout_centerHorizontal="true"/>
<Button
android:id="@+id/btnVideo"
android:layout_height="wrap_content"
android:layout_width="96dp"
android:text="VIDEO"
android:layout_toLeftOf="@id/btnStop"
android:layout_alignParentBottom="true"/>
<Button
android:id="@+id/btnStop"
android:layout_height="wrap_content"
android:layout_width="96dp"
android:text="STOP"
android:layout_toLeftOf="@id/btnPhoto"
android:layout_alignParentBottom="true"/>
<Button
android:id="@+id/btnPhoto"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="PHOTO"
android:layout_height="wrap_content"
android:layout_width="96dp"/>
</RelativeLayout>
4. Ask For Camera Permissions
User needs to grant the following permissions: using camera, recording audio and access to storage.
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA",
@Override
"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.RECORD_AUDIO"};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS,
REQUEST_CODE_PERMISSIONS);
}
}
public 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();
this.finish();
}
}
}
5. Get CameraView Ready
private Executor executor = Executors.newSingleThreadExecutor();
CameraSelector cameraSelector;
CameraView mCameraView;mCameraView = findViewById(R.id.view_finder);
mCameraView.setFlash(ImageCapture.FLASH_MODE_AUTO);
//can set flash mode to auto,on,off...
//optional, if you want to use extra feature
ImageCapture.Builder builder = new ImageCapture.Builder();
//Vendor-Extensions (The CameraX extensions dependency in build.gradle)
HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder);
// if has hdr (optional).
if (hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)) {
// Enable hdr.
hdrImageCaptureExtender.enableExtension(cameraSelector);
}
mCameraView.bindToLifecycle((LifecycleOwner) MainActivity.this);
//That's all for initialization. camera is ready.
//CameraView will automatically select main back and front camera if available
//When bound to MainActivity, you don't need to worry about closing or releasing camera.
6. Picture And Video Capture
//take a picture
mCameraView.setCaptureMode(CameraView.CaptureMode.IMAGE);
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file1).build();
mCameraView.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { //save file
}
}
//record a video
mCameraView.setCaptureMode(CameraView.CaptureMode.VIDEO);
mCameraView.startRecording(file, executor, new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull OutputFileResults outputFileResults) {
//save file
}
}
7. Save File And Broadcast To MediaStore So Picture Apps Can Access It
public String getBatchDirectoryName() {
String app_folder_path;
if (android.os.Build.VERSION.SDK_INT >= 29) {//if Android 10,save to this private dir first
app_folder_path = getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString();
} else { //if less than Android 10,still use this deprecated dir
app_folder_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + "/Camera";
}
File dir = new File(app_folder_path);
if (!dir.exists() && !dir.mkdirs()) {
}
return app_folder_path;
}
//Broadcast to MediaStore ...
if (android.os.Build.VERSION.SDK_INT >= 29) { //if Android 10
values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera");
values.put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.MediaColumns.IS_PENDING, true);
Uri uri = getContentResolver().insert(externalContentUri, values);
if (uri != null) {
try {
if (WriteFileToStream(originalFile, getContentResolver().openOutputStream(uri))) {
values.put(MediaStore.MediaColumns.IS_PENDING, false);
getContentResolver().update(uri, values, null, null);
}
} catch (Exception e) {
getContentResolver().delete(uri, null, null);
}
}
originalFile.delete();
} else { //if less than Android 10, still use this deprecated method
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(originalFile));
sendBroadcast(mediaScanIntent);
}
Complete MainActivity.java
package com.haoc.cameraxfullcodedemo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.VideoCapture;
import androidx.camera.extensions.HdrImageCaptureExtender;
import androidx.camera.view.CameraView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import android.Manifest;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static androidx.camera.core.VideoCapture.*;
public class MainActivity extends AppCompatActivity {
static Button btnClose, btnLens, btnVideo, btnStop, btnPhoto;
private Executor executor = Executors.newSingleThreadExecutor();
CameraSelector cameraSelector;
CameraView mCameraView;
private int REQUEST_CODE_PERMISSIONS = 1001;
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA",
"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.RECORD_AUDIO"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS,
REQUEST_CODE_PERMISSIONS);
}
}
private void startCamera() {
btnPhoto = findViewById(R.id.btnPhoto);
btnVideo = findViewById(R.id.btnVideo);
btnStop = findViewById(R.id.btnStop);
btnLens = findViewById(R.id.btnLens);
btnClose = findViewById(R.id.btnClose);
mCameraView = findViewById(R.id.view_finder);
mCameraView.setFlash(ImageCapture.FLASH_MODE_AUTO);
//can set flash mode to auto,on,off...
ImageCapture.Builder builder = new ImageCapture.Builder();
//Vendor-Extensions (The CameraX extensions dependency in build.gradle)
HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder);
// if has hdr (optional).
if (hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)) {
// Enable hdr.
hdrImageCaptureExtender.enableExtension(cameraSelector);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
mCameraView.bindToLifecycle((LifecycleOwner) MainActivity.this);
// set click listener to all buttons
btnPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mCameraView.isRecording()){return;}
SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
final File file1 = new File(getBatchDirectoryName(), mDateFormat.format(new Date()) + ".jpg");
mCameraView.setCaptureMode(CameraView.CaptureMode.IMAGE);
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file1).build();
mCameraView.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
galleryAddPic(file1, 0);
}
});
}
@Override
public void onError(@NonNull ImageCaptureException error) {
error.printStackTrace();
}
}); //image saved callback end
} //onclick end
}); //btnPhoto click listener end
btnVideo.setOnClickListener(v -> {
if(mCameraView.isRecording()){return;}
SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date()) + ".mp4");
mCameraView.setCaptureMode(CameraView.CaptureMode.VIDEO);
mCameraView.startRecording(file, executor, new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull OutputFileResults outputFileResults) {
galleryAddPic(file, 1);
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
//Log.i("TAG",message);
mCameraView.stopRecording();
}
}); //image saved callback end
}); //video listener end
btnStop.setOnClickListener(v -> {
if (mCameraView.isRecording()) {
mCameraView.stopRecording();
}
});
//close app
btnClose.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (mCameraView.isRecording()) {
mCameraView.stopRecording();
}
finish();
}
});// on click listener end
btnLens.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mCameraView.isRecording()) {
return;
}
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
if (mCameraView.hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
mCameraView.toggleCamera();
} else {
return;
}
}//onclick end
}); // btnLens listener end
} //start camera end
@Override
public void onDestroy() {
super.onDestroy();
if (mCameraView.isRecording()) {
mCameraView.stopRecording();
}
finish();
}
public 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();
this.finish();
}
}
}
public String getBatchDirectoryName() {
String app_folder_path;
if (android.os.Build.VERSION.SDK_INT >= 29) {
app_folder_path = getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString();
} else {
app_folder_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + "/Camera";
}
File dir = new File(app_folder_path);
if (!dir.exists() && !dir.mkdirs()) {
}
return app_folder_path;
}
private void galleryAddPic(File originalFile, int mediaType) {
if (!originalFile.exists()) {
return;
}
int pathSeparator = String.valueOf(originalFile).lastIndexOf('/');
int extensionSeparator = String.valueOf(originalFile).lastIndexOf('.');
String filename = pathSeparator >= 0 ? String.valueOf(originalFile).substring(pathSeparator + 1) : String.valueOf(originalFile);
String extension = extensionSeparator >= 0 ? String.valueOf(originalFile).substring(extensionSeparator + 1) : "";
// Credit: https://stackoverflow.com/a/31691791/2373034
String mimeType = extension.length() > 0 ? MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase(Locale.ENGLISH)) : null;
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, filename);
values.put(MediaStore.MediaColumns.DISPLAY_NAME, filename);
values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
if (mimeType != null && mimeType.length() > 0)
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
Uri externalContentUri;
if (mediaType == 0)
externalContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
else if (mediaType == 1)
externalContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
else
externalContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
// Android 10 restricts our access to the raw filesystem, use MediaStore to save media in that case
if (android.os.Build.VERSION.SDK_INT >= 29) {
values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera");
values.put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.MediaColumns.IS_PENDING, true);
Uri uri = getContentResolver().insert(externalContentUri, values);
if (uri != null) {
try {
if (WriteFileToStream(originalFile, getContentResolver().openOutputStream(uri))) {
values.put(MediaStore.MediaColumns.IS_PENDING, false);
getContentResolver().update(uri, values, null, null);
}
} catch (Exception e) {
getContentResolver().delete(uri, null, null);
}
}
originalFile.delete();
} else {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(originalFile));
sendBroadcast(mediaScanIntent);
}
} //gallery add end
private static boolean WriteFileToStream(File file, OutputStream out){
try
{
InputStream in = new FileInputStream( file );
try
{
byte[] buf = new byte[1024];
int len;
while( ( len = in.read( buf ) ) > 0 )
out.write( buf, 0, len );
}
finally
{
try
{
in.close();
}
catch( Exception e )
{
//Log.e( "Unity", "Exception:", e );
}
}
}
catch( Exception e )
{
//Log.e( "Unity", "Exception:", e );
return false;
}
finally
{
try
{
out.close();
}
catch( Exception e )
{
//Log.e( "Unity", "Exception:", e );
}
}
return true;
} //write end
}//Main activity end
Good Work
ReplyDeleteContinue here and also on youtube.