/*
* Copyright 2015 Constant Innovations Inc
*
* 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.constantinnovationsinc.livemultimedia.cameras;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import com.constantinnovationsinc.livemultimedia.callbacks.FramesReadyCallback;
import com.constantinnovationsinc.livemultimedia.callbacks.FrameCatcher;
import com.constantinnovationsinc.livemultimedia.previews.VideoPreview;
import com.constantinnovationsinc.livemultimedia.utilities.SharedVideoMemory;
import java.io.IOException;
import java.util.List;
/****************************************************************************
* The Class wraps around the Pre-Lollipop API for Android Devices
* Note I soon be adding a LollipopCamera classes for Camera2 API
* JellyBeanCamera works from Android version JellyBean and up to KitKat.
* Android 4.3 and 4.4
**************************************************************************/
public class JellyBeanCamera extends BaseCamera {
private static final String TAG = JellyBeanCamera.class.getCanonicalName();
// movie length, in frames
private static int mActiveCameraId = -1;
private int mFrameRate = 30;
public Boolean mYV12ColorFormatSupported = false;
public Boolean mNV21ColorFormatSupported = false;
@SuppressWarnings("deprecation")
public Camera mCamera = null;
public VideoPreview mVideoPreview = null;
public FrameCatcher mFrameCatcher = null;
/*********************************************************************
* Constructor
* @param context - the context associated with this encoding thread
*********************************************************************/
public JellyBeanCamera(Context context) {
super(context);
Log.d(TAG, "JellyBean constructor called!");
}
/*********************************************************************
* Constructor
* @param context - the context associated with this encoding thread
*********************************************************************/
public JellyBeanCamera(Context context, VideoPreview videoPreview) {
super(context);
Log.d(TAG, "JellyBean constructor called!");
mVideoPreview = videoPreview;
}
/*********************************************************************
* startBackCamera
* @return camera - active camera
* @exception IllegalStateException if camera is in wrong state
*********************************************************************/
@SuppressWarnings({"deprecation", "unused"})
public synchronized JellyBeanCamera startBackCamera() throws IllegalStateException{
Log.d(TAG, "startBackCamera()");
setParameters(ENCODING_WIDTH, ENCODING_HEIGHT, BITRATE);
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
setActiveCameraId(Camera.CameraInfo.CAMERA_FACING_BACK);
return this;
}
/*********************************************************************
* startFrontCamera
* @return camera - active camera
*********************************************************************/
@SuppressWarnings({"deprecation", "unused"})
public synchronized JellyBeanCamera startFrontCamera() throws IllegalStateException{
Log.d(TAG, "startFrontCamera()");
setParameters(ENCODING_WIDTH, ENCODING_HEIGHT, BITRATE);
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
setActiveCameraId(Camera.CameraInfo.CAMERA_FACING_FRONT);
return this;
}
/*********************************************************************
* getNumberOfCameras()
* @return int - number of cameras on the device
*********************************************************************/
@SuppressWarnings("deprecation")
public synchronized int getNumberOfCameras() {
// Try to find a front-facing camera (e.g. for videoconferencing).
return Camera.getNumberOfCameras();
}
/*********************************************************************
* isBackCamera()
* @return Boolean - Is the active selected camera the back camera
*********************************************************************/
@SuppressWarnings("deprecation")
public synchronized Boolean isBackCamera() {
Boolean flag = false;
if (getActiveCameraId() == -1)
return false;
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(getActiveCameraId(), info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
flag = true;
}
return flag;
}
/*********************************************************************
* isFrontCamera()
* @return Boolean - Is the active selected camera the front camera
*********************************************************************/
@SuppressWarnings("deprecation")
public synchronized Boolean isFrontCamera() {
Boolean flag = false;
if (getActiveCameraId() == -1)
return false;
Camera.CameraInfo info = new Camera.CameraInfo();
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
flag = true;
}
return flag;
}
/**********************************************************
* stopCamera - Stops the preview and the capture process
**********************************************************/
public synchronized void stopCamera() {
Log.d(TAG, "stopCamera()!");
if (mCamera == null) {
Log.e(TAG, "stopCamera() failed due to null camera");
throw new IllegalStateException("Camera object is Null in stopCamera");
}
mCamera.stopPreview();
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.release();
mCamera = null;
}
/*****************************************************************
* isNV21ColorFormat - IS the format NV21
* @return Boolean - Is it NV21 Colorformat in the preview window
*****************************************************************/
public synchronized Boolean isNV21ColorFormat() {
return mNV21ColorFormatSupported;
}
/*****************************************************************
* isYV12ColorFormat - Is the format NV21
* @return Boolean - Is it YV12 Colorformat in the preview window
*****************************************************************/
public synchronized Boolean isYV12ColorFormat() {
return mYV12ColorFormatSupported;
}
/*****************************************************************
* getImageFormat - What is the the imageFormat
* @return String - Get Image Format
*****************************************************************/
@SuppressWarnings("deprecation")
public synchronized String getImageFormat() {
String value = null;
if (mYV12ColorFormatSupported && mNV21ColorFormatSupported) {
value = "YV12";
} else {
value = "UNKNOWN";
}
return value;
}
/**********************************************************
* setupPreviewWindow encapsulates the current video
* frame capture method
**********************************************************/
@SuppressWarnings("deprecation")
public synchronized void setupPreviewWindow() throws IllegalStateException{
Log.d(TAG, "Camera setup the preview Window");
try {
if (mCamera == null) {
Log.e(TAG, "setupPreviewWindiow() failed due to null camera");
throw new IllegalStateException("Camera object is Null in setupPreviewWindow");
}
Camera.Parameters parameters = mCamera.getParameters();
adjustCameraBasedOnOrientation();
setRecordingHint(true, parameters);
// set the preview size
queryPreviewSettings(parameters);
if (mYV12ColorFormatSupported) {
mImageFormat = ImageFormat.YV12;
parameters.setPreviewFormat(ImageFormat.YV12);
} else if (mNV21ColorFormatSupported) {
mImageFormat = ImageFormat.NV21;
parameters.setPreviewFormat(ImageFormat.NV21);
}
adjustPreviewSize(parameters);
// lock the exposure ans white balance to get a constant preview frame rate
lockExposureAndWhiteBalance(parameters);
// set the frame rate and update the camera parameters
setPreviewFrameRate(parameters, mFrameRate);
mCamera.setParameters(parameters);
} catch (IllegalArgumentException | IllegalStateException e) {
Log.e(TAG, e.toString());
}
}
/**********************************************************
* setupVideoCaptureMethod encapsulates the current video
* frame capture method
**********************************************************/
public synchronized void setupVideoCaptureMethod() {
// set up the callback to capture the video frames
Log.d(TAG, "setupVideoCaptureMethod()");
setupVideoFrameCallback();
}
/*********************************************************************
* setPreviewTexture same as the camera pi but with error handling
* @param surface surfaceTexture of the preview window
* @exception IllegalArgumentException SurfaceTexture is null
* @exception IllegalStateException Camera object is null
* @exception IOException setPreviewTexture can cause this exception
********************************************************************/
public synchronized void setPreviewTexture( SurfaceTexture surface) throws IllegalArgumentException, IllegalStateException, IOException {
if (surface == null) {
throw new IllegalArgumentException("SurfaceTexture is Null in setPreviewTexture()");
}
if (mCamera == null) {
throw new IllegalStateException("Camera object is Null in setPreviewTexture()");
}
// this call throw an IOException
mCamera.setPreviewTexture(surface);
}
/*********************************************************************
* onFramesAvialable is called when this surface receives a new frame
* @param surfaceTexture of the preview window
********************************************************************/
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.w(TAG, "Receiving frames from Texture!");
}
/**********************************************************
* startVideoPreview( start the preview so you cna begin capturing frames
* @return the preview started or not
* @exception IllegalStateException is camera is null
**********************************************************/
@SuppressWarnings("all")
public synchronized Boolean startVideoPreview() throws IllegalStateException{
Log.d(TAG, "startVideoPreview()");
if (mCamera == null ) {
throw new IllegalStateException("Null Camera object in startVideoPreview");
}
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(ENCODING_WIDTH, ENCODING_HEIGHT);
mCamera.setParameters(parameters);
mCamera.startPreview();
return true;
}
/*******************************************************************
* release stops the preview and the video capture and then safety
* releases the camera
******************************************************************/
public synchronized void release() {
Log.d(TAG, "release() called on the Camera class");
if (mCamera != null) {
mCamera.addCallbackBuffer(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if (mFrameCatcher != null) {
mFrameCatcher.setRecordingState(false);
getSharedMemFile().clearMemory();
mFrameCatcher = null;
}
}
/********************************************************************
* adjustCameraBasedOnOrientation() ensure the view is in landscape
*******************************************************************/
public synchronized void adjustCameraBasedOnOrientation() {
if (getContext() == null)
return;
if (getContext().getResources() == null)
return;
if (getContext().getResources().getConfiguration() == null)
return;
if (mCamera == null)
throw new IllegalStateException("Camera object is Null in adjustCameraBasedOnOrientation");
if (getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
mCamera.setDisplayOrientation(270);
}
}
/************************************************************
* ensure the view is in landscape
* @deprecated
************************************************************/
@SuppressWarnings("deprecation")
private synchronized static Camera.Size getBestPreviewSize(List<Camera.Size> previewSizes, int width, int height) {
Camera.Size result = null;
if (width == 0 || height == 0) {
Log.e(TAG, "Width or Height of preview surface is zero!");
return null;
}
for (Camera.Size size : previewSizes) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
}
} else if (result != null) {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
return result;
}
/***********************************************************
* Sets the desired frame size and bit rate.
* @param width width
* @param height height
* @param bitRate the bitRate
**********************************************************/
private synchronized void setParameters(int width, int height, int bitRate) {
if ((width % 16) != 0 || (height % 16) != 0) {
Log.w(TAG, "WARNING: width or height not multiple of 16");
}
mEncodingWidth = width;
mEncodingHeight = height;
mBitRate = bitRate;
}
/**********************************************************
* capture video frame one by one from the preview window
* setup the buffer to hold the images
**********************************************************/
private synchronized void setupVideoFrameCallback() {
Log.d(TAG, "setupVideoFrameCallback(() called on the Camera class");
if (mCamera == null) {
Log.e(TAG, "Camera object is null in setupVideoFrameCallback!");
return;
}
mFrameCatcher = new FrameCatcher( mPreviewWidth,
mPreviewHeight,
getImageFormat(),
mVideoPreview);
long bufferSize;
bufferSize = mPreviewWidth * mPreviewHeight * ImageFormat.getBitsPerPixel(mImageFormat) / 8;
long sizeWeShouldHave = (mPreviewWidth * mPreviewHeight * 3 / 2);
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.setPreviewCallbackWithBuffer( mFrameCatcher );
for (int i = 0; i < NUM_CAMERA_PREVIEW_BUFFERS; i++) {
byte [] cameraBuffer = new byte[(int)bufferSize];
mCamera.addCallbackBuffer(cameraBuffer);
}
}
/******************************************************************************************
* The preview window can supprt different image formats depending on the camera make
* Almost all support NV21 and JPEG
* @param parameters preview window parms
****************************************************************************************/
@SuppressWarnings("deprecation")
private synchronized void queryPreviewSettings(Camera.Parameters parameters) {
List<int[]> supportedFps = parameters.getSupportedPreviewFpsRange();
for (int[] item : supportedFps) {
Log.d(TAG, "Mix preview frame rate supported: " + item[ Camera.Parameters.PREVIEW_FPS_MIN_INDEX]/ 1000 );
Log.d(TAG, "Max preview frame rate supported: " + item[ Camera.Parameters.PREVIEW_FPS_MAX_INDEX]/ 1000 );
}
List<Integer> formats = parameters.getSupportedPreviewFormats();
for (Integer format : formats) {
if (format == null) {
Log.e(TAG, "This camera supports illegal format in preview");
break;
}
switch ( format.intValue() ) {
case ImageFormat.JPEG:
Log.d(TAG, "This camera supports JPEG format in preview");
break;
case ImageFormat.NV16:
Log.d(TAG, "This camera supports NV16 format in preview");
break;
case ImageFormat.NV21:
Log.d(TAG, "This camera supports NV21 format in preview");
mNV21ColorFormatSupported = true;
break;
case ImageFormat.RGB_565:
Log.d(TAG, "This camera supports RGB_5645 format in preview");
break;
case ImageFormat.YUV_420_888:
Log.d(TAG, "This camera supports YUV_420_888 format in preview");
break;
case ImageFormat.YUY2:
Log.d(TAG, "This camera supports YUY2 format in preview");
break;
case ImageFormat.YV12:
Log.d(TAG, "This camera supports YV12 format in preview");
mYV12ColorFormatSupported = true;
break;
case ImageFormat.UNKNOWN:
Log.e(TAG, "This camera supports UNKNOWN format in preview");
break;
default:
Log.e(TAG, "This camera supports illegal format in preview");
break;
}
}
}
/*******************************************************************
* Change this to the resolution you want to capture and encode to
* @param parameters camera preview settings
******************************************************************/
@SuppressWarnings("deprecation")
private synchronized void adjustPreviewSize(Camera.Parameters parameters) {
Log.d(TAG, "adjustPreviewSize(Camera.Parameters parameters)");
mPreviewWidth = parameters.getPreviewSize().width;
mPreviewHeight = parameters.getPictureSize().height;
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
for (Camera.Size size : sizes) {
Log.d(TAG , "Preview sizes supported by this camera is: " + size.width + "x" + size.height);
}
Camera.Size bestSize = getBestPreviewSize(sizes, (int)mPreviewWidth, (int)mPreviewHeight);
if (bestSize != null){
mPreviewWidth = bestSize.width;
mPreviewHeight = bestSize.height;
Log.w(TAG, "Recommend size of the preview window is: " + mPreviewWidth + "," + mPreviewHeight);
} else {
//force it to the hardcoded value
mPreviewWidth = mEncodingWidth;
mPreviewHeight = mEncodingHeight;
Log.d(TAG, "New preview size is: " + mPreviewWidth + "x" + mPreviewHeight);
}
parameters.setPreviewSize((int) mPreviewWidth, (int) mPreviewHeight);
}
@SuppressWarnings("deprecation")
public synchronized void lockExposureAndWhiteBalance(Camera.Parameters parameters) {
// try to lock the camera settings to get the frame rate we want
if (parameters.isAutoExposureLockSupported()) {
parameters.setAutoExposureLock(true);
}
if (parameters.isAutoWhiteBalanceLockSupported()) {
parameters.setAutoWhiteBalanceLock(true);
}
}
/*****************************************************************************************************
* Make sure the preview capture rate is consistent by locking the exposure and white balance rate
* @param parameters camera paramaters
* @param frameRate teh frameRate
****************************************************************************************************/
@SuppressWarnings("deprecation")
public synchronized void setPreviewFrameRate(Camera.Parameters parameters, int frameRate) {
int actualMin = frameRate * 1000;
List<int[]> range = parameters.getSupportedPreviewFpsRange();
parameters.setPreviewFpsRange(actualMin, actualMin); // for 30 fps
}
/*****************************************************************************************************
* getPreviewSizeWidth() the preview window width size
* @return float - widthe
****************************************************************************************************/
public synchronized float getPreviewSizeWidth() {
return mPreviewWidth;
}
public synchronized float getPreviewSizeHeight() {
return mPreviewHeight;
}
/*****************************************************************
* getSharedMemFile
* @return MemoryFile which contain all captured video frames
****************************************************************/
public synchronized SharedVideoMemory getSharedMemFile() throws IllegalStateException {
if (mFrameCatcher == null) {
throw new IllegalStateException( NULL_IN_GET_SHARED_MEM_FILE );
}
SharedVideoMemory shared = null;
if (mFrameCatcher.mRecording && mFrameCatcher.isSavingVideoFrames()) {
shared = mFrameCatcher.getSharedMemFile();
}
return shared;
}
public synchronized void setRecordingState(Boolean state) {
if (mFrameCatcher == null) {
throw new IllegalStateException( NULL_IN_SET_RECORDING_STATE );
}
mFrameCatcher.setRecordingState(state);
}
public synchronized Boolean getRecordingState() throws IllegalStateException {
if (mFrameCatcher == null) {
throw new IllegalStateException( NULL_IN_GET_RECORDING_STATE );
}
return mFrameCatcher.getRecordingState();
}
@SuppressWarnings("deprecation")
public synchronized Camera.Parameters getParameters() throws IllegalStateException{
if (mCamera == null) {
throw new IllegalStateException( NULL_IN_GET_PARAMETERS );
}
return mCamera.getParameters();
}
public synchronized int getActiveCameraId() {
return mActiveCameraId;
}
public synchronized int getBitRate() {
return mBitRate;
}
@SuppressWarnings("deprecation")
public synchronized void setActiveCameraId(int activeCam) {
if (activeCam == Camera.CameraInfo.CAMERA_FACING_BACK ||
activeCam == Camera.CameraInfo.CAMERA_FACING_FRONT) {
mActiveCameraId = activeCam;
} else {
Log.e(TAG, "Unknown Camera Id");
}
}
/*****************************************************************
* setRecordingHint just sets the recording hint and check for null
* @param value true or false
* @param parms the camera parms
* @throws IllegalArgumentException if camera parms is null
******************************************************************/
@SuppressWarnings("deprecation")
public synchronized void setRecordingHint(Boolean value, Camera.Parameters parms) throws IllegalArgumentException {
if (parms == null) {
throw new IllegalArgumentException( NULL_IN_SET_RECORD_HINT );
}
parms.setRecordingHint(value);
}
/*****************************************************************
* setOnFramesReadyCallback just sets up the preview window callback
* implementation approach for video frame capture.
* @param callback - the actual callback function
* @throws IllegalArgumentException, IllegalStateException
******************************************************************/
public synchronized void setOnFramesReadyCallBack(FramesReadyCallback callback) throws IllegalArgumentException, IllegalStateException {
if (callback == null) {
throw new IllegalArgumentException( ARGUMENT_NULL_IN_SET_ONFRAMES_READY_CALLBACK );
}
if (mFrameCatcher == null) {
throw new IllegalStateException( NULL_IN_SET_ONFRAMES_READY_CALLBACK );
}
mFrameCatcher.callback = callback;
}
}