Camera2
This piece will explore Camera2 examples, simple easy to understand step by step examples. The examples are written in Kotlin or Java and are beginner friendly.
What is Camera2?
It is an API defined in the android.hardware package that provides an interface to individual camera devices connected to an Android device.
Camera2 models a camera device as a pipeline, which takes in input requests for capturing a single frame, captures the single image per the request, and then outputs one capture result metadata packet, plus a set of output image buffers for the request.
Example 1: Camera2 - Capture Photo or Pick
This is dead simple Camera2 example that allows you capture a photo from the camera or pick the image.
Step 1: Create Project
Start by creating an empty Android Studio
project.
Step 2: Dependencies
No special or third party dependency is needed.
Step 2: Layout
Design your MainActivity
layout as follows, with an image and a button placed inside a ConstraintLayout:
activity_main.xml
<?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">
<ImageView
android:id="@+id/imageView"
android:layout_width="168dp"
android:layout_height="285dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.316"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCapture"
android:text="Capture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintVertical_bias="0.369" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 3: Write Code
We have only Activity, our MainActivity
. Go ahead and add imports:
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.hardware.camera2.CaptureRequest;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
Extend the AppCompatActivity
:
Then define a function to capture:
public void onCapture(View view){
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(takePictureIntent.resolveActivity(getPackageManager())!=null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
Here is the full code:
MainActivity.java
package com.example.androidbootcamp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.hardware.camera2.CaptureRequest;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
ImageView imageView;
static final int REQUEST_IMAGE_CAPTURE = 1;
public void onCapture(View view){
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(takePictureIntent.resolveActivity(getPackageManager())!=null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
imageView.setImageBitmap(imageBitmap);
}
}
}
Run
Copy the code or download it in the link below, build and run.
Reference
Here are the reference links:
Number | Link |
---|---|
1. | Download Example |
2. | Follow code author |
Example 2: How to Capture Photo and Scan QR Code using Camera2
This example will teach you how to capture photo using camera2. You will also learn how to scan QR Code using the same package.
Step 1: Dependencies
Camera2 itself, as we said is defined in the android.hardware package which is a standard sdk package. However to enable scanning of the QR Code include the following libraries in your dependencies closure in the app level gradle file:
implementation 'com.journeyapps:zxing-android-embedded:3.6.0'
implementation 'com.google.zxing:core:3.3.2'
Step 2: Add Permissions
In the android manifest add the following use-feature
statements:
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<uses-feature
android:name="android.hardware.camera.level.full"
android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" />
Then add the permissions for accessing the camera, reading from and writing to external storage:
<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" />
Step 3: Design Layouts
Start by designing the layout for the main activity. This will provide buttons for initiating Camera actions like capturing a photo or scanning the qr code:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="userAction"
type="com.mili.camera2api.UserAction" />
<variable
name="scannedData"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/scan_qr_code"
app:strokeColor="@color/colorAccent"
android:onClick="@{() -> userAction.onScanQRCode()}"
app:strokeWidth="6dp"
android:layout_margin="8dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_click_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/click_photo"
app:strokeColor="@color/colorAccent"
android:onClick="@{() -> userAction.onClickPhoto()}"
app:strokeWidth="6dp"
android:layout_margin="8dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Scanned Code Data"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_margin="32dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/et_scanned_data"
android:text="@{scannedData}"
android:enabled="false"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</layout>
The Camera activity is customizable. For example you can include buttons to save or delete the photo. Here is the camera activity layout:
activity_camera.xml
<?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=".CameraActivity">
<com.mili.camera2api.AutoFitTextureView
android:id="@+id/texture"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/gl_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ig_photo_preview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/gl_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/gl_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.9" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gp_save_retry"
android:visibility="gone"
app:constraint_referenced_ids="btn_retry_take_photo,btn_delete,ig_photo_preview"/>
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/gp_take_photo"
android:visibility="visible"
app:constraint_referenced_ids="btn_takepicture,texture"/>
<ImageButton
android:id="@+id/btn_takepicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@android:color/transparent"
android:src="@drawable/ic_photo_camera_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_delete"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toEndOf="@id/btn_retry_take_photo"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/texture" />
<ImageButton
android:id="@+id/btn_retry_take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_refresh_black"
app:layout_constraintEnd_toStartOf="@+id/btn_takepicture"
app:layout_constraintStart_toStartOf="parent"
android:background="@android:color/transparent"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_margin="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toTopOf="@id/gl_divider"
/>
<ImageButton
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_delete_forever_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@android:color/transparent"
app:layout_constraintHorizontal_chainStyle="spread"
android:layout_margin="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintStart_toEndOf="@id/btn_takepicture"
app:layout_constraintTop_toTopOf="@+id/gl_divider" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Create Event Handlers
Create a class to contain event handlers raised when the user captures a photo or scans the QR Code:
Step 4: Create Helper methods
Now create a set of static helper methods you will use:
package com.mili.camera2api;
import android.util.Patterns;
import org.json.JSONException;
import org.json.JSONObject;
class HelperClass {
static boolean isDataOfTypeNumber(String data) {
try {
Double.parseDouble(data);
return true;
} catch (NumberFormatException e) {
e.printStackTrace();
return false;
}
}
static boolean isDataOfTypeJson(String data) {
try {
new JSONObject(data);
return true;
} catch (JSONException e) {
return false;
}
}
static boolean isDataAnUrl(String data) {
return android.util.Patterns.WEB_URL.matcher(data).matches();
}
static boolean isDataAnEmailId(String data) {
return Patterns.EMAIL_ADDRESS.matcher(data).matches();
}
}
Step 6: Create a custom TextureView
The camera frame will be defined by the following custom textureview. This will be place in the camera layout:
AutoFitTextureView.java
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
Step 7: Create a Base activity
Now create a base activity from which the other activities will be inheriting. For example checking and handling runtime permissions are needed in both the camera photo capture activity as well as the QR Code scanning activity. So it makes sense to define it here:
BaseActivity.java
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
public abstract class BaseActivity extends AppCompatActivity {
protected static final int REQUEST_CAMERA = 100;
private static final String TAG = "BaseActivity";
protected boolean checkCameraPermission() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == REQUEST_CAMERA && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "onRequestPermissionsResult: permission granted");
} else {
checkCameraPermission();
}
}
}
Step 7: Create a Photo Capture Activity
This class will be responsible for capturing photo using the Camera2 API. It is created by extending the base activity so that we can inherit the runtime permission capabilities:
A SparseIntArray will be created to contain the orientations:
Then surafce rotation angles will be appended to the Orientations sparse int array:
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
This method will be used to create a camera preview session:
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = textureView.getSurfaceTexture();
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
captureRequestBuilder
= cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (cameraDevice == null) {
return;
}
// When the session is ready, we start displaying the preview.
cameraCaptureSessions = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_AUTO);
// Flash is automatically enabled when necessary.
setAutoFlash(captureRequestBuilder);
// Finally, we start displaying the camera preview.
captureRequest = captureRequestBuilder.build();
cameraCaptureSessions.setRepeatingRequest(captureRequest,
captureCallbackListener, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.d(TAG, "onConfigureFailed: failed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Here is how you open the camera:
private void openCamera(int width, int height) {
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
Log.e(TAG, "is camera open");
try {
if (!cameraOpenCloseLockSemaphore.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
if (checkCameraPermission()) {
manager.openCamera(cameraId, stateCallback, mBackgroundHandler);
} else {
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
Log.e(TAG, "openCamera X");
}
And here is how you close the camera:
private void closeCamera() {
try {
cameraOpenCloseLockSemaphore.acquire();
if (cameraCaptureSessions != null) {
cameraCaptureSessions.close();
cameraCaptureSessions = null;
}
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
if (imageReader != null) {
imageReader.close();
imageReader = null;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
cameraOpenCloseLockSemaphore.release();
}
}
To capture a still image, the first step is to lock the focus as follows:
private void lockFocus() {
try {
// This is how to tell the camera to lock focus.
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// Tell #mCaptureCallback to wait for the lock.
mState = STATE_WAITING_LOCK;
cameraCaptureSessions.capture(captureRequestBuilder.build(), captureCallbackListener,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
Then to capture the still image, simply invoke the lockFocus()
method above;
Step 8: Create A Scan Activity
It is an empty activity, yes an activity, but it derives fromthe CaptureActivity
. The only reason to create this activity is to make sure the qr code is in portrait mode and CaptureActivity is part of com.journeyapps.barcodescanner lib. As we cannot access CaptureActivity and set it to portrait we can use this approach:
ScanActivity.java
import com.journeyapps.barcodescanner.CaptureActivity;
public class ScanActivity extends CaptureActivity {
}
Step 9: Create MainActivity
Finally wrap everything inside the main activity:
MainActivity.java
public class MainActivity extends BaseActivity implements UserAction {
IntentIntegrator scanIntent;
ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
getSupportActionBar().show();
binding.setUserAction(this);
checkCameraPermission();
}
@Override
public void onClickPhoto() {
Intent clickPhoto = new Intent(this, CameraActivity.class);
startActivity(clickPhoto);
}
@Override
public void onScanQRCode() {
scanIntent = new IntentIntegrator(this);
scanIntent.setCaptureActivity(ScanActivity.class);
scanIntent.setBeepEnabled(true);
scanIntent.setPrompt(getString(R.string.qr_code_scan_instruction));
scanIntent.initiateScan();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (result != null) {
if (result.getContents() == null) {
Snackbar.make(binding.getRoot(), getResources().getString(R.string.code_not_scanned), Snackbar.LENGTH_LONG).show();
} else {
String dataInQrCode = result.getContents();
binding.setScannedData(formatContentBasedOnData(dataInQrCode));
}
}
}
private String formatContentBasedOnData(String dataInQrCode) {
StringBuilder builder = new StringBuilder();
if (isDataOfTypeNumber(dataInQrCode)) {
builder.append("Data in QR is a Number: \n").append(dataInQrCode);
return builder.toString();
} else if (isDataOfTypeJson(dataInQrCode)) {
builder.append("Data in QR is a JSON Object: \n").append(dataInQrCode);
return builder.toString();
} else if (isDataAnEmailId(dataInQrCode)) {
builder.append("Data in QR is an Email Id: \n").append(dataInQrCode);
return builder.toString();
} else if (isDataAnUrl(dataInQrCode)) {
builder.append("Data in QR is an URL: \n").append(dataInQrCode);
return builder.toString();
} else {
builder.append("Data in QR is a Normal Text: \n").append(dataInQrCode);
return builder.toString();
}
// we can add more option if required
}
}
Run
Run the project and you will get the following:
Here is the demo of the QR Code scan using camera2:
Here is the data contained in the QR Code:
Here is the photo capture demo:
Reference
Find the download link below:
No. | Link |
---|---|
1. | Download Code |
2. | Browse Code |
3. | Follow Code Author |