1048 lines
38 KiB
Java
1048 lines
38 KiB
Java
/*
|
|
* Copyright (C) 2021 pedroSG94.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.pedro.encoder.input.video;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.content.Context;
|
|
import android.graphics.Rect;
|
|
import android.graphics.SurfaceTexture;
|
|
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.CameraMetadata;
|
|
import android.hardware.camera2.CaptureRequest;
|
|
import android.hardware.camera2.CaptureResult;
|
|
import android.hardware.camera2.TotalCaptureResult;
|
|
import android.hardware.camera2.params.Face;
|
|
import android.hardware.camera2.params.MeteringRectangle;
|
|
import android.hardware.camera2.params.StreamConfigurationMap;
|
|
import android.os.Build;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.util.Log;
|
|
import android.util.Range;
|
|
import android.util.Size;
|
|
import android.view.MotionEvent;
|
|
import android.view.Surface;
|
|
import android.view.SurfaceView;
|
|
import android.view.TextureView;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.RequiresApi;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.concurrent.Semaphore;
|
|
|
|
import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;
|
|
import static com.pedro.encoder.input.video.CameraHelper.*;
|
|
|
|
/**
|
|
* Created by pedro on 4/03/17.
|
|
*
|
|
* <p>
|
|
* Class for use surfaceEncoder to buffer encoder.
|
|
* Advantage = you can use all resolutions.
|
|
* Disadvantages = you cant control fps of the stream, because you cant know when the inputSurface
|
|
* was renderer.
|
|
* <p>
|
|
* Note: you can use opengl for surfaceEncoder to buffer encoder on devices 21 < API > 16:
|
|
* https://github.com/google/grafika
|
|
*/
|
|
|
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
public class Camera2ApiManager extends CameraDevice.StateCallback {
|
|
|
|
private final String TAG = "Camera2ApiManager";
|
|
|
|
private CameraDevice cameraDevice;
|
|
private SurfaceView surfaceView;
|
|
private TextureView textureView;
|
|
private Surface surfaceEncoder; //input surfaceEncoder from videoEncoder
|
|
private CameraManager cameraManager;
|
|
private Handler cameraHandler;
|
|
private CameraCaptureSession cameraCaptureSession;
|
|
private boolean prepared = false;
|
|
private String cameraId = null;
|
|
private CameraHelper.Facing facing = Facing.BACK;
|
|
private CaptureRequest.Builder builderInputSurface;
|
|
private float fingerSpacing = 0;
|
|
private float zoomLevel = 0f;
|
|
private boolean lanternEnable = false;
|
|
private boolean videoStabilizationEnable = false;
|
|
private boolean opticalVideoStabilizationEnable = false;
|
|
private boolean autoFocusEnabled = true;
|
|
private boolean running = false;
|
|
private int fps = 30;
|
|
private final Semaphore semaphore = new Semaphore(0);
|
|
private CameraCallbacks cameraCallbacks;
|
|
|
|
//Face detector
|
|
public interface FaceDetectorCallback {
|
|
void onGetFaces(Face[] faces, Rect scaleSensor, int sensorOrientation);
|
|
}
|
|
|
|
private int sensorOrientation = 0;
|
|
private Rect faceSensorScale;
|
|
private FaceDetectorCallback faceDetectorCallback;
|
|
private boolean faceDetectionEnabled = false;
|
|
private int faceDetectionMode;
|
|
|
|
public Camera2ApiManager(Context context) {
|
|
cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
|
}
|
|
|
|
public void prepareCamera(SurfaceView surfaceView, Surface surface, int fps) {
|
|
this.surfaceView = surfaceView;
|
|
this.surfaceEncoder = surface;
|
|
this.fps = fps;
|
|
prepared = true;
|
|
}
|
|
|
|
public void prepareCamera(TextureView textureView, Surface surface, int fps) {
|
|
this.textureView = textureView;
|
|
this.surfaceEncoder = surface;
|
|
this.fps = fps;
|
|
prepared = true;
|
|
}
|
|
|
|
public void prepareCamera(Surface surface, int fps) {
|
|
this.surfaceEncoder = surface;
|
|
this.fps = fps;
|
|
prepared = true;
|
|
}
|
|
|
|
public void prepareCamera(SurfaceTexture surfaceTexture, int width, int height, int fps) {
|
|
surfaceTexture.setDefaultBufferSize(width, height);
|
|
this.surfaceEncoder = new Surface(surfaceTexture);
|
|
this.fps = fps;
|
|
prepared = true;
|
|
}
|
|
|
|
public boolean isPrepared() {
|
|
return prepared;
|
|
}
|
|
|
|
private void startPreview(CameraDevice cameraDevice) {
|
|
try {
|
|
final List<Surface> listSurfaces = new ArrayList<>();
|
|
Surface preview = addPreviewSurface();
|
|
if (preview != null) listSurfaces.add(preview);
|
|
if (surfaceEncoder != preview && surfaceEncoder != null) listSurfaces.add(surfaceEncoder);
|
|
|
|
cameraDevice.createCaptureSession(listSurfaces, new CameraCaptureSession.StateCallback() {
|
|
@Override
|
|
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
|
|
Camera2ApiManager.this.cameraCaptureSession = cameraCaptureSession;
|
|
try {
|
|
CaptureRequest captureRequest = drawSurface(listSurfaces);
|
|
if (captureRequest != null) {
|
|
cameraCaptureSession.setRepeatingRequest(captureRequest,
|
|
faceDetectionEnabled ? cb : null, cameraHandler);
|
|
Log.i(TAG, "Camera configured");
|
|
} else {
|
|
Log.e(TAG, "Error, captureRequest is null");
|
|
}
|
|
} catch (CameraAccessException | NullPointerException e) {
|
|
Log.e(TAG, "Error", e);
|
|
} catch (IllegalStateException e) {
|
|
reOpenCamera(cameraId != null ? cameraId : "0");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
|
|
cameraCaptureSession.close();
|
|
if (cameraCallbacks != null) cameraCallbacks.onCameraError("Configuration failed");
|
|
Log.e(TAG, "Configuration failed");
|
|
}
|
|
}, cameraHandler);
|
|
} catch (CameraAccessException | IllegalArgumentException e) {
|
|
if (cameraCallbacks != null) {
|
|
cameraCallbacks.onCameraError("Create capture session failed: " + e.getMessage());
|
|
}
|
|
Log.e(TAG, "Error", e);
|
|
} catch (IllegalStateException e) {
|
|
reOpenCamera(cameraId != null ? cameraId : "0");
|
|
}
|
|
}
|
|
|
|
private Surface addPreviewSurface() {
|
|
Surface surface = null;
|
|
if (surfaceView != null) {
|
|
surface = surfaceView.getHolder().getSurface();
|
|
} else if (textureView != null) {
|
|
final SurfaceTexture texture = textureView.getSurfaceTexture();
|
|
surface = new Surface(texture);
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
private CaptureRequest drawSurface(List<Surface> surfaces) {
|
|
try {
|
|
builderInputSurface = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
|
|
for (Surface surface : surfaces) if (surface != null) builderInputSurface.addTarget(surface);
|
|
setModeAuto(builderInputSurface);
|
|
adaptFpsRange(fps, builderInputSurface);
|
|
return builderInputSurface.build();
|
|
} catch (CameraAccessException | IllegalStateException e) {
|
|
Log.e(TAG, "Error", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void setModeAuto(CaptureRequest.Builder builderInputSurface) {
|
|
try {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
|
|
} catch (Exception ignored) { }
|
|
}
|
|
|
|
private void adaptFpsRange(int expectedFps, CaptureRequest.Builder builderInputSurface) {
|
|
List<Range<Integer>> fpsRanges = getSupportedFps(null, Facing.BACK);
|
|
if (fpsRanges != null && fpsRanges.size() > 0) {
|
|
Range<Integer> closestRange = fpsRanges.get(0);
|
|
int measure = Math.abs(closestRange.getLower() - expectedFps) + Math.abs(
|
|
closestRange.getUpper() - expectedFps);
|
|
for (Range<Integer> range : fpsRanges) {
|
|
if (CameraHelper.discardCamera2Fps(range, facing)) continue;
|
|
if (range.getLower() <= expectedFps && range.getUpper() >= expectedFps) {
|
|
int curMeasure = Math.abs(((range.getLower() + range.getUpper()) / 2) - expectedFps);
|
|
if (curMeasure < measure) {
|
|
closestRange = range;
|
|
measure = curMeasure;
|
|
} else if (curMeasure == measure) {
|
|
if (Math.abs(range.getUpper() - expectedFps) < Math.abs(closestRange.getUpper() - expectedFps)) {
|
|
closestRange = range;
|
|
measure = curMeasure;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Log.i(TAG, "fps: " + closestRange.getLower() + " - " + closestRange.getUpper());
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, closestRange);
|
|
}
|
|
}
|
|
|
|
public List<Range<Integer>> getSupportedFps(Size size, Facing facing) {
|
|
try {
|
|
CameraCharacteristics characteristics = null;
|
|
try {
|
|
characteristics = getCharacteristicsForFacing(cameraManager, facing);
|
|
} catch (CameraAccessException ignored) { }
|
|
if (characteristics == null) return null;
|
|
Range<Integer>[] fpsSupported = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
|
|
if (size != null) {
|
|
StreamConfigurationMap streamConfigurationMap =
|
|
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
|
List<Range<Integer>> list = new ArrayList<>();
|
|
long fd = streamConfigurationMap.getOutputMinFrameDuration(SurfaceTexture.class, size);
|
|
int maxFPS = (int)(10f / Float.parseFloat("0." + fd));
|
|
for (Range<Integer> r : fpsSupported) {
|
|
if (r.getUpper() <= maxFPS) {
|
|
list.add(r);
|
|
}
|
|
}
|
|
return list;
|
|
} else {
|
|
return Arrays.asList(fpsSupported);
|
|
}
|
|
} catch (IllegalStateException e) {
|
|
Log.e(TAG, "Error", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public int getLevelSupported() {
|
|
try {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return -1;
|
|
Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
|
|
if (level == null) return -1;
|
|
return level;
|
|
} catch (IllegalStateException e) {
|
|
Log.e(TAG, "Error", e);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public void openCamera() {
|
|
openCameraBack();
|
|
}
|
|
|
|
public void openCameraBack() {
|
|
openCameraFacing(Facing.BACK);
|
|
}
|
|
|
|
public void openCameraFront() {
|
|
openCameraFacing(Facing.FRONT);
|
|
}
|
|
|
|
public void openLastCamera() {
|
|
if (cameraId == null) {
|
|
openCameraBack();
|
|
} else {
|
|
openCameraId(cameraId);
|
|
}
|
|
}
|
|
|
|
public void setCameraFacing(CameraHelper.Facing cameraFacing) {
|
|
try {
|
|
String cameraId = getCameraIdForFacing(cameraManager, cameraFacing);
|
|
if (cameraId != null) {
|
|
facing = cameraFacing;
|
|
this.cameraId = cameraId;
|
|
}
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
|
|
public void setCameraId(String cameraId) {
|
|
this.cameraId = cameraId;
|
|
}
|
|
|
|
public CameraHelper.Facing getCameraFacing() {
|
|
return facing;
|
|
}
|
|
|
|
public Size[] getCameraResolutionsBack() {
|
|
return getCameraResolutions(Facing.BACK);
|
|
}
|
|
|
|
public Size[] getCameraResolutionsFront() {
|
|
return getCameraResolutions(Facing.FRONT);
|
|
}
|
|
|
|
public Size[] getCameraResolutions(Facing facing) {
|
|
try {
|
|
CameraCharacteristics characteristics = getCharacteristicsForFacing(cameraManager, facing);
|
|
if (characteristics == null) {
|
|
return new Size[0];
|
|
}
|
|
|
|
StreamConfigurationMap streamConfigurationMap =
|
|
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
|
if (streamConfigurationMap == null) return new Size[0];
|
|
Size[] outputSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
|
|
return outputSizes != null ? outputSizes : new Size[0];
|
|
} catch (CameraAccessException | NullPointerException e) {
|
|
Log.e(TAG, "Error", e);
|
|
return new Size[0];
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
public CameraCharacteristics getCameraCharacteristics() {
|
|
try {
|
|
return cameraId != null ? cameraManager.getCameraCharacteristics(cameraId) : null;
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean enableVideoStabilization() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return false;
|
|
int[] modes = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
|
|
List<Integer> videoStabilizationList = new ArrayList<>();
|
|
for (int vsMode : modes) {
|
|
videoStabilizationList.add(vsMode);
|
|
}
|
|
if (!videoStabilizationList.contains(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON)) {
|
|
Log.e(TAG, "video stabilization unsupported");
|
|
return false;
|
|
}
|
|
|
|
if (builderInputSurface != null) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
|
|
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
|
|
videoStabilizationEnable = true;
|
|
}
|
|
return videoStabilizationEnable;
|
|
}
|
|
|
|
public void disableVideoStabilization() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
int[] modes = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
|
|
List<Integer> videoStabilizationList = new ArrayList<>();
|
|
for (int vsMode : modes) {
|
|
videoStabilizationList.add(vsMode);
|
|
}
|
|
if (!videoStabilizationList.contains(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON)) {
|
|
Log.e(TAG, "video stabilization unsupported");
|
|
return;
|
|
}
|
|
if (builderInputSurface != null) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
|
|
CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF);
|
|
videoStabilizationEnable = false;
|
|
}
|
|
}
|
|
|
|
public boolean isVideoStabilizationEnabled() {
|
|
return videoStabilizationEnable;
|
|
}
|
|
|
|
public boolean enableOpticalVideoStabilization() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return false;
|
|
|
|
int[] opticalStabilizationModes = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
|
|
List<Integer> opticalStabilizationList = new ArrayList<>();
|
|
for (int vsMode : opticalStabilizationModes) {
|
|
opticalStabilizationList.add(vsMode);
|
|
}
|
|
|
|
if (!opticalStabilizationList.contains(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON)) {
|
|
Log.e(TAG, "OIS video stabilization unsupported");
|
|
return false;
|
|
}
|
|
if (builderInputSurface != null) {
|
|
builderInputSurface.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE,
|
|
CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON);
|
|
opticalVideoStabilizationEnable = true;
|
|
}
|
|
return opticalVideoStabilizationEnable;
|
|
}
|
|
|
|
public void disableOpticalVideoStabilization() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
int[] modes = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
|
|
List<Integer> videoStabilizationList = new ArrayList<>();
|
|
for (int vsMode : modes) {
|
|
videoStabilizationList.add(vsMode);
|
|
}
|
|
if (!videoStabilizationList.contains(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON)) {
|
|
Log.e(TAG, "OIS video stabilization unsupported");
|
|
return;
|
|
}
|
|
if (builderInputSurface != null) {
|
|
builderInputSurface.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE,
|
|
CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_OFF);
|
|
opticalVideoStabilizationEnable = false;
|
|
}
|
|
}
|
|
|
|
public boolean isOpticalStabilizationEnabled() {
|
|
return opticalVideoStabilizationEnable;
|
|
}
|
|
|
|
public void setFocusDistance(float distance) {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
if (distance < 0) distance = 0f; //avoid invalid value
|
|
builderInputSurface.set(CaptureRequest.LENS_FOCUS_DISTANCE, distance);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setExposure(int value) {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
Range<Integer> supportedExposure =
|
|
characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
|
|
if (supportedExposure != null && builderInputSurface != null) {
|
|
if (value > supportedExposure.getUpper()) value = supportedExposure.getUpper();
|
|
if (value < supportedExposure.getLower()) value = supportedExposure.getLower();
|
|
try {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, value);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public int getExposure() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return 0;
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
return builderInputSurface.get(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
public int getMaxExposure() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return 0;
|
|
Range<Integer> supportedExposure =
|
|
characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
|
|
if (supportedExposure != null) {
|
|
return supportedExposure.getUpper();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public int getMinExposure() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return 0;
|
|
Range<Integer> supportedExposure =
|
|
characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
|
|
if (supportedExposure != null) {
|
|
return supportedExposure.getLower();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public void tapToFocus(MotionEvent event) {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
int pointerId = event.getPointerId(0);
|
|
int pointerIndex = event.findPointerIndex(pointerId);
|
|
// Get the pointer's current position
|
|
float x = event.getX(pointerIndex);
|
|
float y = event.getY(pointerIndex);
|
|
|
|
Rect touchRect = new Rect((int) (x - 100), (int) (y - 100),
|
|
(int) (x + 100), (int) (y + 100));
|
|
MeteringRectangle focusArea = new MeteringRectangle(touchRect, MeteringRectangle.METERING_WEIGHT_DONT_CARE);
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
//cancel any existing AF trigger (repeated touches, etc.)
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{focusArea});
|
|
builderInputSurface.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select camera facing
|
|
*
|
|
* @param selectedCameraFacing - CameraCharacteristics.LENS_FACING_FRONT,
|
|
* CameraCharacteristics.LENS_FACING_BACK,
|
|
* CameraCharacteristics.LENS_FACING_EXTERNAL
|
|
*/
|
|
public void openCameraFacing(Facing selectedCameraFacing) {
|
|
try {
|
|
String cameraId = getCameraIdForFacing(cameraManager, selectedCameraFacing);
|
|
if (cameraId != null) {
|
|
openCameraId(cameraId);
|
|
} else {
|
|
Log.e(TAG, "Camera not supported"); // TODO maybe we want to throw some exception here?
|
|
}
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
|
|
public boolean isLanternSupported() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return false;
|
|
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
|
if (available == null) return false;
|
|
return available;
|
|
}
|
|
|
|
public boolean isLanternEnabled() {
|
|
return lanternEnable;
|
|
}
|
|
|
|
/**
|
|
* @required: <uses-permission android:name="android.permission.FLASHLIGHT"/>
|
|
*/
|
|
public void enableLantern() throws Exception {
|
|
if (isLanternSupported()) {
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
builderInputSurface.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
lanternEnable = true;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
} else {
|
|
Log.e(TAG, "Lantern unsupported");
|
|
throw new Exception("Lantern unsupported");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @required: <uses-permission android:name="android.permission.FLASHLIGHT"/>
|
|
*/
|
|
public void disableLantern() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
|
|
if (available == null) return;
|
|
if (available) {
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
builderInputSurface.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
lanternEnable = false;
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void enableAutoFocus() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
int[] supportedFocusModes =
|
|
characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
|
|
if (supportedFocusModes != null) {
|
|
List<Integer> focusModesList = new ArrayList<>();
|
|
for (int i : supportedFocusModes) focusModesList.add(i);
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
if (!focusModesList.isEmpty()) {
|
|
//cancel any existing AF trigger
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
if (focusModesList.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
autoFocusEnabled = true;
|
|
} else if (focusModesList.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_AUTO);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
autoFocusEnabled = true;
|
|
} else {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, focusModesList.get(0));
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
autoFocusEnabled = false;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void disableAutoFocus() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
int[] supportedFocusModes =
|
|
characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
|
|
if (supportedFocusModes != null) {
|
|
if (builderInputSurface != null) {
|
|
for (int mode : supportedFocusModes) {
|
|
try {
|
|
if (mode == CaptureRequest.CONTROL_AF_MODE_OFF) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE,
|
|
CaptureRequest.CONTROL_AF_MODE_OFF);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
autoFocusEnabled = false;
|
|
return;
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean isAutoFocusEnabled() {
|
|
return autoFocusEnabled;
|
|
}
|
|
|
|
public boolean enableFaceDetection(FaceDetectorCallback faceDetectorCallback) {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) {
|
|
Log.e(TAG, "face detection called with camera stopped");
|
|
return false;
|
|
}
|
|
faceSensorScale = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
|
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
|
int[] fd = characteristics.get(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);
|
|
if (fd == null || fd.length == 0) {
|
|
Log.e(TAG, "face detection unsupported");
|
|
return false;
|
|
}
|
|
Integer maxFD = characteristics.get(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT);
|
|
if (maxFD == null || maxFD <= 0) {
|
|
Log.e(TAG, "face detection unsupported");
|
|
return false;
|
|
}
|
|
List<Integer> fdList = new ArrayList<>();
|
|
for (int FaceD : fd) {
|
|
fdList.add(FaceD);
|
|
}
|
|
this.faceDetectorCallback = faceDetectorCallback;
|
|
faceDetectionEnabled = true;
|
|
faceDetectionMode = Collections.max(fdList);
|
|
setFaceDetect(builderInputSurface, faceDetectionMode);
|
|
prepareFaceDetectionCallback();
|
|
return true;
|
|
}
|
|
|
|
public void disableFaceDetection() {
|
|
if (faceDetectionEnabled) {
|
|
faceDetectorCallback = null;
|
|
faceDetectionEnabled = false;
|
|
faceDetectionMode = 0;
|
|
prepareFaceDetectionCallback();
|
|
}
|
|
}
|
|
|
|
public boolean isFaceDetectionEnabled() {
|
|
return faceDetectorCallback != null;
|
|
}
|
|
|
|
private void setFaceDetect(CaptureRequest.Builder requestBuilder, int faceDetectMode) {
|
|
if (faceDetectionEnabled) {
|
|
requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, faceDetectMode);
|
|
}
|
|
}
|
|
|
|
public void setCameraCallbacks(CameraCallbacks cameraCallbacks) {
|
|
this.cameraCallbacks = cameraCallbacks;
|
|
}
|
|
|
|
private void prepareFaceDetectionCallback() {
|
|
try {
|
|
cameraCaptureSession.stopRepeating();
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
|
|
private final CameraCaptureSession.CaptureCallback cb =
|
|
new CameraCaptureSession.CaptureCallback() {
|
|
|
|
@Override
|
|
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
|
|
@NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
|
|
Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
|
|
if (faceDetectorCallback != null) {
|
|
faceDetectorCallback.onGetFaces(faces, faceSensorScale, sensorOrientation);
|
|
}
|
|
}
|
|
};
|
|
|
|
@SuppressLint("MissingPermission")
|
|
public void openCameraId(String cameraId) {
|
|
this.cameraId = cameraId;
|
|
if (prepared) {
|
|
HandlerThread cameraHandlerThread = new HandlerThread(TAG + " Id = " + cameraId);
|
|
cameraHandlerThread.start();
|
|
cameraHandler = new Handler(cameraHandlerThread.getLooper());
|
|
try {
|
|
cameraManager.openCamera(cameraId, this, cameraHandler);
|
|
semaphore.acquireUninterruptibly();
|
|
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
|
|
running = true;
|
|
Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
|
|
if (facing == null) return;
|
|
this.facing = LENS_FACING_FRONT == facing ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK;
|
|
if (cameraCallbacks != null) {
|
|
cameraCallbacks.onCameraChanged(this.facing);
|
|
}
|
|
} catch (CameraAccessException | SecurityException e) {
|
|
if (cameraCallbacks != null) {
|
|
cameraCallbacks.onCameraError("Open camera " + cameraId + " failed");
|
|
}
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
} else {
|
|
Log.e(TAG, "Camera2ApiManager need be prepared, Camera2ApiManager not enabled");
|
|
}
|
|
}
|
|
|
|
public String[] getCamerasAvailable() {
|
|
try {
|
|
return cameraManager.getCameraIdList();
|
|
} catch (CameraAccessException e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean isRunning() {
|
|
return running;
|
|
}
|
|
|
|
public void switchCamera() {
|
|
try {
|
|
String cameraId;
|
|
if (cameraDevice == null || facing == Facing.FRONT) {
|
|
cameraId = getCameraIdForFacing(cameraManager, Facing.BACK);
|
|
} else {
|
|
cameraId = getCameraIdForFacing(cameraManager, Facing.FRONT);
|
|
}
|
|
if (cameraId == null) cameraId = "0";
|
|
reOpenCamera(cameraId);
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
|
|
public void reOpenCamera(String cameraId) {
|
|
if (cameraDevice != null) {
|
|
closeCamera(false);
|
|
if (textureView != null) {
|
|
prepareCamera(textureView, surfaceEncoder, fps);
|
|
} else if (surfaceView != null) {
|
|
prepareCamera(surfaceView, surfaceEncoder, fps);
|
|
} else {
|
|
prepareCamera(surfaceEncoder, fps);
|
|
}
|
|
openCameraId(cameraId);
|
|
}
|
|
}
|
|
|
|
public Range<Float> getZoomRange() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return new Range<>(1f, 1f);
|
|
Range<Float> zoomRanges = null;
|
|
//only camera limited or better support this feature.
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
|
|
getLevelSupported() != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
|
zoomRanges = characteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
|
|
}
|
|
if (zoomRanges == null) {
|
|
Float maxZoom = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
|
|
if (maxZoom == null) maxZoom = 1f;
|
|
zoomRanges = new Range<>(1f, maxZoom);
|
|
}
|
|
return zoomRanges;
|
|
}
|
|
|
|
public Float getZoom() {
|
|
return zoomLevel;
|
|
}
|
|
|
|
public float[] getOpticalZooms() {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return null;
|
|
return characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
|
|
}
|
|
|
|
public void setOpticalZoom(float level) {
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
if (builderInputSurface != null) {
|
|
try {
|
|
builderInputSurface.set(CaptureRequest.LENS_FOCAL_LENGTH, level);
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setZoom(float level) {
|
|
try {
|
|
Range<Float> zoomRange = getZoomRange();
|
|
//Avoid out range level
|
|
if (level <= zoomRange.getLower()) level = zoomRange.getLower();
|
|
else if (level > zoomRange.getUpper()) level = zoomRange.getUpper();
|
|
|
|
CameraCharacteristics characteristics = getCameraCharacteristics();
|
|
if (characteristics == null) return;
|
|
|
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
|
|
getLevelSupported() != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
|
|
builderInputSurface.set(CaptureRequest.CONTROL_ZOOM_RATIO, level);
|
|
} else {
|
|
Rect rect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
|
|
if (rect == null) return;
|
|
//This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect
|
|
float ratio = 1f / level;
|
|
//croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped
|
|
int croppedWidth = rect.width() - Math.round((float) rect.width() * ratio);
|
|
int croppedHeight = rect.height() - Math.round((float) rect.height() * ratio);
|
|
//Finally, zoom represents the zoomed visible area
|
|
Rect zoom = new Rect(croppedWidth / 2, croppedHeight / 2, rect.width() - croppedWidth / 2,
|
|
rect.height() - croppedHeight / 2);
|
|
builderInputSurface.set(CaptureRequest.SCALER_CROP_REGION, zoom);
|
|
}
|
|
cameraCaptureSession.setRepeatingRequest(builderInputSurface.build(),
|
|
faceDetectionEnabled ? cb : null, null);
|
|
zoomLevel = level;
|
|
} catch (CameraAccessException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
|
|
public void setZoom(MotionEvent event) {
|
|
float currentFingerSpacing;
|
|
if (event.getPointerCount() > 1) {
|
|
currentFingerSpacing = getFingerSpacing(event);
|
|
float delta = 0.1f;
|
|
if (fingerSpacing != 0) {
|
|
float newLevel = zoomLevel;
|
|
if (currentFingerSpacing > fingerSpacing) {
|
|
newLevel += delta;
|
|
} else if (currentFingerSpacing < fingerSpacing) {
|
|
newLevel -= delta;
|
|
}
|
|
//This method avoid out of range
|
|
setZoom(newLevel);
|
|
}
|
|
fingerSpacing = currentFingerSpacing;
|
|
}
|
|
}
|
|
|
|
private void resetCameraValues() {
|
|
lanternEnable = false;
|
|
zoomLevel = 1.0f;
|
|
}
|
|
|
|
public void stopRepeatingEncoder() {
|
|
if (cameraCaptureSession != null) {
|
|
try {
|
|
cameraCaptureSession.stopRepeating();
|
|
surfaceEncoder = null;
|
|
Surface preview = addPreviewSurface();
|
|
if (preview != null) {
|
|
CaptureRequest captureRequest = drawSurface(Collections.singletonList(preview));
|
|
if (captureRequest != null) {
|
|
cameraCaptureSession.setRepeatingRequest(captureRequest, null, cameraHandler);
|
|
}
|
|
} else {
|
|
Log.e(TAG, "preview surface is null");
|
|
}
|
|
} catch (CameraAccessException | IllegalStateException e) {
|
|
Log.e(TAG, "Error", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void closeCamera() {
|
|
closeCamera(true);
|
|
}
|
|
|
|
public void closeCamera(boolean resetSurface) {
|
|
resetCameraValues();
|
|
if (cameraCaptureSession != null) {
|
|
cameraCaptureSession.close();
|
|
cameraCaptureSession = null;
|
|
}
|
|
if (cameraDevice != null) {
|
|
cameraDevice.close();
|
|
cameraDevice = null;
|
|
}
|
|
if (cameraHandler != null) {
|
|
cameraHandler.getLooper().quitSafely();
|
|
cameraHandler = null;
|
|
}
|
|
if (resetSurface) {
|
|
surfaceEncoder = null;
|
|
builderInputSurface = null;
|
|
}
|
|
prepared = false;
|
|
running = false;
|
|
}
|
|
|
|
@Override
|
|
public void onOpened(@NonNull CameraDevice cameraDevice) {
|
|
this.cameraDevice = cameraDevice;
|
|
startPreview(cameraDevice);
|
|
semaphore.release();
|
|
if (cameraCallbacks != null) cameraCallbacks.onCameraOpened();
|
|
Log.i(TAG, "Camera opened");
|
|
}
|
|
|
|
@Override
|
|
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
|
|
cameraDevice.close();
|
|
semaphore.release();
|
|
if (cameraCallbacks != null) cameraCallbacks.onCameraDisconnected();
|
|
Log.i(TAG, "Camera disconnected");
|
|
}
|
|
|
|
@Override
|
|
public void onError(@NonNull CameraDevice cameraDevice, int i) {
|
|
cameraDevice.close();
|
|
semaphore.release();
|
|
if (cameraCallbacks != null) cameraCallbacks.onCameraError("Open camera failed: " + i);
|
|
Log.e(TAG, "Open failed: " + i);
|
|
}
|
|
|
|
@Nullable
|
|
private String getCameraIdForFacing(CameraManager cameraManager, CameraHelper.Facing facing)
|
|
throws CameraAccessException {
|
|
int selectedFacing = getFacing(facing);
|
|
for (String cameraId : cameraManager.getCameraIdList()) {
|
|
Integer cameraFacing =
|
|
cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.LENS_FACING);
|
|
if (cameraFacing != null && cameraFacing == selectedFacing) {
|
|
return cameraId;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Nullable
|
|
public String getCameraIdForFacing(CameraHelper.Facing facing) {
|
|
try {
|
|
return getCameraIdForFacing(cameraManager, facing);
|
|
} catch (Exception e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private CameraCharacteristics getCharacteristicsForFacing(CameraManager cameraManager,
|
|
CameraHelper.Facing facing) throws CameraAccessException {
|
|
String cameraId = getCameraIdForFacing(cameraManager, facing);
|
|
return cameraId != null ? cameraManager.getCameraCharacteristics(cameraId) : null;
|
|
}
|
|
|
|
private static int getFacing(CameraHelper.Facing facing) {
|
|
return facing == CameraHelper.Facing.BACK ? CameraMetadata.LENS_FACING_BACK
|
|
: CameraMetadata.LENS_FACING_FRONT;
|
|
}
|
|
} |