I am writing an Android plugin that will take data from the camera preview and send it to Unity. (For various reasons, I am not able to use the Unity WebCamTexture) I am able to get the camera preview data and sent it to Unity, however, the image is always rotated 90 degrees. My Unity app is set to always be in portrait mode.
On my Pixel XL, the front and back images are rotated in opposite directions. Here is a photo of my app when using the front and back facing cameras.
I have created the function AdjustOrientation in NativeCamera.java in an attempt to fix the orientation, but calling it does not seem to to have any effect.
I have found links to places that say to add in code similar to what is in the AdjustOrientation function to fix the problem, but none of them have solved the problem.
Here is a link I have investigated that did not solve my problem.
Android - Camera preview is sideways
I have tried different variations of
mCamera.setDisplayOrientation()
params.setRotation()
but have not had any luck.
I only need the image to be in the correct orientation in
public void onPreviewFrame(byte[] data, Camera camera)
It does not matter if a saved image would be upside down or rotated. I am simply passing the data on to my Unity Project and the only purpose of the Android Plugin is to pass the camera data to Unity. As long as the image data is correct inside OnPreviewFrame I would be all set.
I know there is a newer Camera2 API and that it deprecates the original Camera, but I would really like to be able to fix this with my existing plugin without having to write a new plugin that uses Camera2.
Here is a link to my project.
https://drive.google.com/open?id=1MD-NRVf0YhhVIiRUOiBptvSwh9wtK3V7
Thanks in advance.
John Lawrie
Updates. Here is the source of my .java file for easier reference. If you look in AdjustOrientation you can see I have some code to try to adjust displayorientation. It appears to have no effect.
package com.test.camerapreview;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.support.annotation.Dimension;
import android.util.Log;
import android.util.Size;
import android.view.Display;
import android.view.Surface;
import com.unity3d.player.UnityPlayer;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class NativeCamera implements Camera.PreviewCallback {
public static NativeCamera instance;
public static String gameObjectTargetName;
private static Activity myActivity;
Camera mCamera;
SurfaceTexture texture;
int nativeTexturePointer = -1;
int prevHeight;
int prevWidth;
//
// Call this function first.
//
public static void Setup(String gameObjectName, Activity theActivity){
gameObjectTargetName = gameObjectName;
myActivity = theActivity;
instance = new NativeCamera();
}
public int startCamera(int idx, int width, int height) {
nativeTexturePointer = createExternalTexture();
texture = new SurfaceTexture(nativeTexturePointer);
mCamera = Camera.open(idx);
setupCamera(idx, width, height);
try {
mCamera.setPreviewTexture(texture);
mCamera.setPreviewCallback(this);
AdjustOrientation(width, height);
mCamera.startPreview();
Log.i("Unity", "JAVA: camera started");
} catch (IOException ioe) {
Log.w("Unity", "JAVA: CAM LAUNCH FAILED");
}
return nativeTexturePointer;
}
public void stopCamera() {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
Log.i("Unity", "JAVA: Camera stopped");
}
private int createExternalTexture() {
int[] textureIdContainer = new int[1];
GLES20.glGenTextures(1, textureIdContainer, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIdContainer[0]);
return textureIdContainer[0];
}
#SuppressLint("NewApi")
private void setupCamera(int cameraID, int width, int height) {
Camera.Parameters params = mCamera.getParameters();
params.setRecordingHint(true);
// params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
params.setPreviewFormat(17);
params.setZoom(0);
// 16 ~ NV16 ~ YCbCr
// 17 ~ NV21 ~ YCbCr ~ DEFAULT *
// 4 ~ RGB_565
// 256~ JPEG
// 20 ~ YUY2 ~ YcbCr ...
// 842094169 ~ YV12 ~ 4:2:0 YCrCb comprised of WXH Y plane, W/2xH/2 Cr & Cb. see documentation *
// params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
Camera.Size previewSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPreviewSizes());
int previewWidth = previewSize.width;
int previewHeight = previewSize.height;
params.setPictureSize(previewWidth, previewHeight);
params.setPreviewSize(previewWidth, previewHeight);
params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
params.setExposureCompensation(0);
//
// Fix the orientation.
//
// int orientation = detectCameraDisplayOrientation(cameraID);
// mCamera.setDisplayOrientation(orientation);
// params.setRotation(orientation);
// Camera.CameraInfo info = new Camera.CameraInfo();
// Camera.getCameraInfo(cameraID, info);
// Log.d("Unity", "info.orientation = " + info.orientation);
// params.setRotation(-90);
// mCamera.setDisplayOrientation(-90);
// mCamera.setDisplayOrientation(90);
// params.setRotation(90);
try{
mCamera.setParameters(params);
} catch (Exception e){
Log.i("Unity", "ERROR: " + e.getMessage());
}
Camera.Size mCameraPreviewSize = params.getPreviewSize();
prevWidth = mCameraPreviewSize.width;
prevHeight = mCameraPreviewSize.height;
int[] fpsRange = new int[2];
params.getPreviewFpsRange(fpsRange);
String previewFacts = mCameraPreviewSize.width + "x" + mCameraPreviewSize.height;
if (fpsRange[0] == fpsRange[1]) {
previewFacts += " #" + (fpsRange[0] / 1000.0) + "fps";
} else {
previewFacts += " #[" + (fpsRange[0] / 1000.0) + " - " + (fpsRange[1] / 1000.0) + "] fps";
}
Log.i("Unity", "JAVA: previewFacts=" + previewFacts);
}
private void AdjustOrientation(int width, int height) {
Camera.Parameters parameters = mCamera.getParameters();
Display display = myActivity.getWindowManager().getDefaultDisplay();
if(display.getRotation() == Surface.ROTATION_0) {
Camera.Size previewSize = getOptimalSize(height, width, mCamera.getParameters().getSupportedPreviewSizes());
prevWidth = previewSize.width;
prevHeight = previewSize.height;
mCamera.setDisplayOrientation(90);
}
else if(display.getRotation() == Surface.ROTATION_90) {
Camera.Size previewSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPreviewSizes());
prevWidth = previewSize.width;
prevHeight = previewSize.height;
}
else if(display.getRotation() == Surface.ROTATION_180) {
Camera.Size previewSize = getOptimalSize(height, width, mCamera.getParameters().getSupportedPreviewSizes());
prevWidth = previewSize.width;
prevHeight = previewSize.height;
}
else { //if(display.getRotation() == Surface.ROTATION_270) {
Camera.Size previewSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPreviewSizes());
prevWidth = previewSize.width;
prevHeight = previewSize.height;
mCamera.setDisplayOrientation(180);
}
parameters.setPreviewSize(prevWidth, prevHeight);
mCamera.setParameters(parameters);
}
private Camera.Size getOptimalSize(int width, int height, List<Camera.Size> sizes) {
if(mCamera == null)
return null;
final double ASPECT_TOLERANCE = 0.1;
double targetRatio=(double)width / height;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetWidth = width;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
Log.i("Unity", "RES: size=" + size.width + "/" + size.height + " Aspect Ratio: " + ratio + " target width: " + width + " target height: " + height);
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
continue;
}
if (Math.abs(size.width - targetWidth) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.width - targetWidth);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.width - targetWidth) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.width - targetWidth);
}
}
}
Log.i("Unity", "optimal size=" + optimalSize.width + "/" + optimalSize.height + "/ Aspect Ratio: " + (double) optimalSize.width / optimalSize.height);
return optimalSize;
}
public int getPreviewSizeWidth() {
return prevWidth;
}
public int getPreviewSizeHeight() {
return prevHeight;
}
public String GetPreviewSizes(int id) {
Camera cam = Camera.open(id);
Camera.Parameters params = cam.getParameters();
Gson gson = new Gson();
String JSON = gson.toJson(params.getSupportedPreviewSizes());
cam.release();
Log.d("Unity", "Supported sizes are " + JSON);
return JSON;
}
public byte[] bytes;
#Override
public void onPreviewFrame(byte[] data, Camera camera) {
bytes = data;
UnityPlayer.UnitySendMessage(gameObjectTargetName, "GetBuffer", "");
}
}
Despite spending a lot of time trying to get the camera to rotate the image with SetDisplayOrientation, for some reason none of that had any effect. I ended up doing what Alex Cohen suggested and put manually rotated the image in OnPreviewFrame
This is a link to the page that gives the code to how to rotate the image.
Rotate an YUV byte array on Android
in camera preview... set `mCamera.setPreviewDisplay(holder);
mCamera = Camera.open(0);
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
mCamera.setDisplayOrientation(90);`
Camera.setDisplayOrientation only affects the preview output that's passed to Camera.setPreviewDisplay or Camera.setPreviewTexture. That's documented in the API reference for setDisplayOrientation:
This does not affect the order of byte array passed in onPreviewFrame(byte[], Camera), JPEG pictures, or recorded videos.
If you need to adjust the output of onPreviewFrame, then you need to do it yourself. The rotation you need to apply is the same value you'd pass to setDisplayOrientation, in the clockwise direction.
Related
Normally the android camera api with a PreviewCallback freezes the camera after a picture is taken and this can be used as an approval screen for the image taken. My problem is this is not the behavior on phones like the samsung S6 and S6 edge. those two phones continue with the live preview after the picture is taken.
The pictures taken are still good and can be used for later use but the preview isnt showing, and the way my app is set up, i have a check and an x mark for the user to approve what they just took a photo of. Instead they have this overlay for check and "x" and a live preview on the background. And this only happens on the samsung s6/s6 edge.
Any idea what can cause an issue like this on specific devices?
Code used for preview and picture taking with some logging everywhere:
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;
private List<Camera.Size> mSupportPictureSizes;
// 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);
Log.d(TAG, "CameraPreview: ");
// 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()
{
Log.d(TAG, "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: http://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
mCamera = camera;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mSupportPictureSizes = mCamera.getParameters().getSupportedPictureSizes();
for(Camera.Size size : mSupportedPreviewSizes){
Log.d(TAG, "supportedPreviewSizes width: " + size.width + " x " + size.height);
}
for(Camera.Size size : mSupportPictureSizes){
Log.d(TAG, "mSupportPictureSizes width: " + size.width + " x " + size.height);
}
mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();
Camera.Parameters parameters = mCamera.getParameters();
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// Set the camera to Auto Flash mode.
if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
}
mCamera.setParameters(parameters);
requestLayout();
}
/**
* The Surface has been created, now tell the camera where to draw the preview.
* #param holder
*/
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated: ");
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Dispose of the camera preview.
* #param holder
*/
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed: ");
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) {
// 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
Log.d(TAG, "surfaceChanged: preview surface does not exist");
return;
}
Log.d(TAG, "surfaceChanged: start & stop preview");
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or
// reformatting changes here
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
public SurfaceHolder getHolder() {
return super.getHolder();
}
/**
* Calculate the measurements of the layout
* #param widthMeasureSpec
* #param heightMeasureSpec
*/
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Source: http://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){
Log.d(TAG, "onMeasure: w x h: " + width + " x " + height);
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, 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)
{
Log.d(TAG, "onLayout: ");
// Source: http://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);
Log.d(TAG, "onLayout: rotation0");
break;
case Surface.ROTATION_90:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
Log.d(TAG, "onLayout: rotation90");
break;
case Surface.ROTATION_180:
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
Log.d(TAG, "onLayout: rotation180");
break;
case Surface.ROTATION_270:
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
mCamera.setDisplayOrientation(180);
Log.d(TAG, "onLayout: rotation270");
break;
}
}
final int scaledChildHeight = previewHeight * width / previewWidth;
mCameraView.layout(0, height - scaledChildHeight, width, height);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
Log.d(TAG, "getOptimalPreviewSize: ");
final double ASPECT_TOLERANCE = 0.05;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE){
continue;
}
if (Math.abs(size.height - targetHeight) < minDiff) {
Log.d(TAG, "getOptimalPreviewSize: found a match");
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
Log.d(TAG, "getOptimalPreviewSize: couldn't find match");
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
Log.d(TAG, "getOptimalPreviewSize: " + optimalSize.width + " x HEIGHT: " + optimalSize.height);
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) {
Camera.Parameters parameters = camera.getParameters();
Camera.Size mPictureTakenSize = parameters.getPictureSize();
System.out.println("picture taken size: " + mPictureTakenSize.width + " x " + mPictureTakenSize.height);
if(data!=null){
Log.d(TAG, "onPictureTaken: data not null, setting data to currentData");
// currentData = data;
setPictureTakenData(data);
cameraViewModel.setPreview(true);
}
else{
onProgress = false;
Log.d(TAG, "onPictureTaken: data is null");
}
}
};
I just ran into the same issue. It looks like you can fix it by manually calling camera.stopPreview() in the onPictureTaken method of the PictureCallback.
The camera will not stop preview automatically after picture taken, so you need call mCamera.stopPreview() manually:
if (mCamera != null) {
try {
mCamera.stopPreview();
pauseFlash();
} catch (Exception e) {
e.printStackTrace();
}
}
// if current camera flash mode is TORCH, you need pause it.
private void pauseFlash() {
if (currentFlashMode.equals(Camera.Parameters.FLASH_MODE_TORCH)) {
params = mCamera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
mCamera.setParameters(params);
}
}
And then, after do something and navigate back to camera preview:
#Override
public void startCameraPreview() {
if (mCamera != null) {
try {
mCamera.startPreview();
resumeFlash();
} catch (Exception e) {
e.printStackTrace();
// TAKE CARE IF YOUR CAMERA HAS BEEN RELEASED
}
}
}
void resumeFlash() {
flashChange(currentFlashMode);
}
And I have some performance tips: You should find the best picture size and preview size at the first time user use your app, store those in SharedPreferences and you can use after without re-caculate.
Hope this help.
I am using custom camera and working fine but the issue is image is saving with very low (poor) quality. To overcome with it , i have tried all suggestions and implementations. Like ,
parameters.setJpegQuality(100);
parameters.setPictureFormat(ImageFormat.JPEG);
this is not working. After that i have used
List<Size> sizes = cameraParams.getSupportedPictureSizes();
Camera.Size size = sizes.get(0);
for(int i=0;i<sizes.size();i++)
{
if(sizes.get(i).width > size.width)
size = sizes.get(i);
}
cameraParams.setPictureSize(mPictureSize.width, mPictureSize.height);
This is also not working. Its saving with poor quality still.
Note : Camera preview is showing proper with good quality but the issue is when saving captured image to sdcard folder.
Advanced help would be appreciated!!
Finally my issue solved.
Here I was setting parameters for camera preview before i was capturing the image
public void takePicture() {
mCamera.takePicture(new ShutterCallback() {
#Override
public void onShutter() {
}
}, new PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
}
}, new PictureCallback() {
#Override
public void onPictureTaken(final byte[] data, Camera camera) {
data1 = data;
if (mCamera != null) {
mCamera.stopPreview();
}
}
});
}
So before i called this function in my fragment i have set parameters before this method.
mPreview.setParams(params);// This was the mistake what i have done !
mPreview.takePicture();
finally solved after removing mPreview.setParams(params);
I can show you methods for resetting preview size:
You should change your parameters of preview at Camera.
private void setImageSize() {
Camera.Size size = CameraUtil.findClosetImageSize(mRxCamera.getNativeCamera(), PREFER_SIZE);
mCamera.getNativeCamera().getParameters().setPictureSize(size.width, size.height);
WIDTH = size.width;
HEIGHT = size.height;
}
and after You need to change layout sizes
private void resetPreviewSize() {
final boolean widthIsMax = mWidth > mHeight;
final Camera.Size size = mCamera.getNativeCamera().getParameters().getPreviewSize();
final RectF rectDisplay = new RectF();
final RectF rectPreview = new RectF();
rectDisplay.set(0, 0, mWidth, mHeight);
final int width = widthIsMax ? size.width : size.height;
final int height = widthIsMax ? size.height : size.width;
rectPreview.set(0, 0, width, height);
final Matrix matrix = new Matrix();
matrix.setRectToRect(rectDisplay, rectPreview, Matrix.ScaleToFit.START);
matrix.invert(matrix);
matrix.mapRect(rectPreview);
mCameraView.getLayoutParams().height = (int) (rectPreview.bottom);
mCameraView.getLayoutParams().width = (int) (rectPreview.right);
mCameraView.requestLayout();
}
and if you need
public static Camera.Size findClosetImageSize(Camera camera, Point preferSize) {
int preferX = preferSize.x;
int preferY = preferSize.y;
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> allSupportSizes = parameters.getSupportedPictureSizes();
Log.d(TAG, "all support Image size: " + dumpPreviewSizeList(allSupportSizes));
int minDiff = Integer.MAX_VALUE;
int index = 0;
for (int i = 0; i < allSupportSizes.size(); i++) {
Camera.Size size = allSupportSizes.get(i);
int x = size.width;
int y = size.height;
int diff = Math.abs(x - preferX) + Math.abs(y - preferY);
if (diff < minDiff) {
minDiff = diff;
index = i;
}
}
return allSupportSizes.get(index);
}
I am trying to create custom camera preview logic that actually works properly, for all scenarios:
any device: phone, tablet
any camera: front-facing, rear-facing
android.hardware.Camera
My android:minSdkVersion is 14 and android:targetSdkVersion is 21.
I have implemented custom camera preview class to set display orientation for camera preview and working very well for all devices without only Nexus devices. Nexus devices have I think default 180 orientation.
While I am starting my camera in Nexus devices it is showing inverted. To overcome with i have checked with Build.MANUFACTURER & Build.MODEL to identify the device and set orientation according to it.
if (Build.MODEL.equals("Nexus 6P") && Build.MANUFACTURER.equals("Huawei")) mCamera.setDisplayOrientation(90);
else mCamera.setDisplayOrientation(270);
But it is not working. So can anyone have idea to overcome with this and recommended way !! Advance help would be appreciated !!
As far as I know this problem doesn't occur on Nexus 5. I had to deal with it on a Nexus 5X and I lost some time trying(Click here to see why). As a big note, I can't guarantee that it's the best solution, but it fixed all the problems I had. To solve it I did something like this:
I created a class CameraPreview extends SurfaceView only to encapsulate all the preview initialisation in one place. Here is the constructor for that class:
public CameraPreview(Context context, int screenRotation, Camera camera) {
super(context);
mCamera = camera;
mScreenRotation = screenRotation;
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
}
To create the preview I used this: mPreview = new CameraPreview(getContext(), screenRotation, mCamera);, where screenRotation is int screenRotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
When the surfaceCreated callback is called:
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(holder);
setCameraDisplayOrientation(mScreenRotation, 0, mCamera);
Here,0 comes from the cameraId (it might be different if you're using the front facing camera). And here is all the magic:
public void setCameraDisplayOrientation(int screenRotation, int cameraId, Camera camera) {
int rotation = getRotationAngle(screenRotation, cameraId);
camera.setDisplayOrientation(rotation);
}
public static int getRotationAngle(int screenRotation, int cameraId) {
Camera.CameraInfo info = new Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int degrees = 0;
switch (screenRotation) {
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 result;
}
I hope that this will fix the problem, but before starting to write it, make sure that you read the link I provided to see why the problem takes place. Hope this will fix it for you too.
To initiate in activity :
CameraPreviewNew mPreview = new ResizableCameraPreview(this, cameraId, CameraPreviewNew.LayoutMode.NoBlank, false, screenHeight, screenWidth); // cameraId for front or rear
LinearLayout.LayoutParams previewLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
frameCamera.addView(mPreview, 0, previewLayoutParams);
CameraPreviewNew.java
public class CameraPreviewNew extends SurfaceView implements SurfaceHolder.Callback {
private static boolean DEBUGGING = false;
private static final String LOG_TAG = "CameraPreviewSample";
private static final String CAMERA_PARAM_ORIENTATION = "orientation";
private static final String CAMERA_PARAM_LANDSCAPE = "landscape";
private static final String CAMERA_PARAM_PORTRAIT = "portrait";
protected Activity mActivity;
private SurfaceHolder mHolder;
protected Camera mCamera;
protected List<Camera.Size> mPreviewSizeList;
protected List<Camera.Size> mPictureSizeList;
protected Camera.Size mPreviewSize;
protected Camera.Size mPictureSize;
private int mSurfaceChangedCallDepth = 0;
private int mCameraId;
private LayoutMode mLayoutMode;
private int mCenterPosX = -1;
private int mCenterPosY;
private int screenHeight, screenWidth;
PreviewReadyCallback mPreviewReadyCallback = null;
public enum LayoutMode {
FitToParent, // Scale to the size that no side is larger than the parent
NoBlank // Scale to the size that no side is smaller than the parent
}
public interface PreviewReadyCallback {
void onPreviewReady();
}
/**
* State flag: true when surface's layout size is set and surfaceChanged()
* process has not been completed.
*/
protected boolean mSurfaceConfiguring = false;
public CameraPreviewNew(Activity activity, int cameraId, LayoutMode mode, int screenHeight, int screenWidth) {
super(activity); // Always necessary
mActivity = activity;
mLayoutMode = mode;
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// mHolder.setFixedSize(fixWidth, fixHeight);
// FileLog.v("Camera ID ::::::::::: " + cameraId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
if (Camera.getNumberOfCameras() > cameraId) {
mCameraId = cameraId;
} else {
mCameraId = 0;
}
} else {
mCameraId = 0;
}
// FileLog.d("Camera ID ::::::::::: " + cameraId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
mCamera = Camera.open(mCameraId);
} else {
mCamera = Camera.open();
}
Camera.Parameters cameraParams = mCamera.getParameters();
mPreviewSizeList = cameraParams.getSupportedPreviewSizes();
mPictureSizeList = cameraParams.getSupportedPictureSizes();
// FileLog.d("Preview Size ID ::::::::::: " + mPreviewSizeList);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
mCamera.release();
mCamera = null;
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mSurfaceChangedCallDepth++;
doSurfaceChanged(width, height);
mSurfaceChangedCallDepth--;
}
public void doSurfaceChanged(int width, int height) {
mCamera.stopPreview();
Camera.Parameters cameraParams = mCamera.getParameters();
boolean portrait = isPortrait();
// The code in this if-statement is prevented from executed again when surfaceChanged is
// called again due to the change of the layout size in this if-statement.
if (!mSurfaceConfiguring) {
Camera.Size previewSize = determinePreviewSize(portrait, width, height);
Camera.Size pictureSize = determinePictureSize(previewSize);
if (DEBUGGING) { Log.v(LOG_TAG, "Desired Preview Size - w: " + width + ", h: " + height); }
mPreviewSize = previewSize;
mPictureSize = pictureSize;
mSurfaceConfiguring = adjustSurfaceLayoutSize(previewSize, portrait, width, height);
// Continue executing this method if this method is called recursively.
// Recursive call of surfaceChanged is very special case, which is a path from
// the catch clause at the end of this method.
// The later part of this method should be executed as well in the recursive
// invocation of this method, because the layout change made in this recursive
// call will not trigger another invocation of this method.
if (mSurfaceConfiguring && (mSurfaceChangedCallDepth <= 1)) {
return;
}
}
configureCameraParameters(cameraParams, portrait);
mSurfaceConfiguring = false;
try {
mCamera.startPreview();
} catch (Exception e) {
Log.w(LOG_TAG, "Failed to start preview: " + e.getMessage());
// Remove failed size
mPreviewSizeList.remove(mPreviewSize);
mPreviewSize = null;
// Reconfigure
if (mPreviewSizeList.size() > 0) { // prevent infinite loop
surfaceChanged(null, 0, width, height);
} else {
Log.w(LOG_TAG, "Gave up starting preview");
}
}
if (null != mPreviewReadyCallback) {
mPreviewReadyCallback.onPreviewReady();
}
}
/**
* #param portrait
* #param reqWidth must be the value of the parameter passed in surfaceChanged
* #param reqHeight must be the value of the parameter passed in surfaceChanged
* #return Camera.Size object that is an element of the list returned from Camera.Parameters.getSupportedPreviewSizes.
*/
protected Camera.Size determinePreviewSize(boolean portrait, int reqWidth, int reqHeight) {
// Meaning of width and height is switched for preview when portrait,
// while it is the same as user's view for surface and metrics.
// That is, width must always be larger than height for setPreviewSize.
int reqPreviewWidth; // requested width in terms of camera hardware
int reqPreviewHeight; // requested height in terms of camera hardware
if (portrait) {
reqPreviewWidth = reqHeight;
reqPreviewHeight = reqWidth;
} else {
reqPreviewWidth = reqWidth;
reqPreviewHeight = reqHeight;
}
if (DEBUGGING) {
Log.v(LOG_TAG, "Listing all supported preview sizes");
for (Camera.Size size : mPreviewSizeList) {
Log.v(LOG_TAG, " w: " + size.width + ", h: " + size.height);
}
Log.v(LOG_TAG, "Listing all supported picture sizes");
for (Camera.Size size : mPictureSizeList) {
Log.v(LOG_TAG, " w: " + size.width + ", h: " + size.height);
}
}
// Adjust surface size with the closest aspect-ratio
float reqRatio = ((float) reqPreviewWidth) / reqPreviewHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
Camera.Size retSize = null;
for (Camera.Size size : mPreviewSizeList) {
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
retSize = size;
}
}
retSize = mPreviewSizeList.get(0);
return retSize;
}
protected Camera.Size determinePictureSize(Camera.Size previewSize) {
Camera.Size retSize = null;
for (Camera.Size size : mPictureSizeList) {
if (size.equals(previewSize)) {
return size;
}
}
if (DEBUGGING) { Log.v(LOG_TAG, "Same picture size not found."); }
// if the preview size is not supported as a picture size
float reqRatio = ((float) previewSize.width) / previewSize.height;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
for (Camera.Size size : mPictureSizeList) {
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
retSize = size;
}
}
retSize = mPictureSizeList.get(0);
return retSize;
}
protected boolean adjustSurfaceLayoutSize(Camera.Size previewSize, boolean portrait,
int availableWidth, int availableHeight) {
float tmpLayoutHeight, tmpLayoutWidth;
if (portrait) {
tmpLayoutHeight = previewSize.width;
tmpLayoutWidth = previewSize.height;
} else {
tmpLayoutHeight = previewSize.height;
tmpLayoutWidth = previewSize.width;
}
float factH, factW, fact;
factH = availableHeight / tmpLayoutHeight;
factW = availableWidth / tmpLayoutWidth;
if (mLayoutMode == LayoutMode.FitToParent) {
// Select smaller factor, because the surface cannot be set to the size larger than display metrics.
if (factH < factW) {
fact = factH;
} else {
fact = factW;
}
} else {
if (factH < factW) {
fact = factW;
} else {
fact = factH;
}
}
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)this.getLayoutParams();
int layoutHeight = (int) (tmpLayoutHeight * fact);
int layoutWidth = (int) (tmpLayoutWidth * fact);
if (DEBUGGING) {
Log.v(LOG_TAG, "Preview Layout Size - w: " + layoutWidth + ", h: " + layoutHeight);
Log.v(LOG_TAG, "Scale factor: " + fact);
}
boolean layoutChanged;
if ((layoutWidth != this.getWidth()) || (layoutHeight != this.getHeight())) {
int diffHeight = (screenHeight - layoutHeight) / 2;
layoutParams.height = layoutHeight + diffHeight;
int diffWidth = (screenWidth - layoutWidth) / 2;
layoutParams.width = layoutWidth + diffWidth;
if (mCenterPosX >= 0) {
layoutParams.topMargin = mCenterPosY - (layoutHeight / 2);
layoutParams.leftMargin = mCenterPosX - (layoutWidth / 2);
}
this.setLayoutParams(layoutParams); // this will trigger another surfaceChanged invocation.
layoutChanged = true;
} else {
layoutChanged = false;
}
return layoutChanged;
}
/**
* #param x X coordinate of center position on the screen. Set to negative value to unset.
* #param y Y coordinate of center position on the screen.
*/
public void setCenterPosition(int x, int y) {
mCenterPosX = x;
mCenterPosY = y;
}
protected void configureCameraParameters(Camera.Parameters cameraParams, boolean portrait) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { // for 2.1 and before
if (portrait) {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_PORTRAIT);
} else {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_LANDSCAPE);
}
} else { // for 2.2 and later
int angle;
Display display = mActivity.getWindowManager().getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: // This is display orientation
angle = 90; // This is camera orientation
break;
case Surface.ROTATION_90:
angle = 0;
break;
case Surface.ROTATION_180:
angle = 270;
break;
case Surface.ROTATION_270:
angle = 180;
break;
default:
angle = 90;
break;
}
Log.v(LOG_TAG, "angle: " + angle);
mCamera.setDisplayOrientation(angle);
}
cameraParams.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
cameraParams.setPictureSize(mPictureSize.width, mPictureSize.height);
cameraParams.setZoom(0);
// if (cameraParams.isZoomSupported()) {
final int maxZoomLevel = cameraParams.getMaxZoom();
Log.e("max ZOOM ", "is " + maxZoomLevel);
// }
// cameraParams.setPreviewSize(fixWidth, fixHeight);
// cameraParams.setPictureSize(fixWidth, fixHeight);
if (DEBUGGING) {
Log.v(LOG_TAG, "Preview Actual Size - w: " + mPreviewSize.width + ", h: " + mPreviewSize.height);
Log.v(LOG_TAG, "Picture Actual Size - w: " + mPictureSize.width + ", h: " + mPictureSize.height);
}
mCamera.setParameters(cameraParams);
}
#SuppressWarnings("unused")
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.2;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
Log.d("Camera", "Checking size " + size.width + "w " + size.height
+ "h");
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the
// requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
stop();
}
public void stop() {
try {
if (null == mCamera) {
return;
}
if(mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}catch (Exception e){
e.printStackTrace();
}
}
public boolean isPortrait() {
return (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
}
public void setOneShotPreviewCallback(PreviewCallback callback) {
if (null == mCamera) {
return;
}
mCamera.setOneShotPreviewCallback(callback);
}
public void setPreviewCallback(PreviewCallback callback) {
if (null == mCamera) {
return;
}
mCamera.setPreviewCallback(callback);
}
public Camera.Size getPreviewSize() {
return mPreviewSize;
}
public void setOnPreviewReady(PreviewReadyCallback cb) {
mPreviewReadyCallback = cb;
}
public Camera getPreviewCamera() {
return mCamera;
}
}
ResizableCameraPreview.java
public class ResizableCameraPreview extends CameraPreviewNew {
private static boolean DEBUGGING = false;
private static final String LOG_TAG = "ResizableCameraPreviewSample";
/**
* #param activity
* #param addReversedSizes is set to true to add reversed values of supported preview-sizes to the list.
*/
public ResizableCameraPreview(Activity activity, int cameraId, LayoutMode mode, boolean addReversedSizes, int screenHeight, int screenWidth) {
super(activity, cameraId, mode, screenHeight, screenWidth);
if (addReversedSizes) {
List<Camera.Size> sizes = mPreviewSizeList;
int length = sizes.size();
for (int i = 0; i < length; i++) {
Camera.Size size = sizes.get(i);
Camera.Size revSize = mCamera.new Size(size.height, size.width);
sizes.add(revSize);
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mCamera.stopPreview();
Camera.Parameters cameraParams = mCamera.getParameters();
boolean portrait = isPortrait();
if (!mSurfaceConfiguring) {
Camera.Size previewSize = determinePreviewSize(portrait, width, height);
Camera.Size pictureSize = determinePictureSize(previewSize);
Log.v(LOG_TAG, "Desired Preview Size - w: " + width + ", h: " + height);
mPreviewSize = previewSize;
mPictureSize = pictureSize;
mSurfaceConfiguring = adjustSurfaceLayoutSize(previewSize, portrait, width, height);
if (mSurfaceConfiguring) {
return;
}
}
configureCameraParameters(cameraParams, portrait);
mSurfaceConfiguring = false;
try {
mCamera.startPreview();
} catch (Exception e) {
Log.w(LOG_TAG, "Failed to start preview: " + e.getMessage());
}
}
/**
*
* #param index selects preview size from the list returned by CameraPreview.getSupportedPreivewSizes().
* #param width is the width of the available area for this view
* #param height is the height of the available area for this view
*/
public void setPreviewSize(int index, int width, int height) {
mCamera.stopPreview();
Camera.Parameters cameraParams = mCamera.getParameters();
boolean portrait = isPortrait();
Camera.Size previewSize = mPreviewSizeList.get(index);
Camera.Size pictureSize = determinePictureSize(previewSize);
if (DEBUGGING) { Log.v(LOG_TAG, "Requested Preview Size - w: " + previewSize.width + ", h: " + previewSize.height); }
mPreviewSize = previewSize;
mPictureSize = pictureSize;
boolean layoutChanged = adjustSurfaceLayoutSize(previewSize, portrait, width, height);
if (layoutChanged) {
mSurfaceConfiguring = true;
return;
}
configureCameraParameters(cameraParams, portrait);
try {
mCamera.startPreview();
} catch (Exception e) {
}
mSurfaceConfiguring = false;
}
public List<Camera.Size> getSupportedPreivewSizes() {
return mPreviewSizeList;
}
}
Xml file :
<FrameLayout
android:id="#+id/frm"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:id="#+id/frameCamera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</FrameLayout>
i created a camera app based on tutorial. the preview class i use is from api-Demos "CameraPreview". I added a modification from here (preview was always rotated by 90°). So this is how i set preview size:
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// Now that the size is known, set up the camera parameters and begin
// the preview.
Camera.Parameters parameters = mCamera.getParameters();
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if (display.getRotation() == Surface.ROTATION_0) {
parameters.setPreviewSize(mPreviewSize.height, mPreviewSize.width);
mCamera.setDisplayOrientation(90);
}
if (display.getRotation() == Surface.ROTATION_90) {
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
}
if (display.getRotation() == Surface.ROTATION_180) {
parameters.setPreviewSize(mPreviewSize.height, mPreviewSize.width);
}
if (display.getRotation() == Surface.ROTATION_270) {
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setDisplayOrientation(180);
}
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
}
But the Preview is displayed with wrong aspect ratio. Is it because of the code above or probably because of the layout i use?:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="#+id/button_capture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="#string/capture" />
<FrameLayout
android:id="#+id/camera_preview"
android:layout_width="100dp"
android:layout_height="match_parent"/>
So how to get the correct aspect ratio? Thanks in advance.
P.S. i read the answer from: Android camera preview look strange
But this isn't working for me.
Try changing the preview sizes with adding this function:
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.05;
double targetRatio = (double) w/h;
if (sizes==null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Find size
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
And the setting the sizes from these optimized values:
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Camera.Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
The following code modifies the width/height of the camera preview container to match the aspect ratio of the camera preview.
Camera.Size size = camera.getParameters().getPreviewSize();
//landscape
float ratio = (float)size.width/size.height;
//portrait
//float ratio = (float)size.height/size.width;
preview = (FrameLayout) findViewById(R.id.camera_preview);
int new_width=0, new_height=0;
if(preview.getWidth()/preview.getHeight()<ratio){
new_width = Math.round(preview.getHeight()*ratio);
new_height = cameraPreview.getHeight();
}else{
new_width = preview.getWidth();
new_height = Math.round(preview.getWidth()/ratio);
}
preview.setLayoutParams(new FrameLayout.LayoutParams(new_width, new_height));
Using the above solution, using the method private Size getOptimalPreviewSize(List sizes, int w, int h).
Worked fine! I was having problems with the aspect ratio on portrait orientation: Here it´s my solution using. Mixing it with android's documentation:
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
Camera.Parameters params = mCamera.getParameters();
params.set("orientation", "portrait");
Size optimalSize=getOptimalPreviewSize(params.getSupportedPreviewSizes(), getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
params.setPreviewSize(optimalSize.width, optimalSize.height);
mCamera.setParameters(params);
// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
}
problem is really in way you layout things.
there is overriden onLayout in Preview class. idea of its work is to set child SurfaceView size according to found optimal Size. but it doesn't take rotation into account, so you need to do it by yourself:
if (mPreviewSize != null) {
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
}
instead of
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
Trick is to swap width and height which is done because of 90 degree rotation achieved by
mCamera.setDisplayOrientation(90);
You might also consider forking child preview size setting depending on orientation that you set in
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
//...
}
(in provided by me code it is always for 90 degree rotation, for 180 you don't have to do anyhing and when you don't set any rotation, there is no need to swap width and height)
Another thing worth mentioning - when calculating getOptimalPreviewSize for case when you have rotation and you swap child width and height you also should pass parent(Preview) width and height swapped in onMeasure:
if (mSupportedPreviewSizes != null) {
//noinspection SuspiciousNameCombination
final int previewWidth = height;
//noinspection SuspiciousNameCombination
final int previewHeight = width;
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, previewWidth, previewHeight);
}
Henric's answer didn't work for me, so I've created another method which determines the optimal preview size for any camera given the target view current width and height and also the activity orientation:
public static Size getOptimalPreviewSize(List<Camera.Size> cameraPreviewSizes, int targetWidth, int targetHeight, boolean isActivityPortrait) {
if (CommonUtils.isEmpty(cameraPreviewSizes)) {
return null;
}
int optimalHeight = Integer.MIN_VALUE;
int optimalWidth = Integer.MIN_VALUE;
for (Camera.Size cameraPreviewSize : cameraPreviewSizes) {
boolean isCameraPreviewHeightBigger = cameraPreviewSize.height > cameraPreviewSize.width;
int actualCameraWidth = cameraPreviewSize.width;
int actualCameraHeight = cameraPreviewSize.height;
if (isActivityPortrait) {
if (!isCameraPreviewHeightBigger) {
int temp = cameraPreviewSize.width;
actualCameraWidth = cameraPreviewSize.height;
actualCameraHeight = temp;
}
} else {
if (isCameraPreviewHeightBigger) {
int temp = cameraPreviewSize.width;
actualCameraWidth = cameraPreviewSize.height;
actualCameraHeight = temp;
}
}
if (actualCameraWidth > targetWidth || actualCameraHeight > targetHeight) {
// finds only smaller preview sizes than target size
continue;
}
if (actualCameraWidth > optimalWidth && actualCameraHeight > optimalHeight) {
// finds only better sizes
optimalWidth = actualCameraWidth;
optimalHeight = actualCameraHeight;
}
}
Size optimalSize = null;
if (optimalHeight != Integer.MIN_VALUE && optimalWidth != Integer.MIN_VALUE) {
optimalSize = new Size(optimalWidth, optimalHeight);
}
return optimalSize;
}
This uses a custom Size object, because Android's Size is available after API 21.
public class Size {
private int width;
private int height;
public Size(int width, int height) {
this.width = width;
this.height = height;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
You can determine the width and height of a view by listening for its global layout changes and then you can set the new dimensions. This also shows how to programmatically determine activity orientation:
cameraPreviewLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// gets called after layout has been done but before display.
cameraPreviewLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
boolean isActivityPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
Size optimalCameraPreviewSize = CustomUtils.getOptimalPreviewSize(cameraPreview.getCameraSizes(), cameraPreviewLayout.getWidth(), cameraPreviewLayout.getHeight(), isActivityPortrait);
if (optimalCameraPreviewSize != null) {
LinearLayout.LayoutParams cameraPreviewLayoutParams = new LinearLayout.LayoutParams(optimalCameraPreviewSize.getWidth(), optimalCameraPreviewSize.getHeight());
cameraPreviewLayout.setLayoutParams(cameraPreviewLayoutParams);
}
}
});
I want to get a full screen portrait camera preview. Using Google's sample, the preview is in landscape format across the portrait screen. When I set to the preview to 480 x 864, the reverse of working landscape dimensions, I get a setParameters failed error.
I have read all the StackOverflow I can find, but I can't figure why dimensions supported in landscape fail in portrait. Nor can I find a way to force full screen portrait preview. The Camera app on the phone provides full screen portrait preview so I know it is not a hardware limitation.
I'm testing with a Droid 3 running Android 2.3.3
I would appreciate any suggestions.
public class SimplePreview extends ViewGroup implements SurfaceHolder.Callback {
private final String TAG = "Preview";
SurfaceView mSurfaceView;
SurfaceHolder mHolder;
Size mPreviewSize;
List<Size> mSupportedPreviewSizes;
Camera mCamera;
int cameraId;
Activity activity;
public SimplePreview(Activity context, int defaultCamera) {
super(context);
activity = context;
cameraId = defaultCamera;
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void setCamera(Camera camera) {
mCamera = camera;
if (mCamera != null) {
mSupportedPreviewSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
requestLayout();
}
}
public void switchCamera(Camera camera) {
setCamera(camera);
try {
camera.setPreviewDisplay(mHolder);
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
camera.setParameters(parameters);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We purposely disregard child measurements because act as a
// wrapper to a SurfaceView that centers the camera preview instead
// of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
Log.i(TAG, "setting View measured dimensions to width: " + width
+ " height: " + height);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
height);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null) {
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
}
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth) {
final int scaledChildWidth = previewWidth * height
/ previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width
/ previewWidth;
child.layout(0, (height - scaledChildHeight) / 2, width,
(height + scaledChildHeight) / 2);
}
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return, so stop the preview.
if (mCamera != null) {
mCamera.stopPreview();
}
}
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
Log.v(TAG, " width: " + size.width + " height: " + size.height);
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
Log.i(TAG, "optimal preview width: " + optimalSize.width + " height: "
+ optimalSize.height);
return optimalSize;
}
public void surfaceCreated(SurfaceHolder holder) {
// The Surface has been created, acquire the camera and tell it where
// to draw.
try {
if (mCamera != null) {
final int width = getWidth();
final int height = getHeight();
Log.i(TAG, "view width: " + width + " height: " + height);
if (height > width) {
Log.i(TAG, "in portrait mode so rotate camera preview");
// THis line fixed the camera display orientation. seems to
// have to be called before setPreviewDisplay()
mCamera.setDisplayOrientation(90);
}
mCamera.setPreviewDisplay(holder);
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}
/**
* orientation and rotation work done here. Now that the size is known, set
* up the camera parameters and begin the preview.
*/
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mCamera != null) {
mCamera.stopPreview();
Camera.Parameters parameters = mCamera.getParameters();
// mCamera.setDisplayOrientation(90);
final int width = getWidth();
final int height = getHeight();
Log.i(TAG, "view width: " + width + " height: " + height);
if (height > width) {
Log.i(TAG, "portrait: setting preview width: " + 480
+ " height: " + 864);
parameters.setPreviewSize(480, 864);
// had no effect
parameters.set("orientation", "portrait");
} else {
Log.i(TAG, "landscape: setting preview width: "
+ mPreviewSize.width + " height: "
+ mPreviewSize.height);
parameters.setPreviewSize(mPreviewSize.width,
mPreviewSize.height);
}
requestLayout();
// *** following line throws setParameters failed error ***
mCamera.setParameters(parameters);
mCamera.startPreview();
}
}
}
And the call from the Activity's onCreate():
preview = new SimplePreview(this, defaultCameraId);
setContentView(preview);
try this code:
package com.example.dragme;
import java.io.IOException;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.os.Build;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
public Camera mCamera;
private static boolean DEBUGGING = true;
private static final String LOG_TAG = "CameraPreviewSample";
private static final String CAMERA_PARAM_ORIENTATION = "orientation";
private static final String CAMERA_PARAM_LANDSCAPE = "landscape";
private static final String CAMERA_PARAM_PORTRAIT = "portrait";
protected Activity mActivity;
protected List<Camera.Size> mPreviewSizeList;
protected List<Camera.Size> mPictureSizeList;
protected Camera.Size mPreviewSize;
protected Camera.Size mPictureSize;
public CameraPreview(Context context, Camera camera) {
super(context);
mActivity=(Activity)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("CameraView", "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
try {
Camera.Parameters cameraParams = mCamera.getParameters();
boolean portrait = isPortrait();
configureCameraParameters(cameraParams, portrait);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d("CameraView", "Error starting camera preview: " + e.getMessage());
}
}
public void onPause() {
if (null == mCamera) {
return;
}
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
protected void configureCameraParameters(Camera.Parameters cameraParams, boolean portrait) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { // for 2.1 and before
if (portrait) {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_PORTRAIT);
} else {
cameraParams.set(CAMERA_PARAM_ORIENTATION, CAMERA_PARAM_LANDSCAPE);
}
} else { // for 2.2 and later
int angle;
Display display = mActivity.getWindowManager().getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: // This is display orientation
angle = 90; // This is camera orientation
break;
case Surface.ROTATION_90:
angle = 0;
break;
case Surface.ROTATION_180:
angle = 270;
break;
case Surface.ROTATION_270:
angle = 180;
break;
default:
angle = 90;
break;
}
Log.v(LOG_TAG, "angle: " + angle);
mCamera.setDisplayOrientation(angle);
}
cameraParams.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
cameraParams.setPictureSize(mPictureSize.width, mPictureSize.height);
if (DEBUGGING) {
Log.v(LOG_TAG, "Preview Actual Size - w: " + mPreviewSize.width + ", h: " + mPreviewSize.height);
Log.v(LOG_TAG, "Picture Actual Size - w: " + mPictureSize.width + ", h: " + mPictureSize.height);
}
mCamera.setParameters(cameraParams);
}
public boolean isPortrait() {
return (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT);
}
}