Android Camera2 displays black and distorted JPEG image on TextureView? - android

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.

Related

Video recording using camera 2 for Portrait and Landscape in Android

I am implementing video recording using Camera 2 API. Video is getting recorded and uploaded, playing also.
But it works fine in Portrait mode. When I am recording in landscape mode, it is not showing in Landscape. Orientation is a bit odd and is playing reversed.
My code snippet is here:
public class Camera2VideoFragment extends Fragment implements View.OnClickListener {
private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
private boolean orientation = true;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final String TAG = "Testing";
boolean flag = false;
private static final String VIDEO_DIRECTORY_NAME = "Powerconsent";
private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
private static final SparseIntArray DEFAULT_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);
}
/**
* An {#link AutoFitTextureView} for camera preview.
*/
private AutoFitTextureView mTextureView;
/**
* Button to record video
*/
private ImageView mButtonVideo;
/**
* Button to record video
*/
private ImageView mSaveVideo;
private File mOutputFile;
/**
* A refernce to the opened {#link android.hardware.camera2.CameraDevice}.
*/
private CameraDevice mCameraDevice;
/**
* A reference to the current {#link android.hardware.camera2.CameraCaptureSession} for preview.
*/
private CameraCaptureSession mPreviewSession;
/**
* {#link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {#link TextureView}.
*/
private TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
int width, int height) {
openCamera(width, height);
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
int width, int height) {
Log.d("Testing", "onSurfaceTextureSizeChanged");
configureTransform(width, height);
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
Log.d("Testing", "onSurfaceTextureDestroyed");
return true;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
// Log.d("Testing", "onSurfaceTextureUpdated");
}
};
/**
* The {#link android.util.Size} of camera preview.
*/
private Size mPreviewSize;
String destVideoPath = null;
/**
* The {#link android.util.Size} of video recording.
*/
private Size mVideoSize;
PowerManager.WakeLock wl = null;
/**
* Camera preview.
*/
private CaptureRequest.Builder mPreviewBuilder;
/**
* MediaRecorder
*/
private MediaRecorder mMediaRecorder;
private File mCurrentFile;
Chronometer mChronometer;
/**
* Whether the app is recording video now
*/
private boolean mIsRecordingVideo;
private Integer mSensorOrientation;
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mBackgroundThread;
/**
* A {#link Handler} for running tasks in the background.
*/
private Handler mBackgroundHandler;
/**
* A {#link Semaphore} to prevent the app from exiting before closing the camera.
*/
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
/**
* {#link CameraDevice.StateCallback} is called when {#link CameraDevice} changes its status.
*/
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
#Override
public void onOpened(CameraDevice cameraDevice) {
mCameraDevice = cameraDevice;
startPreview();
mCameraOpenCloseLock.release();
if (null != mTextureView) {
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
}
}
#Override
public void onDisconnected(CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
#Override
public void onError(CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
Activity activity = getActivity();
if (null != activity) {
activity.finish();
}
}
};
public static Camera2VideoFragment newInstance() {
Camera2VideoFragment fragment = new Camera2VideoFragment();
// fragment.setRetainInstance(true);
return fragment;
}
/**
* In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes larger
* than 1080p, since MediaRecorder cannot handle such a high-resolution video.
*
* #param choices The list of available sizes
* #return The video size
*/
private static Size chooseVideoSize(Size[] choices) {
for (Size size : choices) {
if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
return size;
}
}
Log.e(TAG, "Couldn't find any suitable video size");
return choices[choices.length - 1];
}
private void screenAlive() {
PowerManager pm = (PowerManager)getActivity().getSystemService(Context.POWER_SERVICE);
wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Testing:");
wl.acquire(120*60*1000L);
}
/**
* Given {#code choices} of {#code Size}s supported by a camera, chooses the smallest one whose
* width and height are at least as large as the respective requested values, and whose aspect
* ratio matches with the specified value.
*
* #param choices The list of sizes that the camera supports for the intended output class
* #param width The minimum desired width
* #param height The minimum desired height
* #param aspectRatio The aspect ratio
* #return The optimal {#code Size}, or an arbitrary one if none were big enough
*/
private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<Size>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height) {
bigEnough.add(option);
}
}
// Pick the smallest of those, assuming we found any
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private int currentOrientation = -1;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("Testing","onCreateView !!!");
View view = inflater.inflate(R.layout.fragment_camera2_video, container, false);
RelativeLayout frameLayout = (RelativeLayout) view.findViewById(R.id.toplayout);
mChronometer = new Chronometer(getActivity());
// RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) frameLayout.getLayoutParams();//new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(50, 50, 0, 0);
mChronometer.setLayoutParams(layoutParams);
Typeface font = Typeface.createFromAsset(getActivity().getAssets(), "fonts/psans.ttf");
mChronometer.setTypeface(font, Typeface.NORMAL);
mChronometer.setTextSize(20);
mChronometer.setGravity(Gravity.CENTER_HORIZONTAL);
// mChronometer.setBackgroundColor(Color.BLACK);
mChronometer.setTextColor(Color.WHITE);
frameLayout.addView(mChronometer);
screenAlive();
return view;
}
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
mButtonVideo = (ImageView) view.findViewById(R.id.mRecordVideo);
mButtonVideo.setOnClickListener(this);
mSaveVideo = (ImageView) view.findViewById(R.id.mSaveVideo);
mSaveVideo.setOnClickListener(this);
// view.findViewById(R.id.info).setOnClickListener(this);
}
#Override
public void onResume() {
super.onResume();
Log.d("Testing","onResume !!!");
startBackgroundThread();
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
#Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.mRecordVideo: {
if (mIsRecordingVideo) {
stopRecordingVideo();
} else {
mChronometer.setBase(SystemClock.elapsedRealtime());
mChronometer.start();
startRecordingVideo();
}
break;
}
case R.id.mSaveVideo:
File destinationPath = new File(String.valueOf(getActivity().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)));
File file = new File(destinationPath.getAbsolutePath());
AppLogger.d( "Video destination Path:: "+file.toString()+ " "+Environment.getExternalStorageDirectory());
mOutputFile = getCurrentFile();
Log.d("Testing", "Output file path:: "+mOutputFile.getAbsolutePath());
// notificationBuilder.setProgress(PROGRESS_MAX, PROGRESS_CURRENT, false);
// notificationManager.notify(notificationID, notificationBuilder.build());
Uri videoURI = FileProvider.getUriForFile(getActivity(),
APPLICATION_ID + ".provider", mOutputFile);
Intent service = new Intent(getActivity(), VideoCompressionService.class);
// Add extras to the bundle
service.putExtra("videouri", videoURI);
// service.putExtra("destVideoPath", destVideoPath);
service.putExtra("destVideoPath", mOutputFile.getPath());
// Start the service
getActivity().startService(service);
getActivity().finish();
break;
}
}
protected File getCurrentFile() {
return mCurrentFile;
}
/**
* Starts a background thread and its {#link Handler}.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
* Stops the background thread and its {#link Handler}.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Tries to open a {#link CameraDevice}. The result is listened by `mStateCallback`.
*/
private void openCamera(int width, int height) {
final Activity activity = getActivity();
if (null == activity || activity.isFinishing()) {
return;
}
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
String cameraId = manager.getCameraIdList()[0];
// Choose the sizes for camera preview and video recording
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics
.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
Log.d("Testing", "Sensor Orientation: "+mSensorOrientation);
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, mVideoSize);
int orientation = getResources().getConfiguration().orientation;
Log.d("Testing", "normal Orientation: "+orientation);
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
configureTransform(width, height);
mMediaRecorder = new MediaRecorder();
if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
manager.openCamera(cameraId, mStateCallback, null);
} catch (CameraAccessException e) {
Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show();
activity.finish();
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
new ErrorDialog().show(getFragmentManager(), "dialog");
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.");
}
}
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mMediaRecorder) {
mMediaRecorder.release();
mMediaRecorder = null;
}
}
// catch (InterruptedException e) {
catch (Exception e) {
Log.d(TAG, "exception:: "+e.getMessage());
// throw new RuntimeException("Interrupted while trying to lock camera closing.");
} finally {
mCameraOpenCloseLock.release();
}
}
/**
* Start the camera preview.
*/
private void startPreview() {
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
setUpMediaRecorder();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<Surface>();
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
#Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
updatePreview();
}
#Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Activity activity = getActivity();
if (null != activity) {
Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
}
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Update the camera preview. {#link #startPreview()} needs to be called in advance.
*/
private void updatePreview() {
if (null == mCameraDevice) {
return;
}
try {
setUpCaptureRequestBuilder(mPreviewBuilder);
HandlerThread thread = new HandlerThread("CameraPreview");
thread.start();
mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
}
/**
* Configures the necessary {#link android.graphics.Matrix} transformation to `mTextureView`.
* This method should not to be called until the camera preview size is determined in
* openCamera, or until the size of `mTextureView` is fixed.
*
* #param viewWidth The width of `mTextureView`
* #param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight) {
Activity activity = getActivity();
if (null == mTextureView || null == mPreviewSize || null == activity) {
return;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
mTextureView.setTransform(matrix);
}
private void setUpMediaRecorder() throws IOException {
final Activity activity = getActivity();
if (null == activity) {
return;
}
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mCurrentFile = getVideoFile(activity);
mMediaRecorder.setOutputFile(getVideoFile(activity).getAbsolutePath());
mMediaRecorder.setVideoEncodingBitRate(2000000);
// mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
Log.d("Testing", "Captured rate::: 15");
// mMediaRecorder.setCaptureRate(15.03);
mMediaRecorder.setVideoSize(1280, 720);
// mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
Log.d("Testing", "rotation value:: "+rotation);
int orientation = ORIENTATIONS.get(rotation);
mMediaRecorder.setOrientationHint(orientation);
mMediaRecorder.prepare();
}
private int getOrientation(int rotation) {
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}
private File getVideoFile(Context context) {
return getOutputMediaFile();
}
private File getOutputMediaFile() {
// External sdcard file location
File mediaStorageDir = new File(String.valueOf(getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES)));
// new File(getE);
// Create storage directory if it does not exist
if (!mediaStorageDir.exists()) {
if (!mediaStorageDir.mkdirs()) {
// Log.d(TAG, "Oops! Failed create "+ VIDEO_DIRECTORY_NAME + " directory");
return null;
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
File mediaFile;
//String vidFileName = Prefs.getString("docid", "null")+"_"+ timeStamp;
mediaFile = new File(mediaStorageDir.getPath() + File.separator
+ Prefs.getString("docid", "null") + "_"+ timeStamp + ".mp4");
return mediaFile;
}
private void startRecordingVideo() {
try {
// UI
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mButtonVideo.setImageResource(R.drawable.ic_stop);
mSaveVideo.setVisibility(View.GONE);
}
});
mIsRecordingVideo = true;
// Start recording
if (flag == true){
closePreviewSession();
startPreview();
// setUpMediaRecorder();
// Log.d(TAG, "Media record stopped, setting up again");
// if (mTextureView.isAvailable()) {
// openCamera(mTextureView.getWidth(), mTextureView.getHeight());
// } else {
// mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
// }
// startPreview();
// setUpMediaRecorder();
}
mMediaRecorder.start();
} catch (Exception e) {
Log.d(TAG, "Exception:: "+e.getMessage());
e.printStackTrace();
}
}
private void closePreviewSession() {
if (mPreviewSession != null) {
mPreviewSession.close();
mPreviewSession = null;
}
}
private void stopRecordingVideo() {
// UI
mIsRecordingVideo = false;
mChronometer.stop();
mButtonVideo.setImageResource(R.drawable.ic_record);
mSaveVideo.setVisibility(View.VISIBLE);
try {
mMediaRecorder.stop();
mMediaRecorder.reset();
flag = true;
}
catch (Exception e) {
Log.d(TAG, "media recorder released "+e.getMessage());
e.printStackTrace();
}
}
/**
* Compares two {#code Size}s based on their areas.
*/
static class CompareSizesByArea implements Comparator<Size> {
#Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
public static class ErrorDialog extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
return new AlertDialog.Builder(activity)
.setMessage("This device doesn't support Camera2 API.")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
activity.finish();
}
})
.create();
}
}
}
The camera preview and recording is done in a fragment. Can some one please check and help me here.
Thanks,
Arindam.
You're setting MediaRecorder.setOrientationHint() based on purely the display orientation of the device.
If you want it to be based also on how the device is oriented in the real world (so that down in the video is played back as down), you need to add more code to listen to orientation sensors of the device.
You can see code that should work for MediaRecorder as well in the reference docs of JPEG orientation in the camera API:
https://developer.android.com/reference/android/hardware/camera2/CaptureRequest?hl=en#JPEG_ORIENTATION
which uses OrientationEventListener
private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) {
if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return 0;
int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
// Round device orientation to a multiple of 90
deviceOrientation = (deviceOrientation + 45) / 90 * 90;
// Reverse device orientation for front-facing cameras
boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;
if (facingFront) deviceOrientation = -deviceOrientation;
// Calculate desired JPEG orientation relative to camera orientation to make
// the image upright relative to the device orientation
int jpegOrientation = (sensorOrientation + deviceOrientation + 360) % 360;
return jpegOrientation;
}
I added the below code. Still it did not solve.
private int getVideoOrientation(CameraCharacteristics c, int deviceOrientation) {
if (deviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) return 0;
int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
// Round device orientation to a multiple of 90
deviceOrientation = (deviceOrientation + 45) / 90 * 90;
// Reverse device orientation for front-facing cameras
boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;
if (facingFront) deviceOrientation = -deviceOrientation;
// Calculate desired JPEG orientation relative to camera orientation to make
// the image upright relative to the device orientation
int videoOrientation = (sensorOrientation + deviceOrientation + 360) % 360;
return videoOrientation;
}
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
// Choose the sizes for camera preview and video recording
int orientation = getVideoOrientation(characteristics, rotation); // getResources().getConfiguration().orientation);
mMediaRecorder.setOrientationHint(orientation);
mMediaRecorder.prepare();

