In previous post we created test, which gets desiredcameraId
, then we opened CameraDevice
for that cameraId
. In brief we write this code:
package com.example.myapplication; import android.Manifest; import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.rule.GrantPermissionRule; import androidx.test.runner.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class ExampleInstrumentedTest { @Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA); // create variable for holding device private CameraDevice cameraDevice; // create state object, to pass to open method. Implements required methods private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { // this method is most important for us. Assign opened device to our variable defined above cameraDevice = camera; Log.d("ExampleInstrumentedTest", "CameraDevice.StateCallback::onOpened"); } @Override public void onDisconnected(@NonNull CameraDevice camera) { camera.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { camera.close(); } }; @Test public void test1() throws CameraAccessException { // get context (remember to use getTargetContext()) Context context = InstrumentationRegistry.getTargetContext(); // get camera manager CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); // iterate over all device's cameras String cameraId = null; for (String camId : manager.getCameraIdList()) { // and choose apropriete one, based on camera characteristics CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId); Integer LENS_FACING = characteristics.get(CameraCharacteristics.LENS_FACING); if (LENS_FACING != null && LENS_FACING == CameraCharacteristics.LENS_FACING_BACK) { cameraId = camId; break; } } HandlerThread mBackgroundThread = new HandlerThread("CameraThread"); mBackgroundThread.start(); Handler backgroundHandler = new Handler(mBackgroundThread.getLooper()); if (cameraId != null) { manager.openCamera(cameraId, mStateCallback, backgroundHandler); } } }
To capture single photo we need:
CameraCaptureSession
object: (1)
ImageReader
object (2)
ImageReader.OnImageAvailableListener
object and connect to ImageReader
object (4)CameraCaptureSession.StateCallback
object and get session object (5)CaptureRequest
object (6)CameraCaptureSession.CaptureCallback
(7)In this example we will capture image and save it to jpeg file.
As title said we create listener, which is called, when Image from camera is available. That's all. We need to save it to file. More detailed listener gets ImageReader
, which can get latest image. Then this image can be easly saved into file. After that, all elements should be closed: Reader, Image and File.
// create ImageAvailableListener ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() { // only one methon to implement @Override public void onImageAvailable(final ImageReader reader) { // read image from ImageReader final Image image = reader.acquireLatestImage(); // save file in background thread, to don't block UI/Camera backgroundHandler.post(new Runnable() { // in anonymous class impelemtn run method @Override public void run() { //get content ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { File file = getFile(image); output = new FileOutputStream(file); // save content to file output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { // close image image.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } // close reader reader.close(); } // get file private File getFile(Image image) { long timestamp = image.getTimestamp(); File externalFileDir = Environment.getExternalStorageDirectory(); String folderPath = String.format("%s/%s/%s/", externalFileDir, Environment.DIRECTORY_DCIM, "MyApplication"); File folder = new File(folderPath); if (!folder.exists()) { boolean success = folder.mkdirs(); } File file = new File(String.format("%s/%s.%s", folderPath, timestamp, "jpg")); Log.d("ExampleInstrumentedTest", "OnImageAvailableListener:save: " + file.getAbsolutePath()); return file; } }); } };
Anonyous runner class can be refactored to inner class. You can name it for example: TestImageSaver
CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap configurationMap = cameraCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] jpegOutputSized = configurationMap.getOutputSizes(ImageFormat.JPEG); Size largestJpegSize = Collections.max(Arrays.asList(jpegOutputSized), new Comparator() { @Override public int compare(Size o1, Size o2) { return Long.signum((long) o1.getWidth() * o1.getHeight() - (long) o2.getWidth() * o2.getHeight()); } });
ImageReader
ImageReader imageReader = ImageReader.newInstance(largestJpeg.getWidth(), largestJpeg.getHeight(), ImageFormat.JPEG, 5); imageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);
CameraCaptureSession.StateCallback
object and get session objectsession object should be a test class' field and session state callback as well. Inside state callback, we assign configured session, to field.
// this code is part of class definition // session object private CameraCaptureSession cameraCaptureSession; // session state callback private CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession = session; Log.d(TAG, "CameraCaptureSession.StateCallback::onConfigured"); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } };
// this code is part of test method cameraDevice.createCaptureSession(Collections.singletonList(imageReader.getSurface()), sessionStateCallback, backgroundHandler);
CaptureRequest
object// create RequestBuilder CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // set surface in builder requestBuilder.addTarget(imageReader.getSurface()); // build a single request - it will be passed to capture method CaptureRequest request = requestBuilder.build();
CameraCaptureSession.CaptureCallback
object// may be part of class definition (class field) CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureStarted"); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureCompleted"); } };
It may be not obvious, but when camera code is invoked with any handler object. Then this method is not run explicite in that moment. Part of this method is posted to a handler, and is invoked some kind "in background". In other words we should know how multithreading is working in Java. To solve this problem, just run capture method in the same thread handler, as other camera methods was invoked. To do this, google for: "java handler post" or look:
backgroundHandler.post(new Runnable() { @Override public void run() { try { Log.d("ExampleInstrumentedTest", "cameraCaptureSession.capture"); cameraCaptureSession.capture(request, captureCallback, backgroundHandler); } catch (CameraAccessException e) { Assert.fail("CameraAccessException during creating request!"); e.printStackTrace(); } } }); // at the end, add waiting, to finish all thread tasks try { // explicit sleep, to finish capturing image Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
package com.example.myapplication; import android.Manifest; import android.content.Context; import android.graphics.ImageFormat; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.util.Size; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.rule.GrantPermissionRule; import androidx.test.runner.AndroidJUnit4; import junit.framework.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @RunWith(AndroidJUnit4.class) @SmallTest public class ExampleInstrumentedTest { @Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA); // create variable for holding device private CameraDevice cameraDevice; // create state object, to pass to open method. Implements required methods private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { // this method is most important for us. Assign opened device to our variable defined above cameraDevice = camera; Log.d("ExampleInstrumentedTest", "CameraDevice.StateCallback::onOpened"); } @Override public void onDisconnected(@NonNull CameraDevice camera) { camera.close(); } @Override public void onError(@NonNull CameraDevice camera, int error) { camera.close(); } }; private CameraCaptureSession cameraCaptureSession; private CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { cameraCaptureSession = session; Log.d("ExampleInstrumentedTest", "CameraCaptureSession.StateCallback::onConfigured"); } @Override public void onConfigureFailed(@android.support.annotation.NonNull CameraCaptureSession session) { } }; private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureStarted"); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureCompleted"); } }; @Test public void test1() throws CameraAccessException { // get context (remember to use getTargetContext()) final Context context = InstrumentationRegistry.getTargetContext(); // get camera manager CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); // iterate over all device's cameras String cameraId = null; for (String camId : manager.getCameraIdList()) { // and choose apropriete one, based on camera characteristics CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId); Integer LENS_FACING = characteristics.get(CameraCharacteristics.LENS_FACING); if (LENS_FACING != null && LENS_FACING == CameraCharacteristics.LENS_FACING_BACK) { cameraId = camId; break; } } CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap configurationMap = cameraCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] jpegOutputSized = configurationMap.getOutputSizes(ImageFormat.JPEG); Size largestJpegSize = Collections.max(Arrays.asList(jpegOutputSized), new Comparator() { @Override public int compare(Size o1, Size o2) { return Long.signum((long) o1.getWidth() * o1.getHeight() - (long) o2.getWidth() * o2.getHeight()); } }); HandlerThread mBackgroundThread = new HandlerThread("CameraThread"); mBackgroundThread.start(); final Handler backgroundHandler = new Handler(mBackgroundThread.getLooper()); if (cameraId != null) { manager.openCamera(cameraId, mStateCallback, backgroundHandler); } ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(final ImageReader reader) { final Image image = reader.acquireLatestImage(); backgroundHandler.post(new Runnable() { @Override public void run() { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { File file = getFile(image); output = new FileOutputStream(file); output.write(bytes); } catch (IOException e) { e.printStackTrace(); } finally { image.close(); if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } reader.close(); } private File getFile(Image image) { long timestamp = image.getTimestamp(); File externalFileDir = Environment.getExternalStorageDirectory(); String folderPath = String.format("%s/%s/%s/", externalFileDir, Environment.DIRECTORY_DCIM, "MyApplication"); File folder = new File(folderPath); if (!folder.exists()) { boolean success = folder.mkdirs(); } File file = new File(String.format("%s/%s.%s", folderPath, timestamp, "jpg")); Log.d("ExampleInstrumentedTest", "OnImageAvailableListener:save: " + file.getAbsolutePath()); return file; } }); } }; final ImageReader imageReader = ImageReader.newInstance(largestJpegSize.getWidth(), largestJpegSize.getHeight(), ImageFormat.JPEG, 5); imageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler); cameraDevice.createCaptureSession(Collections.singletonList(imageReader.getSurface()), sessionStateCallback, backgroundHandler); Log.d("ExampleInstrumentedTest", "createCaptureRequest"); CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); Log.d("ExampleInstrumentedTest", "addTarget"); requestBuilder.addTarget(imageReader.getSurface()); Log.d("ExampleInstrumentedTest", "requestBuilder.build"); final CaptureRequest request = requestBuilder.build(); backgroundHandler.post(new Runnable() { @Override public void run() { try { Log.d("ExampleInstrumentedTest", "cameraCaptureSession.capture"); cameraCaptureSession.capture(request, captureCallback, backgroundHandler); } catch (CameraAccessException e) { Assert.fail("CameraAccessException during creating request!"); e.printStackTrace(); } } }); try { // explicit sleep, to finish capturing image Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }