Basic usage of Camera2 API
refer to: https://developer.android.google.cn/reference/android/hardware/camera2/package-summary
request access camera permission
there need add permission declaration in AndroidManifest.xml
<!-- false means APP can run on device which has no camera -->
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
then, there need to dynamically request access camera permission
public static final int REQUEST_PERMISSION_CODE = 100;
public boolean permissionAccepted = false;
private void checkPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE);
} else {
permissionAccepted = true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
permissionAccepted = true;
} else {
permissionAccepted = false;
Toast.makeText(this, "this APP need to access CAMERA!!", Toast.LENGTH_SHORT).show();
}
}
}
get CameraManager:
CameraManager
is a system service manager for detecting, characterizing, and connecting to CameraDevices
.
private CameraManager cameraManager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
open CameraDevide
Open a connection to a camera with the given ID.
Use getCameraIdList()
to get the list of available camera devices. Note that even if an id is listed, open may fail if the device is disconnected between the calls to getCameraIdList()
and openCamera(String, StateCallback, Handler)
, or if a higher-priority camera API client begins using the camera device.
function:
public void openCamera (String cameraId, CameraDevice.StateCallback callback, Handler handler)
example:
public void openCamera(String cameraId, int frameRate) {
try {
// check permission
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
this.frameRate = frameRate;
imageReader = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2);
imageReader.setOnImageAvailableListener(onImageAvailableListener, cameraHandler);
cameraManager.openCamera(cameraId, stateCallback, cameraHandler);
} else {
Toast.makeText(context, "can not access CAMERA, please give me permission!!", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(((MainActivity)context), new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
where, cameraHandler
is created as follow:
private HandlerThread handlerThread;
handlerThread = new HandlerThread("camera2Thread");
handlerThread.start();
CameraHandler cameraHandler = new Handler(handlerThread.getLooper())
and stateCallback
is used to listen the state of CameraDevice
:
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
// do something when camera opened.
// here, when the camera opened, create a cameraCaptureSession to request to capture image
cameraDevice = camera;
createCameraPreviewSession(camera);
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
Log.d("vv", "onDisconnected: camera disconnected!");
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
cameraDevice = null;
Log.d("vv", "onDisconnected: camera error!");
}
};
create cameraCaptureSession
A configured capture session for a CameraDevice
, used for capturing images from the camera or reprocessing images captured from the camera in the same session previously.
private void createCameraPreviewSession(CameraDevice camera) {
try {
captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
previewSurface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
}
captureRequestBuilder.addTarget(previewSurface); // for preview on SurfaceView or TextureView
captureRequestBuilder.addTarget(imageReader.getSurface()); // get frame by a imageReader
// set frame rate by new Range<>(60, 60)
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(60, 60));
camera.createCaptureSession(Arrays.asList(previewSurface, imageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
cameraCaptureSession = session;
try {
captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
if (turnOnFlash) {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
}
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), captureCallback, cameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Toast.makeText(context, "Failed to create camera capture session \n camera " + camera.getId() + " may not exist", Toast.LENGTH_SHORT).show();
}
}, cameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
where, previewSurface
can be SurfaceView
, need use it in layout.xml
first. Then, get Surface
object like:
SurfaceView surfaceView = findViewById(R.id.surfaceView);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
surfaceHolder.setFormat(PixelFormat.TRANSPARENT);
surfaceHolder.setFixedSize(640, 480);
surfaceHolder.addCallback(surfaceCallBack);
Surface previewSurface = surfaceHolder.getSurface();
private final SurfaceHolder.Callback surfaceCallBack = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
};
imageReader
is created as follow:
ImageReader imageReader = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2);
imageReader.setOnImageAvailableListener(onImageAvailableListener, cameraHandler);
where, onImageAvailableListener
is refer to ImageReader
private final ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireLatestImage();
if (image != null) {
// process image
if (dataFile == null) {
filePath = context.getExternalFilesDir(null).getAbsolutePath();
@SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
filename = filename + "_(" + sdf.format(new Date(System.currentTimeMillis())) + ").txt";
String mFilePath = filePath + "/" + filename;
dataFile = new File(mFilePath);
}
int red = DataProcess.process(image, dataFile);
Message msg = Message.obtain();
msg.what = 1;
msg.arg1 = red;
((MainActivity) context).handler.sendMessage(msg);
image.close();
}
}
};
captureRequestBuilder
is instance of CaptureRequest.Builder
A builder for capture requests.
To obtain a builder instance, use the CameraDevice#createCaptureRequest
method, which initializes the request fields to one of the templates defined in CameraDevice
.
captureCallback
is a callback object for tracking the progress of a CaptureRequest submitted to the camera device.
This callback is invoked when a request triggers a capture to start, and when the capture is complete. In case on an error capturing an image, the error method is triggered instead of the completion method.
int count = 0;
private final CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
// I calculate frame rate here.
count++;
if (count == 30) {
long currFrameTime = System.currentTimeMillis();
float frameRate = (float) (1000.0 / ((currFrameTime - lastFrameTime) / 30.0));
lastFrameTime = currFrameTime;
count = 0;
((MainActivity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
((MainActivity)context).updateFrameRate(frameRate);
}
});
}
if (!((MainActivity)context).timerStarted) {
((MainActivity)context).runTimer();
}
}
};