Face Tracker CameraSource Android: How to brighten front camera quality?

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!

Camera live preview freezes on camera switch

I am creating a custom camera app. The problem I am facing is that the camera preview freezes when I switch between the front and the back camera. When starting the activity with either the front or back camera by calling
boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK)
in the OnCreateView method of the fragment, both cameras will display on startup as expected. As soon as I call the same method in the on click listener of my switch button, the camera freezes immediately.
This is a new implementation which I tried where all the code is within the same fragment instead of a custom class based on the question here: Custom class camera live preview freezes on camera switch yet the result is exactly the same. I am pretty sure I need to do something with the surface view to bind it to the new camera but I am at a loss of how to do this. Anybody have some pointers?
My activity:
public class Camera2ActivityFragment extends Fragment {
// Native camera.
private Camera mCamera;
// View to display the camera output.
private CameraPreview mPreview;
// Reference to the containing view.
private View mCameraView;
/**
* Default empty constructor.
*/
public Camera2ActivityFragment(){
super();
}
/**
* Static factory method
* #param sectionNumber
* #return
*/
public static Camera2ActivityFragment newInstance(int sectionNumber) {
Camera2ActivityFragment fragment = new Camera2ActivityFragment();
//Bundle args = new Bundle();
//args.putInt(ARG_SECTION_NUMBER, sectionNumber);
//fragment.setArguments(args);
return fragment;
}
/**
* OnCreateView fragment override
* #param inflater
* #param container
* #param savedInstanceState
* #return
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_camera2, container, false);
boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK);
if(opened == false){
Log.d("CameraGuide","Error, Camera failed to open");
return view;
}
// Trap the capture button.
Button captureButton = (Button) view.findViewById(R.id.btnCameraStart);
captureButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
// get an image from the camera
mCamera.takePicture(null, null, mPicture);
}
}
);
Button switchCameraButton = (Button) view.findViewById(R.id.btnSwitchCamera);
switchCameraButton.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
safeCameraOpenInView(getView(), Camera.CameraInfo.CAMERA_FACING_FRONT); //ISSUE OCCURS HERE!
}
}
);
return view;
}
/**
* Recommended "safe" way to open the camera.
* #param view
* #return
*/
private boolean safeCameraOpenInView(View view, int camID) {
boolean qOpened = false;
releaseCameraAndPreview();
//mCamera = getCameraInstance(Camera.CameraInfo.CAMERA_FACING_BACK);
mCamera = getCameraInstance(camID);
mCameraView = view;
qOpened = (mCamera != null);
if(qOpened == true){
mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_view);
preview.addView(mPreview);
mPreview.startCameraPreview();
}
return qOpened;
}
/**
* Safe method for getting a camera instance.
* #return
*/
public static Camera getCameraInstance(int camID){
Camera c = null;
try {
c = Camera.open(camID); // attempt to get a Camera instance
}
catch (Exception e){
e.printStackTrace();
}
return c; // returns null if camera is unavailable
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
releaseCameraAndPreview();
}
/**
* Clear any existing preview / camera.
*/
private void releaseCameraAndPreview() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
if(mPreview != null){
mPreview.destroyDrawingCache();
mPreview.mCamera = null;
}
}
/**
* Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
* excellent StackOverflow answer provided below.
*
* Reference / Credit: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
*/
class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
// SurfaceHolder
private SurfaceHolder mHolder;
// Our Camera.
private Camera mCamera;
// Parent Context.
private Context mContext;
// Camera Sizing (For rotation, orientation changes)
private Camera.Size mPreviewSize;
// List of supported preview sizes
private List<Camera.Size> mSupportedPreviewSizes;
// Flash modes supported by this camera
private List<String> mSupportedFlashModes;
// View holding this camera.
private View mCameraView;
public CameraPreview(Context context, Camera camera, View cameraView) {
super(context);
// Capture the context
mCameraView = cameraView;
mContext = context;
setCamera(camera);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setKeepScreenOn(true);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* Begin the preview of the camera input.
*/
public void startCameraPreview()
{
try{
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
}
catch(Exception e){
e.printStackTrace();
}
}
/**
* Extract supported preview and flash modes from the camera.
* #param camera
*/
private void setCamera(Camera camera)
{
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
parameters.setRotation(90);
//parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
}
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
//mCam = Camera.open();
//mCam.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCamera != null){
mCamera.stopPreview();
}
}
/**
* React to surface changed events
* #param holder
* #param format
* #param w
* #param h
*/
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();
//mCamera.release();
} 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);
parameters.setRotation(90);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.enableShutterSound(true);
mCamera.startPreview();
} catch (Exception e){
//Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
/*
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null){
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
*/
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
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);
}
/**
* Update the layout based on rotation and orientation changes.
* #param changed
* #param left
* #param top
* #param right
* #param bottom
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
// Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
if (changed) {
final int width = right - left;
final int height = bottom - top;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null){
Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
switch (display.getRotation())
{
case Surface.ROTATION_0:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
mCamera.setDisplayOrientation(90);
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
/**
*
* #param sizes
* #param width
* #param height
* #return
*/
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
{
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) height / width;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = height;
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;
}
}
/**
* Picture Callback for handling a picture capture and saving it out to a file.
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = getOutputMediaFile();
if (pictureFile == null){
Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
.show();
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/**
* Used to return the camera File output.
* #return
*/
private File getOutputMediaFile(){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), "Pocket Booth");
if (! mediaStorageDir.exists()){
if (! mediaStorageDir.mkdirs()){
Log.d("Camera Guide", "Required media storage does not exist");
return null;
}
}
// Create a media file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
mediaFile = new File(mediaStorageDir.getPath() + File.separator +
"IMG_"+ timeStamp + ".jpg");
//DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());
return mediaFile;
}
}
Well, Here I'm going to write a tutorial for you about capturing an
image using Camera by enabling some common features of camera.
Step 1 : Create a preview class
/**
* A basic Camera preview class
*/
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context, Camera camera) {
super(context);
mCamera = camera;
// 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) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
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) {
// 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
startPreview();
}
public void resetCamera(Camera camera) {
mCamera = camera;
}
public void startPreview() {
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
Step 2 : Use FrameLayout to hold the preview.
<FrameLayout
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Step 3 : Get the camera and send to the preview class. you may need to set the parameters that you need prior passing the camera.
/**
* Create our Preview view and set it as the content of UI.
*/
private void initCameraPreview(final int cameraId, final boolean createPreview) {
mCamera = getCameraInstance(cameraId);
setupCameraParameters(cameraId);
if (createPreview) {
mPreview = new CameraPreview(this, mCamera);
mPreviewHolder.addView(mPreview);
}
mReadyToCapture = true;
}
/**
* A safe way to get an instance of the Camera object.
*/
private Camera getCameraInstance(int cameraId) {
Camera c = null;
try {
c = Camera.open(cameraId); // attempt to get a Camera instance
} catch (Exception e) {
e.printStackTrace();
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
/**
* Measure and Setup the camera parameters.
*/
private void setupCameraParameters(int cameraId) {
boolean hasFlash;
Camera.Parameters parameters = mCamera.getParameters();
mPreviewSize = determineBestPreviewSize(parameters);
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
Camera.Size bestPictureSize = determineBestPictureSize(parameters);
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
hasFlash = Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH);
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
hasFlash = Util.hasFrontCameraFlash(parameters);
} else {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
if (hasFlash)
parameters.setFlashMode(mFlashMode);
int[] orientations = Util.getCameraDisplayOrientation(this, cameraId);
mDisplayOrientation = orientations[0];
mLayoutOrientation = orientations[1];
mCamera.setDisplayOrientation(mDisplayOrientation);
mCamera.setParameters(parameters);
}
private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
return determineBestSize(sizes);
}
private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
return determineBestSize(sizes);
}
private Camera.Size determineBestSize(List<Camera.Size> sizes) {
Camera.Size bestSize = null;
for (Camera.Size currentSize : sizes) {
boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
boolean isInBounds = currentSize.width <= PICTURE_SIZE_MAX_WIDTH;
if (isDesiredRatio && isInBounds && isBetterSize) {
bestSize = currentSize;
}
}
if (bestSize == null) {
return sizes.get(0);
}
return bestSize;
}
Step 4 : Writing method for swapping camera
/**
* Swapping between system cameras
*/
private void swapCamera() {
if (!(Camera.getNumberOfCameras() > 1)) {
/* No front facing camera to switch.*/
return;
}
mReadyToCapture = false;
mCamera.stopPreview();
releaseCamera(false);
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
else
mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
initCameraPreview(mCurrentCameraId, false);
mPreview.resetCamera(mCamera);
mPreview.startPreview();
}
Step 5 : Method for toggling flash
/**
* Toggling camera flash to ON/OFF
*/
private void toggleFlash() {
if (Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH)) {
Camera.Parameters parameters = mCamera.getParameters();
if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
if (!Util.hasFrontCameraFlash(parameters)) {
/* Front facing camera doesn\'t supported flash. */
return;
}
}
mReadyToCapture = false;
if (Camera.Parameters.FLASH_MODE_ON.equals(parameters.getFlashMode())) {
mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
} else {
mFlashMode = Camera.Parameters.FLASH_MODE_ON;
}
mCameraHandler.post(new Runnable() {
#Override
public void run() {
mCamera.stopPreview();
releaseCamera(false);
initCameraPreview(mCurrentCameraId, false);
mPreview.resetCamera(mCamera);
mPreview.startPreview();
}
});
} else {
/* warning_no_flash */
}
}
Step 6: Methods for handling camera during the states changes of a screen
/**
* Release the camera for other applications
*/
private void releaseCamera(boolean remove) {
if (mCamera != null) {
if (remove)
mPreview.getHolder().removeCallback(mPreview);
mCamera.release();
mCamera = null;
}
}
Step 7: Utility classes.
/**
* Check whether the given feature available in s/m
*
* #return Returns true if the devices supports the feature, else
* false.
*/
public static boolean hasSystemFeature(Context context, String feature) {
return context.getPackageManager().hasSystemFeature(feature);
}
/**
* Check whether front camera flash feature available in s/m
*/
public static boolean hasFrontCameraFlash(Camera.Parameters cameraParameters) {
boolean result = true;
if (cameraParameters.getFlashMode() == null) {
result = false;
}
List<String> supportedFlashModes = cameraParameters.getSupportedFlashModes();
if (supportedFlashModes == null || supportedFlashModes.isEmpty()
|| supportedFlashModes.size() == 1 &&
supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
result = false;
}
return result;
}
/**
* Showing camera in the same orientation as the display
*/
public static int[] getCameraDisplayOrientation(Activity activity,
int cameraId) {
Camera.CameraInfo info =
new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = activity.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 result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
return new int[]{result, degrees};
}
Step 8: Capturing
// Get an image from the camera
if (null != mCamera && mReadyToCapture) {
mCameraOrientationListener.rememberOrientation();
mCamera.takePicture(mShutter, null, mPicture)
}
/**
* Camera shutter sound callback,
* used to enable sound while capture
*/
private Camera.ShutterCallback mShutter = new Camera.ShutterCallback() {
#Override
public void onShutter() {
}
};
/**
* Camera picture callback
*/
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
mReadyToCapture = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
int rotation = ((mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ? mDisplayOrientation :
((360 - mDisplayOrientation) % 360)) + mCameraOrientationListener.getRememberedOrientation()
+ mLayoutOrientation) % 360;
if (rotation != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
}
};
Step 9: Camera orientation listener for handling image rotation
/**
* Orientation listener to remember the device's orientation when the user presses
* the shutter button.
* <p/>
* The orientation will be normalized to return it in steps of 90 degrees
* (0, 90, 180, 270).
*/
public class CameraOrientationListener extends OrientationEventListener {
private int currentNormalizedOrientation;
private int rememberedNormalizedOrientation;
public CameraOrientationListener(Context context) {
super(context, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onOrientationChanged(int orientation) {
if (orientation != ORIENTATION_UNKNOWN) {
currentNormalizedOrientation = normalize(orientation);
}
}
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() {
rememberedNormalizedOrientation = currentNormalizedOrientation;
}
public int getRememberedOrientation() {
return rememberedNormalizedOrientation;
}
}
Step 10: States handling
#Override
public void onPause() {
super.onPause();
mReadyToCapture = false;
releaseCamera(true);
}
#Override
public void onResume() {
super.onResume();
removePreview();
mReadyToCapture = false;
smoothCameraLoading();
}
private void removePreview() {
mPreviewHolder.removeAllViews();
}
private void smoothCameraLoading() {
mCameraHandler.post(new Runnable() {
#Override
public void run() {
initCameraPreview(mCurrentCameraId, true);
}
});
}
Step 11: Instance variable used
private String mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
private int mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
private int mDisplayOrientation;
private int mLayoutOrientation;
private boolean mReadyToCapture = false;
private Camera.Size mPreviewSize;
private FrameLayout mPreviewHolder;
private Camera mCamera;
private CameraPreview mPreview;
private Handler mCameraHandler;
private CameraOrientationListener mCameraOrientationListener;
private FrameLayout mRootView;
In my camera app, it works with somethings different from yours:
1. Open camera when surfaceCreated.
2. StartPreview and no stopPreview in surfaceChanged.
3. Release camera in surfaceDestoryed.
To change the camera, simply set the surfaceView to INVISIBLE, then set your camera parameters, then set the surfaceView to VISIBLE again. In my app, I only do it like this:
mbtCamera.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(cameraMax > 1){
mSurfaceView.setVisibility(View.INVISIBLE);
if(mCameraParam.isZoomSupported()) cameraZoomRatios.clear();
if(cameraUsing == 0) cameraUsing = 1; else cameraUsing = 0;
mCameraOverlayView.setScaleFactor(1.0f);
mSurfaceView.setVisibility(View.VISIBLE);
}
}
});
Hope this will help!
The complete callback:
class CameraSurfaceHolderCallback implements SurfaceHolder.Callback {
#Override
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open(cameraUsing);
mCameraParam = mCamera.getParameters();
supportFlashMode = mCameraParam.getSupportedFlashModes();
if(supportFlashMode == null) mbtFlash.setVisibility(View.INVISIBLE);
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraUsing, info);
int rotation = 0;
if (info.facing == CameraInfo.CAMERA_FACING_FRONT){
rotation = (info.orientation - rt + 360) % 360;
if(mCameraParam.getSupportedFlashModes() == null) mbtFlash.setVisibility(View.INVISIBLE);
}
else{ // back-facing camera
rotation = (info.orientation + rt) % 360;
mbtFlash.setVisibility(View.VISIBLE);
if(flashMode.equals(Camera.Parameters.FLASH_MODE_AUTO)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_automatic);
else if(flashMode.equals(Camera.Parameters.FLASH_MODE_OFF)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_off);
else if(flashMode.equals(Camera.Parameters.FLASH_MODE_ON)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_on);
Matrix rot = new Matrix();
rot.setRotate(360-rt, icons[0].getWidth()/2, icons[0].getHeight()/2);
icons[0] = Bitmap.createBitmap(icons[0], 0, 0, icons[0].getWidth(), icons[0].getHeight(), rot, true);
mbtFlash.setImageBitmap(icons[0]);
mCameraParam.setFlashMode(flashMode);
}
if(mCameraParam.isZoomSupported()){
cameraZoomRatios = mCameraParam.getZoomRatios();
mCameraOverlayView.setMaxScaleFactor(cameraZoomRatios.get(mCameraParam.getMaxZoom())/100f);
}else mCameraOverlayView.setMaxScaleFactor(1.0f);
List<Size> ss = mCameraParam.getSupportedPictureSizes();
Size maxResolution = ss.get(0);
long pixel1, pixel2;
pixel1 = maxResolution.width * maxResolution.height;
for(int i=0; i<ss.size(); i++){
pixel2 = ss.get(i).width * ss.get(i).height;
if(pixel2 > pixel1){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
mCameraParam.setPictureSize(maxResolution.width, maxResolution.height);
mCameraParam.setJpegQuality(100);
LayoutParams rlParams = (LayoutParams) mSurfaceView.getLayoutParams();
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
Display dp = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
dp.getMetrics(dm);
float dpiScale = dm.density;
float wScaleFactor = (dm.widthPixels-10*dpiScale)/maxResolution.height;
float hScaleFactor = (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale)/maxResolution.width;
if(wScaleFactor < hScaleFactor){
rlParams.width = (int) (dm.widthPixels - 10*dpiScale);
rlParams.height = (int) (maxResolution.width * wScaleFactor);
}else{
rlParams.width = (int) (maxResolution.height * hScaleFactor);
rlParams.height = (int) (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale);
}
mSurfaceView.setLayoutParams(rlParams);
mCameraOverlayView.setLayoutParams(rlParams);
ss = mCameraParam.getSupportedJpegThumbnailSizes();
float photoAspectRatio, thumbAspectRatio;
photoAspectRatio = (float)maxResolution.width / maxResolution.height;
thumbAspectRatio = 0;
pixel1 = 0;
for(int i=0; i<ss.size(); i++){
if(ss.get(i).height != 0) thumbAspectRatio = (float)ss.get(i).width / ss.get(i).height;
if(thumbAspectRatio == photoAspectRatio){
if(pixel1 == 0)
{
maxResolution = ss.get(i);
pixel1 = ss.get(i).width * ss.get(i).height;
}else{
pixel2 = ss.get(i).width * ss.get(i).height;
if((pixel2 < pixel1)&&(pixel2 != 0)){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
}
}
if(pixel1 != 0){
mCameraParam.setJpegThumbnailSize(maxResolution.width, maxResolution.height);
mCameraParam.setJpegThumbnailQuality(100);
}
List<String> focusModes = mCameraParam.getSupportedFocusModes();
if(focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)){
mCameraParam.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
focusingColor = Color.YELLOW;
}
int minEv = mCameraParam.getMinExposureCompensation();
int maxEv = mCameraParam.getMaxExposureCompensation();
if((minEv == 0)&&(maxEv == 0)) mCameraOverlayView.setEVCompensate(false);
else mCameraOverlayView.setEVCompensate(minEv, maxEv);
mCameraParam.setRotation(rotation);
mCamera.setParameters(mCameraParam);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
try {
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mSurfaceView.getHolder());
List<Size> ss = mCameraParam.getSupportedPreviewSizes();
Size maxResolution = ss.get(0);
long pixel1, pixel2;
float photoAspectRatio, previewAspectRatio;
photoAspectRatio = (float)mCameraParam.getPictureSize().width / mCameraParam.getPictureSize().height;
previewAspectRatio = 0;
pixel1 = 0;
for(int i=0; i<ss.size(); i++){
if(ss.get(i).height != 0) previewAspectRatio = (float)ss.get(i).width / ss.get(i).height;
if(previewAspectRatio == photoAspectRatio){
if(pixel1 == 0)
{
maxResolution = ss.get(i);
pixel1 = ss.get(i).width * ss.get(i).height;
}else{
pixel2 = ss.get(i).width * ss.get(i).height;
if(pixel2 > pixel1){
maxResolution = ss.get(i);
pixel1 = pixel2;
}
}
}
}
if(pixel1 != 0) mCameraParam.setPreviewSize(maxResolution.width, maxResolution.height);
mCamera.setParameters(mCameraParam);
mCamera.startPreview();
}
catch(Exception e){}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
}
}
I used it in activity and no fragment. I am not sure this will make something different.
I think that there is a little problem in the safeCameraOpenInView method and that is:
Your new camera view is made under the frozen picture.
So you should edit this line: preview.addView(mPreview);
as this: preview.addView(mPreview, preview.getChildCount());
Or you can first delete the previous camera view and then add new one :
preview.removeAllViews();
preview.addView(mPreview);
Hope that this will work.

