diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 2e1484c2f..73772f094 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -50,6 +50,7 @@ afterEvaluate { } dependencies { + implementation(libs.androidx.annotation) implementation(libs.kotlinx.coroutines.android) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.junit) diff --git a/common/src/main/java/com/pedro/common/Extensions.kt b/common/src/main/java/com/pedro/common/Extensions.kt index 54d200891..8b8ed58ce 100644 --- a/common/src/main/java/com/pedro/common/Extensions.kt +++ b/common/src/main/java/com/pedro/common/Extensions.kt @@ -16,10 +16,13 @@ package com.pedro.common +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CaptureRequest import android.media.MediaCodec import android.os.Build import android.os.Handler import android.os.Looper +import androidx.annotation.RequiresApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.UnsupportedEncodingException @@ -116,4 +119,14 @@ fun getSuspendContext(): Continuation { override val context = Dispatchers.IO override fun resumeWith(result: Result) {} } +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +fun CameraCharacteristics.secureGet(key: CameraCharacteristics.Key): T? { + return try { get(key) } catch (e: IllegalArgumentException) { null } +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP) +fun CaptureRequest.Builder.secureGet(key: CaptureRequest.Key): T? { + return try { get(key) } catch (e: IllegalArgumentException) { null } } \ No newline at end of file diff --git a/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.java b/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.java deleted file mode 100644 index b1828061d..000000000 --- a/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.java +++ /dev/null @@ -1,1162 +0,0 @@ -/* - * Copyright (C) 2024 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 static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT; -import static com.pedro.encoder.input.video.CameraHelper.Facing; -import static com.pedro.encoder.input.video.CameraHelper.getFingerSpacing; - -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.media.Image; -import android.media.ImageReader; -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 com.pedro.encoder.input.video.facedetector.FaceDetectorCallback; -import com.pedro.encoder.input.video.facedetector.UtilsKt; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Semaphore; - -/** - * Created by pedro on 4/03/17. - * - *

- * 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. - *

- * 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 final 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; - - public interface ImageCallback { - void onImageAvailable(Image image); - } - - private int sensorOrientation = 0; - private Rect faceSensorScale; - private FaceDetectorCallback faceDetectorCallback; - private boolean faceDetectionEnabled = false; - private int faceDetectionMode; - private ImageReader imageReader; - - 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) { - Size optimalResolution = Camera2ResolutionCalculator.INSTANCE.getOptimalResolution(new Size(width, height), getCameraResolutions(facing)); - Log.i(TAG, "optimal resolution set to: " + optimalResolution.getWidth() + "x" + optimalResolution.getHeight()); - surfaceTexture.setDefaultBufferSize(optimalResolution.getWidth(), optimalResolution.getHeight()); - this.surfaceEncoder = new Surface(surfaceTexture); - this.fps = fps; - prepared = true; - } - - public void prepareCamera(SurfaceTexture surfaceTexture, int width, int height, int fps, Facing facing) { - this.facing = facing; - prepareCamera(surfaceTexture, width, height, fps); - } - - public void prepareCamera(SurfaceTexture surfaceTexture, int width, int height, int fps, String cameraId) { - this.facing = getFacingByCameraId(cameraManager, cameraId); - prepareCamera(surfaceTexture, width, height, fps); - } - - public boolean isPrepared() { - return prepared; - } - - private void startPreview(CameraDevice cameraDevice) { - try { - final List listSurfaces = new ArrayList<>(); - Surface preview = addPreviewSurface(); - if (preview != null) listSurfaces.add(preview); - if (surfaceEncoder != preview && surfaceEncoder != null) listSurfaces.add(surfaceEncoder); - if (imageReader != null) listSurfaces.add(imageReader.getSurface()); - 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 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> fpsRanges = getSupportedFps(null, Facing.BACK); - if (fpsRanges != null && fpsRanges.size() > 0) { - Range closestRange = fpsRanges.get(0); - int measure = Math.abs(closestRange.getLower() - expectedFps) + Math.abs( - closestRange.getUpper() - expectedFps); - for (Range 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> getSupportedFps(Size size, Facing facing) { - try { - CameraCharacteristics characteristics = null; - try { - characteristics = getCharacteristicsForFacing(cameraManager, facing); - } catch (CameraAccessException ignored) { } - if (characteristics == null) return null; - Range[] fpsSupported = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); - if (size != null) { - StreamConfigurationMap streamConfigurationMap = - characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - List> list = new ArrayList<>(); - long fd = streamConfigurationMap.getOutputMinFrameDuration(SurfaceTexture.class, size); - int maxFPS = (int)(10f / Float.parseFloat("0." + fd)); - for (Range 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]; - } - } - - public Size[] getCameraResolutions(String cameraId) { - try { - CameraCharacteristics characteristics = getCharacteristicsForId(cameraManager, cameraId); - 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 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 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 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 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 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 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 supportedExposure = - characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); - if (supportedExposure != null) { - return supportedExposure.getLower(); - } - return 0; - } - - public boolean tapToFocus(MotionEvent event) { - boolean result = false; - CameraCharacteristics characteristics = getCameraCharacteristics(); - if (characteristics == null) return false; - 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); - if (x < 100 || y < 100) return false; - - 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); - autoFocusEnabled = true; - result = true; - } catch (Exception e) { - Log.e(TAG, "Error", e); - } - } - return result; - } - - /** - * 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: - */ - 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: - */ - 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 boolean enableAutoFocus() { - boolean result = false; - CameraCharacteristics characteristics = getCameraCharacteristics(); - if (characteristics == null) return false; - int[] supportedFocusModes = - characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); - if (supportedFocusModes != null) { - List 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; - } - } - result = autoFocusEnabled; - } catch (Exception e) { - Log.e(TAG, "Error", e); - } - } - } - return result; - } - - public boolean disableAutoFocus() { - boolean result = false; - CameraCharacteristics characteristics = getCameraCharacteristics(); - if (characteristics == null) return false; - 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 true; - } - } catch (Exception e) { - Log.e(TAG, "Error", e); - } - } - } - } - return result; - } - - 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 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 && faces != null) { - faceDetectorCallback.onGetFaces(UtilsKt.mapCamera2Faces(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 getZoomRange() { - CameraCharacteristics characteristics = getCameraCharacteristics(); - if (characteristics == null) return new Range<>(1f, 1f); - Range 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 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; - } - - public void addImageListener(int width, int height, int format, int maxImages, boolean autoClose, ImageCallback listener) { - boolean wasRunning = running; - closeCamera(false); - if (wasRunning) closeCamera(false); - if (imageReader != null) removeImageListener(); - HandlerThread imageThread = new HandlerThread(TAG + " imageThread"); - imageThread.start(); - imageReader = ImageReader.newInstance(width, height, format, maxImages); - imageReader.setOnImageAvailableListener(reader -> { - Image image = reader.acquireLatestImage(); - if (image != null) { - listener.onImageAvailable(image); - if (autoClose) image.close(); - } - }, new Handler(imageThread.getLooper())); - if (wasRunning) { - if (textureView != null) { - prepareCamera(textureView, surfaceEncoder, fps); - } else if (surfaceView != null) { - prepareCamera(surfaceView, surfaceEncoder, fps); - } else { - prepareCamera(surfaceEncoder, fps); - } - openLastCamera(); - } - } - - public void removeImageListener() { - boolean wasRunning = running; - if (wasRunning) closeCamera(false); - if (imageReader != null) { - imageReader.close(); - imageReader = null; - } - if (wasRunning) { - if (textureView != null) { - prepareCamera(textureView, surfaceEncoder, fps); - } else if (surfaceView != null) { - prepareCamera(surfaceView, surfaceEncoder, fps); - } else { - prepareCamera(surfaceEncoder, fps); - } - openLastCamera(); - } - } - - @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; - } - - private Facing getFacingByCameraId(CameraManager cameraManager, String cameraId) { - try { - for (String id : cameraManager.getCameraIdList()) { - if (id.equals(cameraId)) { - Integer cameraFacing = cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.LENS_FACING); - if (cameraFacing == CameraMetadata.LENS_FACING_BACK) return Facing.BACK; - else return Facing.FRONT; - } - } - return Facing.BACK; - } catch (CameraAccessException e) { - return Facing.BACK; - } - } - - @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 getCharacteristicsForId(cameraManager, cameraId); - } - - @Nullable - private CameraCharacteristics getCharacteristicsForId(CameraManager cameraManager, - String cameraId) throws CameraAccessException { - 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; - } -} \ No newline at end of file diff --git a/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.kt b/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.kt new file mode 100644 index 000000000..e7832059c --- /dev/null +++ b/encoder/src/main/java/com/pedro/encoder/input/video/Camera2ApiManager.kt @@ -0,0 +1,886 @@ +/* + * Copyright (C) 2024 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.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.MeteringRectangle +import android.hardware.camera2.params.OutputConfiguration +import android.hardware.camera2.params.SessionConfiguration +import android.media.Image +import android.media.ImageReader +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 androidx.annotation.RequiresApi +import com.pedro.common.secureGet +import com.pedro.encoder.input.video.Camera2ResolutionCalculator.getOptimalResolution +import com.pedro.encoder.input.video.CameraHelper.Facing +import com.pedro.encoder.input.video.facedetector.FaceDetectorCallback +import com.pedro.encoder.input.video.facedetector.mapCamera2Faces +import java.util.concurrent.Executors +import java.util.concurrent.Semaphore +import kotlin.math.abs +import kotlin.math.max + +/** + * Created by pedro on 4/03/17. + * + * + * + * 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. + * + * + * 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) +class Camera2ApiManager(context: Context) : CameraDevice.StateCallback() { + private val TAG = "Camera2ApiManager" + + private var cameraDevice: CameraDevice? = null + private var surfaceEncoder: Surface? = null //input surfaceEncoder from videoEncoder + private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager + private var cameraHandler: Handler? = null + private var cameraCaptureSession: CameraCaptureSession? = null + var isPrepared: Boolean = false + private set + private var cameraId: String = "0" + private var facing = Facing.BACK + private var builderInputSurface: CaptureRequest.Builder? = null + private var fingerSpacing = 0f + private var zoomLevel = 0f + var isLanternEnabled: Boolean = false + private set + var isVideoStabilizationEnabled: Boolean = false + private set + var isOpticalStabilizationEnabled: Boolean = false + private set + var isAutoFocusEnabled: Boolean = true + private set + var isRunning: Boolean = false + private set + private var fps = 30 + private val semaphore = Semaphore(0) + private var cameraCallbacks: CameraCallbacks? = null + + interface ImageCallback { + fun onImageAvailable(image: Image) + } + + private var sensorOrientation = 0 + private var faceSensorScale: Rect? = null + private var faceDetectorCallback: FaceDetectorCallback? = null + private var faceDetectionEnabled = false + private var faceDetectionMode = 0 + private var imageReader: ImageReader? = null + + init { + cameraId = try { getCameraIdForFacing(Facing.BACK) } catch (e: Exception) { "0" } + } + + fun prepareCamera(surface: Surface?, fps: Int) { + this.surfaceEncoder = surface + this.fps = fps + isPrepared = true + } + + fun prepareCamera(surfaceTexture: SurfaceTexture, width: Int, height: Int, fps: Int) { + val optimalResolution = getOptimalResolution(Size(width, height), getCameraResolutions(facing)) + Log.i(TAG, "optimal resolution set to: " + optimalResolution.width + "x" + optimalResolution.height) + surfaceTexture.setDefaultBufferSize(optimalResolution.width, optimalResolution.height) + this.surfaceEncoder = Surface(surfaceTexture) + this.fps = fps + isPrepared = true + } + + fun prepareCamera(surfaceTexture: SurfaceTexture, width: Int, height: Int, fps: Int, facing: Facing) { + this.facing = facing + prepareCamera(surfaceTexture, width, height, fps) + } + + fun prepareCamera(surfaceTexture: SurfaceTexture, width: Int, height: Int, fps: Int, cameraId: String) { + this.facing = getFacingByCameraId(cameraManager, cameraId) + prepareCamera(surfaceTexture, width, height, fps) + } + + private fun startPreview(cameraDevice: CameraDevice) { + try { + val listSurfaces = mutableListOf() + surfaceEncoder?.let { listSurfaces.add(it) } + imageReader?.let { listSurfaces.add(it.surface) } + val captureRequest = drawSurface(cameraDevice, listSurfaces) + createCaptureSession( + cameraDevice, + listSurfaces, + onConfigured = { + cameraCaptureSession = it + try { + it.setRepeatingRequest( + captureRequest, + if (faceDetectionEnabled) cb else null, cameraHandler + ) + } catch (e: IllegalStateException) { + reOpenCamera(cameraId) + } catch (e: Exception) { + cameraCallbacks?.onCameraError("Create capture session failed: " + e.message) + Log.e(TAG, "Error", e) + } + }, + onConfiguredFailed = { + it.close() + cameraCallbacks?.onCameraError("Configuration failed") + Log.e(TAG, "Configuration failed") + }, + cameraHandler + ) + } catch (e: IllegalStateException) { + reOpenCamera(cameraId) + } catch (e: Exception) { + cameraCallbacks?.onCameraError("Create capture session failed: " + e.message) + Log.e(TAG, "Error", e) + } + } + + @Throws(IllegalStateException::class, Exception::class) + private fun drawSurface(cameraDevice: CameraDevice, surfaces: List): CaptureRequest { + val builderInputSurface = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + for (surface in surfaces) builderInputSurface.addTarget(surface) + builderInputSurface.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO) + adaptFpsRange(fps, builderInputSurface) + this.builderInputSurface = builderInputSurface + return builderInputSurface.build() + } + + private fun adaptFpsRange(expectedFps: Int, builderInputSurface: CaptureRequest.Builder) { + val fpsRanges = getSupportedFps(null, Facing.BACK) + if (fpsRanges.isNotEmpty()) { + var closestRange = fpsRanges[0] + var measure = (abs((closestRange.lower - expectedFps).toDouble()) + abs( + (closestRange.upper - expectedFps).toDouble() + )).toInt() + for (range in fpsRanges) { + if (CameraHelper.discardCamera2Fps(range, facing)) continue + if (range.lower <= expectedFps && range.upper >= expectedFps) { + val curMeasure = abs((((range.lower + range.upper) / 2) - expectedFps).toDouble()).toInt() + if (curMeasure < measure) { + closestRange = range + measure = curMeasure + } else if (curMeasure == measure) { + if (abs((range.upper - expectedFps).toDouble()) < abs((closestRange.upper - expectedFps).toDouble())) { + closestRange = range + measure = curMeasure + } + } + } + } + Log.i(TAG, "fps: " + closestRange.lower + " - " + closestRange.upper) + builderInputSurface.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, closestRange) + } + } + + fun getSupportedFps(size: Size?, facing: Facing): List> { + try { + val characteristics = cameraManager.getCameraCharacteristics(getCameraIdForFacing(facing)) + val fpsSupported = characteristics.secureGet(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES) ?: return emptyList() + val streamConfigurationMap = characteristics.secureGet(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: return emptyList() + val fd = streamConfigurationMap.getOutputMinFrameDuration(SurfaceTexture::class.java, size) + val maxFPS = (10f / "0.$fd".toFloat()).toInt() + return fpsSupported.filter { it.upper <= maxFPS } + } catch (e: Exception) { + return emptyList() + } + } + + val levelSupported: Int + get() { + val characteristics = cameraCharacteristics ?: return -1 + return characteristics.secureGet(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ?: -1 + } + + fun openCamera() { + openCameraBack() + } + + fun openCameraBack() { + openCameraFacing(Facing.BACK) + } + + fun openCameraFront() { + openCameraFacing(Facing.FRONT) + } + + fun openLastCamera() { + openCameraId(cameraId) + } + + fun setCameraId(cameraId: String) { + this.cameraId = cameraId + } + + var cameraFacing: Facing + get() = facing + set(cameraFacing) { + try { + val cameraId = getCameraIdForFacing(cameraFacing) + facing = cameraFacing + this.cameraId = cameraId + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + val cameraResolutionsBack: Array + get() = getCameraResolutions(Facing.BACK) + + val cameraResolutionsFront: Array + get() = getCameraResolutions(Facing.FRONT) + + fun getCameraResolutions(facing: Facing): Array = getCameraResolutions(getCameraIdForFacing(facing)) + + fun getCameraResolutions(cameraId: String): Array { + try { + val characteristics = cameraManager.getCameraCharacteristics(cameraId) + val streamConfigurationMap = characteristics.secureGet(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: return arrayOf() + val outputSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java) + return outputSizes ?: arrayOf() + } catch (e: Exception) { + Log.e(TAG, "Error", e) + return arrayOf() + } + } + + val cameraCharacteristics: CameraCharacteristics? + get() { + try { + return cameraManager.getCameraCharacteristics(cameraId) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + return null + } + } + + fun enableVideoStabilization(): Boolean { + val characteristics = cameraCharacteristics ?: return false + val builderInputSurface = this.builderInputSurface ?: return false + val modes = characteristics.secureGet(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ?: return false + if (!modes.contains(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON)) return false + builderInputSurface.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON) + isVideoStabilizationEnabled = true + return isVideoStabilizationEnabled + } + + fun disableVideoStabilization() { + val characteristics = cameraCharacteristics ?: return + val builderInputSurface = this.builderInputSurface ?: return + val modes = characteristics.secureGet(CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ?: return + if (!modes.contains(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON)) return + builderInputSurface.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF) + isVideoStabilizationEnabled = false + } + + fun enableOpticalVideoStabilization(): Boolean { + val characteristics = cameraCharacteristics ?: return false + val builderInputSurface = this.builderInputSurface ?: return false + val opticalStabilizationModes = characteristics.secureGet(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION) ?: return false + if (!opticalStabilizationModes.contains(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON)) return false + builderInputSurface.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON) + isOpticalStabilizationEnabled = true + return isOpticalStabilizationEnabled + } + + fun disableOpticalVideoStabilization() { + val characteristics = cameraCharacteristics ?: return + val builderInputSurface = this.builderInputSurface ?: return + val modes = characteristics.secureGet(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION) ?: return + if (!modes.contains(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON)) return + builderInputSurface.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_OFF) + isOpticalStabilizationEnabled = false + } + + fun setFocusDistance(distance: Float) { + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + try { + builderInputSurface.set(CaptureRequest.LENS_FOCUS_DISTANCE, max(0f, distance)) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + var exposure: Int + get() { + val builderInputSurface = this.builderInputSurface ?: return 0 + return builderInputSurface.secureGet(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION) ?: 0 + } + set(value) { + val characteristics = cameraCharacteristics ?: return + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + val supportedExposure = characteristics.secureGet(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) ?: return + val v = value.coerceIn(supportedExposure.lower, supportedExposure.upper) + try { + builderInputSurface.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, v) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + + val maxExposure: Int + get() { + val characteristics = cameraCharacteristics ?: return 0 + val supportedExposure = characteristics.secureGet(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)?.upper ?: 0 + return supportedExposure + } + + val minExposure: Int + get() { + val characteristics = cameraCharacteristics ?: return 0 + val supportedExposure = characteristics.secureGet(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)?.lower ?: 0 + return supportedExposure + } + + fun tapToFocus(event: MotionEvent): Boolean { + val builderInputSurface = this.builderInputSurface ?: return false + val cameraCaptureSession = this.cameraCaptureSession ?: return false + var result = false + val pointerId = event.getPointerId(0) + val pointerIndex = event.findPointerIndex(pointerId) + // Get the pointer's current position + val x = event.getX(pointerIndex) + val y = event.getY(pointerIndex) + if (x < 100 || y < 100) return false + + val touchRect = Rect( + (x - 100).toInt(), (y - 100).toInt(), + (x + 100).toInt(), (y + 100).toInt() + ) + val focusArea = MeteringRectangle(touchRect, MeteringRectangle.METERING_WEIGHT_DONT_CARE) + 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(), + if (faceDetectionEnabled) cb else null, null + ) + builderInputSurface.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(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(), + if (faceDetectionEnabled) cb else null, null + ) + isAutoFocusEnabled = true + result = true + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + return result + } + + /** + * Select camera facing + * + * @param selectedCameraFacing - CameraCharacteristics.LENS_FACING_FRONT, + * CameraCharacteristics.LENS_FACING_BACK, + * CameraCharacteristics.LENS_FACING_EXTERNAL + */ + fun openCameraFacing(selectedCameraFacing: Facing) { + try { + val cameraId = getCameraIdForFacing(selectedCameraFacing) + openCameraId(cameraId) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + val isLanternSupported: Boolean + get() { + val characteristics = cameraCharacteristics ?: return false + val available = characteristics.secureGet(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: return false + return available + } + + /** + * @required: + */ + @Throws(Exception::class) + fun enableLantern() { + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + if (isLanternSupported) { + try { + builderInputSurface.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + isLanternEnabled = true + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } else { + Log.e(TAG, "Lantern unsupported") + throw Exception("Lantern unsupported") + } + } + + /** + * @required: + */ + fun disableLantern() { + val characteristics = cameraCharacteristics ?: return + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + val available = characteristics.secureGet(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: return + if (available) { + try { + builderInputSurface.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + isLanternEnabled = false + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + } + + fun enableAutoFocus(): Boolean { + var result = false + val characteristics = cameraCharacteristics ?: return false + val supportedFocusModes = characteristics.secureGet(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES) ?: return false + val builderInputSurface = this.builderInputSurface ?: return false + val cameraCaptureSession = this.cameraCaptureSession ?: return false + + try { + if (supportedFocusModes.isNotEmpty()) { + //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(), + if (faceDetectionEnabled) cb else null, null + ) + if (supportedFocusModes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { + builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) + isAutoFocusEnabled = true + } else if (supportedFocusModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO)) { + builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO) + isAutoFocusEnabled = true + } else { + builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, supportedFocusModes[0]) + isAutoFocusEnabled = false + } + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + } + result = isAutoFocusEnabled + } catch (e: Exception) { + isAutoFocusEnabled = false + Log.e(TAG, "Error", e) + } + return result + } + + fun disableAutoFocus(): Boolean { + val result = false + val characteristics = cameraCharacteristics ?: return false + val builderInputSurface = this.builderInputSurface ?: return false + val cameraCaptureSession = this.cameraCaptureSession ?: return false + val supportedFocusModes = characteristics.secureGet(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES) ?: return false + for (mode in supportedFocusModes) { + try { + if (mode == CaptureRequest.CONTROL_AF_MODE_OFF) { + builderInputSurface.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + isAutoFocusEnabled = false + return true + } + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + return result + } + + fun enableFaceDetection(faceDetectorCallback: FaceDetectorCallback?): Boolean { + val characteristics = cameraCharacteristics ?: return false + val builderInputSurface = this.builderInputSurface ?: return false + faceSensorScale = characteristics.secureGet(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE) + sensorOrientation = characteristics.secureGet(CameraCharacteristics.SENSOR_ORIENTATION) ?: return false + val fd = characteristics.secureGet(CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES) ?: return false + val maxFD = characteristics.secureGet(CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT) ?: return false + if (fd.isEmpty() || maxFD <= 0) return false + this.faceDetectorCallback = faceDetectorCallback + faceDetectionEnabled = true + faceDetectionMode = fd.toList().max() + if (faceDetectionEnabled) { + builderInputSurface.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, faceDetectionMode) + } + prepareFaceDetectionCallback() + return true + } + + fun disableFaceDetection() { + if (faceDetectionEnabled) { + faceDetectorCallback = null + faceDetectionEnabled = false + faceDetectionMode = 0 + prepareFaceDetectionCallback() + } + } + + fun isFaceDetectionEnabled() = faceDetectorCallback != null + + fun setCameraCallbacks(cameraCallbacks: CameraCallbacks?) { + this.cameraCallbacks = cameraCallbacks + } + + private fun prepareFaceDetectionCallback() { + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + try { + cameraCaptureSession.stopRepeating() + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + private val cb: CameraCaptureSession.CaptureCallback = object : CameraCaptureSession.CaptureCallback() { + override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { + val faces = result.get(CaptureResult.STATISTICS_FACES) ?: return + faceDetectorCallback?.onGetFaces(mapCamera2Faces(faces), faceSensorScale, sensorOrientation) + } + } + + @SuppressLint("MissingPermission") + fun openCameraId(cameraId: String) { + this.cameraId = cameraId + if (isPrepared) { + val cameraHandlerThread = HandlerThread("$TAG Id = $cameraId") + cameraHandlerThread.start() + cameraHandler = Handler(cameraHandlerThread.looper) + try { + cameraManager.openCamera(cameraId, this, cameraHandler) + semaphore.acquireUninterruptibly() + val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) + isRunning = true + val facing = cameraCharacteristics.secureGet(CameraCharacteristics.LENS_FACING) ?: return + this.facing = if (CameraMetadata.LENS_FACING_FRONT == facing) Facing.FRONT else Facing.BACK + cameraCallbacks?.onCameraChanged(this.facing) + } catch (e: Exception) { + cameraCallbacks?.onCameraError("Open camera $cameraId failed") + Log.e(TAG, "Error", e) + } + } else { + Log.e(TAG, "Camera2ApiManager need be prepared, Camera2ApiManager not enabled") + } + } + + val camerasAvailable: Array = cameraManager.cameraIdList + + fun switchCamera() { + try { + val cameraId = if (cameraDevice == null || facing == Facing.FRONT) { + getCameraIdForFacing(Facing.BACK) + } else { + getCameraIdForFacing(Facing.FRONT) + } + reOpenCamera(cameraId) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + fun reOpenCamera(cameraId: String) { + if (cameraDevice != null) { + closeCamera(false) + prepareCamera(surfaceEncoder, fps) + openCameraId(cameraId) + } + } + + val zoomRange: Range + get() { + val characteristics = cameraCharacteristics ?: return Range(1f, 1f) + var zoomRanges: Range? = null + //only camera limited or better support this feature. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && levelSupported != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + zoomRanges = characteristics.secureGet(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE) + } + if (zoomRanges == null) { + val maxZoom = characteristics.secureGet(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 1f + zoomRanges = Range(1f, maxZoom) + } + return zoomRanges + } + + var zoom: Float + get() = zoomLevel + set(level) { + val characteristics = cameraCharacteristics ?: return + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + val l = level.coerceIn(zoomRange.lower, zoomRange.upper) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && levelSupported != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) { + builderInputSurface.set(CaptureRequest.CONTROL_ZOOM_RATIO, l) + } else { + val rect = characteristics.secureGet(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE) ?: return + //This ratio is the ratio of cropped Rect to Camera's original(Maximum) Rect + val ratio = 1f / l + //croppedWidth and croppedHeight are the pixels cropped away, not pixels after cropped + val croppedWidth = rect.width() - Math.round(rect.width().toFloat() * ratio) + val croppedHeight = rect.height() - Math.round(rect.height().toFloat() * ratio) + //Finally, zoom represents the zoomed visible area + val zoom = Rect( + croppedWidth / 2, croppedHeight / 2, rect.width() - croppedWidth / 2, + rect.height() - croppedHeight / 2 + ) + builderInputSurface.set(CaptureRequest.SCALER_CROP_REGION, zoom) + } + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + zoomLevel = l + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + fun getOpticalZooms(): Array { + val characteristics = cameraCharacteristics ?: return arrayOf() + return characteristics.secureGet(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.toTypedArray() ?: arrayOf() + } + + fun setOpticalZoom(level: Float) { + val builderInputSurface = this.builderInputSurface ?: return + val cameraCaptureSession = this.cameraCaptureSession ?: return + try { + builderInputSurface.set(CaptureRequest.LENS_FOCAL_LENGTH, level) + cameraCaptureSession.setRepeatingRequest( + builderInputSurface.build(), + if (faceDetectionEnabled) cb else null, null + ) + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + fun setZoom(event: MotionEvent) { + val currentFingerSpacing: Float + if (event.pointerCount > 1) { + currentFingerSpacing = CameraHelper.getFingerSpacing(event) + val delta = 0.1f + if (fingerSpacing != 0f) { + var newLevel = zoomLevel + if (currentFingerSpacing > fingerSpacing) { + newLevel += delta + } else if (currentFingerSpacing < fingerSpacing) { + newLevel -= delta + } + //This method avoid out of range + zoomLevel = newLevel + } + fingerSpacing = currentFingerSpacing + } + } + + fun stopRepeatingEncoder() { + val cameraCaptureSession = this.cameraCaptureSession ?: return + try { + cameraCaptureSession.stopRepeating() + surfaceEncoder = null + } catch (e: Exception) { + Log.e(TAG, "Error", e) + } + } + + @JvmOverloads + fun closeCamera(resetSurface: Boolean = true) { + isLanternEnabled = false + zoomLevel = 1.0f + cameraCaptureSession?.close() + cameraCaptureSession = null + cameraDevice?.close() + cameraDevice = null + cameraHandler?.looper?.quitSafely() + cameraHandler = null + if (resetSurface) { + surfaceEncoder = null + builderInputSurface = null + } + isPrepared = false + isRunning = false + } + + fun addImageListener(width: Int, height: Int, format: Int, maxImages: Int, autoClose: Boolean, listener: ImageCallback) { + val wasRunning = isRunning + closeCamera(false) + if (wasRunning) closeCamera(false) + removeImageListener() + val imageThread = HandlerThread("$TAG imageThread") + imageThread.start() + val imageReader = ImageReader.newInstance(width, height, format, maxImages) + imageReader.setOnImageAvailableListener({ reader: ImageReader -> + val image = reader.acquireLatestImage() + if (image != null) { + listener.onImageAvailable(image) + if (autoClose) image.close() + } + }, Handler(imageThread.looper)) + if (wasRunning) { + prepareCamera(surfaceEncoder, fps) + openLastCamera() + } + this.imageReader = imageReader + } + + fun removeImageListener() { + val imageReader = this.imageReader ?: return + val wasRunning = isRunning + if (wasRunning) closeCamera(false) + imageReader.close() + if (wasRunning) { + prepareCamera(surfaceEncoder, fps) + openLastCamera() + } + this.imageReader = null + } + + override fun onOpened(cameraDevice: CameraDevice) { + this.cameraDevice = cameraDevice + startPreview(cameraDevice) + semaphore.release() + cameraCallbacks?.onCameraOpened() + Log.i(TAG, "Camera opened") + } + + override fun onDisconnected(cameraDevice: CameraDevice) { + cameraDevice.close() + semaphore.release() + cameraCallbacks?.onCameraDisconnected() + Log.i(TAG, "Camera disconnected") + } + + override fun onError(cameraDevice: CameraDevice, i: Int) { + cameraDevice.close() + semaphore.release() + cameraCallbacks?.onCameraError("Open camera failed: $i") + Log.e(TAG, "Open failed: $i") + } + + @JvmOverloads + fun getCameraIdForFacing(facing: Facing, cameraManager: CameraManager = this.cameraManager): String { + val selectedFacing = if (facing == Facing.BACK) CameraMetadata.LENS_FACING_BACK else CameraMetadata.LENS_FACING_FRONT + val ids = cameraManager.cameraIdList + for (cameraId in ids) { + val cameraFacing = cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.LENS_FACING) + if (cameraFacing != null && cameraFacing == selectedFacing) { + return cameraId + } + } + if (ids.isEmpty()) throw CameraOpenException("Camera no detected") + return ids[0] + } + + private fun getFacingByCameraId(cameraManager: CameraManager, cameraId: String): Facing { + try { + for (id in cameraManager.cameraIdList) { + if (id == cameraId) { + val cameraFacing = cameraManager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.LENS_FACING) + return if (cameraFacing == CameraMetadata.LENS_FACING_BACK) Facing.BACK + else Facing.FRONT + } + } + return Facing.BACK + } catch (e: Exception) { + return Facing.BACK + } + } + + @Suppress("DEPRECATION") + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun createCaptureSession( + cameraDevice: CameraDevice, + surfaces: List, + onConfigured: (CameraCaptureSession) -> Unit, + onConfiguredFailed: (CameraCaptureSession) -> Unit, + handler: Handler? + ) { + val callback = object: CameraCaptureSession.StateCallback() { + override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { + onConfigured(cameraCaptureSession) + } + + override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) { + onConfiguredFailed(cameraCaptureSession) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val config = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + surfaces.map { OutputConfiguration(it) }, + Executors.newSingleThreadExecutor(), + callback + ) + cameraDevice.createCaptureSession(config) + } else { + cameraDevice.createCaptureSession(surfaces, callback, handler) + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/pedro/library/base/Camera2Base.java b/library/src/main/java/com/pedro/library/base/Camera2Base.java index 5534a85c7..087b8ad77 100644 --- a/library/src/main/java/com/pedro/library/base/Camera2Base.java +++ b/library/src/main/java/com/pedro/library/base/Camera2Base.java @@ -27,9 +27,6 @@ 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; @@ -90,8 +87,6 @@ public abstract class Camera2Base { private MicrophoneManager microphoneManager; private AudioEncoder audioEncoder; private boolean streaming = false; - private SurfaceView surfaceView; - private TextureView textureView; private GlInterface glInterface; protected boolean audioInitialized = false; private boolean onPreview = false; @@ -100,41 +95,15 @@ public abstract class Camera2Base { private int previewWidth, previewHeight; private final FpsListener fpsListener = new FpsListener(); - /** - * @deprecated This view produce rotations problems and could be unsupported in future versions. - * Use {@link Camera2Base#Camera2Base(OpenGlView)} - * instead. - */ - @Deprecated - public Camera2Base(SurfaceView surfaceView) { - this.surfaceView = surfaceView; - this.context = surfaceView.getContext(); - init(context); - } - - /** - * @deprecated This view produce rotations problems and could be unsupported in future versions. - * Use {@link Camera2Base#Camera2Base(OpenGlView)} - * instead. - */ - @Deprecated - public Camera2Base(TextureView textureView) { - this.textureView = textureView; - this.context = textureView.getContext(); - init(context); - } - public Camera2Base(OpenGlView openGlView) { context = openGlView.getContext(); glInterface = openGlView; init(context); } - public Camera2Base(Context context, boolean useOpengl) { + public Camera2Base(Context context) { this.context = context; - if (useOpengl) { - glInterface = new GlStreamInterface(context); - } + glInterface = new GlStreamInterface(context); isBackground = true; init(context); } @@ -311,14 +280,12 @@ public void setFocusDistance(float distance) { */ public boolean prepareVideo(int width, int height, int fps, int bitrate, int iFrameInterval, int rotation, int profile, int level) { - if (onPreview && glInterface != null && (width != previewWidth || height != previewHeight + if (onPreview && (width != previewWidth || height != previewHeight || fps != videoEncoder.getFps() || rotation != videoEncoder.getRotation())) { stopPreview(); } - boolean result = videoEncoder.prepareVideoEncoder(width, height, fps, bitrate, rotation, + return videoEncoder.prepareVideoEncoder(width, height, fps, bitrate, rotation, iFrameInterval, FormatVideoEncoder.SURFACE, profile, level); - prepareCameraManager(); - return result; } public boolean prepareVideo(int width, int height, int fps, int bitrate, int iFrameInterval, @@ -469,25 +436,23 @@ public void replaceView(OpenGlView openGlView) { * OpenGl. */ private void replaceGlInterface(GlInterface glInterface) { - if (this.glInterface != null && Build.VERSION.SDK_INT >= 18) { - if (isStreaming() || isRecording() || isOnPreview()) { - Point size = this.glInterface.getEncoderSize(); - cameraManager.closeCamera(); - this.glInterface.removeMediaCodecSurface(); - this.glInterface.stop(); - this.glInterface = glInterface; - int w = size.x; - int h = size.y; - int rotation = videoEncoder.getRotation(); - if (rotation == 90 || rotation == 270) { - h = size.x; - w = size.y; - } - prepareGlView(w, h, rotation); - cameraManager.openLastCamera(); - } else { - this.glInterface = glInterface; + if (isStreaming() || isRecording() || isOnPreview()) { + Point size = this.glInterface.getEncoderSize(); + cameraManager.closeCamera(); + this.glInterface.removeMediaCodecSurface(); + this.glInterface.stop(); + this.glInterface = glInterface; + int w = size.x; + int h = size.y; + int rotation = videoEncoder.getRotation(); + if (rotation == 90 || rotation == 270) { + h = size.x; + w = size.y; } + prepareGlView(w, h, rotation); + cameraManager.openLastCamera(); + } else { + this.glInterface = glInterface; } } @@ -517,14 +482,7 @@ public void startPreview(String cameraId, int width, int height, int fps, int ro previewHeight = height; videoEncoder.setFps(fps); videoEncoder.setRotation(rotation); - if (surfaceView != null) { - cameraManager.prepareCamera(surfaceView.getHolder().getSurface(), videoEncoder.getFps()); - } else if (textureView != null) { - cameraManager.prepareCamera(new Surface(textureView.getSurfaceTexture()), - videoEncoder.getFps()); - } else if (glInterface != null) { - prepareGlView(width, height, rotation); - } + prepareGlView(width, height, rotation); cameraManager.openCameraId(cameraId); onPreview = true; } else if (!isStreaming() && !onPreview && isBackground) { @@ -589,9 +547,7 @@ public void stopPreview() { */ public void stopCamera() { if (onPreview) { - if (glInterface != null) { - glInterface.stop(); - } + glInterface.stop(); cameraManager.closeCamera(); onPreview = false; previewWidth = 0; @@ -650,28 +606,26 @@ public void requestKeyFrame() { } private void prepareGlView(int width, int height, int rotation) { - if (glInterface != null) { - int w = width; - int h = height; - boolean isPortrait = false; - if (rotation == 90 || rotation == 270) { - h = width; - w = height; - isPortrait = true; - } - glInterface.setEncoderSize(w, h); - if (glInterface instanceof GlStreamInterface glStreamInterface) { - glStreamInterface.setPreviewResolution(w, h); - glStreamInterface.setIsPortrait(isPortrait); - } - glInterface.setRotation(rotation == 0 ? 270 : rotation - 90); - if (!glInterface.isRunning()) glInterface.start(); - if (videoEncoder.getInputSurface() != null && videoEncoder.isRunning()) { - glInterface.addMediaCodecSurface(videoEncoder.getInputSurface()); - } - cameraManager.prepareCamera(glInterface.getSurfaceTexture(), videoEncoder.getWidth(), - videoEncoder.getHeight(), videoEncoder.getFps()); + int w = width; + int h = height; + boolean isPortrait = false; + if (rotation == 90 || rotation == 270) { + h = width; + w = height; + isPortrait = true; + } + glInterface.setEncoderSize(w, h); + if (glInterface instanceof GlStreamInterface glStreamInterface) { + glStreamInterface.setPreviewResolution(w, h); + glStreamInterface.setIsPortrait(isPortrait); } + glInterface.setRotation(rotation == 0 ? 270 : rotation - 90); + if (!glInterface.isRunning()) glInterface.start(); + if (videoEncoder.getInputSurface() != null && videoEncoder.isRunning()) { + glInterface.addMediaCodecSurface(videoEncoder.getInputSurface()); + } + cameraManager.prepareCamera(glInterface.getSurfaceTexture(), videoEncoder.getWidth(), + videoEncoder.getHeight(), videoEncoder.getFps()); } protected abstract void stopStreamImp(); @@ -687,19 +641,10 @@ public void stopStream() { if (!recordController.isRecording()) { onPreview = !isBackground; if (audioInitialized) microphoneManager.stop(); - if (glInterface != null) { - glInterface.removeMediaCodecSurface(); - if (glInterface instanceof GlStreamInterface) { - glInterface.stop(); - cameraManager.closeCamera(); - } - } else { - if (isBackground) { - cameraManager.closeCamera(); - onPreview = false; - } else { - cameraManager.stopRepeatingEncoder(); - } + glInterface.removeMediaCodecSurface(); + if (glInterface instanceof GlStreamInterface) { + glInterface.stop(); + cameraManager.closeCamera(); } videoEncoder.stop(); if (audioInitialized) audioEncoder.stop(); @@ -827,7 +772,7 @@ public void setZoom(MotionEvent event) { * @Experimental * @return optical zoom values available */ - public float[] getOpticalZooms() { + public Float[] getOpticalZooms() { return cameraManager.getOpticalZooms(); } @@ -909,23 +854,7 @@ public boolean tapToFocus(MotionEvent event) { } public GlInterface getGlInterface() { - if (glInterface != null) { - return glInterface; - } else { - throw new RuntimeException("You can't do it. You are not using Opengl"); - } - } - - private void prepareCameraManager() { - if (textureView != null) { - cameraManager.prepareCamera(textureView, videoEncoder.getInputSurface(), - videoEncoder.getFps()); - } else if (surfaceView != null) { - cameraManager.prepareCamera(surfaceView, videoEncoder.getInputSurface(), - videoEncoder.getFps()); - } else if (glInterface == null) { - cameraManager.prepareCamera(videoEncoder.getInputSurface(), videoEncoder.getFps()); - } + return glInterface; } /** @@ -946,7 +875,7 @@ public void setVideoBitrateOnFly(int bitrate) { public void forceFpsLimit(boolean enabled) { int fps = enabled ? videoEncoder.getFps() : 0; videoEncoder.setForceFps(fps); - if (glInterface != null) glInterface.forceFpsLimit(fps); + glInterface.forceFpsLimit(fps); } /** diff --git a/library/src/main/java/com/pedro/library/generic/GenericCamera2.kt b/library/src/main/java/com/pedro/library/generic/GenericCamera2.kt index f8feb9c27..ce0e6d2b6 100644 --- a/library/src/main/java/com/pedro/library/generic/GenericCamera2.kt +++ b/library/src/main/java/com/pedro/library/generic/GenericCamera2.kt @@ -62,7 +62,7 @@ class GenericCamera2: Camera2Base { init(connectChecker) } - constructor(context: Context, useOpenGl: Boolean, connectChecker: ConnectChecker) : super(context, useOpenGl) { + constructor(context: Context, connectChecker: ConnectChecker) : super(context) { init(connectChecker) } diff --git a/library/src/main/java/com/pedro/library/multiple/MultiCamera2.kt b/library/src/main/java/com/pedro/library/multiple/MultiCamera2.kt index 7e56b7ac0..69253b514 100644 --- a/library/src/main/java/com/pedro/library/multiple/MultiCamera2.kt +++ b/library/src/main/java/com/pedro/library/multiple/MultiCamera2.kt @@ -97,12 +97,12 @@ class MultiCamera2 : Camera2Base { } constructor( - context: Context, useOpengl: Boolean, + context: Context, connectCheckerRtmpList: Array?, connectCheckerRtspList: Array?, connectCheckerSrtList: Array?, connectCheckerUdpList: Array? - ) : super(context, useOpengl) { + ) : super(context) { initialize(connectCheckerRtmpList, connectCheckerRtspList, connectCheckerSrtList, connectCheckerUdpList) } diff --git a/library/src/main/java/com/pedro/library/rtmp/RtmpCamera2.kt b/library/src/main/java/com/pedro/library/rtmp/RtmpCamera2.kt index 1cde2adc4..2ed5a7866 100644 --- a/library/src/main/java/com/pedro/library/rtmp/RtmpCamera2.kt +++ b/library/src/main/java/com/pedro/library/rtmp/RtmpCamera2.kt @@ -50,8 +50,7 @@ class RtmpCamera2 : Camera2Base { init(connectChecker) } - constructor(context: Context, useOpengl: Boolean, connectChecker: ConnectChecker): super( - context, useOpengl) { + constructor(context: Context, connectChecker: ConnectChecker): super(context) { init(connectChecker) } diff --git a/library/src/main/java/com/pedro/library/rtsp/RtspCamera2.kt b/library/src/main/java/com/pedro/library/rtsp/RtspCamera2.kt index 8a663c8ff..2fe74d104 100644 --- a/library/src/main/java/com/pedro/library/rtsp/RtspCamera2.kt +++ b/library/src/main/java/com/pedro/library/rtsp/RtspCamera2.kt @@ -50,8 +50,7 @@ class RtspCamera2 : Camera2Base { init(connectChecker) } - constructor(context: Context, useOpengl: Boolean, connectChecker: ConnectChecker): super( - context, useOpengl) { + constructor(context: Context, connectChecker: ConnectChecker): super(context) { init(connectChecker) } diff --git a/library/src/main/java/com/pedro/library/srt/SrtCamera2.kt b/library/src/main/java/com/pedro/library/srt/SrtCamera2.kt index b4f9cac3b..e0f76e3ec 100644 --- a/library/src/main/java/com/pedro/library/srt/SrtCamera2.kt +++ b/library/src/main/java/com/pedro/library/srt/SrtCamera2.kt @@ -50,8 +50,7 @@ class SrtCamera2 : Camera2Base { init(connectChecker) } - constructor(context: Context, useOpengl: Boolean, connectChecker: ConnectChecker): super( - context, useOpengl) { + constructor(context: Context, connectChecker: ConnectChecker): super(context) { init(connectChecker) } diff --git a/library/src/main/java/com/pedro/library/udp/UdpCamera2.kt b/library/src/main/java/com/pedro/library/udp/UdpCamera2.kt index c43a5de41..4fbc49b56 100644 --- a/library/src/main/java/com/pedro/library/udp/UdpCamera2.kt +++ b/library/src/main/java/com/pedro/library/udp/UdpCamera2.kt @@ -50,8 +50,7 @@ class UdpCamera2: Camera2Base { init(connectChecker) } - constructor(context: Context, useOpengl: Boolean, connectChecker: ConnectChecker): super( - context, useOpengl) { + constructor(context: Context, connectChecker: ConnectChecker): super(context) { init(connectChecker) } diff --git a/library/src/main/java/com/pedro/library/util/sources/video/Camera2Source.kt b/library/src/main/java/com/pedro/library/util/sources/video/Camera2Source.kt index 224e6d9ce..4125d8cc3 100644 --- a/library/src/main/java/com/pedro/library/util/sources/video/Camera2Source.kt +++ b/library/src/main/java/com/pedro/library/util/sources/video/Camera2Source.kt @@ -167,9 +167,9 @@ class Camera2Source(context: Context): VideoSource() { if (isRunning()) camera.disableFaceDetection() } - fun isFaceDetectionEnabled() = camera.isFaceDetectionEnabled + fun isFaceDetectionEnabled() = camera.isFaceDetectionEnabled() - fun camerasAvailable(): Array? = camera.camerasAvailable + fun camerasAvailable(): Array = camera.camerasAvailable fun openCameraId(id: String) { if (isRunning()) stop()