I've been working on making my custom camera activity on Android, but when rotating the camera, the aspect ratio of the surface view gets messed up.
In my oncreate for the activity, I set the framelayout which holds the surface view that displays the camera's parameters.
//FrameLayout that will hold the camera preview
FrameLayout previewHolder = (FrameLayout) findViewById(R.id.camerapreview);
//Setting camera's preview size to the best preview size
Size optimalSize = null;
camera = getCameraInstance();
double aspectRatio = 0;
if(camera != null){
//Setting the camera's aspect ratio
Camera.Parameters parameters = camera.getParameters();
List<Size> sizes = parameters.getSupportedPreviewSizes();
optimalSize = CameraPreview.getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
aspectRatio = (float)optimalSize.width/optimalSize.height;
}
if(optimalSize!= null){
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
previewHolder.setLayoutParams(params);
LayoutParams surfaceParams = new LayoutParams(LayoutParams.MATCH_PARENT, (int)(getResources().getDisplayMetrics().widthPixels*aspectRatio));
cameraPreview.setLayoutParams(surfaceParams);
}
cameraPreview.setCamera(camera);
//Adding the preview to the holder
previewHolder.addView(cameraPreview);
Then, in the Surface view I set the camera's parameters to be displayed
public void setCamera(Camera camera) {
if (mCamera == camera) { return; }
mCamera = camera;
if (mCamera != null) {
requestLayout();
try {
mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
e.printStackTrace();
}
if(mCamera != null){
//Setting the camera's aspect ratio
Camera.Parameters parameters = mCamera.getParameters();
List<Size> sizes = parameters.getSupportedPreviewSizes();
Size optimalSize = getOptimalPreviewSize(sizes, getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels);
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
mCamera.setParameters(parameters);
}
/*
Important: Call startPreview() to start updating the preview surface. Preview must
be started before you can take a picture.
*/
mCamera.startPreview();
}
}
You can see that the LEGO man grows taller and skinnier when the phone is rotated:
How can I ensure that the aspect ratio for my camera view is correct?
I'm using this method -> based on API Demos to get my Preview Size:
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio=(double)h / w;
if (sizes == null) return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
As you can see you have to input width and height of your screen. This method will calculate screen ratio based on those values and then from the list of supportedPreviewSizes it will choose the best for you from avaliable ones. Get your supportedPreviewSize list in place where Camera object isn't null by using
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
And then on in onMeasure you can get your optimal previewSize like that:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
}
And then (in my code in surfaceChanged method, like I said I'm using API Demos structure of CameraActivity code, you can generate it in Eclipse):
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();
And one hint for you, because I did almost the same app like you. Good practice for Camera Activity is to hide StatusBar. Applications like Instagram are doing it. It reduces your screen height value and change your ratio value. It is possible to get strange Preview Sizes on some devices (your SurfaceView will be cut a little)
And to answer your question, how to check if your preview ratio is correct? Then get height and width of parameters that you set in:
mCamera.setParameters(parameters);
your set ratio is equal to height/width. If you want camera to look good on your screen then height/width ratio of parameters that you set to camera must be the same as height(minus status bar)/width ratio of your screen.
F1Sher's solution is nice but sometimes doesn't work. Particularly, when your surfaceView doesn't cover whole screen. In this case you need to override onMeasure() method.
I have copied my code here for your reference.
Since I measured surfaceView based on width then I have little bit white gap at the end of my screen that I filled it by design. You are able to fix this issue if you keep height and increase width by multiply it to ratio. However, it will squish surfaceView slightly.
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private Context mContext;
private SurfaceHolder mHolder;
private Camera mCamera;
private List<Camera.Size> mSupportedPreviewSizes;
private Camera.Size mPreviewSize;
public CameraPreview(Context context, Camera camera) {
super(context);
mContext = context;
mCamera = camera;
// supported preview sizes
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
for(Camera.Size str: mSupportedPreviewSizes)
Log.e(TAG, str.width + "/" + str.height);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
// empty. surfaceChanged will take care of stuff
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
// set preview size and make any resize, rotate or reformatting changes here
// start preview with new settings
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
if (mPreviewSize!=null) {
float ratio;
if(mPreviewSize.height >= mPreviewSize.width)
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
else
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
// One of these methods should be used, second method squishes preview slightly
setMeasuredDimension(width, (int) (width * ratio));
// setMeasuredDimension((int) (width * ratio), height);
}
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
NOTE: MY SOLUTION IS A CONTINUATION OF HESAM'S SOLUTION: https://stackoverflow.com/a/22758359/1718734
What I address: Hesam's said there is a little white space that may appear on some phones, like this:
Hesam suggested a second solution, but that squishes the preview. And on some devices, it heavily distorts.
So how do we fix this problem. It is simple...by multiplying the aspect ratios till it fills in the screen. I have noticed, several popular apps such as Snapchat, WhatsApp, etc works the same way.
All you have to do is add this to the onMeasure method:
float camHeight = (int) (width * ratio);
float newCamHeight;
float newHeightRatio;
if (camHeight < height) {
newHeightRatio = (float) height / (float) mPreviewSize.height;
newCamHeight = (newHeightRatio * camHeight);
Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
} else {
newCamHeight = camHeight;
setMeasuredDimension(width, (int) newCamHeight);
Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
}
This will calculate the screen height and gets the ratio of the screen height and the mPreviewSize height. Then it multiplies the camera's width and height by the new height ratio and the set the measured dimension accordingly.
And the next thing you know, you end up with this :D
This also works well with he front camera. I believe this is the best way to go about this. Now the only thing left for my app is to save the preview itself upon clicking on "Capture." But ya, this is it.
OK, so I think there is no sufficient answer for general camera preview stretching problem. Or at least I didn't find one. My app also suffered this stretching syndrome and it took me a while to puzzle together a solution from all the user answers on this portal and internet.
I tried #Hesam's solution but it didn't work and left my camera preview majorly distorted.
First I show the code of my solution (the important parts of the code) and then I explain why I took those steps. There is room for performance modifications.
Main activity xml layout:
<RelativeLayout
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/camera_preview"
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
Camera Preview:
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder prHolder;
private Camera prCamera;
public List<Camera.Size> prSupportedPreviewSizes;
private Camera.Size prPreviewSize;
#SuppressWarnings("deprecation")
public YoCameraPreview(Context context, Camera camera) {
super(context);
prCamera = camera;
prSupportedPreviewSizes = prCamera.getParameters().getSupportedPreviewSizes();
prHolder = getHolder();
prHolder.addCallback(this);
prHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
public void surfaceCreated(SurfaceHolder holder) {
try {
prCamera.setPreviewDisplay(holder);
prCamera.startPreview();
} catch (IOException e) {
Log.d("Yologram", "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (prHolder.getSurface() == null){
return;
}
try {
prCamera.stopPreview();
} catch (Exception e){
}
try {
Camera.Parameters parameters = prCamera.getParameters();
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
parameters.setPreviewSize(prPreviewSize.width, prPreviewSize.height);
prCamera.setParameters(parameters);
prCamera.setPreviewDisplay(prHolder);
prCamera.startPreview();
} catch (Exception e){
Log.d("Yologram", "Error starting camera preview: " + e.getMessage());
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
if (prSupportedPreviewSizes != null) {
prPreviewSize =
getOptimalPreviewSize(prSupportedPreviewSizes, width, height);
}
}
public Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
}
Main activity:
public class MainActivity extends Activity {
...
#SuppressLint("NewApi")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
maCamera = getCameraInstance();
maLayoutPreview = (FrameLayout) findViewById(R.id.camera_preview);
maPreview = new CameraPreview(this, maCamera);
Point displayDim = getDisplayWH();
Point layoutPreviewDim = calcCamPrevDimensions(displayDim,
maPreview.getOptimalPreviewSize(maPreview.prSupportedPreviewSizes,
displayDim.x, displayDim.y));
if (layoutPreviewDim != null) {
RelativeLayout.LayoutParams layoutPreviewParams =
(RelativeLayout.LayoutParams) maLayoutPreview.getLayoutParams();
layoutPreviewParams.width = layoutPreviewDim.x;
layoutPreviewParams.height = layoutPreviewDim.y;
layoutPreviewParams.addRule(RelativeLayout.CENTER_IN_PARENT);
maLayoutPreview.setLayoutParams(layoutPreviewParams);
}
maLayoutPreview.addView(maPreview);
}
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
private Point getDisplayWH() {
Display display = this.getWindowManager().getDefaultDisplay();
Point displayWH = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(displayWH);
return displayWH;
}
displayWH.set(display.getWidth(), display.getHeight());
return displayWH;
}
private Point calcCamPrevDimensions(Point disDim, Camera.Size camDim) {
Point displayDim = disDim;
Camera.Size cameraDim = camDim;
double widthRatio = (double) displayDim.x / cameraDim.width;
double heightRatio = (double) displayDim.y / cameraDim.height;
// use ">" to zoom preview full screen
if (widthRatio < heightRatio) {
Point calcDimensions = new Point();
calcDimensions.x = displayDim.x;
calcDimensions.y = (displayDim.x * cameraDim.height) / cameraDim.width;
return calcDimensions;
}
// use "<" to zoom preview full screen
if (widthRatio > heightRatio) {
Point calcDimensions = new Point();
calcDimensions.x = (displayDim.y * cameraDim.width) / cameraDim.height;
calcDimensions.y = displayDim.y;
return calcDimensions;
}
return null;
}
}
My commentary:
The point of all this is, that although you calculate the optimal camera size in getOptimalPreviewSize() you only pick the closest ratio to fit your screen. So unless the ratio is exactly the same the preview will stretch.
Why will it stretch? Because your FrameLayout camera preview is set in layout.xml to match_parent in width and height. So that is why the preview will stretch to full screen.
What needs to be done is to set camera preview layout width and height to match the chosen camera size ratio, so the preview keeps its aspect ratio and won't distort.
I tried to use the CameraPreview class to do all the calculations and layout changes, but I couldn't figure it out. I tried to apply this solution, but SurfaceView doesn't recognize getChildCount () or getChildAt (int index). I think, I got it working eventually with a reference to maLayoutPreview, but it was misbehaving and applied the set ratio to my whole app and it did so after first picture was taken. So I let it go and moved the layout modifications to the MainActivity.
In CameraPreview I changed prSupportedPreviewSizes and getOptimalPreviewSize() to public so I can use it in MainActivity. Then I needed the display dimensions (minus the navigation/status bar if there is one) and chosen optimal camera size. I tried to get the RelativeLayout (or FrameLayout) size instead of display size, but it was returning zero value. This solution didn't work for me. The layout got it's value after onWindowFocusChanged (checked in the log).
So I have my methods for calculating the layout dimensions to match the aspect ratio of chosen camera size. Now you just need to set LayoutParams of your camera preview layout. Change the width, height and center it in parent.
There are two choices how to calculate the preview dimensions. Either you want it to fit the screen with black bars (if windowBackground is set to null) on the sides or top/bottom. Or you want the preview zoomed to full screen. I left comment with more information in calcCamPrevDimensions().
Hi the getOptimalPreview() that is here didn't worked for me so I want to share my version:
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
if (sizes==null) return null;
Camera.Size optimalSize = null;
double ratio = (double)h/w;
double minDiff = Double.MAX_VALUE;
double newDiff;
for (Camera.Size size : sizes) {
newDiff = Math.abs((double)size.width/size.height - ratio);
if (newDiff < minDiff) {
optimalSize = size;
minDiff = newDiff;
}
}
return optimalSize;
}
Just to make this thread more complete i am adding my version of answer:
What i wanted to achieve:
The surface view shouldn't be stretched, and it should cover the whole screen, Moreover, there was only a landscape mode in my app.
Solution:
The solution is an extremely small extension to F1sher's solution:
=> First step is to integrate F1sher's solution.
=> Now, there might arise a scenario in F1sher's solution when the surface view doesn't covers the whole screen, The solution is to make the surface view greater than the screen dimensions so that it covers the whole screen, for that:
size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), screenWidth, screenHeight);
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(size.width, size.height);
mCamera.setParameters(parameters);
double screenRatio = (double) screenHeight / screenWidth;
double previewRatio = (double) size.height / size.width;
if (previewRatio > screenRatio) /*if preview ratio is greater than screen ratio then we will have to recalculate the surface height while keeping the surface width equal to the screen width*/
{
RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
params1.addRule(RelativeLayout.CENTER_IN_PARENT);
flPreview.setLayoutParams(params1);
flPreview.setClipChildren(false);
LayoutParams surfaceParams = new LayoutParams(screenWidth, (int) (screenWidth * previewRatio));
surfaceParams.gravity = Gravity.CENTER;
mPreview.setLayoutParams(surfaceParams);
}
else /*if preview ratio is smaller than screen ratio then we will have to recalculate the surface width while keeping the surface height equal to the screen height*/
{
RelativeLayout.LayoutParams params1 = new RelativeLayout.LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
params1.addRule(RelativeLayout.CENTER_IN_PARENT);
flPreview.setLayoutParams(params1);
flPreview.setClipChildren(false);
LayoutParams surfaceParams = new LayoutParams((int) ((double) screenHeight / previewRatio), screenHeight);
surfaceParams.gravity = Gravity.CENTER;
mPreview.setLayoutParams(surfaceParams);
}
flPreview.addView(mPreview);
/* The TopMost layout used is the RelativeLayout, flPreview is the FrameLayout in which Surface View is added, mPreview is an instance of a class which extends SurfaceView */
#Hesam 's answer is correct, CameraPreview will work in all portrait devices, but if the device is in landscape mode or in multi-window mode, this code is working perfect, just replace onMeasure()
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {//portrait
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, height, width);
} else
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);//landscape
if (mPreviewSize == null) return;
float ratio;
if (mPreviewSize.height >= mPreviewSize.width) {
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
} else ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ((Activity) mContext).isInMultiWindowMode()) {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ||
!(Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
setMeasuredDimension(width, (int) (width / ratio));
} else {
setMeasuredDimension((int) (height / ratio), height);
}
} else {
if ((Surface.ROTATION_0 == rotation || Surface.ROTATION_180 == rotation)) {
Log.e("---", "onMeasure: " + height + " - " + width * ratio);
//2264 - 2400.0 pix c -- yes
//2240 - 2560.0 samsung -- yes
//1582 - 1440.0 pix 2 -- no
//1864 - 2048.0 sam tab -- yes
//848 - 789.4737 iball -- no
//1640 - 1600.0 nexus 7 -- no
//1093 - 1066.6667 lenovo -- no
//if width * ratio is > height, need to minus toolbar height
if ((width * ratio) < height)
setMeasuredDimension(width, (int) (width * ratio));
else
setMeasuredDimension(width, (int) (width * ratio) - toolbarHeight);
} else {
setMeasuredDimension((int) (height * ratio), height);
}
}
requestLayout();
}
I figured out what's the problem - it is with orientation changes. If you change camera orientation to 90 or 270 degrees than you need to swap width and height of supported sizes and all will be ok.
Also surface view should lie in a frame layout and have center gravity.
Here is example on C# (Xamarin):
public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height)
{
_camera.StopPreview();
// find best supported preview size
var parameters = _camera.GetParameters();
var supportedSizes = parameters.SupportedPreviewSizes;
var bestPreviewSize = supportedSizes
.Select(x => new { Width = x.Height, Height = x.Width, Original = x }) // HACK swap height and width because of changed orientation to 90 degrees
.OrderBy(x => Math.Pow(Math.Abs(x.Width - width), 3) + Math.Pow(Math.Abs(x.Height - height), 2))
.First();
if (height == bestPreviewSize.Height && width == bestPreviewSize.Width)
{
// start preview if best supported preview size equals current surface view size
parameters.SetPreviewSize(bestPreviewSize.Original.Width, bestPreviewSize.Original.Height);
_camera.SetParameters(parameters);
_camera.StartPreview();
}
else
{
// if not than change surface view size to best supported (SurfaceChanged will be called once again)
var layoutParameters = _surfaceView.LayoutParameters;
layoutParameters.Width = bestPreviewSize.Width;
layoutParameters.Height = bestPreviewSize.Height;
_surfaceView.LayoutParameters = layoutParameters;
}
}
Pay attention that camera parameters should be set as original size (not swapped), and surface view size should be swapped.
i tried all the solution above but none of them works for me. finaly i solved it myself, and find actually it's quite easy. there are two points you need to be careful.
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
this previewSize must be one of the camera supported resolution, which can be get as below:
List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();
usually one of the rawSupportedSize equals to the device resolution.
Second, place your SurfaceView in a FrameLayout and set the surface layout height and width in surfaceChanged method as above
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) surfaceView.getLayoutParams();
layoutParams.height = cameraResolution.x;
layoutParams.width = cameraResolution.y;
Ok, things done, hope this could help you.
Very important point here to understand , the SurfaceView size must be the same as the camera parameters size , it means they have the same aspect ratio then the Stretch effect will go off .
You have to get the correct supported camera preview size using params.getSupportedPreviewSizes() choose one of them and then change your SurfaceView and its holders to this size.
My requirements are the camera preview need to be fullscreen and keep the aspect ratio.
Hesam and Yoosuf's solution was great but I do see a high zoom problem for some reason.
The idea is the same, have the preview container center in parent and increase the width or height depend on the aspect ratios until it can cover the entire screen.
One thing to note is the preview size is in landscape because we set the display orientation.
camera.setDisplayOrientation(90);
The container that we will add the SurfaceView view to:
<RelativeLayout
android:id="#+id/camera_preview_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"/>
Add the preview to it's container with center in parent in your activity.
this.cameraPreview = new CameraPreview(this, camera);
cameraPreviewContainer.removeAllViews();
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
cameraPreviewContainer.addView(cameraPreview, 0, params);
Inside the CameraPreview class:
#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 (holder.getSurface() == null) {
// preview surface does not exist
return;
}
stopPreview();
// set preview size and make any resize, rotate or
// reformatting changes here
try {
Camera.Size nativePictureSize = CameraUtils.getNativeCameraPictureSize(camera);
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
parameters.setPictureSize(nativePictureSize.width, nativePictureSize.height);
camera.setParameters(parameters);
camera.setDisplayOrientation(90);
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (Exception e){
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (supportedPreviewSizes != null && optimalSize == null) {
optimalSize = CameraUtils.getOptimalSize(supportedPreviewSizes, width, height);
Log.i(TAG, "optimal size: " + optimalSize.width + "w, " + optimalSize.height + "h");
}
float previewRatio = (float) optimalSize.height / (float) optimalSize.width;
// previewRatio is height/width because camera preview size are in landscape.
float measuredSizeRatio = (float) width / (float) height;
if (previewRatio >= measuredSizeRatio) {
measuredHeight = height;
measuredWidth = (int) ((float)height * previewRatio);
} else {
measuredWidth = width;
measuredHeight = (int) ((float)width / previewRatio);
}
Log.i(TAG, "Preview size: " + width + "w, " + height + "h");
Log.i(TAG, "Preview size calculated: " + measuredWidth + "w, " + measuredHeight + "h");
setMeasuredDimension(measuredWidth, measuredHeight);
}
a lil late for you but hopefully not for everybody.
What you can use to ensure a specific ration is the attribute:
layout_constraintDimensionRatio="ration_a:ratio_b"
It solved my issues.
Below solution is an update to the #Hesam's answer, which addresses the following:
Final view is distorted in few screens
Preview is laid at the very top of screen, not centralized, which looks very weird.
You just have to update the onMeasure like this, everything else remains the same.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
//centralize preview
FrameLayout.LayoutParams surfaceParams = new FrameLayout.LayoutParams(width, (int) height);
surfaceParams.gravity = Gravity.CENTER;
this.setLayoutParams(surfaceParams);
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
if (mPreviewSize != null) {
float ratio;
if (mPreviewSize.height >= mPreviewSize.width)
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
else
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
setMeasuredDimension(width, (int) (width * ratio));
//fix distortion, based on this answer https://stackoverflow.com/a/30634009/6688493
float camHeight = (int) (width * ratio);
float newCamHeight;
float newHeightRatio;
if (camHeight < height) {
newHeightRatio = (float) height / (float) mPreviewSize.height;
newCamHeight = (newHeightRatio * camHeight);
Log.e(TAG, camHeight + " " + height + " " + mPreviewSize.height + " " + newHeightRatio + " " + newCamHeight);
setMeasuredDimension((int) (width * newHeightRatio), (int) newCamHeight);
Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | H_ratio - " + newHeightRatio + " | A_width - " + (width * newHeightRatio) + " | A_height - " + newCamHeight);
} else {
newCamHeight = camHeight;
setMeasuredDimension(width, (int) newCamHeight);
Log.e(TAG, mPreviewSize.width + " | " + mPreviewSize.height + " | ratio - " + ratio + " | A_width - " + (width) + " | A_height - " + newCamHeight);
}
}
}
You must set cameraView.getLayoutParams().height and cameraView.getLayoutParams().width according to the aspect ratio you want.
I gave up the calculations and simply get the size of the view where I want the camera preview displayed and set the camera's preview size the same (just flipped width/height due to rotation) in my custom SurfaceView implementation:
#Override // CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Display display = ((WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
if (display.getRotation() == Surface.ROTATION_0) {
final Camera.Parameters params = camera.getParameters();
// viewParams is from the view where the preview is displayed
params.setPreviewSize(viewParams.height, viewParams.width);
camera.setDisplayOrientation(90);
requestLayout();
camera.setParameters(params);
}
// I do not enable rotation, so this can otherwise stay as is
}
Related
I'm developing an application that is painting the camera on a SurfaceView. At the beginning I found trouble with the views. They were not correctly shown. So I used the following method to correct the aspect ratio problem:
NOTE: This method is found in several places as the one to correct aspect ratio for Android camera.
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio;
// Check wheter is portrait or landscape
if (orientation == 1)
targetRatio = (double) h / w;
else
targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
This worked perfect, and my camera was shown correctly. But I found problems in newer devices, such as the LG G3. It had a resolution that this method considered that was the most appropiate one, but it was showing the image with pillar boxes on portrait mode, like the image show below:
Why is this happening? How can I solve this pillar box on portrait mode?
Unless there is a bug in that code, you are getting the 'best' result possible.
The aspect ratio of the phone's camera is not guaranteed to match that of the phone's screen.
It is just a matter of deciding what the criteria are for the best result. That code is Google's suggestion for deciding what is the best camera resolution for displaying on the phone's screen.
You might not agree with Google. In that case you will need to write your own algorithm for deciding what is the best camera resolution. You might optimize for aspect-ratio, or for the resolution along the width or height. You may try to fit the screen, or you could clip bits of the image to fill the entire screen. It's up to you.
Note: the code you posted tries to find a camera resolution that is within 10% of the screen's aspect-ratio and has a height that matches the screen's the closest.
If there is no camera resolution within 10% from the screen aspect-ratio, then it picks the camera resolution that has a height that matches the screen's the closest. In this case you would have significant black bars around the pillar box.
In my case I had other views in the same activity, so I wanted to fix the dimensions of the camera preview layout on the first place. In the layout you need to give the max desired dimensions (weights).
I 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 to its global layout changes and then you can set the new dimensions. This also shows how to programmatically determine activity's 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);
}
}
});
You need to also rotate the camera orientation which is another issue.
I'm trying to capture photos directly using the camera api, but this is the preview I got:
& this is the image taken after calling takePicture() which is bigger than the preview itself:
(note: I cropped the height of the previous 2 photos to enhance question readability, & kept the width as is)
I'm using this utility method to choose best optimal preview size before starting the camera preview:
public static Camera.Size getBestAspectPreviewSize(int displayOrientation,
int width,
int height,
Camera.Parameters parameters) {
double targetRatio = (double) width / height;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
if (displayOrientation == 90 || displayOrientation == 270) {
targetRatio = (double) height / width;
}
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
Collections.sort(sizes,
Collections.reverseOrder(new SizeComparator()));
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) < minDiff) {
optimalSize = size;
minDiff = Math.abs(ratio - targetRatio);
}
if (minDiff < 0.0d) {
break;
}
}
return (optimalSize);
}
& this method to choose a suitable picture size:
public static Camera.Size getBiggestSafePictureSize(Camera.Parameters parameters) {
Camera.Size result = null;
long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long availableMemory = Runtime.getRuntime().maxMemory() - used;
for (Camera.Size size : parameters.getSupportedPictureSizes()) {
int newArea = size.width * size.height;
long neededMemory = newArea * 4 * 4; // newArea * 4 Bytes/pixel * 4 needed copies of the bitmap (for safety :) )
if (neededMemory > availableMemory)
continue;
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
if (newArea > resultArea) {
result = size;
}
}
}
return (result);
}
& this is the camera preview element in the layout:
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/cameraPreview"></FrameLayout>
& I'm following the official documentation for creating the camera preview itself
So, how to force the camera preview to show the exact photo that will be taken?
Finally I found it :)
according to this answer & I quote:
While the typical camera is a 4:3 aspect ratio, the preview may also be available in 5:3 and 16:9 ratios and this seems to be accomplished by actually extending the horizontal field of view...
So we need to find a preview size & a picture size, both with 4:3 aspect ratio to be able to utilize the full angle of the camera, so I changed my code like this:
public static Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
return determineBestSize(sizes);
}
public static Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
return determineBestSize(sizes);
}
protected static Camera.Size determineBestSize(List<Camera.Size> sizes) {
Camera.Size bestSize = null;
long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long availableMemory = Runtime.getRuntime().maxMemory() - used;
for (Camera.Size currentSize : sizes) {
int newArea = currentSize.width * currentSize.height;
long neededMemory = newArea * 4 * 4; // newArea * 4 Bytes/pixel * 4 needed copies of the bitmap (for safety :) )
boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
boolean isSafe = neededMemory < availableMemory;
if (isDesiredRatio && isBetterSize && isSafe) {
bestSize = currentSize;
}
}
if (bestSize == null) {
return sizes.get(0);
}
return bestSize;
}
You should run the same loop over the sizes returned by parameters.getSupportedPictureSizes(), and not rely on the default picture size. Furthermore, I would rather look for the best corresponding pair of preview/picture sizes, and let the picture be cropped on the screen if this aspect ratio does not match the aspect ratio of cameraPreview layout.
Hello I am making an android application in which I am using custom camera for recording camera.I am having problem on samsung device.I can not set the profile of Media recorder to CamcorderProfile.get(cameraid, CamcorderProfile.QUALITY_HIGH)
also when try to use
profile = CamcorderProfile.get(cameraid, CamcorderProfile.QUALITY_LOW);
profile.videoFrameHeight=360;
profile.videoFrameWidth=640;
then my app is working on some devices but crashes on many devices.Any type of help will be appreciable.Thanks in advance please check the code
Camera.Parameters param = mCamera.getParameters();
param.set( "cam_mode", 1 );
// Enable video stabilization. Convenience methods not available in API
// level <= 14
String vstabSupported = param.get("video-stabilization-supported");
if ("true".equals(vstabSupported)) {
param.set("video-stabilization", "true");
}
List<Size> sizes = mCamera.getParameters() .getSupportedVideoSizes();
mCamera.setParameters( param );
mMediaRecorder = new MediaRecorder();
// Step 1: Unlock and set camera to MediaRecorder
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
// Step 2: Set sources
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
String deviceMan = android.os.Build.MANUFACTURER;
Toast.makeText(getApplicationContext(), deviceMan, Toast.LENGTH_SHORT).show();
// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
CamcorderProfile profile = CamcorderProfile.get(cameraid, CamcorderProfile.QUALITY_LOW);
// if(!CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)){Log.d("", "the camcorder profile instance is null");
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
if (currentapiVersion >= android.os.Build.VERSION_CODES.FROYO){
// Do something for froyo and above versions
boolean tellbol=CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH);
if(deviceMan.equals("samsung")){
profile = CamcorderProfile.get(cameraid, CamcorderProfile.QUALITY_LOW);
profile.videoFrameHeight=360;
profile.videoFrameWidth=640;
}
else{
profile = CamcorderProfile.get(cameraid, CamcorderProfile.QUALITY_HIGH);
}
mMediaRecorder.setProfile(profile);
} else{
// do something for phones running an SDK before froyo
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
// mMediaRecorder.setVideoSize(720, 480);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
}
// Step 4: Set output file
mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
// Step 5: Set the preview output
// Step 6: Prepare configured MediaRecorder
try {
mMediaRecorder.prepare();
} catch (IllegalStateException e) {
Log.d("Video", "IllegalStateException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
} catch (IOException e) {
Log.d("Video", "IOException preparing MediaRecorder: " + e.getMessage());
releaseMediaRecorder();
return false;
}
return true;
This happens because you are requesting the dimensions that you want, but not everycamera is capable of every dimension.
Besides, some devices works with camera aspect ratios slightly diferent, so if you are requesting a rectangle with a wrong ratio, or with a dimensions diferent from the supported, it will crash in some devices.
What to do?
Step 1.
you have to check for the supported sizes. You can do it with
Camera.Parameters p = myCamera.getParameters();
List<Size> previewsizes = p.getSupportedPreviewSizes();
List<Size> videosizes = p.getSupportedVideoSizes();
and then, you can choose one. If you want to automatize this, you can go further, and follow the
Step 2
write a function to select the best available size, which will receive the supported sizes, and the desired size, something like:
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;
}
And the last step, set the parameters
Step 3
private int desiredwidth=640, desiredheight=360;
Size optimalPreviewSize = getOptimalPreviewSize(previewsizes, desiredwidth, desiredheight);
Size optimalVideoSize = getOptimalPreviewSize(videosizes, desiredwidth, desiredheight);
p.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
mCamera.unlock();
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSize(optimalVideoSize.width, optimalVideoSize.height);
myCamera.setParameters(p);
With this, your camera app will work in every device with a camera!
UPDATE
With the getOptimalPreviewSize that i wrote, you get the size whose ratio is closer to the desired, and if none is good enough, you get the one which height is closed to the desired. if you want to give more importante to the size, you can make an easy change, something like
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;
if ( you want ratio as closed to what i asked for)
{ 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);
}
}
}
if (you want height as closed to what i asked for) { //you can do other for width
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
if (you want the bigest one) {
minDiff = 0;
for (Size size : sizes) {
if ( size.height * size.width > minDiff ) {
optimalSize = size;
minDiff = size.height * size.width ;
}
}
}
return optimalSize;
}
With using the parameters you can set the video preview width and height..
Camera.Parameters param = camera.getParameters();
param.setPreviewSize("your width"
"your height");
camera.setParameters(param);
The below is my Android camera parameter printed using flaten(). I use Surface view and open my camera manually by using camera.open(). I set camera.paramter using setParameter().
-max=30;zoom=0;taking-picture-zoom=0;zoom-supported=true;sharpness-min=0;sharpness=10;contrast=5;whitebalance=auto;jpeg-quality=100;preview-format-values=yuv420sp;jpeg-thumbnail-quality=75;preview-format=yuv420sp;preview-size=576x432;focal-length=4.92;iso=auto;meter-mode=meter-center;front-camera-mode=mirror;flash-mode-values=off,auto,on,torch;preview-frame-rate-values=15;preview-frame-rate=15;focus-mode-values=auto,infinity;jpeg-thumbnail-width=768;jpeg-thumbnail-size-values=768x576,640x480,512x384,0x0;zoom-ratios=100,114,131,151,174,200;saturation-def=5;preview-size-values=1280x720,800x480,768x432,720x480,640x480,576x432,480x320,400x240,384x288,352x288,320x240,272x272,240x240,240x160,176x144;smart-contrast=off;picture-size-values=3264x2448,3264x1952,2592x1952,2592x1552,2048x1536,2048x1216,1600x1200,1280x960,1280x768,1024x768,640x480,640x384,512x384,400x400,272x272;contrast-min=0;min-exposure-compensation=-4;brightness-min=0;antibanding=auto;taking-picture-zoom-min=0;saturation-min=1;contrast-max=10;vertical-view-angle=38;taking-picture-zoom-max=30;contrast-def=5;brightness-max=6;horizontal-view-angle=49.5;brightness=3;jpeg-thumbnail-height=576;cam-mode=0;focus-mode=auto;sharpness-def=10;front-camera-mode-values=mirror,reverse;picture-format-values=jpeg;saturation-max=10;max-exposure-compensation=4;exposure-compensation=0;exposure-compensation-step=0.5;flash-mode=off;effect-values=none,mono,negative,solarize,sepia,posterize,aqua;meter-mode-values=meter-average,meter-center,meter-spot;picture-size=1024x768;max-zoom=5;effect=none;saturation=5;whitebalance-values=auto,incandescent,fluorescent,daylight,cloudy-daylight;picture-format=jpeg;brightness-def=3;iso-values=auto,deblur,100,200,400,800,1250;antibanding-values=off,50hz,60hz,auto**
**
My camera images is not consistently same in all device. Certain
Droid and old phones have image blur and image shrinking issues. Is
there any specific parameter which need a fine tuning??
**
It has to do with the different preview sizes there are for the different phones.
The following method I used in a camera preview related android project:
Inside the surfaceCreated Method:
Camera camera = Camera.open(CameraInfo.CAMERA_FACING_BACK);
final Camera.Parameters params = camera.getParameters();
final Size size = getOptimalSize();
params.setPreviewSize(size.width, size.height);
camera.setParameters(params);
Inside the same activity:
private Size getOptimalSize() {
Camera.Size result = null;
final Camera.Parameters parameters = camera.getParameters();
Log.i(Preview.class.getSimpleName(), "window width: " + getWidth() + ", height: " + getHeight());
for (final Camera.Size size : parameters.getSupportedPreviewSizes()) {
if (size.width <= getWidth() * PREVIEW_SIZE_FACTOR && size.height <= getHeight() * PREVIEW_SIZE_FACTOR) {
if (result == null) {
result = size;
} else {
final int resultArea = result.width * result.height;
final int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
if (result == null) {
result = parameters.getSupportedPreviewSizes().get(0);
}
Log.i(Preview.class.getSimpleName(), "Using PreviewSize: " + result.width + " x " + result.height);
return result;
}
Of course I should add, that the factor we used was:
PREVIEW_SIZE_FACTOR = 1.30;
I have managed to use the answer here to rotate the camera preview to portrait:
http://code.google.com/p/zxing/issues/detail?id=178#c46
however the preview itself is stretched/warped - the height appears to be stretching to fill the box (but it could be that the width is too large for the screen and it is being squished to fit)
Is there any way to 'wrap' the content rather than make it stretch to fit the surfaceview?
EDIT: I sorted it by altering the 'findBestPreviewSizeValue' method in the 'CameraConfigurationManager' and reversing the X and Y points (thanks to sean owen for the pointer below!), here is the code:
private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString,
Point screenResolution) {
int bestX = 0;
int bestY = 0;
int diff = Integer.MAX_VALUE;
for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
previewSize = previewSize.trim();
int dimPosition = previewSize.indexOf('x');
if (dimPosition < 0) {
Log.w(TAG, "Bad preview-size: " + previewSize);
continue;
}
int newX;
int newY;
try {
newY = Integer.parseInt(previewSize.substring(0, dimPosition));
newX = Integer.parseInt(previewSize.substring(dimPosition + 1));
//original:
//newX = Integer.parseInt(previewSize.substring(0, dimPosition));
//newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
} catch (NumberFormatException nfe) {
Log.w(TAG, "Bad preview-size: " + previewSize);
continue;
}
int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
if (newDiff == 0) {
bestX = newY;
bestY = newX;
//original:
//bestX = newX;
//bestY = newY;
break;
} else if (newDiff < diff) {
bestX = newY;
bestY = newX;
//original:
//bestX = newX;
//bestY = newY;
diff = newDiff;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}
return null;
}
You have to do a fair bit more to get this to work than just set the preview to portrait mode. For example, you need to choose an appropriate preview size now that it's in portrait, not landscape.
Wrapping is surely not desirable? No you can't make it wrap; it will always fling the whole preview image onto the SurfaceView, stretching if needed. I suppose you could reimplement a lot of that code to do something different, but it would be quite hard.
I had the same problem but I could resolved it by inverting the aspect ratio on the CameraConfigurationManager.java file inside the findBestPreviewSizeValue changing this
float screenAspectRatio = (float) screenResolution.x / (float) screenResolution.y;
to this
float screenAspectRatio = (float) screenResolution.y / (float) screenResolution.x;
I found the solution here.
http://bighow.net/4660010-Android_zxing___portrait_camera_preview_surfaceview_is_stretched_warped.html
You can get Priview size by this function:
private List mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
if (mSupportedPreviewSizes != null) {
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
}
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) h / w;
if (sizes == null)
return null;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
for (Camera.Size size : sizes) {
double ratio = (double) size.height / size.width;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
float ratio;
if (mPreviewSize.height >= mPreviewSize.width)
ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
else
ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;
after getting ratio set your size in
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension((int) (width * ratio), height);
}
I managed to get this to work by changing below code inCameraConfigurationManager.java:
float screenAspectRatio = (float) screenResolution.x / (float) screenResolution.y;
To:
float screenAspectRatio = (float) screenResolution.y / (float) screenResolution.x;
AND
float aspectRatio = (float) maybeFlippedWidth / (float) maybeFlippedHeight;
TO:
float aspectRatio = (float) maybeFlippedHeight / (float) maybeFlippedWidth;
I did this alongside this post: Zxing Camera in Portrait mode on Android
Hope this helps others.
The reason of deformation may is that the camera's preview aspect ratio does not match the display's aspect ratio.
Most aspect ratio of cellphone screen is 4:3, such as 2560x1440, 1920x1080 and so on. When you use parameter.setPreviewSize(width,height) to set camera preview dimension, if the value of height : width does not equals your phone screen's aspect ratio,the picture of camera will be deformed to fill thg screen.