Android setFocusArea and Auto Focus

I've been battling with this feature for couple of days now...
It seems, that camera is ignoring(?) focus areas that I've defined. Here is snippets of the code:
Focusing:
protected void focusOnTouch(MotionEvent event) {
if (camera != null) {
Rect rect = calculateFocusArea(event.getX(), event.getY());
Parameters parameters = camera.getParameters();
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(Lists.newArrayList(new Camera.Area(rect, 500)));
camera.setParameters(parameters);
camera.autoFocus(this);
}
}
Focus area calculation:
private Rect calculateFocusArea(float x, float y) {
int left = clamp(Float.valueOf((x / getSurfaceView().getWidth()) * 2000 - 1000).intValue(), focusAreaSize);
int top = clamp(Float.valueOf((y / getSurfaceView().getHeight()) * 2000 - 1000).intValue(), focusAreaSize);
return new Rect(left, top, left + focusAreaSize, top + focusAreaSize);
}
Couple of log events from Camera.AutoFocusCallback#onAutoFocus
Log.d(TAG, String.format("Auto focus success=%s. Focus mode: '%s'. Focused on: %s",
focused,
camera.getParameters().getFocusMode(),
camera.getParameters().getFocusAreas().get(0).rect.toString()));
08-27 11:19:42.240: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-109, 643 - -13, 739)
08-27 11:19:55.514: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(20, 457 - 116, 553)
08-27 11:19:58.037: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-159, 536 - -63, 632)
08-27 11:20:00.129: DEBUG/MyCameraActivity(26268): Auto focus success=true. Focus mode: 'auto'. Focused on: Rect(-28, 577 - 68, 673)
Visually it looks like focus succeeds on logged area, but the suddenly it loses focus and focus on center (0, 0), or what takes bigger part of SurfaceView is obtained.
focusAreaSize used in calculation is about 210px (96dp).
Testing on HTC One where Camera.getParameters().getMaxNumFocusAreas() is 1.
Initial focus mode (before first tap) is set to FOCUS_MODE_CONTINUOUS_PICTURE.
Am I doing something wrong here?
Tinkering with Camera.Area rectangle size or weight doesn't show any noticeable effect.
My problem was much simpler :)
All I had to do is cancel previously called autofocus. Basically the correct order of actions is this:
protected void focusOnTouch(MotionEvent event) {
if (camera != null) {
camera.cancelAutoFocus();
Rect focusRect = calculateTapArea(event.getX(), event.getY(), 1f);
Rect meteringRect = calculateTapArea(event.getX(), event.getY(), 1.5f);
Parameters parameters = camera.getParameters();
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(Lists.newArrayList(new Camera.Area(focusRect, 1000)));
if (meteringAreaSupported) {
parameters.setMeteringAreas(Lists.newArrayList(new Camera.Area(meteringRect, 1000)));
}
camera.setParameters(parameters);
camera.autoFocus(this);
}
}
Update
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
...
Parameters p = camera.getParameters();
if (p.getMaxNumMeteringAreas() > 0) {
this.meteringAreaSupported = true;
}
...
}
/**
* Convert touch position x:y to {#link Camera.Area} position -1000:-1000 to 1000:1000.
*/
private Rect calculateTapArea(float x, float y, float coefficient) {
int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
int left = clamp((int) x - areaSize / 2, 0, getSurfaceView().getWidth() - areaSize);
int top = clamp((int) y - areaSize / 2, 0, getSurfaceView().getHeight() - areaSize);
RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
matrix.mapRect(rectF);
return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom));
}
private int clamp(int x, int min, int max) {
if (x > max) {
return max;
}
if (x < min) {
return min;
}
return x;
}
Beside setting:
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
you need to set:
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
if you want real 'live' auto-focus. Also, it will be good to check available focuses:
List<String> focusModes = parameters.getSupportedFocusModes();
LLog.d("focusModes=" + focusModes);
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
On Samsung S6 you must set this with little delay (~ 500 ms) after getting camera preview.
I had this problem today :/
And after hours of struggling, I found the solution!
It's strange, but it appears that setting focus-mode to "macro" right before setting focus-areas solved the problem ;)
params.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
params.setFocusAreas(focusAreas);
mCamera.setParameters(params);
I have Galaxy S3 with Android 4.1.2
I hope this will work for you either :)
use FOCUS_MODE_FIXED
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
mCamera = Camera.open(mCameraId);
} else {
mCamera = Camera.open();
}
cameraParams = mCamera.getParameters();
// set the focus mode
cameraParams.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
// set Camera parameters
mCamera.setParameters(cameraParams);
Hi, try below code copy and change for yourself
public class CameraActivity extends AppCompatActivity implements Camera.AutoFocusCallback {
private Camera camera;
private FrameLayout fl_camera_preview;
...
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.camera_activity );
//this View, is lens camera
fl_camera_preview = findViewById( R.id.fl_camera_preview );
Button someButtonCapturePicture = findViewById(R.id.someButtonCapturePicture);
pictureCall = getPictureCallback();
//check camera access
if ( getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) ) {
if ( safeCameraOpen(0) ) {
cameraPreview = new CameraPreview( this, camera );
fl_camera_preview.addView( cameraPreview );
someButtonCapturePicture.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
camera.takePicture(null, null, pictureCall);
}
});
} else {
Log.w(TAG, "getCameraInstance: Camera is not available (in use or does not exist)." );
}
}
}
private boolean safeCameraOpen(int id) {
boolean qOpened = false;
try {
camera = Camera.open( id );
// set some parameters
Camera.Parameters par = camera.getParameters();
List<Camera.Size> supportedPreviewSizes = par.getSupportedPreviewSizes();
for ( Camera.Size cs : supportedPreviewSizes ) {
if ( cs.height == 720 ) {
par.setPictureSize(cs.width, cs.height);
par.setPreviewSize(cs.width, cs.height);
break;
}
}
camera.setParameters(par);
qOpened = ( camera != null );
} catch (Exception e) {
Log.e(TAG, "safeCameraOpen: failed to open Camera");
e.printStackTrace();
}
return qOpened;
}
public void touchFocusCamera( final Rect touchFocusRect ) {
//Convert touche coordinate, in width and height to -/+ 1000 range
final Rect targetFocusRect = new Rect(
touchFocusRect.left * 2000/fl_camera_preview.getWidth() - 1000,
touchFocusRect.top * 2000/fl_camera_preview.getHeight() - 1000,
touchFocusRect.right * 2000/fl_camera_preview.getWidth() - 1000,
touchFocusRect.bottom * 2000/fl_camera_preview.getHeight() - 1000);
final List<Camera.Area> focusList = new ArrayList<Camera.Area>();
Camera.Area focusArea = new Camera.Area(targetFocusRect, 1000);
focusList.add(focusArea);
Camera.Parameters para = camera.getParameters();
List<String> supportedFocusModes = para.getSupportedFocusModes();
if ( supportedFocusModes != null &&
supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO) ) {
try {
para.setFocusAreas(focusList);
para.setMeteringAreas(focusList);
camera.setParameters(para);
camera.autoFocus( CameraActivity.this );
} catch (Exception e) {
Log.e(TAG, "handleFocus: " + e.getMessage() );
}
}
}
#Override
public void onAutoFocus(boolean success, Camera camera) {
if ( success ) {
camera.cancelAutoFocus();
}
float focusDistances[] = new float[3];
camera.getParameters().getFocusDistances(focusDistances);
}
/**
* Get Bitmap from camera
* #return picture
*/
private Camera.PictureCallback getPictureCallback() {
Camera.PictureCallback picture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.i(TAG, "onPictureTaken: size bytes photo: " + data.length );
}
};
return picture;
}
...
}
//And SurfaceView with Callback
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
SurfaceHolder holder;
Camera camera;
public CameraPreview( Context context, Camera _camera ) {
super(context);
camera = _camera;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
holder = getHolder();
holder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, now tell the camera where to draw the preview.
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if( event.getAction() == MotionEvent.ACTION_DOWN ) {
// Get the pointer's current position
float x = event.getX();
float y = event.getY();
float touchMajor = event.getTouchMajor();
float touchMinor = event.getTouchMinor();
Rect touchRect = new Rect(
(int)(x - touchMajor/2),
(int)(y - touchMinor/2),
(int)(x + touchMajor/2),
(int)(y + touchMinor/2));
((CameraActivity)getContext())
.touchFocusCamera( touchRect );
}
return true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 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 (this.holder.getSurface() == null) {
// preview surface does not exist
return;
}
// stop preview before making changes
try {
camera.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.setPreviewDisplay(this.holder);
camera.startPreview();
} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
...
}

