Face Tracker app based on Google Vision Face Tracker. By default, Face Tracker use rear/back camera, but I want to detect faces with front camera.
This is the code for CameraSourcePreview that google vision provide:
package com.google.android.gms.samples.vision.face.facetracker.ui.camera;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import com.google.android.gms.common.images.Size;
import com.google.android.gms.vision.CameraSource;
import java.io.IOException;
public class CameraSourcePreview extends ViewGroup {
private static final String TAG = "CameraSourcePreview";
private Context mContext;
private SurfaceView mSurfaceView;
private boolean mStartRequested;
private boolean mSurfaceAvailable;
private CameraSource mCameraSource;
private GraphicOverlay mOverlay;
public CameraSourcePreview(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mStartRequested = false;
mSurfaceAvailable = false;
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
}
public void start(CameraSource cameraSource) throws IOException {
if (cameraSource == null) {
stop();
}
mCameraSource = cameraSource;
if (mCameraSource != null) {
mStartRequested = true;
startIfReady();
}
}
public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
mOverlay = overlay;
start(cameraSource);
}
public void stop() {
if (mCameraSource != null) {
mCameraSource.stop();
}
}
public void release() {
if (mCameraSource != null) {
mCameraSource.release();
mCameraSource = null;
}
}
private void startIfReady() throws IOException {
if (mStartRequested && mSurfaceAvailable) {
mCameraSource.start(mSurfaceView.getHolder());
if (mOverlay != null) {
Size size = mCameraSource.getPreviewSize();
int min = Math.min(size.getWidth(), size.getHeight());
int max = Math.max(size.getWidth(), size.getHeight());
if (isPortraitMode()) {
// Swap width and height sizes when in portrait, since it will be rotated by
// 90 degrees
mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
} else {
mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
}
mOverlay.clear();
}
mStartRequested = false;
}
}
private class SurfaceCallback implements SurfaceHolder.Callback {
#Override
public void surfaceCreated(SurfaceHolder surface) {
mSurfaceAvailable = true;
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surface) {
mSurfaceAvailable = false;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int width = 640;
int height = 480;
if (mCameraSource != null) {
Size size = mCameraSource.getPreviewSize();
if (size != null) {
width = size.getWidth();
height = size.getHeight();
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode()) {
int tmp = width;
width = height;
height = tmp;
}
final int layoutWidth = right - left;
final int layoutHeight = bottom - top;
// Computes height and width for potentially doing fit width.
int childWidth = layoutWidth;
int childHeight = (int)(((float) layoutWidth / (float) width) * height);
// If height is too tall using fit width, does fit height instead.
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int)(((float) layoutHeight / (float) height) * width);
}
for (int i = 0; i < getChildCount(); ++i) {
getChildAt(i).layout(0, 0, childWidth, childHeight);
}
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}
private boolean isPortraitMode() {
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return true;
}
Log.d(TAG, "isPortraitMode returning false by default");
return false;
}
}
I call camera source with this method:
private void startCameraSource() {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
Face Tracker front camera still too dark compare with default phone camera app.
How to brighten front camera in face tracker google vision? Is it related with surface view?
<com.google.android.gms.samples.vision.face.facetracker.ui.camera.CameraSourcePreview
android:id="#+id/preview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.00"
android:weightSum="1">
<com.google.android.gms.samples.vision.face.facetracker.ui.camera.GraphicOverlay
android:id="#+id/faceOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="0.79" />
</com.google.android.gms.samples.vision.face.facetracker.ui.camera.CameraSourcePreview>
It is not related with the SurfaceView at all. It is a Camera API misconfiguration. You will have to make some additional changes inside your CameraSource.java file.
You can find it on this GitHub repository
First, you need to know that it is an Exposure Problem. It refers to the light the camera allows to receive on the lens. You need to know if your Camera supports Exposure Compensation. You will have to query getMinExposureCompensation() and getMaxExposureCompensation() from your Camera.Parameters instance. As the documentation explains, if both methods return 0, exposure compensation is not supported and there's nothing you can do.
Luckily most of the time this characteristic is supported by all phones. Now you can check the current camera exposure by calling getExposureCompensation() which will return the default value (usually 0, which means exposure is not adjusted). Now to prevent dark images, you only have to set your new exposure between the min and max values using setExposureCompensation() and apply the Camera.Parameters to your camera.
Finally, you can Lock the Exposure to avoid losing the configuration using setAutoExposureLock(), getAutoExposureLock() and most important: before setting the Exposure Lock you must be sure that isAutoExposureLockSupported() returned true.
Good Luck!
Related
Im making a test app for a friend, on the Samsung S20.
The Samsung S20 has a ToF (Time of Flight) camera facing the back.
I will like to display the ToF image preview & regular camera preview on a TextureView side by side.
Im able to get the ToF sensor and convert its raw output to visual output using a color mask and display depth ranges visually (red farthest, oranges, etc..), see the screenshot:
Below is relevant code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="619dp"
android:background="#FFFFFFFF">
<TextureView
android:id="#+id/regularBackCamera"
android:layout_width="320dp"
android:layout_height="240dp"
android:layout_marginEnd="44dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.899" />
<TextView
android:id="#+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Raw ToF Data"
android:textColor="#android:color/primary_text_light"
app:layout_constraintEnd_toEndOf="#+id/rawData"
app:layout_constraintStart_toStartOf="#+id/rawData"
app:layout_constraintTop_toBottomOf="#+id/rawData" />
<TextureView
android:id="#+id/rawData"
android:layout_width="320dp"
android:layout_height="240dp"
android:layout_marginStart="44dp"
app:layout_constraintBottom_toTopOf="#+id/regularBackCamera"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.485" />
<TextView
android:id="#+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="120dp"
android:text="Back Camera"
android:textColor="#android:color/primary_text_light"
app:layout_constraintStart_toStartOf="#+id/regularBackCamera"
app:layout_constraintTop_toBottomOf="#+id/regularBackCamera" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
MainActivity class:
/* This is an example of getting and processing ToF data
*/
public class MainActivity extends AppCompatActivity implements DepthFrameVisualizer, RegularCameraFrameVisualizer {
private static final String TAG = MainActivity.class.getSimpleName();
public static final int CAM_PERMISSIONS_REQUEST = 0;
private TextureView rawDataView;
private TextureView regularImageView;
private Matrix ToFBitmapTransform;
private Matrix regularBackCameraBitmapTransform;
private BackToFCamera backToFCamera;
private RegularBackCamera regularBackCamera;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rawDataView = findViewById(R.id.rawData);
regularImageView = findViewById(R.id.regularBackCamera);
checkCamPermissions();
}
#Override
protected void onPause() {
super.onPause();
if ( backToFCamera !=null)
{
backToFCamera.getCamera().close();
backToFCamera = null;
}
if ( regularBackCamera!= null)
{
regularBackCamera.getCamera().close();
regularBackCamera = null;
}
}
#Override
protected void onResume() {
super.onResume();
backToFCamera = new BackToFCamera(this, this);
String tofCameraId = backToFCamera.openCam(null);
regularBackCamera = new RegularBackCamera(this, this);
//pass in tofCameraId to avoid opening again since both regular cam & ToF camera are back facing
regularBackCamera.openCam(tofCameraId);
}
#Override
protected void onDestroy() {
super.onDestroy(); // Add this line
}
private void checkCamPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAM_PERMISSIONS_REQUEST);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
#Override
public void onRawDataAvailable(Bitmap bitmap) {
renderBitmapForToFToTextureView(bitmap, rawDataView);
}
#Override
public void onRegularImageAvailable(Bitmap bitmap) {
renderBitmapToTextureView( bitmap,regularImageView);
}
/* We don't want a direct camera preview since we want to get the frames of data directly
from the camera and process.
This takes a converted bitmap and renders it onto the surface, with a basic rotation
applied.
*/
private void renderBitmapForToFToTextureView(Bitmap bitmap, TextureView textureView) {
if (bitmap!=null && textureView!=null) {
Canvas canvas = textureView.lockCanvas();
canvas.drawBitmap(bitmap, ToFBitmapTransform(textureView), null);
textureView.unlockCanvasAndPost(canvas);
}
}
private void renderBitmapToTextureView(Bitmap bitmap, TextureView textureView) {
if (bitmap!=null && textureView!=null)
{
Canvas canvas = textureView.lockCanvas();
if (canvas!=null) {
canvas.drawBitmap(bitmap, regularBackCamBitmapTransform(textureView), null);
textureView.unlockCanvasAndPost(canvas);
}
}
}
private Matrix ToFBitmapTransform(TextureView view) {
if (view!=null) {
if (ToFBitmapTransform == null || view.getWidth() == 0 || view.getHeight() == 0) {
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
int centerX = view.getWidth() / 2;
int centerY = view.getHeight() / 2;
int bufferWidth = DepthFrameAvailableListener.SAMSUNG_S20_TOF_WIDTH;
int bufferHeight = DepthFrameAvailableListener.SAMSUNG_S20_TOF_HEIGHT;
RectF bufferRect = new RectF(0, 0, bufferWidth, bufferHeight);
RectF viewRect = new RectF(0, 0, view.getWidth(), view.getHeight());
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.CENTER);
Log.i(TAG, " rotation:" + rotation);
if (Surface.ROTATION_90 == rotation) {
matrix.postRotate(270, centerX, centerY);
} else if (Surface.ROTATION_270 == rotation) {
matrix.postRotate(90, centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
} else {
//strange but works!
matrix.postRotate(90, centerX, centerY);
}
ToFBitmapTransform = matrix;
}
}
return ToFBitmapTransform;
}
private Matrix regularBackCamBitmapTransform(TextureView view) {
if (view!=null) {
if (regularBackCameraBitmapTransform == null || view.getWidth() == 0 || view.getHeight() == 0) {
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF bufferRect = new RectF(0, 0, MAX_PREVIEW_WIDTH,MAX_PREVIEW_HEIGHT);
RectF viewRect = new RectF(0, 0, view.getWidth(), view.getHeight());
matrix.setRectToRect(bufferRect, viewRect, Matrix.ScaleToFit.CENTER);
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
Log.i(TAG, " rotation:" + rotation);
if (Surface.ROTATION_90 == rotation) {
matrix.postRotate(270, centerX, centerY);
} else if (Surface.ROTATION_270 == rotation) {
matrix.postRotate(90, centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
} else {
//strange but works!
matrix.postRotate(90, centerX, centerY);
}
regularBackCameraBitmapTransform = matrix;
}
}
return regularBackCameraBitmapTransform;
}
}
Listener that signals a frame is available for display, look at the function publishOriginalBitmap():
import static com.example.opaltechaitestdepthmap.RegularBackCamera.MAX_PREVIEW_HEIGHT;
import static com.example.opaltechaitestdepthmap.RegularBackCamera.MAX_PREVIEW_WIDTH;
public class BackCameraFrameAvailableListener implements ImageReader.OnImageAvailableListener {
private static final String TAG = BackCameraFrameAvailableListener.class.getSimpleName();
private RegularCameraFrameVisualizer regularCameraFrameVisualizer;
public BackCameraFrameAvailableListener(RegularCameraFrameVisualizer regularCameraFrameVisualizer) {
this.regularCameraFrameVisualizer = regularCameraFrameVisualizer;
}
#Override
public void onImageAvailable(ImageReader reader) {
try {
Image image = reader.acquireNextImage();
if (image != null && image.getFormat() == ImageFormat.JPEG)
{
publishOriginalBitmap(image);
}
}
catch (Exception e) {
Log.e(TAG, "Failed to acquireNextImage: " + e.getMessage());
}
}
private void publishOriginalBitmap(final Image image) {
if (regularCameraFrameVisualizer != null) {
new Thread() {
public void run() {
Bitmap bitmap = returnBitmap(image);
if (bitmap != null) {
regularCameraFrameVisualizer.onRegularImageAvailable(bitmap);
bitmap.recycle();
}
}
}.start();
}
}
private Bitmap returnBitmap(Image image) {
Bitmap bitmap = null;
// width=1920,height=1080
int width =1920;
int height =1080;
if (image!=null) {
Log.i(TAG,"returnBitmap,CONSTANT MAX width:"+MAX_PREVIEW_WIDTH +",MAX height:"+MAX_PREVIEW_HEIGHT);
Log.i(TAG,"BEFORE returnBitmap,image.width:"+width +",height:"+height );
if (image!=null) {
Image.Plane[] planes = image.getPlanes();
if (planes!=null && planes.length>0) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
image.close();
Log.i(TAG,"buffer size:"+buffer.capacity());
float currenBufferSize = buffer.capacity();
float jpegReportedArea = width * height;
if (currenBufferSize >=jpegReportedArea ) {
Log.i(TAG,"currenBufferSize >=jpegReportedArea ");
float quotient = jpegReportedArea/currenBufferSize ;
float f_width = width * quotient;
width = (int) Math.ceil(f_width);
float f_height = height * quotient;
height = (int) Math.ceil(f_height);
}
else
{
Log.i(TAG,"currenBufferSize <jpegReportedArea ");
float quotient = currenBufferSize / jpegReportedArea;
float f_width = (width * quotient);
width = (int) Math.ceil(f_width);
float f_height = (height * quotient);
height = (int) Math.ceil(f_height);
}
Log.i(TAG,"AFTER width:"+width+",height:"+height);
//***here bitmap is black
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
buffer.rewind();
if (bitmap!=null) {
bitmap.copyPixelsFromBuffer(buffer);
}
}
}
}
return bitmap;
}
}
The interface used by the listener to signal image is ready:
package com.example.opaltechaitestdepthmap;
import android.graphics.Bitmap;
public interface RegularCameraFrameVisualizer {
void onRegularImageAvailable(Bitmap bitmap);
}
Handles camera states:
public class RegularBackCamera extends CameraDevice.StateCallback {
private static final String TAG = RegularBackCamera.class.getSimpleName();
private static int FPS_MIN = 15;
private static int FPS_MAX = 30;
public static final int MAX_PREVIEW_WIDTH = 1920;
public static final int MAX_PREVIEW_HEIGHT = 1080;
private Context context;
private CameraManager cameraManager;
private ImageReader RawSensorPreviewReader;
private CaptureRequest.Builder previewBuilder;
private BackCameraFrameAvailableListener imageAvailableListener;
private String cameraId;
private CameraDevice camera;
public RegularBackCamera(Context context, RegularCameraFrameVisualizer frameVisualizer) {
this.context = context;
cameraManager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
imageAvailableListener = new BackCameraFrameAvailableListener(frameVisualizer);
}
// Open the back camera and start sending frames
public String openCam(String idToExclude) {
this.cameraId = getBackCameraID(idToExclude);
Size size = openCamera(this.cameraId);
//Tried this DID NOT WORK Size smallerPreviewSize =chooseSmallerPreviewSize();
RawSensorPreviewReader = ImageReader.newInstance(MAX_PREVIEW_WIDTH,
MAX_PREVIEW_HEIGHT, ImageFormat.JPEG,2);
Log.i(TAG,"ImageFormat.JPEG, width:"+size.getWidth()+", height:"+ size.getHeight());
RawSensorPreviewReader.setOnImageAvailableListener(imageAvailableListener, null);
return this.cameraId;
}
private String getBackCameraID(String idToExclude) {
String cameraId = null;
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
try {
if (idToExclude!=null) {
for (String camera : cameraManager.getCameraIdList()) {
//avoid getting same camera
if (!camera.equalsIgnoreCase(idToExclude)) {
//avoid return same camera twice as 1 sensor can only be accessed once
CameraCharacteristics chars = cameraManager.getCameraCharacteristics(camera);
final int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
boolean facingBack = chars.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK;
if (facingBack) {
cameraId = camera;
// Note that the sensor size is much larger than the available capture size
SizeF sensorSize = chars.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
Log.i(TAG, "Sensor size: " + sensorSize);
// Since sensor size doesn't actually match capture size and because it is
// reporting an extremely wide aspect ratio, this FoV is bogus
float[] focalLengths = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
if (focalLengths.length > 0) {
float focalLength = focalLengths[0];
double fov = 2 * Math.atan(sensorSize.getWidth() / (2 * focalLength));
Log.i(TAG, "Calculated FoV: " + fov);
}
}
}//end avoid getting same camera
}//end for
}
else
{
for (String camera : cameraManager.getCameraIdList()) {
//avoid return same camera twice as 1 sensor can only be accessed once
CameraCharacteristics chars = cameraManager.getCameraCharacteristics(camera);
final int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
boolean facingFront = chars.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK;
if (facingFront) {
cameraId = camera;
// Note that the sensor size is much larger than the available capture size
SizeF sensorSize = chars.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
Log.i(TAG, "Sensor size: " + sensorSize);
// Since sensor size doesn't actually match capture size and because it is
// reporting an extremely wide aspect ratio, this FoV is bogus
float[] focalLengths = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
if (focalLengths.length > 0) {
float focalLength = focalLengths[0];
double fov = 2 * Math.atan(sensorSize.getWidth() / (2 * focalLength));
Log.i(TAG, "Calculated FoV: " + fov);
}
}
}//end for
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return cameraId ;
}
//opens camera based on ID & returns optimal size caped at maximum size based on docs
private Size openCamera(String cameraId) {
Size size = null;
try{
int permission = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
if(PackageManager.PERMISSION_GRANTED == permission) {
if ( cameraManager!=null) {
if (cameraId!=null) {
cameraManager.openCamera(cameraId, this, null);
CameraCharacteristics characteristics
= cameraManager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
size = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizeByArea());
if (size.getWidth() > MAX_PREVIEW_WIDTH || size.getHeight() > MAX_PREVIEW_HEIGHT)
{
size = new Size( MAX_PREVIEW_WIDTH ,MAX_PREVIEW_HEIGHT);
}
List<Size> sizes = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
for (int i=0; i<sizes.size(); i++)
{
Log.i(RegularBackCamera.class.toString(),"JPEG sizes, width="+sizes.get(i).getWidth()+","+"height="+sizes.get(i).getHeight());
}
}
}
}else{
Log.e(TAG,"Permission not available to open camera");
}
}catch (CameraAccessException | IllegalStateException | SecurityException e){
Log.e(TAG,"Opening Camera has an Exception " + e);
e.printStackTrace();
}
return size;
}
#Override
public void onOpened(#NonNull CameraDevice camera) {
try {
this.camera = camera;
previewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.set(CaptureRequest.JPEG_ORIENTATION, 0);
Range<Integer> fpsRange = new Range<>(FPS_MIN, FPS_MAX);
previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
previewBuilder.addTarget(RawSensorPreviewReader.getSurface());
List<Surface> targetSurfaces = Arrays.asList(RawSensorPreviewReader.getSurface());
camera.createCaptureSession(targetSurfaces,
new CameraCaptureSession.StateCallback() {
#Override
public void onConfigured(#NonNull CameraCaptureSession session) {
onCaptureSessionConfigured(session);
}
#Override
public void onConfigureFailed(#NonNull CameraCaptureSession session) {
Log.e(TAG,"!!! Creating Capture Session failed due to internal error ");
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void onCaptureSessionConfigured(#NonNull CameraCaptureSession session) {
Log.i(TAG,"Capture Session created");
previewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
session.setRepeatingRequest(previewBuilder.build(), null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
#Override
public void onDisconnected(#NonNull CameraDevice camera) {
if (camera!=null)
{
camera.close();
camera = null;
}
}
#Override
public void onError(#NonNull CameraDevice camera, int error) {
if (camera!=null)
{
camera.close();
Log.e(TAG,"onError,cameraID:"+camera.getId()+",error:"+error);
camera = null;
}
}
protected Size chooseSmallerPreviewSize()
{
CameraManager cm = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics cc = null;
try {
cc = cm.getCameraCharacteristics(this.cameraId);
} catch (CameraAccessException e) {
e.printStackTrace();
}
StreamConfigurationMap streamConfigs = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = streamConfigs.getOutputSizes( ImageFormat.JPEG);
Size smallerPreviewSize = chooseVideoSize( sizes);
return smallerPreviewSize;
}
//Rerefences: https://stackoverflow.com/questions/46997776/camera2-api-error-failed-to-create-capture-session
protected Size chooseVideoSize(Size[] choices) {
List<Size> smallEnough = new ArrayList<>();
for (Size size : choices) {
if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
smallEnough.add(size);
}
}
if (smallEnough.size() > 0) {
return Collections.max(smallEnough, new CompareSizeByArea());
}
return choices[choices.length - 1];
}
public CameraDevice getCamera() {
return camera;
}
}
Helper to sort preview sizes:
public class CompareSizeByArea implements Comparator<Size> {
#Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
I included the code for the regular camera only since the regular camera was not displaying, however the code for obtaining the ToF camera & listeners is the exactly the same except ToF specific logic.
I'm not seeing any exceptions or errors in the app logs, however the logs system show:
E/CHIUSECASE: [ERROR ] chxusecase.cpp:967 ReturnFrameworkResult() ChiFrame: 0 App Frame: 0 - pResult contains more buffers (1) than the expected number of buffers (0) to return to the framework!
E/CamX: [ERROR][CORE ] camxnode.cpp:4518 CSLFenceCallback() Node::FastAECRealtime_IFE0 : Type:65536 Fence 3 handler failed in node fence handler
E/CamX: [ERROR][SENSOR ] camxsensornode.cpp:9279 GetSensorMode() Sensor name: s5k2la
E/CamX: [ERROR][SENSOR ] camxsensornode.cpp:9302 GetSensorMode() W x H : 4032, 3024
E//vendor/bin/hw/vendor.samsung.hardware.camera.provider#3.0-service_64: vendor/qcom/proprietary/commonsys-intf/adsprpc/src/fastrpc_apps_user.c:750: Error 0xe08132b8: remote_handle_invoke failed
E/CamX: [ERROR][ISP ] camxispiqmodule.h:1871 IsTuningModeDataChanged() Invalid pointer to current tuning mode parameters (0x0)
E/CamX: [ERROR][PPROC ] camxipenode.cpp:9529 GetFaceROI() Face ROI is not published
**1) How can I display the regular back facing camera as a Bitmap on TextureView, correctly?
Save that bitmap as JPEG or PNG in internal drive**
Thanks a million!
If you want to actually covert an Image in JPEG format to a Bitmap, you can't just copy the bytes over, as you do with:
Log.i(TAG,"AFTER width:"+width+",height:"+height);
//***here bitmap is black
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
buffer.rewind();
if (bitmap!=null) {
bitmap.copyPixelsFromBuffer(buffer);
}
You need to actually decode the compressed JPEG, for example with BitmapFactory.decodeByteArray. That'll just produce a Bitmap from the Image contents, though you have to create a byte[] from the plane[0] ByteBuffer.
However, you really don't want to capture JPEGs here, those tend to be slow and won't get you very good frame rate. Unless you have a strong reason, just use the TextureView's SurfaceTexture as a target for the camera (by creating a Surface from the SurfaceTexture). That'll pass data in an efficient device-specific format, and you don't have to do any copying (still have to handle the scaling, though).
And if you need to modify the preview data before drawing, use the YUV_420_888 format, which is also efficient and will run at 30fps. But that takes quite a bit more effort to draw to screen, since you'll have to convert to RGB.
I don't quite understand what you're trying to achieve, but maybe I can push you in the right direction.
JPG is a compressed file format, so using it for a camera preview is a no-go. You generally want to let Camera directly draw onto the TextureView without any compression.
You did leave a comment that you need to do some kind of processing first, but did you try using a different file format if this kind of processing needs to be done realtime while showing a preview? Any kind of a compressed image format will generally result in bad performance.
You can also show a preview directly while occasionally saving a compressed JPG/PNG on the external storage. You can do that with Camera2, though CameraX has a much simpler way of doing it via use cases.
I am creating the Android app with the camera functionality.
The camera screen contains the toolbar on the top, the surfaceview (camera preview) below the toolbar, and the camera control buttons on the bottom of the screen. The screen is always in portrait.
[Some lines of the code do not related to the issue are deleted]
This is my fragment FragmentCamera
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.dmc.R;
import com.dmc.entities.Preview;
public class CameraFragment implements View.OnClickListener, View.OnTouchListener {
public static final String ARG_CAMERA_MODE = "camera.mode";
public static final String TYPE_CAMERA_MODE_IMAGE = "image";
public static final String TYPE_CAMERA_MODE_VIDEO = "video";
public MediaRecorder mrec = new MediaRecorder();
private Camera camera;
private String mCameraMode = TYPE_CAMERA_MODE_IMAGE; //or video
private com.dmc.entities.Preview preview;
private ImageView btnStopRecording;
private SurfaceView surfaceView;
private View view;
public static FrCamera getInstance(String cameraMode) {
CameraFragment fragment = new CameraFragment();
Bundle bundle = new Bundle(1);
bundle.putString(ARG_CAMERA_MODE, cameraMode);
return fragment.setArguments(bundle);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCameraMode = getArguments().getString(ARG_CAMERA_MODE);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_camera, container, false);
view.setOnTouchListener(this);
btnStopRecording = (ImageView) view.findViewById(R.id.btnStopRecording);
if (!mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) {
btnStopRecording.setOnClickListener(this);
}
surfaceView = (SurfaceView) view.findViewById(R.id.surfaceView);
view.findViewById(R.id.imgCameraTakePicture).setOnClickListener(this);
preview = new Preview(getActivity(), (SurfaceView) view.findViewById(R.id.surfaceView));
preview.setKeepScreenOn(true);
return view;
}
#Override
public void onStart() {
super.onStart();
int numCams = Camera.getNumberOfCameras();
if (numCams > 0) {
try {
camera = Camera.open(0);
preview.setCamera(camera);
camera.startPreview();
} catch (RuntimeException ex) {
}
}
}
#Override
public void onPause() {
if (camera != null) {
camera.stopPreview();
preview.setCamera(null);
camera.release();
camera = null;
}
super.onPause();
}
#Override
public void onResume() {
super.onResume();
camera.startPreview();
}
private void startVideoRecording() {
try {
mrec = new MediaRecorder();
mrec.setCamera(camera);
mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
mrec.setProfile(profile);
camera.lock();
camera.unlock();
mrec.setPreviewDisplay(preview.mHolder.getSurface());
mrec.setOutputFile(outVideoFile.getPath());
mrec.setOrientationHint(Preview.rotate);
mrec.prepare();
mrec.start();
} catch (Exception ex) {
Log.e(getClass().getName(), ex.getMessage());
}
}
protected void stopRecording() {
if (mrec != null) {
mrec.stop();
mrec.release();
}
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.imgCameraTakenPicture:
// Save image
break;
case R.id.btnStopRecording:
stopRecording();
break;
case R.id.imgCameraTakePicture:
if (mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE)) {
camera.takePicture(shutterCallback, rawCallback, jpegCallback);
} else
startVideoRecording();
break;
}
}
}
This is the Preview
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import java.io.IOException;
import java.util.List;
public class Preview extends ViewGroup implements SurfaceHolder.Callback {
public static final float RATIO = 0.75f;
public static int rotate;
public SurfaceView mSurfaceView;
public SurfaceHolder mHolder;
Size mPreviewSize;
List<Size> mSupportedPreviewSizes;
Camera mCamera;
public Preview(Context context, SurfaceView sv) {
super(context);
mSurfaceView = sv;
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
requestLayout();
// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
params.setJpegThumbnailQuality(100);
params.setJpegQuality(100);
// Configure image format. RGB_565 is the most common format.
List<Integer> formats = params.getSupportedPictureFormats();
if (formats.contains(PixelFormat.RGB_565))
params.setPictureFormat(PixelFormat.RGB_565);
else if (formats.contains(PixelFormat.JPEG))
params.setPictureFormat(PixelFormat.JPEG);
else params.setPictureFormat(formats.get(0));
Camera.CameraInfo camInfo = new Camera.CameraInfo();
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
int cameraRotationOffset = camInfo.orientation;
Camera.Parameters parameters = mCamera.getParameters();
int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break; // Natural orientation
case Surface.ROTATION_90:
degrees = 90;
break; // Landscape left
case Surface.ROTATION_180:
degrees = 180;
break;// Upside down
case Surface.ROTATION_270:
degrees = 270;
break;// Landscape right
}
int displayRotation;
if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
displayRotation = (cameraRotationOffset + degrees) % 360;
//displayRotation = (360 - displayRotation) % 360; // compensate the mirror
} else { // back-facing
displayRotation = (cameraRotationOffset - degrees + 360) % 360;
}
mCamera.setDisplayOrientation(displayRotation);
if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotate = (360 + cameraRotationOffset + degrees) % 360;
} else {
rotate = (360 + cameraRotationOffset - degrees) % 360;
}
parameters.set("orientation", "portrait");
parameters.setRotation(rotate);
mCamera.setParameters(params);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height / previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width / previewWidth;
child.layout(0, (height - scaledChildHeight) / 2,
width, (height + scaledChildHeight) / 2);
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
}
} catch (IOException exception) {}
}
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mHolder.getSurface() == null) {
return;
}
stopPreview();
setCamera(mCamera);
startPreview();
mCamera.startPreview();
}
public void startPreview() {
try {
if (mCamera != null) {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
} catch (Exception e) {}
}
public void stopPreview() {
try {
if (mCamera != null)
mCamera.stopPreview();
} catch (Exception e) {}
}
}
The expected result is on the image on left side. The obtained result is on the image on the right side. The camera preview is stretched. How to fix the right aspect ratio of the camera preview?
Source code for Camera1 and Android 4-5:
#Override
protected void onResume() {
super.onResume();
camera = Camera.open(CAMERA_ID);
setPreviewSize();
}
#Override
protected void onPause() {
super.onPause();
if (camera != null)
camera.release();
camera = null;
}
...
void setPreviewSize() {
// получаем размеры экрана
Display display = getWindowManager().getDefaultDisplay();
int w1 = display.getWidth();
int h1 = display.getHeight();
boolean widthIsMax = display.getWidth() > display.getHeight();
// определяем размеры превью камеры
Camera.Size size = camera.getParameters().getPreviewSize();
RectF rectDisplay = new RectF();
RectF rectPreview = new RectF();
// RectF экрана, соотвествует размерам экрана
rectDisplay.set(0, 0, w1, h1);
// подготовка матрицы преобразования
Matrix matrix = new Matrix();
// RectF первью
if (widthIsMax) {
// превью в горизонтальной ориентации
rectPreview.set(0, 0, size.width, size.height);
// если экран будет "втиснут" в превью (третий вариант из урока)
matrix.setRectToRect(rectPreview, rectDisplay,
Matrix.ScaleToFit.START);
} else {
// превью в вертикальной ориентации
rectPreview.set(0, 0, size.height, size.width);
// если превью будет "втиснут" в экран (второй вариант из урока)
matrix.setRectToRect(rectPreview, rectDisplay,
Matrix.ScaleToFit.START);
}
// преобразование
matrix.mapRect(rectPreview);
// установка размеров surface из получившегося преобразования
h0 = (int) (rectPreview.bottom);
w0 = (int) (rectPreview.right);
surfaceView.getLayoutParams().height = h0;
surfaceView.getLayoutParams().width = w0;
}
See http://startandroid.ru/ru/uroki/vse-uroki-spiskom/264-urok-132-kamera-vyvod-izobrazhenija-na-ekran-obrabotka-povorota.html
Below is the solution of Camera Preview Stretched
It's worked on making my custom camera activity
I set the framelayout which holds the surface view that displays the camera's parameters.
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private Context mContext;
private SurfaceHolder mHolder;
private Camera mCamera;
private List<Camera.Size> mSupportedPreviewSizes;
private Camera.Size mPreviewSize;
public CameraPreview(Context context, Camera camera) {
super(context);
mContext = context;
mCamera = camera;
// supported preview sizes
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
for(Camera.Size str: mSupportedPreviewSizes)
Log.e(TAG, str.width + "/" + str.height);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// empty. surfaceChanged will take care of stuff
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or reformatting changes here
// start preview with new settings
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
if (mPreviewSize!=null) {
float ratio;
if(mPreviewSize.height >= mPreviewSize.width)
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
else
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
// One of these methods should be used, second method squishes preview slightly
setMeasuredDimension(width, (int) (width * ratio));
// setMeasuredDimension((int) (width * ratio), height);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
Reference link:
Android Camera Preview Stretched
I am creating a camera application using android camera api by this example: https://examples.javacodegeeks.com/android/core/hardware/camera-hardware/android-camera-example/
I made some changes according to my needs and you can see the code below. The problem is, camera preview for back camera works quite fine on my LG G3 device but on LG G4 it gets too slow. It also works fine with front camera on both devices.
What can cause this problem ?
CameraPreview:
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback{
private static String TAG = "CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
private List<Size> mSupportedPreviewSizes;
private Size mPreviewSize;
public CameraPreview(Context context, Camera camera){
super(context);
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
try{
Log.d(TAG, "Surface Created..");
if(mCamera == null){
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.setDisplayOrientation(90);
mCamera.startPreview();
}
} catch (IOException e) {
Log.d(VIEW_LOG_TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void refreshCamera(Camera camera) {
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
setCamera(camera);
try {
Log.d(TAG,"RefreshCamera..");
if(mCamera == null){
Log.d(TAG,"Camera null");
}
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.setPreviewDisplay(mHolder);
mCamera.setPreviewDisplay(mHolder);
mCamera.setDisplayOrientation(90);
mCamera.startPreview();
} catch (Exception e) {
Log.d(VIEW_LOG_TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
Log.d(TAG, "Surface Changed..");
refreshCamera(mCamera);
}
public void setCamera(Camera camera) {
//method to set a camera instance
mCamera = camera;
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mCamera.release();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG,"onMeasure..");
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if(mSupportedPreviewSizes==null){
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
}else{
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
Log.d(TAG, "mPreviewSize set.. Width: " + mPreviewSize.width+" Height: " + mPreviewSize.height);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio=(double)h / w;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
I can share the Activity class if its needed..
Thanks..
I had the same problem using surface view .use the below preview class.its working for me for all the device screen.
public class SquareCameraPreview extends SurfaceView {
public static final String TAG = SquareCameraPreview.class.getSimpleName();
private static final int INVALID_POINTER_ID = -1;
private static final int ZOOM_OUT = 0;
private static final int ZOOM_IN = 1;
private static final int ZOOM_DELTA = 1;
private static final int FOCUS_SQR_SIZE = 100;
private static final int FOCUS_MAX_BOUND = 1000;
private static final int FOCUS_MIN_BOUND = -FOCUS_MAX_BOUND;
private static final double ASPECT_RATIO = 3.0 / 4.0;
private Camera mCamera;
private float mLastTouchX;
private float mLastTouchY;
// For scaling
private int mMaxZoom;
private boolean mIsZoomSupported;
private int mActivePointerId = INVALID_POINTER_ID;
private int mScaleFactor = 1;
private ScaleGestureDetector mScaleDetector;
// For focus
private boolean mIsFocus;
private boolean mIsFocusReady;
private Camera.Area mFocusArea;
private ArrayList<Camera.Area> mFocusAreas;
public SquareCameraPreview(Context context) {
super(context);
init(context);
}
public SquareCameraPreview(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SquareCameraPreview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
mFocusArea = new Camera.Area(new Rect(), 1000);
mFocusAreas = new ArrayList<Camera.Area>();
mFocusAreas.add(mFocusArea);
}
/**
* Measure the view and its content to determine the measured width and the
* measured height
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
final boolean isPortrait =
getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
if (isPortrait) {
if (width > height * ASPECT_RATIO) {
width = (int) (height * ASPECT_RATIO + 0.5);
} else {
height = (int) (width / ASPECT_RATIO + 0.5);
}
} else {
if (height > width * ASPECT_RATIO) {
height = (int) (width * ASPECT_RATIO + 0.5);
} else {
width = (int) (height / ASPECT_RATIO + 0.5);
}
}
setMeasuredDimension(width, height);
}
public int getViewWidth() {
return getWidth();
}
public int getViewHeight() {
return getHeight();
}
public void setCamera(Camera camera) {
mCamera = camera;
if (camera != null) {
Camera.Parameters params = camera.getParameters();
mIsZoomSupported = params.isZoomSupported();
if (mIsZoomSupported) {
mMaxZoom = params.getMaxZoom();
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mIsFocus = true;
mLastTouchX = event.getX();
mLastTouchY = event.getY();
mActivePointerId = event.getPointerId(0);
break;
}
case MotionEvent.ACTION_UP: {
if (mIsFocus && mIsFocusReady) {
handleFocus(mCamera.getParameters());
}
mActivePointerId = INVALID_POINTER_ID;
break;
}
case MotionEvent.ACTION_POINTER_DOWN: {
mCamera.cancelAutoFocus();
mIsFocus = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
mActivePointerId = INVALID_POINTER_ID;
break;
}
}
return true;
}
private void handleZoom(Camera.Parameters params) {
int zoom = params.getZoom();
if (mScaleFactor == ZOOM_IN) {
if (zoom < mMaxZoom) zoom += ZOOM_DELTA;
} else if (mScaleFactor == ZOOM_OUT) {
if (zoom > 0) zoom -= ZOOM_DELTA;
}
params.setZoom(zoom);
mCamera.setParameters(params);
}
private void handleFocus(Camera.Parameters params) {
float x = mLastTouchX;
float y = mLastTouchY;
if (!setFocusBound(x, y)) return;
List<String> supportedFocusModes = params.getSupportedFocusModes();
if (supportedFocusModes != null
&& supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
Log.d(TAG, mFocusAreas.size() + "");
params.setFocusAreas(mFocusAreas);
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(params);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success, Camera camera) {
// Callback when the auto focus completes
}
});
}
}
public void setIsFocusReady(final boolean isFocusReady) {
mIsFocusReady = isFocusReady;
}
private boolean setFocusBound(float x, float y) {
int left = (int) (x - FOCUS_SQR_SIZE / 2);
int right = (int) (x + FOCUS_SQR_SIZE / 2);
int top = (int) (y - FOCUS_SQR_SIZE / 2);
int bottom = (int) (y + FOCUS_SQR_SIZE / 2);
if (FOCUS_MIN_BOUND > left || left > FOCUS_MAX_BOUND) return false;
if (FOCUS_MIN_BOUND > right || right > FOCUS_MAX_BOUND) return false;
if (FOCUS_MIN_BOUND > top || top > FOCUS_MAX_BOUND) return false;
if (FOCUS_MIN_BOUND > bottom || bottom > FOCUS_MAX_BOUND) return false;
mFocusArea.rect.set(left, top, right, bottom);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor = (int) detector.getScaleFactor();
handleZoom(mCamera.getParameters());
return true;
}
}
}
public class CameraFragment extends Fragment implements SurfaceHolder.Callback, Camera.PictureCallback {
public static int HIGH_RESOLUTION_WIDTH=1000;
public static int HIGH_RESOLUTION_HIGHT=1500;
public static String clorie_count;
private long startTime = 0L;
public static final String TAG = CameraFragment.class.getSimpleName();
public static final String CAMERA_ID_KEY = "camera_id";
public static final String CAMERA_FLASH_KEY = "flash_mode";
public static final String IMAGE_INFO = "image_info";
private static final int PICTURE_SIZE_MAX_WIDTH = 1280;
private static final int PREVIEW_SIZE_MAX_WIDTH = 640;
private int mCameraID;
private String mFlashMode;
private Camera mCamera;
private SquareCameraPreview mPreviewView;
private SurfaceHolder mSurfaceHolder;
private boolean mIsSafeToTakePhoto = false;
private ImageParameters mImageParameters;
private CameraOrientationListener mOrientationListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
context = activity;
mOrientationListener = new CameraOrientationListener(context);
}
View view;
Dialog listDialog;
#SuppressLint("NewApi")
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.camera_fragment, container, false);
if (savedInstanceState == null) {
mCameraID = getBackCameraID();
mFlashMode = CameraSettingPreferences.getCameraFlashMode(getActivity());
mImageParameters = new ImageParameters();
} else {
mCameraID = savedInstanceState.getInt(CAMERA_ID_KEY);
mFlashMode = savedInstanceState.getString(CAMERA_FLASH_KEY);
mImageParameters = savedInstanceState.getParcelable(IMAGE_INFO);
}
mPreviewView = (SquareCameraPreview) view.findViewById(R.id.camerapreview);
mPreviewView.getHolder().addCallback(CameraFragment.this);
mImageParameters.mIsPortrait =getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
capture_icon.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
takePicture();
}
});
return view;
}
private void takePicture() {
if (mIsSafeToTakePhoto) {
setSafeToTakePhoto(false);
mOrientationListener.rememberOrientation();
// Shutter callback occurs after the image is captured. This can
// be used to trigger a sound to let the user know that image is taken
Camera.ShutterCallback shutterCallback = null;
// Raw callback occurs when the raw image data is available
Camera.PictureCallback raw = null;
// postView callback occurs when a scaled, fully processed
// postView image is available.
Camera.PictureCallback postView = null;
// jpeg callback occurs when the compressed image is available
mCamera.takePicture(shutterCallback, raw, postView, this);
}
}
private void setSafeToTakePhoto(final boolean isSafeToTakePhoto) {
mIsSafeToTakePhoto = isSafeToTakePhoto;
}
private void setCameraFocusReady(final boolean isFocusReady) {
if (this.mPreviewView != null) {
mPreviewView.setIsFocusReady(isFocusReady);
}
}
/**
* Determine the current display orientation and rotate the camera preview
* accordingly
*/
private void determineDisplayOrientation() {
CameraInfo cameraInfo = new CameraInfo();
Camera.getCameraInfo(mCameraID, cameraInfo);
// Clockwise rotation needed to align the window display to the natural position
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: {
degrees = 0;
break;
}
case Surface.ROTATION_90: {
degrees = 90;
break;
}
case Surface.ROTATION_180: {
degrees = 180;
break;
}
case Surface.ROTATION_270: {
degrees = 270;
break;
}
}
int displayOrientation;
// CameraInfo.Orientation is the angle relative to the natural position of the device
// in clockwise rotation (angle that is rotated clockwise from the natural position)
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
// Orientation is angle of rotation when facing the camera for
// the camera image to match the natural orientation of the device
displayOrientation = (cameraInfo.orientation + degrees) % 360;
displayOrientation = (360 - displayOrientation) % 360;
} else {
displayOrientation = (cameraInfo.orientation - degrees + 360) % 360;
}
mImageParameters.mDisplayOrientation = displayOrientation;
mImageParameters.mLayoutOrientation = degrees;
mCamera.setDisplayOrientation(mImageParameters.mDisplayOrientation);
}
private void setupCamera() {
// Never keep a global parameters
Camera.Parameters parameters = mCamera.getParameters();
Size bestPreviewSize = determineBestPreviewSize(parameters);
Size bestPictureSize = determineBestPictureSize(parameters);
parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
// Set continuous picture focus, if it's supported
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
final View changeCameraFlashModeBtn = getView().findViewById(R.id.flash);
List<String> flashModes = parameters.getSupportedFlashModes();
if (flashModes != null && flashModes.contains(mFlashMode)) {
parameters.setFlashMode(mFlashMode);
changeCameraFlashModeBtn.setVisibility(View.VISIBLE);
} else {
changeCameraFlashModeBtn.setVisibility(View.INVISIBLE);
}
// Lock in the changes
mCamera.setParameters(parameters);
}
private Size determineBestPreviewSize(Camera.Parameters parameters) {
return determineBestSize(parameters.getSupportedPreviewSizes(), PREVIEW_SIZE_MAX_WIDTH);
}
private Size determineBestPictureSize(Camera.Parameters parameters) {
return determineBestSize(parameters.getSupportedPictureSizes(), PICTURE_SIZE_MAX_WIDTH);
}
private Size determineBestSize(List<Size> sizes, int widthThreshold) {
Size bestSize = null;
Size size;
int numOfSizes = sizes.size();
for (int i = 0; i < numOfSizes; i++) {
size = sizes.get(i);
boolean isDesireRatio = (size.width / 4) == (size.height / 3);
boolean isBetterSize = (bestSize == null) || size.width > bestSize.width;
if (isDesireRatio && isBetterSize) {
bestSize = size;
}
}
if (bestSize == null) {
Log.d(TAG, "cannot find the best camera size");
return sizes.get(sizes.size() - 1);
}
return bestSize;
}
/**
* Start the camera preview
*/
private void startCameraPreview() {
determineDisplayOrientation();
setupCamera();
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
mCamera.startPreview();
setSafeToTakePhoto(true);
setCameraFocusReady(true);
} catch (IOException e) {
Log.d(TAG, "Can't start camera preview due to IOException " + e);
e.printStackTrace();
}
}
private void getCamera(int cameraID) {
try {
mCamera = Camera.open(cameraID);
mPreviewView.setCamera(mCamera);
} catch (Exception e) {
Log.d(TAG, "Can't open camera with id " + cameraID);
e.printStackTrace();
}
}
#Override
public void onPictureTaken(byte[] arg0, Camera arg1) {
// TODO Auto-generated method stub
HERE YOU GET THE PHOTO BYTE[] arg0
CONVERT IT TO BITMAP OR STROE INTO A FILE
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
mSurfaceHolder = holder;
getCamera(mCameraID);
startCameraPreview();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
private static class CameraOrientationListener extends OrientationEventListener {
private int mCurrentNormalizedOrientation;
private int mRememberedNormalOrientation;
public CameraOrientationListener(Context context) {
super(context, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onOrientationChanged(int orientation) {
if (orientation != ORIENTATION_UNKNOWN) {
mCurrentNormalizedOrientation = normalize(orientation);
}
}
/**
* #param degrees Amount of clockwise rotation from the device's natural position
* #return Normalized degrees to just 0, 90, 180, 270
*/
private int normalize(int degrees) {
if (degrees > 315 || degrees <= 45) {
return 0;
}
if (degrees > 45 && degrees <= 135) {
return 90;
}
if (degrees > 135 && degrees <= 225) {
return 180;
}
if (degrees > 225 && degrees <= 315) {
return 270;
}
throw new RuntimeException("The physics as we know them are no more. Watch out for anomalies.");
}
public void rememberOrientation() {
mRememberedNormalOrientation = mCurrentNormalizedOrientation;
}
public int getRememberedNormalOrientation() {
rememberOrientation();
return mRememberedNormalOrientation;
}
}
/*#Override
public void onResume() {
super.onResume();
if (mCamera == null) {
restartPreview();
}
}*/
#Override
public void onStop() {
mOrientationListener.disable();
// stop the preview
if (mCamera != null) {
stopCameraPreview();
mCamera.release();
mCamera = null;
}
if (getKillStatus()) {
android.os.Process.killProcess(android.os.Process.myPid());
}
// CameraSettingPreferences.saveCameraFlashMode(getActivity(), mFlashMode);
super.onStop();
}
#Override
public void onResume() {
super.onResume();
/*if (camera != null) {
camera.stopPreview();
preview.setupCamera(null,isResolutionHigh);
camera.release();
camera = null;
}
int numCams = Camera.getNumberOfCameras();
if (numCams > 0) {
try {
camera = Camera.open(0);
camera.startPreview();
preview.setupCamera(camera,isResolutionHigh);
} catch (RuntimeException ex) {
// Toast.makeText(ctx, getString(R.string.camera_not_found),
// Toast.LENGTH_LONG).show();
}
}*/
if (mCamera == null) {
restartPreview();
}
}
private void restartPreview() {
if (mCamera != null) {
stopCameraPreview();
mCamera.release();
mCamera = null;
}
getCamera(mCameraID);
startCameraPreview();
}
/**
* Stop the camera preview
*/
private void stopCameraPreview() {
setSafeToTakePhoto(false);
setCameraFocusReady(false);
// Nulls out callbacks, stops face detection
mCamera.stopPreview();
mPreviewView.setCamera(null);
}
private int getBackCameraID() {
return CameraInfo.CAMERA_FACING_BACK;
}
}
AND YOUR XML FILE
<com.demo.camera.SquareCameraPreview
android:id="#+id/camerapreview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
ImageParameter Class
public class ImageParameters implements Parcelable {
public boolean mIsPortrait;
public int mDisplayOrientation;
public int mLayoutOrientation;
public int mCoverHeight, mCoverWidth;
public int mPreviewHeight, mPreviewWidth;
public ImageParameters(Parcel in) {
mIsPortrait = (in.readByte() == 1);
mDisplayOrientation = in.readInt();
mLayoutOrientation = in.readInt();
mCoverHeight = in.readInt();
mCoverWidth = in.readInt();
mPreviewHeight = in.readInt();
mPreviewWidth = in.readInt();
}
public ImageParameters() {}
public int calculateCoverWidthHeight() {
return Math.abs(mPreviewHeight - mPreviewWidth) / 2;
}
public int getAnimationParameter() {
return mIsPortrait ? mCoverHeight : mCoverWidth;
}
public boolean isPortrait() {
return mIsPortrait;
}
public ImageParameters createCopy() {
ImageParameters imageParameters = new ImageParameters();
imageParameters.mIsPortrait = mIsPortrait;
imageParameters.mDisplayOrientation = mDisplayOrientation;
imageParameters.mLayoutOrientation = mLayoutOrientation;
imageParameters.mCoverHeight = mCoverHeight;
imageParameters.mCoverWidth = mCoverWidth;
imageParameters.mPreviewHeight = mPreviewHeight;
imageParameters.mPreviewWidth = mPreviewWidth;
return imageParameters;
}
public String getStringValues() {
return "is Portrait: " + mIsPortrait + "," +
"\ncover height: " + mCoverHeight + " width: " + mCoverWidth
+ "\npreview height: " + mPreviewHeight + " width: " + mPreviewWidth;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (mIsPortrait ? 1 : 0));
dest.writeInt(mDisplayOrientation);
dest.writeInt(mLayoutOrientation);
dest.writeInt(mCoverHeight);
dest.writeInt(mCoverWidth);
dest.writeInt(mPreviewHeight);
dest.writeInt(mPreviewWidth);
}
public static final Creator<ImageParameters> CREATOR = new Parcelable.Creator<ImageParameters>() {
#Override
public ImageParameters createFromParcel(Parcel source) {
return new ImageParameters(source);
}
#Override
public ImageParameters[] newArray(int size) {
return new ImageParameters[size];
}
};
}
Another class you need camera setting preference
public class CameraSettingPreferences {
private static final String FLASH_MODE = "squarecamera__flash_mode";
private static SharedPreferences getCameraSettingPreferences(#NonNull final Context context) {
return context.getSharedPreferences("com.desmond.squarecamera", Context.MODE_PRIVATE);
}
protected static void saveCameraFlashMode(#NonNull final Context context, #NonNull final String cameraFlashMode) {
final SharedPreferences preferences = getCameraSettingPreferences(context);
if (preferences != null) {
final SharedPreferences.Editor editor = preferences.edit();
editor.putString(FLASH_MODE, cameraFlashMode);
editor.apply();
}
}
public static String getCameraFlashMode(#NonNull final Context context) {
final SharedPreferences preferences = getCameraSettingPreferences(context);
if (preferences != null) {
return preferences.getString(FLASH_MODE, Camera.Parameters.FLASH_MODE_AUTO);
}
return Camera.Parameters.FLASH_MODE_AUTO;
}
}
Let me know if you have any doubts.
I am using the FaceTracker sample from the Android vision API. However, I am experiencing difficulty in recording videos while the overlays are drawn on them.
One way is to store bitmaps as images and process them using FFmpeg or Xuggler to merge them as videos, but I am wondering if there is a better solution to this problem if we can record video at runtime as the preview is projected.
Update 1:
I updated the following class with media recorder, but the recording is still not working. It is throwing the following error when I call triggerRecording() function:
MediaRecorder: start called in an invalid state: 4
and I have external storage permission in the Manifest file.
Update 2:
I have fixed the above issue in the code and moved the setupMediaRecorder() in the onSurfaceCreated callback. However, when I stop recording it throws the runtime-exception. According to the documentation if there is no video/audio data Runtime exception will be thrown.
So, what am I missing here?
public class CameraSourcePreview extends ViewGroup {
private static final String TAG = "CameraSourcePreview";
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private MediaRecorder mMediaRecorder;
/**
* Whether the app is recording video now
*/
private boolean mIsRecordingVideo;
private Context mContext;
private SurfaceView mSurfaceView;
private boolean mStartRequested;
private boolean mSurfaceAvailable;
private CameraSource mCameraSource;
private GraphicOverlay mOverlay;
public CameraSourcePreview(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mStartRequested = false;
mSurfaceAvailable = false;
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
mMediaRecorder = new MediaRecorder();
}
private void setUpMediaRecorder() throws IOException {
mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setOutputFile(Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_DCIM + File.separator + System.currentTimeMillis() + ".mp4");
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoSize(480, 640);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
//int rotation = mContext.getWindowManager().getDefaultDisplay().getRotation();
//int orientation = ORIENTATIONS.get(rotation);
mMediaRecorder.setOrientationHint(ORIENTATIONS.get(0));
mMediaRecorder.prepare();
mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
#Override
public void onError(MediaRecorder mr, int what, int extra) {
Timber.d(mr.toString() + " : what[" + what + "]" + " Extras[" + extra + "]");
}
});
}
public void start(CameraSource cameraSource) throws IOException {
if (cameraSource == null) {
stop();
}
mCameraSource = cameraSource;
if (mCameraSource != null) {
mStartRequested = true;
startIfReady();
}
}
public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
mOverlay = overlay;
start(cameraSource);
}
public void stop() {
if (mCameraSource != null) {
mCameraSource.stop();
}
}
public void release() {
if (mCameraSource != null) {
mCameraSource.release();
mCameraSource = null;
}
}
private void startIfReady() throws IOException {
if (mStartRequested && mSurfaceAvailable) {
mCameraSource.start(mSurfaceView.getHolder());
if (mOverlay != null) {
Size size = mCameraSource.getPreviewSize();
int min = Math.min(size.getWidth(), size.getHeight());
int max = Math.max(size.getWidth(), size.getHeight());
if (isPortraitMode()) {
// Swap width and height sizes when in portrait, since it will be rotated by
// 90 degrees
mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
} else {
mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
}
mOverlay.clear();
}
mStartRequested = false;
}
}
private class SurfaceCallback implements SurfaceHolder.Callback {
#Override
public void surfaceCreated(SurfaceHolder surface) {
mSurfaceAvailable = true;
surface.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// setup the media recorder
try {
setUpMediaRecorder();
} catch (IOException e) {
e.printStackTrace();
}
try {
startIfReady();
} catch (IOException e) {
Timber.e(TAG, "Could not start camera source.", e);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surface) {
mSurfaceAvailable = false;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
}
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int width = 320;
int height = 240;
if (mCameraSource != null) {
Size size = mCameraSource.getPreviewSize();
if (size != null) {
width = size.getWidth();
height = size.getHeight();
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode()) {
int tmp = width;
width = height;
height = tmp;
}
final int layoutWidth = right - left;
final int layoutHeight = bottom - top;
// Computes height and width for potentially doing fit width.
int childWidth = layoutWidth;
int childHeight = (int) (((float) layoutWidth / (float) width) * height);
// If height is too tall using fit width, does fit height instead.
if (childHeight > layoutHeight) {
childHeight = layoutHeight;
childWidth = (int) (((float) layoutHeight / (float) height) * width);
}
for (int i = 0; i < getChildCount(); ++i) {
getChildAt(i).layout(0, 0, childWidth, childHeight);
}
try {
startIfReady();
} catch (IOException e) {
Timber.e(TAG, "Could not start camera source.", e);
}
}
private boolean isPortraitMode() {
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return true;
}
Timber.d(TAG, "isPortraitMode returning false by default");
return false;
}
private void startRecordingVideo() {
try {
// Start recording
mMediaRecorder.start();
mIsRecordingVideo = true;
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
private void stopRecordingVideo() {
// UI
mIsRecordingVideo = false;
// Stop recording
mMediaRecorder.stop();
mMediaRecorder.reset();
}
public void triggerRecording() {
if (mIsRecordingVideo) {
stopRecordingVideo();
Timber.d("Recording stopped");
} else {
startRecordingVideo();
Timber.d("Recording starting");
}
}
}
Solution 1: From Android Lollipop, a MediaProjection API was introduced which in conjunction with MediaRecorder can be used to save a SurfaceView to a video file. This example shows how to output a SurfaceView to a video file.
Solution 2: Alternatively, you can use one of the neat Encoder classes provided in the Grafika repository. Note that this will require you to port the FaceTracker application so that it is using OpenGL to perform all rendering. This is because Grafika samples utilise the OpenGL pipeline for fast read and write of texture data.
There is a minimal example which achieves exactly what you want using a CircularEncoder in the ContinuousCaptureActivity class. This provides an example of Frame Blitting, simultaneously displaying frame buffer data to the screen and outputting to a video.
The major change would be to utilise a Grafika WindowSurface instead of a SurfaceView for the FaceTracker application, this sets up the EGL Context allowing you to save frame buffer data to a file via the Encoder. Once you can render everything to the WindowSurface, it is trivial to set up recording in the same way as the ContinuousCaptureActivity class.
I'm creating a camera app that implements it's own camera preview for taking pictures.
The app is currently forced into portrait mode.
My problem is that the preview from the camera is slightly stretched (the aspect ratio is a bit off).
The funny thing is that I'm setting my SurfaceView size to always match the preview size. This ensures that the aspect ratio should always be perserved... but it isn't...
Here is the layout i use to show my camera preview:
public class Cp extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "CameraPreview";
private boolean mPreviewRunning = false;
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Size mPreviewSize;
private List<Size> mSupportedPreviewSizes;
private Camera mCamera;
public boolean IsPreviewRunning() {
return mPreviewRunning;
}
public Cp(Context context) {
this(context, null, 0);
}
public Cp(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Cp(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
requestLayout();
}
}
public void switchCamera(Camera camera) {
setCamera(camera);
try {
camera.setPreviewDisplay(mHolder);
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
camera.setParameters(parameters);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a wrapper to
// a SurfaceView that
// centers the camera preview instead of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes == null && mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
}
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
height);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
}
if (previewWidth == 0) {
previewWidth = 1;
}
if (previewHeight == 0) {
previewHeight = 1;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height
/ previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width
/ previewWidth;
child.layout(0, (height - scaledChildHeight) / 2, width,
(height + scaledChildHeight) / 2);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where to
// draw.
try {
if (mCamera != null) {
Parameters params = mCamera.getParameters();
mSupportedPreviewSizes = params.getSupportedPreviewSizes();
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
stop();
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mCamera != null) {
// Now that the size is known, set up the camera parameters and
// begin the preview.
Camera.Parameters parameters = mCamera.getParameters();
if (mPreviewSize != null) {
parameters.setPreviewSize(mPreviewSize.width,
mPreviewSize.height);
}
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
mPreviewRunning = true;
}
}
public void stop() {
if (mCamera != null) {
mCamera.stopPreview();
mPreviewRunning = false;
mCamera = null;
}
}
}
Please note that in onLayout width and height are swapped because the app is always running in portrait.
I'm including some pictures to show you what the problem looks like:
I've debugged the code. The preview size is being set to 1280x720 and the size of the layout is also being correctly adjusted to match that size.
I've also tried different layouts but the result was always the same...
I was having the same problem, after some days in this puzzle, my Java class end up on this code:
So, The problem was happening because the camera display had a size (hight x width) 576x720, and my display was 1184x720. So, the camera preview (my surface view class) stretched to fill in the parent.
So, the approach that worked is to make this view bigger than my screen, and that just display the area of my screen. So, I had to use this weird frame combination (a relative layout inside a linearlayout), so that the size of the relative will be as big as I want - the surface view will fill in it -, and the linear will take just the part that I wanna display.
package com.example.frame.camera;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.example.frame.MainActivity;
public class NativeCamera extends SurfaceView implements SurfaceHolder.Callback {
static private NativeCamera instance;
private LinearLayout frame = null;
private RelativeLayout innerFrame = null;
private Camera camera;
private final SurfaceHolder previewHolder;
private static final String TAG = "NativeCamera.java";
private boolean inPreview = false;
private boolean cameraConfigured = false;
private boolean frontCamera = false;
private Camera.Size size;
static public NativeCamera getInstance() {
if (NativeCamera.instance == null) {
if (MainActivity.debug) {
Log.d(TAG, "Creating Camera Singleton");
}
NativeCamera.instance = new NativeCamera(MainActivity.instance);
}
return NativeCamera.instance;
}
public void onResume() {
if (MainActivity.debug) {
Log.d(TAG, "onResume");
}
camera = Camera.open();
if (size != null) {
initPreview(size.width, size.height);
}
startPreview();
}
public void onPause() {
if (MainActivity.debug) {
Log.d(TAG, "onPause");
}
if (inPreview) {
camera.stopPreview();
}
camera.release();
camera = null;
inPreview = false;
}
public void onDestroy() {
if (MainActivity.debug) {
Log.d(TAG, "onDestroy");
}
NativeCamera.instance = null;
}
public void onSwitch() {
frontCamera = !frontCamera;
if (inPreview) {
camera.stopPreview();
}
camera.release();
int cam = frontCamera ? 1 : 0;
camera = Camera.open(cam);
if (size != null) {
initPreview(size.width, size.height);
}
startPreview();
}
private NativeCamera(Context context) {
super(context);
// TODO Auto-generated constructor stub
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
previewHolder = getHolder();
previewHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
// previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
innerFrame = new RelativeLayout(MainActivity.instance);
innerFrame.addView(this);
frame = new LinearLayout(MainActivity.instance);
frame.addView(innerFrame);
}
public LinearLayout getFrame() {
return frame;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
if (MainActivity.debug) {
Log.d(TAG, "surfaceChanged");
}
initPreview(width, height);
startPreview();
}
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
// no-op -- wait until surfaceChanged()
}
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// no-op
}
private Camera.Size getBestPreviewSize(int width, int height,
Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
this.size = result;
return (result);
}
private void initPreview(int width, int height) {
if (camera != null && previewHolder.getSurface() != null) {
if (!cameraConfigured) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
parameters.setPreviewSize(size.width, size.height);
camera.setParameters(parameters);
cameraConfigured = true;
// Setting up correctly the view
double ratio = size.height / (double) size.width;
LayoutParams params = innerFrame.getLayoutParams();
params.height = MainActivity.size.y;
params.width = (int) (MainActivity.size.y * ratio);
innerFrame.setLayoutParams(params);
int deslocationX = (int) (params.width / 2.0 - MainActivity.size.x / 2.0);
innerFrame.animate().translationX(-deslocationX);
}
}
try {
camera.setPreviewDisplay(previewHolder);
camera.setDisplayOrientation(90);
} catch (Throwable t) {
Log.e(TAG, "Exception in setPreviewDisplay()", t);
Toast.makeText(MainActivity.instance, t.getMessage(), Toast.LENGTH_LONG).show();
}
}
}
private void startPreview() {
if (MainActivity.debug) {
Log.d(TAG, "startPreview");
}
if (cameraConfigured && camera != null) {
camera.startPreview();
inPreview = true;
}
}
}
I have been trying to solve the same issue... Below code worked for me:
private void setMyPreviewSize(int width, int height) {
// Get the set dimensions
float newProportion = (float) width / (float) height;
// Get the width of the screen
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
float screenProportion = (float) screenWidth / (float) screenHeight;
// Get the SurfaceView layout parameters
android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
if (newProportion > screenProportion) {
lp.width = screenWidth;
lp.height = (int) ((float) screenWidth / newProportion );
} else {
lp.width = (int) (newProportion * (float) screenHeight);
lp.height = screenHeight;
}
// Commit the layout parameters
surfaceView.setLayoutParams(lp);
}
You can see the thread: Resizing surface view for aspect ratio change in video display in android
Here's a solution
surfaceChanged is just to get the best preview size
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.e("MYTAG", "surfaceChanged " );
Camera.Parameters myParameters = camera.getParameters();
Camera.Size myBestSize = getBestPreviewSize(width, height, myParameters);
if(myBestSize != null){
myParameters.setPreviewSize(myBestSize.width, myBestSize.height);
camera.setParameters(myParameters);
camera.startPreview();
}
}
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageReview.setImageBitmap(bitmap);
}
This will set the captured image on imageReview, but you need a method to get the rotation of the bitmap
you need to set imageView scaleType attribute for stretched image issue
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
/>