如何在Android中修复相机预览(surfaceview)的正确纵横比?
Posted
技术标签:
【中文标题】如何在Android中修复相机预览(surfaceview)的正确纵横比?【英文标题】:How to fix the right aspect ratio of the camera preview (surfaceview) in Android? 【发布时间】:2017-05-03 21:21:45 【问题描述】:我正在创建带有相机功能的 android 应用。 相机屏幕包含顶部的工具栏、工具栏下方的surfaceview(相机预览)以及屏幕底部的相机控制按钮。屏幕始终是纵向的。
[删除部分与问题无关的代码]
这是我的片段FragmentCamera
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.dmc.R;
import com.dmc.entities.Preview;
public class CameraFragment implements View.OnClickListener, View.OnTouchListener
public static final String ARG_CAMERA_MODE = "camera.mode";
public static final String TYPE_CAMERA_MODE_IMAGE = "image";
public static final String TYPE_CAMERA_MODE_VIDEO = "video";
public MediaRecorder mrec = new MediaRecorder();
private Camera camera;
private String mCameraMode = TYPE_CAMERA_MODE_IMAGE; //or video
private com.dmc.entities.Preview preview;
private ImageView btnStopRecording;
private SurfaceView surfaceView;
private View view;
public static FrCamera getInstance(String cameraMode)
CameraFragment fragment = new CameraFragment();
Bundle bundle = new Bundle(1);
bundle.putString(ARG_CAMERA_MODE, cameraMode);
return fragment.setArguments(bundle);
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
mCameraMode = getArguments().getString(ARG_CAMERA_MODE);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
view = inflater.inflate(R.layout.fragment_camera, container, false);
view.setOnTouchListener(this);
btnStopRecording = (ImageView) view.findViewById(R.id.btnStopRecording);
if (!mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE))
btnStopRecording.setOnClickListener(this);
surfaceView = (SurfaceView) view.findViewById(R.id.surfaceView);
view.findViewById(R.id.imgCameraTakePicture).setOnClickListener(this);
preview = new Preview(getActivity(), (SurfaceView) view.findViewById(R.id.surfaceView));
preview.setKeepScreenOn(true);
return view;
@Override
public void onStart()
super.onStart();
int numCams = Camera.getNumberOfCameras();
if (numCams > 0)
try
camera = Camera.open(0);
preview.setCamera(camera);
camera.startPreview();
catch (RuntimeException ex)
@Override
public void onPause()
if (camera != null)
camera.stopPreview();
preview.setCamera(null);
camera.release();
camera = null;
super.onPause();
@Override
public void onResume()
super.onResume();
camera.startPreview();
private void startVideoRecording()
try
mrec = new MediaRecorder();
mrec.setCamera(camera);
mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mrec.setAudiosource(MediaRecorder.AudioSource.MIC);
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
mrec.setProfile(profile);
camera.lock();
camera.unlock();
mrec.setPreviewDisplay(preview.mHolder.getSurface());
mrec.setOutputFile(outVideoFile.getPath());
mrec.setOrientationHint(Preview.rotate);
mrec.prepare();
mrec.start();
catch (Exception ex)
Log.e(getClass().getName(), ex.getMessage());
protected void stopRecording()
if (mrec != null)
mrec.stop();
mrec.release();
@Override
public void onClick(View v)
switch (v.getId())
case R.id.imgCameraTakenPicture:
// Save image
break;
case R.id.btnStopRecording:
stopRecording();
break;
case R.id.imgCameraTakePicture:
if (mCameraMode.equals(TYPE_CAMERA_MODE_IMAGE))
camera.takePicture(shutterCallback, rawCallback, jpegCallback);
else
startVideoRecording();
break;
这是预览
import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import java.io.IOException;
import java.util.List;
public class Preview extends ViewGroup implements SurfaceHolder.Callback
public static final float RATIO = 0.75f;
public static int rotate;
public SurfaceView mSurfaceView;
public SurfaceHolder mHolder;
Size mPreviewSize;
List<Size> mSupportedPreviewSizes;
Camera mCamera;
public Preview(Context context, SurfaceView sv)
super(context);
mSurfaceView = sv;
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
public void setCamera(Camera camera)
mCamera = camera;
if (mCamera != null)
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
requestLayout();
// get Camera parameters
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
else
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
params.setJpegThumbnailQuality(100);
params.setJpegQuality(100);
// Configure image format. RGB_565 is the most common format.
List<Integer> formats = params.getSupportedPictureFormats();
if (formats.contains(PixelFormat.RGB_565))
params.setPictureFormat(PixelFormat.RGB_565);
else if (formats.contains(PixelFormat.JPEG))
params.setPictureFormat(PixelFormat.JPEG);
else params.setPictureFormat(formats.get(0));
Camera.CameraInfo camInfo = new Camera.CameraInfo();
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo);
int cameraRotationOffset = camInfo.orientation;
Camera.Parameters parameters = mCamera.getParameters();
int rotation = ((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation)
case Surface.ROTATION_0:
degrees = 0;
break; // Natural orientation
case Surface.ROTATION_90:
degrees = 90;
break; // Landscape left
case Surface.ROTATION_180:
degrees = 180;
break;// Upside down
case Surface.ROTATION_270:
degrees = 270;
break;// Landscape right
int displayRotation;
if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
displayRotation = (cameraRotationOffset + degrees) % 360;
//displayRotation = (360 - displayRotation) % 360; // compensate the mirror
else // back-facing
displayRotation = (cameraRotationOffset - degrees + 360) % 360;
mCamera.setDisplayOrientation(displayRotation);
if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
rotate = (360 + cameraRotationOffset + degrees) % 360;
else
rotate = (360 + cameraRotationOffset - degrees) % 360;
parameters.set("orientation", "portrait");
parameters.setRotation(rotate);
mCamera.setParameters(params);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
if (changed && getChildCount() > 0)
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
int previewWidth = width;
int previewHeight = height;
if (mPreviewSize != null)
previewWidth = mPreviewSize.width;
previewHeight = mPreviewSize.height;
// Center the child SurfaceView within the parent.
if (width * previewHeight > height * previewWidth)
final int scaledChildWidth = previewWidth * height / previewHeight;
child.layout((width - scaledChildWidth) / 2, 0,
(width + scaledChildWidth) / 2, height);
else
final int scaledChildHeight = previewHeight * width / previewWidth;
child.layout(0, (height - scaledChildHeight) / 2,
width, (height + scaledChildHeight) / 2);
public void surfaceCreated(SurfaceHolder holder)
try
if (mCamera != null)
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
catch (IOException exception)
public void surfaceDestroyed(SurfaceHolder holder)
if (mCamera != null)
mCamera.stopPreview();
mCamera.release();
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
if (mHolder.getSurface() == null)
return;
stopPreview();
setCamera(mCamera);
startPreview();
mCamera.startPreview();
public void startPreview()
try
if (mCamera != null)
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
catch (Exception e)
public void stopPreview()
try
if (mCamera != null)
mCamera.stopPreview();
catch (Exception e)
预期结果在左侧的图像上。获得的结果在右侧的图像上。相机预览被拉伸。如何修复相机预览的正确纵横比?
【问题讨论】:
有什么解决办法吗? @迪伦 如果我理解正确,预览。 onLayout() 永远不会被调用,因为这个视图没有添加到内容视图中,以及其他问题。你的课程应该extend SurfaceView
,并在运行时被夸大,因为在你开始之前你不知道相机的纵横比。
【参考方案1】:
Camera1 和 Android 4-5 的源代码:
@Override
protected void onResume()
super.onResume();
camera = Camera.open(CAMERA_ID);
setPreviewSize();
@Override
protected void onPause()
super.onPause();
if (camera != null)
camera.release();
camera = null;
...
void setPreviewSize()
// получаем размеры экрана
Display display = getWindowManager().getDefaultDisplay();
int w1 = display.getWidth();
int h1 = display.getHeight();
boolean widthIsMax = display.getWidth() > display.getHeight();
// определяем размеры превью камеры
Camera.Size size = camera.getParameters().getPreviewSize();
RectF rectDisplay = new RectF();
RectF rectPreview = new RectF();
// RectF экрана, соотвествует размерам экрана
rectDisplay.set(0, 0, w1, h1);
// подготовка матрицы преобразования
Matrix matrix = new Matrix();
// RectF первью
if (widthIsMax)
// превью в горизонтальной ориентации
rectPreview.set(0, 0, size.width, size.height);
// если экран будет "втиснут" в превью (третий вариант из урока)
matrix.setRectToRect(rectPreview, rectDisplay,
Matrix.ScaleToFit.START);
else
// превью в вертикальной ориентации
rectPreview.set(0, 0, size.height, size.width);
// если превью будет "втиснут" в экран (второй вариант из урока)
matrix.setRectToRect(rectPreview, rectDisplay,
Matrix.ScaleToFit.START);
// преобразование
matrix.mapRect(rectPreview);
// установка размеров surface из получившегося преобразования
h0 = (int) (rectPreview.bottom);
w0 = (int) (rectPreview.right);
surfaceView.getLayoutParams().height = h0;
surfaceView.getLayoutParams().width = w0;
见http://startandroid.ru/ru/uroki/vse-uroki-spiskom/264-urok-132-kamera-vyvod-izobrazhenija-na-ekran-obrabotka-povorota.html
【讨论】:
【参考方案2】:以下是Camera Preview Stretched的解决方案
-
它可以制作我的自定义相机活动
我设置了 framelayout,它包含显示相机参数的表面视图。
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;
参考链接: Android Camera Preview Stretched
【讨论】:
以上是关于如何在Android中修复相机预览(surfaceview)的正确纵横比?的主要内容,如果未能解决你的问题,请参考以下文章
android中没有Surface View的媒体管道中的地标扣除
如何将相机预览传递给 MediaCodec.createInputSurface() 创建的 Surface?