package com.flurgle.camerakit;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import static com.flurgle.camerakit.CameraKit.Constants.FLASH_OFF;
import static com.flurgle.camerakit.CameraKit.Constants.FOCUS_CONTINUOUS;
import static com.flurgle.camerakit.CameraKit.Constants.FOCUS_OFF;
import static com.flurgle.camerakit.CameraKit.Constants.FOCUS_TAP;
import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STANDARD;
import static com.flurgle.camerakit.CameraKit.Constants.METHOD_STILL;
@SuppressWarnings("deprecation")
public class Camera1 extends CameraImpl {
private static final int FOCUS_AREA_SIZE_DEFAULT = 300;
private static final int FOCUS_METERING_AREA_WEIGHT_DEFAULT = 1000;
private static final int DELAY_MILLIS_BEFORE_RESETTING_FOCUS = 3000;
private int mCameraId;
private Camera mCamera;
private Camera.Parameters mCameraParameters;
private Camera.CameraInfo mCameraInfo;
private Size mPreviewSize;
private Size mCaptureSize;
private MediaRecorder mMediaRecorder;
private File mVideoFile;
private Camera.AutoFocusCallback mAutofocusCallback;
private int mDisplayOrientation;
@Facing
private int mFacing;
@Flash
private int mFlash;
@Focus
private int mFocus;
@Method
private int mMethod;
@Zoom
private int mZoom;
@VideoQuality
private int mVideoQuality;
private Handler mHandler = new Handler();
Camera1(CameraListener callback, PreviewImpl preview) {
super(callback, preview);
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
if (mCamera != null) {
setupPreview();
adjustCameraParameters();
}
}
});
mCameraInfo = new Camera.CameraInfo();
}
// CameraImpl:
@Override
void start() {
setFacing(mFacing);
openCamera();
if (mPreview.isReady()) setupPreview();
mCamera.startPreview();
}
@Override
void stop() {
if (mCamera != null) mCamera.stopPreview();
mHandler.removeCallbacksAndMessages(null);
releaseCamera();
}
@Override
void setDisplayOrientation(int displayOrientation) {
this.mDisplayOrientation = displayOrientation;
}
@Override
void setFacing(@Facing int facing) {
int internalFacing = new ConstantMapper.Facing(facing).map();
if (internalFacing == -1) {
return;
}
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
Camera.getCameraInfo(i, mCameraInfo);
if (mCameraInfo.facing == internalFacing) {
mCameraId = i;
mFacing = facing;
break;
}
}
if (mFacing == facing && isCameraOpened()) {
stop();
start();
}
}
@Override
void setFlash(@Flash int flash) {
if (mCameraParameters != null) {
List<String> flashes = mCameraParameters.getSupportedFlashModes();
String internalFlash = new ConstantMapper.Flash(flash).map();
if (flashes != null && flashes.contains(internalFlash)) {
mCameraParameters.setFlashMode(internalFlash);
mFlash = flash;
} else {
String currentFlash = new ConstantMapper.Flash(mFlash).map();
if (flashes == null || !flashes.contains(currentFlash)) {
mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mFlash = FLASH_OFF;
}
}
mCamera.setParameters(mCameraParameters);
} else {
mFlash = flash;
}
}
@Override
void setFocus(@Focus int focus) {
this.mFocus = focus;
switch (focus) {
case FOCUS_CONTINUOUS:
if (mCameraParameters != null) {
detachFocusTapListener();
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else {
setFocus(FOCUS_OFF);
}
}
break;
case FOCUS_TAP:
if (mCameraParameters != null) {
attachFocusTapListener();
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
}
break;
case FOCUS_OFF:
if (mCameraParameters != null) {
detachFocusTapListener();
final List<String> modes = mCameraParameters.getSupportedFocusModes();
if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
} else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
} else {
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
}
break;
}
}
@Override
void setMethod(@Method int method) {
this.mMethod = method;
}
@Override
void setZoom(@Zoom int zoom) {
this.mZoom = zoom;
}
@Override
void setVideoQuality(int videoQuality) {
this.mVideoQuality = videoQuality;
}
@Override
void captureImage() {
switch (mMethod) {
case METHOD_STANDARD:
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
mCameraListener.onPictureTaken(data);
camera.startPreview();
}
});
break;
case METHOD_STILL:
mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
new Thread(new ProcessStillTask(data, camera, calculateCaptureRotation(), new ProcessStillTask.OnStillProcessedListener() {
@Override
public void onStillProcessed(final YuvImage yuv) {
mCameraListener.onPictureTaken(yuv);
}
})).start();
}
});
break;
}
}
@Override
void startVideo() {
initMediaRecorder();
prepareMediaRecorder();
mMediaRecorder.start();
}
@Override
void endVideo() {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
mCameraListener.onVideoTaken(mVideoFile);
}
@Override
Size getCaptureResolution() {
if (mCaptureSize == null && mCameraParameters != null) {
TreeSet<Size> sizes = new TreeSet<>();
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
sizes.add(new Size(size.width, size.height));
}
TreeSet<AspectRatio> aspectRatios = findCommonAspectRatios(
mCameraParameters.getSupportedPreviewSizes(),
mCameraParameters.getSupportedPictureSizes()
);
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> descendingSizes = sizes.descendingIterator();
Size size;
while (descendingSizes.hasNext() && mCaptureSize == null) {
size = descendingSizes.next();
if (targetRatio == null || targetRatio.matches(size)) {
mCaptureSize = size;
break;
}
}
}
return mCaptureSize;
}
@Override
Size getPreviewResolution() {
if (mPreviewSize == null && mCameraParameters != null) {
TreeSet<Size> sizes = new TreeSet<>();
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
sizes.add(new Size(size.width, size.height));
}
TreeSet<AspectRatio> aspectRatios = findCommonAspectRatios(
mCameraParameters.getSupportedPreviewSizes(),
mCameraParameters.getSupportedPictureSizes()
);
AspectRatio targetRatio = aspectRatios.size() > 0 ? aspectRatios.last() : null;
Iterator<Size> descendingSizes = sizes.descendingIterator();
Size size;
while (descendingSizes.hasNext() && mPreviewSize == null) {
size = descendingSizes.next();
if (targetRatio == null || targetRatio.matches(size)) {
mPreviewSize = size;
break;
}
}
}
return mPreviewSize;
}
@Override
boolean isCameraOpened() {
return mCamera != null;
}
// Internal:
private void openCamera() {
if (mCamera != null) {
releaseCamera();
}
mCamera = Camera.open(mCameraId);
mCameraParameters = mCamera.getParameters();
adjustCameraParameters();
mCamera.setDisplayOrientation(calculatePreviewRotation());
mCameraListener.onCameraOpened();
}
private void setupPreview() {
try {
if (mPreview.getOutputClass() == SurfaceHolder.class) {
mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
} else {
mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
mCameraParameters = null;
mPreviewSize = null;
mCaptureSize = null;
mCameraListener.onCameraClosed();
}
}
private int calculatePreviewRotation() {
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return ((mCameraInfo.orientation - mDisplayOrientation) + 360 + 180) % 360;
} else {
return (mCameraInfo.orientation - mDisplayOrientation + 360) % 360;
}
}
private int calculateCaptureRotation() {
int previewRotation = calculatePreviewRotation();
if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
//Front is flipped
return (previewRotation + 180 + 2*mDisplayOrientation + 720) %360;
} else {
return previewRotation;
}
}
private void adjustCameraParameters() {
boolean invertPreviewSizes = mDisplayOrientation%180 != 0;
mPreview.setTruePreviewSize(
invertPreviewSizes? getPreviewResolution().getHeight() : getPreviewResolution().getWidth(),
invertPreviewSizes? getPreviewResolution().getWidth() : getPreviewResolution().getHeight()
);
mCameraParameters.setPreviewSize(
getPreviewResolution().getWidth(),
getPreviewResolution().getHeight()
);
mCameraParameters.setPictureSize(
getCaptureResolution().getWidth(),
getCaptureResolution().getHeight()
);
int rotation = calculateCaptureRotation();
mCameraParameters.setRotation(rotation);
setFocus(mFocus);
setFlash(mFlash);
mCamera.setParameters(mCameraParameters);
}
private TreeSet<AspectRatio> findCommonAspectRatios(List<Camera.Size> previewSizes, List<Camera.Size> captureSizes) {
Set<AspectRatio> previewAspectRatios = new HashSet<>();
for (Camera.Size size : previewSizes) {
if (size.width >= CameraKit.Internal.screenHeight && size.height >= CameraKit.Internal.screenWidth) {
previewAspectRatios.add(AspectRatio.of(size.width, size.height));
}
}
Set<AspectRatio> captureAspectRatios = new HashSet<>();
for (Camera.Size size : captureSizes) {
captureAspectRatios.add(AspectRatio.of(size.width, size.height));
}
TreeSet<AspectRatio> output = new TreeSet<>();
for (AspectRatio aspectRatio : previewAspectRatios) {
if (captureAspectRatios.contains(aspectRatio)) {
output.add(aspectRatio);
}
}
return output;
}
private void initMediaRecorder() {
mMediaRecorder = new MediaRecorder();
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mMediaRecorder.setProfile(getCamcorderProfile(mVideoQuality));
mVideoFile = new File(mPreview.getView().getContext().getExternalFilesDir(null), "video.mp4");
mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
mMediaRecorder.setOrientationHint(mCameraInfo.orientation);
}
private void prepareMediaRecorder() {
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private CamcorderProfile getCamcorderProfile(@VideoQuality int videoQuality) {
CamcorderProfile camcorderProfile = null;
switch (videoQuality) {
case CameraKit.Constants.VIDEO_QUALITY_480P:
if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) {
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_480P);
} else {
return getCamcorderProfile(CameraKit.Constants.VIDEO_QUALITY_LOWEST);
}
break;
case CameraKit.Constants.VIDEO_QUALITY_720P:
if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) {
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_720P);
} else {
return getCamcorderProfile(CameraKit.Constants.VIDEO_QUALITY_480P);
}
break;
case CameraKit.Constants.VIDEO_QUALITY_1080P:
if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) {
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_1080P);
} else {
return getCamcorderProfile(CameraKit.Constants.VIDEO_QUALITY_720P);
}
break;
case CameraKit.Constants.VIDEO_QUALITY_2160P:
try {
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_2160P);
} catch (Exception e) {
return getCamcorderProfile(CameraKit.Constants.VIDEO_QUALITY_HIGHEST);
}
break;
case CameraKit.Constants.VIDEO_QUALITY_HIGHEST:
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH);
break;
case CameraKit.Constants.VIDEO_QUALITY_LOWEST:
camcorderProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_LOW);
break;
}
return camcorderProfile;
}
void setTapToAutofocusListener(Camera.AutoFocusCallback callback) {
if (this.mFocus != FOCUS_TAP) {
throw new IllegalArgumentException("Please set the camera to FOCUS_TAP.");
}
this.mAutofocusCallback = callback;
}
private int getFocusAreaSize() {
return FOCUS_AREA_SIZE_DEFAULT;
}
private int getFocusMeteringAreaWeight() {
return FOCUS_METERING_AREA_WEIGHT_DEFAULT;
}
private void detachFocusTapListener() {
mPreview.getView().setOnTouchListener(null);
}
private void attachFocusTapListener() {
mPreview.getView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (mCamera != null) {
Camera.Parameters parameters = mCamera.getParameters();
String focusMode = parameters.getFocusMode();
Rect rect = calculateFocusArea(event.getX(), event.getY());
List<Camera.Area> meteringAreas = new ArrayList<>();
meteringAreas.add(new Camera.Area(rect, getFocusMeteringAreaWeight()));
if (parameters.getMaxNumFocusAreas() != 0 && focusMode != null &&
(focusMode.equals(Camera.Parameters.FOCUS_MODE_AUTO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_MACRO) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) ||
focusMode.equals(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO))
) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
if (parameters.getMaxNumMeteringAreas() > 0) {
parameters.setMeteringAreas(meteringAreas);
}
if(!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return false; //cannot autoFocus
}
mCamera.setParameters(parameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
resetFocus(success, camera);
}
});
} else if (parameters.getMaxNumMeteringAreas() > 0) {
if(!parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
return false; //cannot autoFocus
}
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(meteringAreas);
parameters.setMeteringAreas(meteringAreas);
mCamera.setParameters(parameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
resetFocus(success, camera);
}
});
} else {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (mAutofocusCallback != null) {
mAutofocusCallback.onAutoFocus(success, camera);
}
}
});
}
}
}
return true;
}
});
}
private void resetFocus(final boolean success, final Camera camera) {
mHandler.removeCallbacksAndMessages(null);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (camera != null) {
camera.cancelAutoFocus();
Camera.Parameters params = camera.getParameters();
if (params.getFocusMode() != Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
params.setFocusAreas(null);
params.setMeteringAreas(null);
camera.setParameters(params);
}
if (mAutofocusCallback != null) {
mAutofocusCallback.onAutoFocus(success, camera);
}
}
}
}, DELAY_MILLIS_BEFORE_RESETTING_FOCUS);
}
private Rect calculateFocusArea(float x, float y) {
int centerX = clamp(Float.valueOf((x / mPreview.getView().getWidth()) * 2000 - 1000).intValue(), getFocusAreaSize());
int centerY = clamp(Float.valueOf((y / mPreview.getView().getHeight()) * 2000 - 1000).intValue(), getFocusAreaSize());
return new Rect(
centerX - getFocusAreaSize() / 2,
centerY - getFocusAreaSize() / 2,
centerX + getFocusAreaSize() / 2,
centerY + getFocusAreaSize() / 2
);
}
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;
}
}