理想的 surfaceView 相机方向和比例(避免预览拉伸)- android
The ideal surfaceView Camera orientations and proportions (avoiding preview stretching) - android
我从一开始就开始了一个包含相机 surfaceView(用于在屏幕上实时预览图像)的项目,我认为实现它应该不会有太大问题,但现在我已经卡在有关预览方向及其比例的部分,我一直在搜索 Stack 关于表面视图方向和拉伸问题的主题,但我根本无法从中得到好的结果。
我想做的是无论phone旋转如何,都以相同的角度获得预览,这样用户就可以一直看到他的脸。
Example picture
如果您能建议我如何更改我的代码以避免预览在 portret 和横向视图中拉伸,我会很高兴。
我在上面附上我的代码
public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraView (Context context, Camera camera){
super(context);
mCamera = camera;
mCamera.setDisplayOrientation(90);
//get the holder and set this class as the callback, so we can get camera data here
mHolder = getHolder();
mHolder.addCallback(this);
//mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
try {
Camera.Parameters parameters = mCamera.getParameters();
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
parameters.setRotation(90);
}
else {
// This is an undocumented although widely known feature
parameters.set("orientation", "landscape");
// For Android 2.2 and above
mCamera.setDisplayOrientation(0);
// Uncomment for Android 2.0 and above
parameters.setRotation(0);
}
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
} catch (IOException e) {
// left blank for now
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
this.getHolder().removeCallback(this);
mCamera.stopPreview();
mCamera.release();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
int width, int height) {
try {
Camera.Parameters parameters = mCamera.getParameters();
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
parameters.setRotation(90);
setCamera(mCamera);
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
}
else {
parameters.set("orientation", "landscape");
mCamera.setDisplayOrientation(0);
parameters.setRotation(0);
}
setCamera(mCamera);
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
} catch (IOException e) {
}
}
public void setCamera(Camera camera){
mCamera = camera; }
在您的方法中,您必须选择要在预览中显示的特定比例,这将产生因屏幕尺寸而异的结果。
你可能想考虑一个不同的方法,你可以定义你自己的相机预览如下:
创建相机预览class:
public class CameraSourcePreview extends ViewGroup {
private static final String TAG = "CameraSourcePreview";
private Context mContext;
private SurfaceView mSurfaceView;
private boolean mStartRequested;
private boolean mSurfaceAvailable;
private CameraSource mCameraSource;
private GraphicOverlay mOverlay;
public CameraSourcePreview(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mStartRequested = false;
mSurfaceAvailable = false;
mSurfaceView = new SurfaceView(context);
mSurfaceView.getHolder().addCallback(new SurfaceCallback());
addView(mSurfaceView);
}
public void start(CameraSource cameraSource) throws IOException {
if (cameraSource == null) {
stop();
}
mCameraSource = cameraSource;
if (mCameraSource != null) {
mStartRequested = true;
startIfReady();
}
}
public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException {
mOverlay = overlay;
start(cameraSource);
}
public void stop() {
if (mCameraSource != null) {
mCameraSource.stop();
}
}
public void release() {
if (mCameraSource != null) {
mCameraSource.release();
mCameraSource = null;
}
}
private void startIfReady() throws IOException {
if (mStartRequested && mSurfaceAvailable) {
mCameraSource.start(mSurfaceView.getHolder());
if (mOverlay != null) {
Size size = mCameraSource.getPreviewSize();
int min = Math.min(size.getWidth(), size.getHeight());
int max = Math.max(size.getWidth(), size.getHeight());
if (isPortraitMode()) {
// Swap width and height sizes when in portrait, since it will be rotated by
// 90 degrees
mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing());
} else {
mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing());
}
mOverlay.clear();
}
mStartRequested = false;
}
}
private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surface) {
mSurfaceAvailable = true;
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surface) {
mSurfaceAvailable = false;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int previewWidth = 320;
int previewHeight = 240;
if (mCameraSource != null) {
Size size = mCameraSource.getPreviewSize();
if (size != null) {
previewWidth = size.getWidth();
previewHeight = size.getHeight();
}
}
// Swap width and height sizes when in portrait, since it will be rotated 90 degrees
if (isPortraitMode()) {
int tmp = previewWidth;
previewWidth = previewHeight;
previewHeight = tmp;
}
final int viewWidth = right - left;
final int viewHeight = bottom - top;
int childWidth;
int childHeight;
int childXOffset = 0;
int childYOffset = 0;
float widthRatio = (float) viewWidth / (float) previewWidth;
float heightRatio = (float) viewHeight / (float) previewHeight;
// To fill the view with the camera preview, while also preserving the correct aspect ratio,
// it is usually necessary to slightly oversize the child and to crop off portions along one
// of the dimensions. We scale up based on the dimension requiring the most correction, and
// compute a crop offset for the other dimension.
if (widthRatio > heightRatio) {
childWidth = viewWidth;
childHeight = (int) ((float) previewHeight * widthRatio);
childYOffset = (childHeight - viewHeight) / 2;
} else {
childWidth = (int) ((float) previewWidth * heightRatio);
childHeight = viewHeight;
childXOffset = (childWidth - viewWidth) / 2;
}
for (int i = 0; i < getChildCount(); ++i) {
// One dimension will be cropped. We shift child over or up by this offset and adjust
// the size to maintain the proper aspect ratio.
getChildAt(i).layout(
-1 * childXOffset, -1 * childYOffset,
childWidth - childXOffset, childHeight - childYOffset);
}
try {
startIfReady();
} catch (IOException e) {
Log.e(TAG, "Could not start camera source.", e);
}
}
private boolean isPortraitMode() {
int orientation = mContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
return true;
}
Log.d(TAG, "isPortraitMode returning false by default");
return false;
}
}
在您的 layout.xml 中添加自定义预览:
<yourpackagename.CameraSourcePreview
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent">
在你的activityclass里面定义camera source的方法,然后就可以在onCreate()中调用这个方法了
/**
* 启动或重新启动相机源(如果存在)。如果相机源还不存在
*(例如,因为在创建相机源之前调用了 onResume),这将被调用
* 再次创建相机源。
*/
private void startCameraSource() {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
- 不要忘记添加以下内容:
/**
* 重启相机。
*/
@Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/**
* 停止相机。
*/
@Override
protected void onPause() {
super.onPause();
mPreview.stop();
}
/**
* Releases the resources associated with the camera source, the associated detector, and the
* rest of the processing pipeline.
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mCameraSource != null) {
mCameraSource.release();
}
}
我从一开始就开始了一个包含相机 surfaceView(用于在屏幕上实时预览图像)的项目,我认为实现它应该不会有太大问题,但现在我已经卡在有关预览方向及其比例的部分,我一直在搜索 Stack 关于表面视图方向和拉伸问题的主题,但我根本无法从中得到好的结果。
我想做的是无论phone旋转如何,都以相同的角度获得预览,这样用户就可以一直看到他的脸。
Example picture
如果您能建议我如何更改我的代码以避免预览在 portret 和横向视图中拉伸,我会很高兴。
我在上面附上我的代码
public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraView (Context context, Camera camera){
super(context);
mCamera = camera;
mCamera.setDisplayOrientation(90);
//get the holder and set this class as the callback, so we can get camera data here
mHolder = getHolder();
mHolder.addCallback(this);
//mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
try {
Camera.Parameters parameters = mCamera.getParameters();
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
parameters.setRotation(90);
}
else {
// This is an undocumented although widely known feature
parameters.set("orientation", "landscape");
// For Android 2.2 and above
mCamera.setDisplayOrientation(0);
// Uncomment for Android 2.0 and above
parameters.setRotation(0);
}
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
} catch (IOException e) {
// left blank for now
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
this.getHolder().removeCallback(this);
mCamera.stopPreview();
mCamera.release();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
int width, int height) {
try {
Camera.Parameters parameters = mCamera.getParameters();
if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
parameters.set("orientation", "portrait");
mCamera.setDisplayOrientation(90);
parameters.setRotation(90);
setCamera(mCamera);
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
}
else {
parameters.set("orientation", "landscape");
mCamera.setDisplayOrientation(0);
parameters.setRotation(0);
}
setCamera(mCamera);
mCamera.setPreviewDisplay(surfaceHolder);
mCamera.startPreview();
} catch (IOException e) {
}
}
public void setCamera(Camera camera){
mCamera = camera; }
在您的方法中,您必须选择要在预览中显示的特定比例,这将产生因屏幕尺寸而异的结果。
你可能想考虑一个不同的方法,你可以定义你自己的相机预览如下:
创建相机预览class:
public class CameraSourcePreview extends ViewGroup { private static final String TAG = "CameraSourcePreview"; private Context mContext; private SurfaceView mSurfaceView; private boolean mStartRequested; private boolean mSurfaceAvailable; private CameraSource mCameraSource; private GraphicOverlay mOverlay; public CameraSourcePreview(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mStartRequested = false; mSurfaceAvailable = false; mSurfaceView = new SurfaceView(context); mSurfaceView.getHolder().addCallback(new SurfaceCallback()); addView(mSurfaceView); } public void start(CameraSource cameraSource) throws IOException { if (cameraSource == null) { stop(); } mCameraSource = cameraSource; if (mCameraSource != null) { mStartRequested = true; startIfReady(); } } public void start(CameraSource cameraSource, GraphicOverlay overlay) throws IOException { mOverlay = overlay; start(cameraSource); } public void stop() { if (mCameraSource != null) { mCameraSource.stop(); } } public void release() { if (mCameraSource != null) { mCameraSource.release(); mCameraSource = null; } } private void startIfReady() throws IOException { if (mStartRequested && mSurfaceAvailable) { mCameraSource.start(mSurfaceView.getHolder()); if (mOverlay != null) { Size size = mCameraSource.getPreviewSize(); int min = Math.min(size.getWidth(), size.getHeight()); int max = Math.max(size.getWidth(), size.getHeight()); if (isPortraitMode()) { // Swap width and height sizes when in portrait, since it will be rotated by // 90 degrees mOverlay.setCameraInfo(min, max, mCameraSource.getCameraFacing()); } else { mOverlay.setCameraInfo(max, min, mCameraSource.getCameraFacing()); } mOverlay.clear(); } mStartRequested = false; } } private class SurfaceCallback implements SurfaceHolder.Callback { @Override public void surfaceCreated(SurfaceHolder surface) { mSurfaceAvailable = true; try { startIfReady(); } catch (IOException e) { Log.e(TAG, "Could not start camera source.", e); } } @Override public void surfaceDestroyed(SurfaceHolder surface) { mSurfaceAvailable = false; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int previewWidth = 320; int previewHeight = 240; if (mCameraSource != null) { Size size = mCameraSource.getPreviewSize(); if (size != null) { previewWidth = size.getWidth(); previewHeight = size.getHeight(); } } // Swap width and height sizes when in portrait, since it will be rotated 90 degrees if (isPortraitMode()) { int tmp = previewWidth; previewWidth = previewHeight; previewHeight = tmp; } final int viewWidth = right - left; final int viewHeight = bottom - top; int childWidth; int childHeight; int childXOffset = 0; int childYOffset = 0; float widthRatio = (float) viewWidth / (float) previewWidth; float heightRatio = (float) viewHeight / (float) previewHeight; // To fill the view with the camera preview, while also preserving the correct aspect ratio, // it is usually necessary to slightly oversize the child and to crop off portions along one // of the dimensions. We scale up based on the dimension requiring the most correction, and // compute a crop offset for the other dimension. if (widthRatio > heightRatio) { childWidth = viewWidth; childHeight = (int) ((float) previewHeight * widthRatio); childYOffset = (childHeight - viewHeight) / 2; } else { childWidth = (int) ((float) previewWidth * heightRatio); childHeight = viewHeight; childXOffset = (childWidth - viewWidth) / 2; } for (int i = 0; i < getChildCount(); ++i) { // One dimension will be cropped. We shift child over or up by this offset and adjust // the size to maintain the proper aspect ratio. getChildAt(i).layout( -1 * childXOffset, -1 * childYOffset, childWidth - childXOffset, childHeight - childYOffset); } try { startIfReady(); } catch (IOException e) { Log.e(TAG, "Could not start camera source.", e); } } private boolean isPortraitMode() { int orientation = mContext.getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { return false; } if (orientation == Configuration.ORIENTATION_PORTRAIT) { return true; } Log.d(TAG, "isPortraitMode returning false by default"); return false; }
}
在您的 layout.xml 中添加自定义预览:
<yourpackagename.CameraSourcePreview android:id="@+id/preview" android:layout_width="match_parent" android:layout_height="match_parent">
在你的activityclass里面定义camera source的方法,然后就可以在onCreate()中调用这个方法了
/** * 启动或重新启动相机源(如果存在)。如果相机源还不存在 *(例如,因为在创建相机源之前调用了 onResume),这将被调用 * 再次创建相机源。 */
private void startCameraSource() {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
- 不要忘记添加以下内容:
/** * 重启相机。 */
@Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/** * 停止相机。 */
@Override
protected void onPause() {
super.onPause();
mPreview.stop();
}
/**
* Releases the resources associated with the camera source, the associated detector, and the
* rest of the processing pipeline.
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mCameraSource != null) {
mCameraSource.release();
}
}