/* * 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.content.Context; import android.content.res.Configuration; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.media.CamcorderProfile; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceView; import android.view.TextureView; import android.view.View; import com.pedro.encoder.Frame; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Created by pedro on 20/01/17. * * This class need use same resolution, fps and imageFormat that VideoEncoder * Tested with YV12 and NV21. *

* Advantage = you can control fps of the stream. * Disadvantages = you cant use all resolutions, only resolution that your camera support. *

* If you want use all resolutions. You can use libYuv for resize images in OnPreviewFrame: * https://chromium.googlesource.com/libyuv/libyuv/ */ public class Camera1ApiManager implements Camera.PreviewCallback, Camera.FaceDetectionListener { private String TAG = "Camera1ApiManager"; private Camera camera = null; private SurfaceView surfaceView; private TextureView textureView; private SurfaceTexture surfaceTexture; private GetCameraData getCameraData; private boolean running = false; private boolean lanternEnable = false; private boolean videoStabilizationEnable = false; private boolean autoFocusEnabled = false; private int cameraSelect; private CameraHelper.Facing facing = CameraHelper.Facing.BACK; private boolean isPortrait = false; private Context context; //default parameters for camera private int width = 640; private int height = 480; private int fps = 30; private int rotation = 0; private int imageFormat = ImageFormat.NV21; private byte[] yuvBuffer; private List previewSizeBack; private List previewSizeFront; private float distance; private CameraCallbacks cameraCallbacks; private final int focusAreaSize = 100; //Face detector public interface FaceDetectorCallback { void onGetFaces(Camera.Face[] faces, Rect scaleSensor, int sensorOrientation); } private final int sensorOrientation = 0; //Value obtained from Camera.Face documentation api about bounds private final Rect faceSensorScale = new Rect(-1000, -1000, 1000, 1000); private FaceDetectorCallback faceDetectorCallback; public Camera1ApiManager(SurfaceView surfaceView, GetCameraData getCameraData) { this.surfaceView = surfaceView; this.getCameraData = getCameraData; this.context = surfaceView.getContext(); init(); } public Camera1ApiManager(TextureView textureView, GetCameraData getCameraData) { this.textureView = textureView; this.getCameraData = getCameraData; this.context = textureView.getContext(); init(); } public Camera1ApiManager(SurfaceTexture surfaceTexture, Context context) { this.surfaceTexture = surfaceTexture; this.context = context; init(); } private void init() { cameraSelect = selectCameraBack(); previewSizeBack = getPreviewSize(); cameraSelect = selectCameraFront(); previewSizeFront = getPreviewSize(); } public void setRotation(int rotation) { this.rotation = rotation; } public void setSurfaceTexture(SurfaceTexture surfaceTexture) { this.surfaceTexture = surfaceTexture; } public int getWidth() { return width; } public int getHeight() { return height; } public void setCameraFacing(CameraHelper.Facing cameraFacing) { facing = cameraFacing; } public void setCameraSelect(int cameraFacing) { cameraSelect = cameraFacing; } public void start(CameraHelper.Facing cameraFacing, int width, int height, int fps) { int facing = cameraFacing == CameraHelper.Facing.BACK ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT; this.width = width; this.height = height; this.fps = fps; cameraSelect = facing == Camera.CameraInfo.CAMERA_FACING_BACK ? selectCameraBack() : selectCameraFront(); start(); } public void start(int facing, int width, int height, int fps) { this.width = width; this.height = height; this.fps = fps; cameraSelect = facing; selectCamera(facing); start(); } public void start(int width, int height, int fps) { start(cameraSelect, width, height, fps); } private void start() { if (!checkCanOpen()) { throw new CameraOpenException("This camera resolution cant be opened"); } yuvBuffer = new byte[width * height * 3 / 2]; try { camera = Camera.open(cameraSelect); Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraSelect, info); facing = info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? CameraHelper.Facing.FRONT : CameraHelper.Facing.BACK; isPortrait = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewSize(width, height); parameters.setPreviewFormat(imageFormat); int[] range = adaptFpsRange(fps, parameters.getSupportedPreviewFpsRange()); Log.i(TAG, "fps: " + range[0] + " - " + range[1]); parameters.setPreviewFpsRange(range[0], range[1]); List supportedFocusModes = parameters.getSupportedFocusModes(); if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) { if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); autoFocusEnabled = true; } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); autoFocusEnabled = true; } else { parameters.setFocusMode(supportedFocusModes.get(0)); autoFocusEnabled = false; } } camera.setParameters(parameters); camera.setDisplayOrientation(rotation); if (surfaceView != null) { camera.setPreviewDisplay(surfaceView.getHolder()); camera.addCallbackBuffer(yuvBuffer); camera.setPreviewCallbackWithBuffer(this); } else if (textureView != null) { camera.setPreviewTexture(textureView.getSurfaceTexture()); camera.addCallbackBuffer(yuvBuffer); camera.setPreviewCallbackWithBuffer(this); } else { camera.setPreviewTexture(surfaceTexture); } camera.startPreview(); running = true; if (cameraCallbacks != null) { cameraCallbacks.onCameraOpened(); cameraCallbacks.onCameraChanged(facing); } Log.i(TAG, width + "X" + height); } catch (IOException e) { if (cameraCallbacks != null) cameraCallbacks.onCameraError(e.getMessage()); Log.e(TAG, "Error", e); } } public void setPreviewOrientation(final int orientation) { this.rotation = orientation; if (camera != null && running) { camera.stopPreview(); camera.setDisplayOrientation(orientation); camera.startPreview(); } } public void setZoom(int level) { try { if (camera != null && running && camera.getParameters() != null && camera.getParameters() .isZoomSupported()) { android.hardware.Camera.Parameters params = camera.getParameters(); int maxZoom = params.getMaxZoom(); if (level > maxZoom) level = maxZoom; else if (level < getMinZoom()) level = getMinZoom(); params.setZoom(level); camera.setParameters(params); } } catch (Exception e) { Log.e(TAG, "Error", e); } } public int getZoom() { try { if (camera != null && running && camera.getParameters() != null && camera.getParameters() .isZoomSupported()) { android.hardware.Camera.Parameters params = camera.getParameters(); return params.getZoom(); } else { return getMinZoom(); } } catch (Exception e) { Log.e(TAG, "Error", e); return getMinZoom(); } } public int getMaxZoom() { try { if (camera != null && running && camera.getParameters() != null && camera.getParameters() .isZoomSupported()) { android.hardware.Camera.Parameters params = camera.getParameters(); return params.getMaxZoom(); } else { return getMinZoom(); } } catch (Exception e) { Log.e(TAG, "Error", e); return getMinZoom(); } } public int getMinZoom() { return 0; } public void setZoom(MotionEvent event) { try { if (camera != null && running && camera.getParameters() != null && camera.getParameters() .isZoomSupported()) { android.hardware.Camera.Parameters params = camera.getParameters(); int maxZoom = params.getMaxZoom(); int zoom = params.getZoom(); float newDist = CameraHelper.getFingerSpacing(event); if (newDist > distance) { if (zoom < maxZoom) { zoom++; } } else if (newDist < distance) { if (zoom > 0) { zoom--; } } distance = newDist; params.setZoom(zoom); camera.setParameters(params); } } catch (Exception e) { Log.e(TAG, "Error", e); } } public void setExposure(int value) { if (camera != null && camera.getParameters() != null) { android.hardware.Camera.Parameters params = camera.getParameters(); if (value > params.getMaxExposureCompensation()) value = params.getMaxExposureCompensation(); else if (value < params.getMinExposureCompensation()) value = params.getMinExposureCompensation(); params.setExposureCompensation(value); camera.setParameters(params); } } public int getExposure() { if (camera != null && camera.getParameters() != null) { android.hardware.Camera.Parameters params = camera.getParameters(); return params.getExposureCompensation(); } return 0; } public int getMaxExposure() { if (camera != null && camera.getParameters() != null) { android.hardware.Camera.Parameters params = camera.getParameters(); return params.getMaxExposureCompensation(); } return 0; } public int getMinExposure() { if (camera != null && camera.getParameters() != null) { android.hardware.Camera.Parameters params = camera.getParameters(); return params.getMinExposureCompensation(); } return 0; } private int selectCameraBack() { return selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK); } private int selectCameraFront() { return selectCamera(Camera.CameraInfo.CAMERA_FACING_FRONT); } private int selectCamera(int facing) { int number = Camera.getNumberOfCameras(); for (int i = 0; i < number; i++) { Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(i, info); if (info.facing == facing) return i; } return 0; } public void stop() { if (camera != null) { camera.stopPreview(); camera.setPreviewCallback(null); camera.setPreviewCallbackWithBuffer(null); camera.release(); camera = null; } running = false; } public boolean isRunning() { return running; } private int[] adaptFpsRange(int expectedFps, List fpsRanges) { expectedFps *= 1000; int[] closestRange = fpsRanges.get(0); int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps); for (int[] range : fpsRanges) { if (range[0] <= expectedFps && range[1] >= expectedFps) { int curMeasure = Math.abs(((range[0] + range[1]) / 2) - expectedFps); if (curMeasure < measure) { closestRange = range; measure = curMeasure; } else if (curMeasure == measure) { if (Math.abs(range[0] - expectedFps) < Math.abs(closestRange[1] - expectedFps)) { closestRange = range; measure = curMeasure; } } } } return closestRange; } @Override public void onPreviewFrame(byte[] data, Camera camera) { getCameraData.inputYUVData(new Frame(data, rotation, facing == CameraHelper.Facing.FRONT && isPortrait, imageFormat)); camera.addCallbackBuffer(yuvBuffer); } public Camera.Size getCameraSize(int width, int height) { if (camera != null) { return camera.new Size(width, height); } else { camera = Camera.open(cameraSelect); Camera.Size size = camera.new Size(width, height); camera.release(); camera = null; return size; } } /** * See: https://developer.android.com/reference/android/graphics/ImageFormat.html to know name of * constant values * Example: 842094169 -> YV12, 17 -> NV21 */ public List getCameraPreviewImageFormatSupported() { List formats; if (camera != null) { formats = camera.getParameters().getSupportedPreviewFormats(); for (Integer i : formats) { Log.i(TAG, "camera format supported: " + i); } } else { camera = Camera.open(cameraSelect); formats = camera.getParameters().getSupportedPreviewFormats(); camera.release(); camera = null; } return formats; } private List getPreviewSize() { List previewSizes; Camera.Size maxSize; if (camera != null) { maxSize = getMaxEncoderSizeSupported(); previewSizes = camera.getParameters().getSupportedPreviewSizes(); } else { camera = Camera.open(cameraSelect); maxSize = getMaxEncoderSizeSupported(); previewSizes = camera.getParameters().getSupportedPreviewSizes(); camera.release(); camera = null; } //discard preview more high than device can record Iterator iterator = previewSizes.iterator(); while (iterator.hasNext()) { Camera.Size size = iterator.next(); if (size.width > maxSize.width || size.height > maxSize.height) { Log.i(TAG, size.width + "X" + size.height + ", not supported for encoder"); iterator.remove(); } } return previewSizes; } public List getPreviewSizeBack() { return previewSizeBack; } public List getPreviewSizeFront() { return previewSizeFront; } /** * @return max size that device can record. */ private Camera.Size getMaxEncoderSizeSupported() { if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { return camera.new Size(3840, 2160); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) { return camera.new Size(1920, 1080); } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) { return camera.new Size(1280, 720); } else { return camera.new Size(640, 480); } } public CameraHelper.Facing getCameraFacing() { return facing; } public void switchCamera() throws CameraOpenException { if (camera != null) { int oldCamera = cameraSelect; int number = Camera.getNumberOfCameras(); for (int i = 0; i < number; i++) { if (cameraSelect != i) { cameraSelect = i; if (!checkCanOpen()) { cameraSelect = oldCamera; throw new CameraOpenException("This camera resolution cant be opened"); } stop(); start(); return; } } } } public void switchCamera(int cameraId) throws CameraOpenException { if (camera != null) { int oldCamera = cameraSelect; cameraSelect = cameraId; if (!checkCanOpen()) { cameraSelect = oldCamera; throw new CameraOpenException("This camera resolution cant be opened"); } stop(); start(); return; } } private boolean checkCanOpen() { List previews; if (cameraSelect == selectCameraBack()) { previews = previewSizeBack; } else { previews = previewSizeFront; } for (Camera.Size size : previews) { if (size.width == width && size.height == height) { return true; } } return false; } public boolean isLanternEnabled() { return lanternEnable; } /** * @required: */ public void enableLantern() throws Exception { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); List supportedFlashModes = parameters.getSupportedFlashModes(); if (supportedFlashModes != null && !supportedFlashModes.isEmpty()) { if (supportedFlashModes.contains(Camera.Parameters.FLASH_MODE_TORCH)) { parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); camera.setParameters(parameters); lanternEnable = true; } else { Log.e(TAG, "Lantern unsupported"); throw new Exception("Lantern unsupported"); } } } } public List getSupportedFps() { List supportedFps; if (camera != null) { supportedFps = camera.getParameters().getSupportedPreviewFpsRange(); } else { camera = Camera.open(cameraSelect); supportedFps = camera.getParameters().getSupportedPreviewFpsRange(); camera.release(); camera = null; } for (int[] range : supportedFps) { range[0] /= 1000; range[1] /= 1000; } return supportedFps; } /** * @required: */ public void disableLantern() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); camera.setParameters(parameters); lanternEnable = false; } } private Camera.AutoFocusCallback autoFocusTakePictureCallback = new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { if (success) { Log.i(TAG, "tapToFocus success"); } else { Log.e(TAG, "tapToFocus failed"); } } }; public void tapToFocus(View view, MotionEvent event) { if (camera != null && camera.getParameters() != null) { Camera.Parameters parameters = camera.getParameters(); if (parameters.getMaxNumMeteringAreas() > 0) { Rect rect = calculateFocusArea(event.getX(), event.getY(), view.getWidth(), view.getHeight()); parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); List meteringAreas = new ArrayList<>(); meteringAreas.add(new Camera.Area(rect, 800)); parameters.setFocusAreas(meteringAreas); try { camera.setParameters(parameters); }catch (Exception e) { Log.i(TAG, "tapToFocus error: " + e.getMessage()); } } camera.autoFocus(autoFocusTakePictureCallback); } } public void enableAutoFocus() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); List supportedFocusModes = parameters.getSupportedFocusModes(); if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) { if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); autoFocusEnabled = true; } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); autoFocusEnabled = true; } else { autoFocusEnabled = false; parameters.setFocusMode(supportedFocusModes.get(0)); } } camera.setParameters(parameters); } } public void disableAutoFocus() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); List supportedFocusModes = parameters.getSupportedFocusModes(); if (supportedFocusModes != null && !supportedFocusModes.isEmpty()) { if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); } else { parameters.setFocusMode(supportedFocusModes.get(0)); } } autoFocusEnabled = false; camera.setParameters(parameters); } } public boolean isAutoFocusEnabled() { return autoFocusEnabled; } public void enableRecordingHint() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); parameters.setRecordingHint(true); camera.setParameters(parameters); } } public void disableRecordingHint() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); parameters.setRecordingHint(false); camera.setParameters(parameters); } } public boolean enableFaceDetection(FaceDetectorCallback faceDetectorCallback) { try { if (camera != null) { this.faceDetectorCallback = faceDetectorCallback; camera.setFaceDetectionListener(this); camera.startFaceDetection(); return true; } else { Log.e(TAG, "face detection called with camera stopped"); return false; } } catch (IllegalArgumentException e) { Log.e(TAG, "face detection unsupported"); return false; } } public void disableFaceDetection() { if (camera != null) { faceDetectorCallback = null; camera.stopFaceDetection(); camera.setFaceDetectionListener(null); } } public boolean enableVideoStabilization() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); if (parameters.isVideoStabilizationSupported()) { parameters.setVideoStabilization(true); videoStabilizationEnable = true; } } return videoStabilizationEnable; } public void disableVideoStabilization() { if (camera != null) { Camera.Parameters parameters = camera.getParameters(); if (parameters.isVideoStabilizationSupported()) { parameters.setVideoStabilization(false); videoStabilizationEnable = false; } } } public boolean isVideoStabilizationEnabled() { return videoStabilizationEnable; } public void setCameraCallbacks(CameraCallbacks cameraCallbacks) { this.cameraCallbacks = cameraCallbacks; } public boolean isFaceDetectionEnabled() { return faceDetectorCallback != null; } @Override public void onFaceDetection(Camera.Face[] faces, Camera camera) { if (faceDetectorCallback != null) faceDetectorCallback.onGetFaces(faces, faceSensorScale, sensorOrientation); } private Rect calculateFocusArea(float x, float y, float previewWidth, float previewHeight) { int left = clamp((int) (x / previewWidth * 2000f - 1000f), focusAreaSize); int top = clamp((int) (y / previewHeight * 2000f - 1000f), focusAreaSize); return new Rect(left, top, left + focusAreaSize, top + focusAreaSize); } private int clamp(int touchCoordinateInCameraReper, int focusAreaSize) { int result; if (Math.abs(touchCoordinateInCameraReper) + focusAreaSize / 2 > 1000){ if (touchCoordinateInCameraReper > 0){ result = 1000 - focusAreaSize / 2; } else { result = -1000 + focusAreaSize / 2; } } else{ result = touchCoordinateInCameraReper - focusAreaSize / 2; } return result; } }