Efficiently load 5 larger than fullscreen Bitmaps into my Android Live Wallpaper and scroll them parralax

So I'm new to Java and this kind of coding.
I'm trying to make a Parrallax scrolling Live Wallpaper. But I'm having memory issues.
Well I have made it, and it works on on the phone I have. But I think the way i have done it is not very efficient at all. Because when I try it on other phones it doesn't work. It breaks at my Out Of Memory catcher. I added another layer and now it does the same thing on my phone too. So I am able to debug it. Basically I guess I'm using up-to or over 16 meg of memory.
If someone could take a look at my code and help me load in the bitmaps more efficiently that would be greatly appreciated.
Here is how I'm currently doing it:
static class Layer {
public Bitmap bitmap;
private float scale = 1.0f;
private Matrix matrix = new Matrix();
public Layer(Bitmap b) {
this.bitmap = b;
}
public void setScale(float factor) {
scale = factor;
}
public Matrix getMatrix(float x, float y) {
if (scale == 1) {
matrix.reset();
} else {
matrix.setScale(scale, scale);
}
matrix.postTranslate(x, y);
return matrix;
}
}
public static List<Integer> findLayers(Integer path) {
List<Integer> files = new ArrayList<Integer>();
files.add(R.drawable.planet_layer4);
files.add(R.drawable.planet_layer3);
files.add(R.drawable.planet_layer2);
files.add(R.drawable.planet_layer1);
files.add(R.drawable.planet_layer0);
return files;
}
private void loadLayers() {
try {
clearLayers();
for (Integer file: layerFiles) {
addLayer(file);
}
recalibrateLayers();
} catch (IOException e) {
layers.clear();
Toast.makeText(LiveWallpaper.this, "There was a problem loading the wallpaper. Please contact the developer.", Toast.LENGTH_LONG).show();
} catch (OutOfMemoryError oom) {
layers.clear();
Toast.makeText(LiveWallpaper.this, "Whoops, we ran out of memory trying to load the images. ", Toast.LENGTH_LONG).show();
}
}
private void addLayer(int name) throws IOException {
Bitmap layer = BitmapFactory.decodeResource(getResources(), name);
if (layer == null) {
throw new IOException("BitmapFactory couldn't decode asset " + name);
}
synchronized(layers) {
layers.add(new Layer(layer));
}
}
private void clearLayers() {
synchronized(layers) {
layers.clear();
}
}
private void recalibrateLayers() {
for (Layer layer : layers) {
final int bitmapHeight = layer.bitmap.getHeight();
layer.setScale((float)mHeight / (float)bitmapHeight);
}
}
#Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mDrawParallax);
}
#Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
mHeight = height;
recalibrateLayers();
drawBackgrounds();
}
And here is where I draw them.
/*
* Draw one frame of the animation. This method gets called repeatedly
* by posting a delayed Runnable. You can do any drawing you want in
* here.
*/
void drawBackgrounds() {
final SurfaceHolder holder = getSurfaceHolder();
final Rect frame = holder.getSurfaceFrame();
mFrame = frame;
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
// draw something
drawParallax(c);
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
}
void drawParallax(Canvas c) {
int frameWidth = mFrame.width();
for (int i=layers.size()-1; i>=0; i--) {
Layer layer = layers.get(i);
Bitmap bitmap = layer.bitmap;
float bitmapWidth = bitmap.getWidth() * layer.scale;
float max = frameWidth - bitmapWidth;
float offset = mOffset * max;
final Matrix m = layer.getMatrix(offset, 0);
c.drawBitmap(bitmap, m, null);
}
}

Categories

Resources