三星 S9(18:9) 相机预览拉长
Posted
技术标签:
【中文标题】三星 S9(18:9) 相机预览拉长【英文标题】:Samsung S9(18:9) camera preview stretched 【发布时间】:2019-04-10 13:25:52 【问题描述】:我正在使用 android Camera API,它在纵向和横向模式下都适用于 16:9 比例的设备。但在三星 S9 18:9 比例设备中,在横向模式下,预览看起来会拉长。在三星 S9 上,我得到了以下支持的预览尺寸 1920X1080,1440X1080, 1088X1088,1280X720,1056X704, 1024X768, 960X720,800X450,720X720,720X480,640X480,352X288,320X240,256X144,176X144
,因此最佳预览尺寸为 1920X1080
,但设备的实际分辨率为 2,220 x 1,080
。所以它看起来被拉伸了。但我需要全屏预览。默认相机预览如何全屏显示?
@SuppressLint("ClickableViewAccessibility")
@SuppressWarnings("deprecation")
public CameraPreview(Context context, Camera.PreviewCallback previewCallback)
super(context);
this.previewCallback = previewCallback;
mContext = context;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
public void setCameraDisplayOrientation(Context activity,
int cameraId, Camera camera)
Camera.CameraInfo info =
new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = ((AppCompatActivity) activity).getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation)
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
else // back-facing
result = (info.orientation - degrees + 360) % 360;
mDisplayOrientation = result;
Log.d(TAG, "setCameraDisplayOrientation: "+mDisplayOrientation);
camera.setDisplayOrientation(result);
public void takePhoto(final PictureCallback pCalback)
mCamera.takePicture(null, null, pCalback);
public void surfaceCreated(SurfaceHolder holder)
// The Surface has been created, acquire the camera and tell it where
// to draw.
if (mCamera != null)
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
//previewCount = 0;
try
mCamera = Camera.open();
//setCameraDisplayOrientation(mContext, 0, mCamera);
/*mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
optimalPreviewSize = getBestAspectPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight);//Bug Fix for Samsung A8
mParameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
mParameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
mParameters.setPreviewFpsRange(30000, 30000);
mCamera.setParameters(mParameters);*/
/*mCamera.setPreviewDisplay(holder);
mCamera.setPreviewCallback(previewCallback);*/
mCamera.setPreviewDisplay(holder);
catch (IOException exception)
mCamera.release();
mCamera = null;
//previewCount = 0;
catch (Exception exception)
mCamera = null;
//previewCount = 0;
if (mCameraPreviewListener != null)
mCameraPreviewListener.onCameraSurfaceCreated();
public void surfaceDestroyed(SurfaceHolder holder)
if (mCamera != null)
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
//previewCount = 0;
if (mCameraPreviewListener != null)
mCameraPreviewListener.onCameraSurfaceDestroyed();
public void stopCamera()
if (mCamera != null)
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
//previewCount = 0;
@SuppressWarnings("null")
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
try
// Now that the size is known, set up the camera parameters and begin
// the preview.
mParameters = mCamera.getParameters();
Log.d("CameraFix", "parameters -> " + mParameters.flatten());
setCameraDisplayOrientation(mContext, 0, mCamera);
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
//Size optimalPreviewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), getWidth(), getHeight());
DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
//Size optimalPreviewSize = getOptimalPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight, getHeight());
mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();
optimalPreviewSize = getBestAspectPreviewSize(mParameters.getSupportedPreviewSizes(), screenWidth, screenHeight);//Bug Fix for Samsung A8
Log.d("CameraFix", "optimalPreviewSize.width -> " + optimalPreviewSize.width);
Log.d("CameraFix", "optimalPreviewSize.height -> " + optimalPreviewSize.height);
mParameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
mParameters.setPictureSize(optimalPreviewSize.width, optimalPreviewSize.height);
mParameters.setPreviewFpsRange(30000, 30000);
/*if (mDisplayOrientation == 0 || mDisplayOrientation == 180)
setLayoutParams(new FrameLayout.LayoutParams(optimalPreviewSize.width, optimalPreviewSize.height,Gravity.CENTER));
*/
Log.d("CameraFix", "setPreviewFpsRange");
mCamera.setParameters(mParameters);
mCamera.setPreviewDisplay(holder);
//SurfaceTexture st = new SurfaceTexture(10);
//mCamera.setPreviewTexture(st);
mCamera.setPreviewCallback(previewCallback);
mCamera.startPreview();
Log.d("CameraFix", "start preview");
if (mCameraPreviewListener != null)
mCameraPreviewListener.onCameraSurfaceChanged();
catch (Exception e)
e.printStackTrace();
Log.d("CameraFix", e.toString());
public void toggleFlash(boolean flashModeOn)
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
Parameters parameters = mCamera.getParameters();
if (flashModeOn)
//parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
parameters.setFlashMode(Parameters.FLASH_MODE_ON);
mCamera.setParameters(parameters);
mCamera.startPreview();
//Toast.makeText(mContext, R.string.flash_mode_on, Toast.LENGTH_SHORT).show();
else
parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
mCamera.setParameters(parameters);
//Toast.makeText(mContext, R.string.flash_mode_off, Toast.LENGTH_SHORT).show();
else
Toast.makeText(mContext, R.string.flash_not_available, Toast.LENGTH_SHORT).show();
/**
* Source for this solution - https://***.com/questions/21354313/camera-preview-quality-in-android-is-poor/21354442#21354442
*
* @param supportedPreviewSizes
* @param screenWidth
* @param screenHeight
* @return
*/
private Size getBestAspectPreviewSize(List<Size> supportedPreviewSizes, int screenWidth, int screenHeight)
double targetRatio = (double) screenWidth / screenHeight;
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
for (int i = 0; i < supportedPreviewSizes.size(); i++)
Size size = supportedPreviewSizes.get(i);
Log.d(TAG, "getBestAspectPreviewSize: supportedPreviewSizes -> "+size.width +"X"+size.height);
Log.d(TAG, "getBestAspectPreviewSize: supportedPreviewSizes -> "+supportedPreviewSizes.toString());
Log.d(TAG, "getBestAspectPreviewSize: mDisplayOrientation -> "+mDisplayOrientation);
if (mDisplayOrientation == 90 || mDisplayOrientation == 270)
Log.d(TAG, "getBestAspectPreviewSize: inside 90 - 270 ");
targetRatio = (double) screenHeight / screenWidth;
Log.d(TAG, "getBestAspectPreviewSize: targetRatio -> "+targetRatio);
Collections.sort(supportedPreviewSizes,
Collections.reverseOrder(new SizeComparator()));
for (Size size : supportedPreviewSizes)
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);
/*if (mDisplayOrientation == 0 || mDisplayOrientation == 180)
if (optimalSize != null)
return mCamera.new Size(optimalSize.height, optimalSize.width);
else
return null;
else
return (optimalSize);
*/
//return mCamera.new Size(2220,1080);
public int getDisplayOrientation()
return mDisplayOrientation;
public void setDisplayOrientation(int displayOrientation)
this.mDisplayOrientation = displayOrientation;
public Parameters getCameraParameters()
return mCamera.getParameters();
public void setCameraPreviewListener(CameraPreviewListener cameraPreviewListener)
mCameraPreviewListener = cameraPreviewListener;
public interface CameraPreviewListener
void onCameraSurfaceCreated();
void onCameraSurfaceChanged();
void onCameraSurfaceDestroyed();
void onCameraPreviewStarted();
private static class SizeComparator implements
Comparator<Size>
@Override
public int compare(Size lhs, Size rhs)
int left = lhs.width * lhs.height;
int right = rhs.width * rhs.height;
if (left < right)
return (-1);
else if (left > right)
return (1);
return (0);
【问题讨论】:
从您的图片中我们可以看到,这并不是完全全屏的。右侧有导航栏,顶部有系统工具栏。为什么不添加一点黑边来保持预览窗口的纵横比为 16:9? 相机预览是全屏的,并在 Framelayout 的顶部添加了导航栏...我知道没有慢跑预览并添加一些空格是没有办法的。所以将预览大小设置为 1920X1080 并将预览居中。现在它看起来像预览框架。但是仍然不明白为什么三星不提供支持的预览尺寸,即使他们已经知道了。 我完全支持你的反问。请注意,您还有一个替代选项,即填充屏幕的所有宽度,但在顶部和底部裁剪预览框(这就像添加负大小的边距)。 @Ramprasad Hi Ram 你能看看我的代码,它发布了一个答案,让我知道。 【参考方案1】:-
您应该再次检查默认相机的可显示区域。我不认为它可以在没有拉伸的情况下完全显示该分辨率。它可能有一个黑色区域、工具栏、状态栏...
您的实施没有任何问题。我们必须找到与您要显示的表面视图相比的最佳支持预览尺寸。在这种情况下,您应该使表面视图 (1920 x 1080) 居中,然后在顶部和底部添加黑色填充区域。
【讨论】:
我将预览大小设置为 1920X1080 并将预览居中。现在它看起来像预览框架。但是仍然不明白为什么三星不提供全屏尺寸作为支持的预览尺寸,即使他们已经知道了。 我们对供应商一无所知。在开发照片共享应用程序时,我在许多设备中都遇到了这个问题。在某些设备中,您可以选择预览分辨率,它们会裁剪预览以保持比例。您必须选择处理所有这些问题的最佳方式。祝你好运。【参考方案2】:请查看我的回答希望对您有所帮助,我通过以下代码解决了拉伸问题,方法名称可能会更改。我分享我的实现,因为我知道在Android中实现相机有多难,所以请不要犹豫,看看下面的部分。
在 ButtonAction 中调用 loadCamera 方法。
private void loadCamera()
if (CommonUtils.deviceHasCamera(getActivityContext))
startBackgroundThread();
mCameraTimeOut=(isPermissionGranted?2500:5000);
if (mTextureView.isAvailable())
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
else
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
else
ShowToastUtils.INSTANCE.showCustomToast(getActivityContext, getString(R.string.msg_no_camera));
最初为相机调用 SurfaceListener
private TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener()
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
int width, int height)
mCameraTimeOut=(isPermissionGranted?2500:5000);
Log.e(TAG1, "chooseOptimalSize"+"-SurfaceTextureListener ---=>Width---=>"+width);
Log.e(TAG1, "chooseOptimalSize"+"-SurfaceTextureListener ---=>Height---=>"+height);
openCamera(width, height);
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
int width, int height)
configureTransform(width, height);
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture)
return true;
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)
;
为纹理选择最佳预览尺寸
//Samsung-S6-choices[0]
//Samsung-S7-edge-choices[6]
//OnePlus-5T-choices[15]
/*Following is used for Camera Preview in TextureView, based on device camera resolution*/
/*
* Given @code choices of @code Sizes supported by a camera, chooses the smallest one whose
* width and height are at least as large as the respective requested values, and whose aspect
* ratio matches with the specified value.
*
* @param choices The list of sizes that the camera supports for the intended output class
* @param width The minimum desired width
* @param height The minimum desired height
* @param aspectRatio The aspect ratio
* @return The optimal @code Size, or an arbitrary one if none were big enough
*/
private Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio)
// Collect the supported resolutions that are at least as big as the preview Surface
int loopCounter=0;
Log.e(TAG1, "Screen-->Width x Height="+screenWidth+" x "+screenHeight);
for (Size size : choices)
Log.e(TAG1, "chooseOptimalSize:"+size);
for (Size size : choices)
int orientation = getActivityContext.getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
if((size.getWidth()/16) == (size.getHeight()/9) && size.getWidth() <=7680 ) //8K UHDTV Super Hi-Vision
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"--LoopPosition---==>"+loopCounter);
return size;
else
Log.e(TAG1, "chooseOptimalSize:--given--"+size);
if((size.getWidth()/16) == (size.getHeight()/9) && ((size.getWidth() <=1280)||(size.getHeight()<=1920)))
mCameraRatio=RATIO_16_9;
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-16:9"+"--LoopPosition---==>"+loopCounter);
return size;
else if((size.getWidth()/18) == (size.getHeight()/9) && ((size.getWidth() <=2160)||(size.getHeight()<=3840)))
mCameraRatio=RATIO_18_9;
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-18:9"+"--LoopPosition---==>"+loopCounter);
return size;
else if((size.getWidth()/18.5) == (size.getHeight()/9) && ((size.getWidth() <=2160)||(size.getHeight()<=3840)))
mCameraRatio=RATIO_18_9;
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-18.5:9"+"--LoopPosition---==>"+loopCounter);
return size;
else if((width/19) == (height/9) && ((width <=2208)||(height<=3216)))
mCameraRatio=RATIO_19_9;
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-19:9"+"--LoopPosition---==>"+loopCounter);
return size;
else if((size.getWidth()/19.5) == (size.getHeight()/9) && ((size.getWidth() <=3840)||(size.getHeight()<=2160)))
mCameraRatio=RATIO_19_9;
Log.e(TAG1, "chooseOptimalSize:"+size.getWidth()+"x"+size.getHeight()+"-19.5:9"+"--LoopPosition---==>"+loopCounter);
return size;
else
Log.e(TAG1, "chooseOptimalSize"+" not proper aspect resolution");
loopCounter++;
打开相机
private void openCamera(int width, int height)
CameraManager manager = (CameraManager) getActivityContext.getSystemService(Context.CAMERA_SERVICE);
try
Log.e(TAG, "tryAcquire");
if (!mCameraOpenCloseLock.tryAcquire(mCameraTimeOut, TimeUnit.MILLISECONDS))
throw new RuntimeException("Time out waiting to lock camera opening.");
String mCameraId = manager.getCameraIdList()[cameraId];
// Choose the sizes for camera preview and video recording
characteristics = manager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
try
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
maximumZoomLevel = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
if (map == null)
throw new RuntimeException("Cannot get available preview/video sizes");
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
/*This Line will configure the Texture size*/
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, mVideoSize);
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
Log.e(TAG1, "Width" + mPreviewSize.getWidth() + "X Height" + mPreviewSize.getHeight());
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
else
Log.e(TAG1, "Width" + mPreviewSize.getHeight() + "X Height" + mPreviewSize.getWidth());
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
//S10 preview Size
/* mTextureView.setAspectRatio(1080, 2280);*/
//mTextureView.setAspectRatio(2208, 2944);
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
configureTransform(width, height);
if (isPermissionGranted)
manager.openCamera(mCameraId, mStateCallback, null);
catch (Exception ex)ex.printStackTrace();finally
map=null;
Runtime.getRuntime().gc();
catch (CameraAccessException e)
Toast.makeText(getActivityContext, "Cannot access the camera.", Toast.LENGTH_SHORT).show();
//getActivityContext.finish();
e.printStackTrace();
catch (NullPointerException e)
e.printStackTrace();
catch (InterruptedException e)
e.printStackTrace();
throw new RuntimeException("Interrupted while trying to lock camera opening.");
用于方向处理的ConfigureTransform方法
/*
* Configures the necessary @link android.graphics.Matrix transformation to `mTextureView`.
* This method should not to be called until the camera preview size is determined in
* openCamera, or until the size of `mTextureView` is fixed.
*
* @param viewWidth The width of `mTextureView`
* @param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight)
if (null == mTextureView || null == mPreviewSize)
return;
int rotation = getActivityContext.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation)
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
else if (Surface.ROTATION_0 == rotation)
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale=Math.max((float) viewWidth / mPreviewSize.getWidth(), (float) viewHeight / mPreviewSize.getHeight());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(0, centerX, centerY);
else if(Surface.ROTATION_180== rotation)
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale=Math.max((float) viewWidth / mPreviewSize.getWidth(), (float) viewHeight / mPreviewSize.getHeight());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(0, centerX, centerY);
try
mTextureView.setTransform(matrix);
catch (Exception ex)ex.printStackTrace();finally
bufferRect=null;
viewRect=null;
matrix=null;
终于开始预览
private void startPreview()
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize)
return;
try
closePreviewSession();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Surface previewSurface = new Surface(texture);
mPreviewBuilder.addTarget(previewSurface);
mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
new CameraCaptureSession.StateCallback()
@Override
public void onConfigured(@NonNull CameraCaptureSession session)
mPreviewSession = session;
updatePreview();
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session)
Toast.makeText(getActivityContext, "Failed", Toast.LENGTH_SHORT).show();
, mBackgroundHandler);
//previewSurface=null;
catch (CameraAccessException e)
e.printStackTrace();
【讨论】:
以上是关于三星 S9(18:9) 相机预览拉长的主要内容,如果未能解决你的问题,请参考以下文章
Android 三星 Galaxy S4 自定义相机预览失真
三星 Galaxy S3 上的 Android 相机预览不正确
GALAXY S9狂怼iPhone X,三星又拍广告黑苹果?
三星又拍广告黑苹果,这次是拿 GALAXY S9 怼 iPhone X
我无法通过三星 S9+ 的 DatagramSocket 发送 DatagramPacket(我使用的不是 UI 线程)