即使设置了正确的尺寸,相机预览也会被拉伸
Posted
技术标签:
【中文标题】即使设置了正确的尺寸,相机预览也会被拉伸【英文标题】:Camera preview stretched even after setting the correct dimensions 【发布时间】:2013-04-10 03:35:30 【问题描述】:我正在创建一个相机应用程序,它实现了自己的相机预览来拍照。 该应用目前被强制进入纵向模式。
我的问题是相机的预览略微拉伸(纵横比有点偏)。 有趣的是,我将 SurfaceView 的大小设置为始终与预览大小匹配。这确保了纵横比应该始终保持不变......但它不是......
这是我用来显示我的相机预览的布局:
public class Cp extends ViewGroup implements SurfaceHolder.Callback
private final String TAG = "CameraPreview";
private boolean mPreviewRunning = false;
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private Size mPreviewSize;
private List<Size> mSupportedPreviewSizes;
private Camera mCamera;
public boolean IsPreviewRunning()
return mPreviewRunning;
public Cp(Context context)
this(context, null, 0);
public Cp(Context context, AttributeSet attrs)
this(context, attrs, 0);
public Cp(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
mSurfaceView = new SurfaceView(context);
addView(mSurfaceView);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
public void setCamera(Camera camera)
mCamera = camera;
if (mCamera != null)
requestLayout();
public void switchCamera(Camera camera)
setCamera(camera);
try
camera.setPreviewDisplay(mHolder);
catch (IOException exception)
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
requestLayout();
camera.setParameters(parameters);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
// We purposely disregard child measurements because act as a wrapper to
// a SurfaceView that
// centers the camera preview instead of stretching it.
final int width = resolveSize(getSuggestedMinimumWidth(),
widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(),
heightMeasureSpec);
setMeasuredDimension(width, height);
if (mSupportedPreviewSizes == null && mCamera != null)
mSupportedPreviewSizes = mCamera.getParameters()
.getSupportedPreviewSizes();
if (mSupportedPreviewSizes != null)
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width,
height);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
if (changed && getChildCount() > 0)
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null)
previewWidth = mPreviewSize.height;
previewHeight = mPreviewSize.width;
if (previewWidth == 0)
previewWidth = 1;
if (previewHeight == 0)
previewHeight = 1;
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth)
final int scaledChildWidth = previewWidth * height
/ previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
else
final int scaledChildHeight = previewHeight * width
/ previewWidth;
child.layout(0, (height - scaledChildHeight) / 2, width,
(height + scaledChildHeight) / 2);
@Override
public void surfaceCreated(SurfaceHolder holder)
// The Surface has been created, acquire the camera and tell it where to
// draw.
try
if (mCamera != null)
Parameters params = mCamera.getParameters();
mSupportedPreviewSizes = params.getSupportedPreviewSizes();
mCamera.setPreviewDisplay(holder);
catch (IOException exception)
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
@Override
public void surfaceDestroyed(SurfaceHolder holder)
// Surface will be destroyed when we return, so stop the preview.
stop();
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h)
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null)
return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes)
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
continue;
if (Math.abs(size.height - targetHeight) < minDiff)
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null)
minDiff = Double.MAX_VALUE;
for (Size size : sizes)
if (Math.abs(size.height - targetHeight) < minDiff)
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
return optimalSize;
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
if (mCamera != null)
// Now that the size is known, set up the camera parameters and
// begin the preview.
Camera.Parameters parameters = mCamera.getParameters();
if (mPreviewSize != null)
parameters.setPreviewSize(mPreviewSize.width,
mPreviewSize.height);
requestLayout();
mCamera.setParameters(parameters);
mCamera.startPreview();
mPreviewRunning = true;
public void stop()
if (mCamera != null)
mCamera.stopPreview();
mPreviewRunning = false;
mCamera = null;
请注意,在onLayout
中,宽度和高度是交换的,因为应用程序始终以纵向运行。
我添加了一些图片来向您展示问题的样子:
我已经调试了代码。预览大小设置为 1280x720,并且布局大小也正在正确调整以匹配该大小。
我也尝试过不同的布局,但结果总是一样...
【问题讨论】:
相关:Camera Preview Stretched on Few android Devices 【参考方案1】:我遇到了同样的问题,在这个谜题中玩了几天之后,我的 Java 类最终出现在以下代码上:
所以,问题出现了,因为相机显示屏的尺寸(高 x 宽)为 576x720,而我的显示屏为 1184x720。因此,相机预览(我的表面视图类)被拉伸以填充父级。
因此,有效的方法是让这个视图比我的屏幕大,并且只显示我的屏幕区域。所以,我不得不使用这种奇怪的框架组合(线性布局内的相对布局),这样相对的大小就会像我想要的一样大 - 表面视图将填充它 - 而线性将只占用我想展示的部分。
package com.example.frame.camera;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.example.frame.MainActivity;
public class NativeCamera extends SurfaceView implements SurfaceHolder.Callback
static private NativeCamera instance;
private LinearLayout frame = null;
private RelativeLayout innerFrame = null;
private Camera camera;
private final SurfaceHolder previewHolder;
private static final String TAG = "NativeCamera.java";
private boolean inPreview = false;
private boolean cameraConfigured = false;
private boolean frontCamera = false;
private Camera.Size size;
static public NativeCamera getInstance()
if (NativeCamera.instance == null)
if (MainActivity.debug)
Log.d(TAG, "Creating Camera Singleton");
NativeCamera.instance = new NativeCamera(MainActivity.instance);
return NativeCamera.instance;
public void onResume()
if (MainActivity.debug)
Log.d(TAG, "onResume");
camera = Camera.open();
if (size != null)
initPreview(size.width, size.height);
startPreview();
public void onPause()
if (MainActivity.debug)
Log.d(TAG, "onPause");
if (inPreview)
camera.stopPreview();
camera.release();
camera = null;
inPreview = false;
public void onDestroy()
if (MainActivity.debug)
Log.d(TAG, "onDestroy");
NativeCamera.instance = null;
public void onSwitch()
frontCamera = !frontCamera;
if (inPreview)
camera.stopPreview();
camera.release();
int cam = frontCamera ? 1 : 0;
camera = Camera.open(cam);
if (size != null)
initPreview(size.width, size.height);
startPreview();
private NativeCamera(Context context)
super(context);
// TODO Auto-generated constructor stub
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
previewHolder = getHolder();
previewHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
// previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
innerFrame = new RelativeLayout(MainActivity.instance);
innerFrame.addView(this);
frame = new LinearLayout(MainActivity.instance);
frame.addView(innerFrame);
public LinearLayout getFrame()
return frame;
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height)
// TODO Auto-generated method stub
if (MainActivity.debug)
Log.d(TAG, "surfaceChanged");
initPreview(width, height);
startPreview();
public void surfaceCreated(SurfaceHolder holder)
// TODO Auto-generated method stub
// no-op -- wait until surfaceChanged()
public void surfaceDestroyed(SurfaceHolder holder)
// TODO Auto-generated method stub
// no-op
private Camera.Size getBestPreviewSize(int width, int height,
Camera.Parameters parameters)
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPreviewSizes())
if (size.width <= width && size.height <= height)
if (result == null)
result = size;
else
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea)
result = size;
this.size = result;
return (result);
private void initPreview(int width, int height)
if (camera != null && previewHolder.getSurface() != null)
if (!cameraConfigured)
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null)
parameters.setPreviewSize(size.width, size.height);
camera.setParameters(parameters);
cameraConfigured = true;
// Setting up correctly the view
double ratio = size.height / (double) size.width;
LayoutParams params = innerFrame.getLayoutParams();
params.height = MainActivity.size.y;
params.width = (int) (MainActivity.size.y * ratio);
innerFrame.setLayoutParams(params);
int deslocationX = (int) (params.width / 2.0 - MainActivity.size.x / 2.0);
innerFrame.animate().translationX(-deslocationX);
try
camera.setPreviewDisplay(previewHolder);
camera.setDisplayOrientation(90);
catch (Throwable t)
Log.e(TAG, "Exception in setPreviewDisplay()", t);
Toast.makeText(MainActivity.instance, t.getMessage(), Toast.LENGTH_LONG).show();
private void startPreview()
if (MainActivity.debug)
Log.d(TAG, "startPreview");
if (cameraConfigured && camera != null)
camera.startPreview();
inPreview = true;
【讨论】:
尚未对其进行全面测试,但这似乎确实有效。不错。 这个解决方案对我不起作用。请参考我的问题***.com/questions/21107208/… getBestPreviewSize() 总是给我较小尺寸的相机,同样的问题会发生视图拉伸。 @Eduardo Reis:我正在尝试像 instagram 一样实现相机预览,但相机 streatch。预览不正确。你能帮我吗 ?提前致谢。等待您的重播。【参考方案2】:我一直在尝试解决同样的问题...下面的代码对我有用:
private void setMyPreviewSize(int width, int height)
// Get the set dimensions
float newProportion = (float) width / (float) height;
// Get the width of the screen
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
float screenProportion = (float) screenWidth / (float) screenHeight;
// Get the SurfaceView layout parameters
android.view.ViewGroup.LayoutParams lp = surfaceView.getLayoutParams();
if (newProportion > screenProportion)
lp.width = screenWidth;
lp.height = (int) ((float) screenWidth / newProportion );
else
lp.width = (int) (newProportion * (float) screenHeight);
lp.height = screenHeight;
// Commit the layout parameters
surfaceView.setLayoutParams(lp);
可以看帖:Resizing surface view for aspect ratio change in video display in android
【讨论】:
【参考方案3】:解决办法
surfaceChanged 只是为了获得最佳预览尺寸
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
Log.e("MYTAG", "surfaceChanged " );
Camera.Parameters myParameters = camera.getParameters();
Camera.Size myBestSize = getBestPreviewSize(width, height, myParameters);
if(myBestSize != null)
myParameters.setPreviewSize(myBestSize.width, myBestSize.height);
camera.setParameters(myParameters);
camera.startPreview();
@Override
public void onPictureTaken(byte[] data, Camera camera)
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageReview.setImageBitmap(bitmap);
这会将捕获的图像设置在imageReview上,但是您需要一个方法来获取位图的旋转
您需要为拉伸图像问题设置 imageView scaleType 属性
<ImageView
android:id="@+id/imageView"
android:layout_
android:layout_
android:scaleType="centerCrop"
/>
【讨论】:
以上是关于即使设置了正确的尺寸,相机预览也会被拉伸的主要内容,如果未能解决你的问题,请参考以下文章