2960x1440 拉伸 Android 相机预览
Posted
技术标签:
【中文标题】2960x1440 拉伸 Android 相机预览【英文标题】:2960x1440 stretching Android Camera Preview 【发布时间】:2019-03-02 16:10:25 【问题描述】:我的相机预览几乎可以在所有屏幕上完美运行,但是当我使用2960x1440
屏幕(例如在手机 S8 和 S9 上)测试我的相机时,预览确实被拉伸了。
我已经包含了为相机设置 preview size
的所有内容。
private static final int MAX_PREVIEW_WIDTH = 2960;
private static final int MAX_PREVIEW_HEIGHT = 1440;
/**
* Given @code choices of @code Sizes supported by a camera, choose the smallest one that
* is at least as large as the respective texture view size, and that is at most as large as the
* respective max size, and whose aspect ratio matches with the specified value. If such size
* doesn't exist, choose the largest one that is at most as large as the respective max size,
* 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 textureViewWidth The width of the texture view relative to sensor coordinate
* @param textureViewHeight The height of the texture view relative to sensor coordinate
* @param maxWidth The maximum width that can be chosen
* @param maxHeight The maximum height that can be chosen
* @param aspectRatio The aspect ratio
* @return The optimal @code Size, or an arbitrary one if none were big enough
*/
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)
List<Size> bigEnough = new ArrayList<>();
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices)
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w)
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight)
bigEnough.add(option);
else
notBigEnough.add(option);
if (bigEnough.size() > 0)
return Collections.min(bigEnough, new CompareSizesByArea());
else if (notBigEnough.size() > 0)
return Collections.max(notBigEnough, new CompareSizesByArea());
else
return choices[0];
/**
* 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 static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio)
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices)
if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height)
bigEnough.add(option);
if (bigEnough.size() > 0)
return Collections.min(bigEnough, new CompareSizesByArea());
else
return choices[0];
/**
* Sets up member variables related to camera.
* @param width The width of available size for camera preview
* @param height The height of available size for camera preview
*/
private void setUpCameraOutputs(int width, int height)
System.out.println("Setup camera outputs.");
try
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class)); //640x480
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
width, height, mVideoSize);
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
int displayRotation =parentactivity.getWindowManager().getDefaultDisplay().getRotation();
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation)
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (mSensorOrientation == 90 || mSensorOrientation == 270)
swappedDimensions = true;
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (mSensorOrientation == 0 || mSensorOrientation == 180)
swappedDimensions = true;
break;
default:
Point displaySize = new Point();
parentactivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (swappedDimensions)
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
if (maxPreviewWidth > MAX_PREVIEW_WIDTH)
maxPreviewWidth = MAX_PREVIEW_WIDTH;
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT)
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, mVideoSize);
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE)
mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
else
mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
catch (IllegalArgumentException e)
e.printStackTrace();
catch (CameraAccessException | NullPointerException e)
e.printStackTrace();
/**
* Creates a new @link CameraCaptureSession for camera preview.
*/
private void createCameraPreviewSession()
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize)
return;
try
closePreviewSession();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback()
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession)
if (null == mCameraDevice)
return;
mCaptureSession = cameraCaptureSession;
try
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
mCaptureCallback, mBackgroundHandler);
catch (Exception e)
e.printStackTrace();
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession)
showToast("Failed to configure camera.");
, null
);
catch (Exception e)
e.printStackTrace();
/**
* Configures the necessary @link Matrix transformation to `mTextureView`.
* This method should be called after the camera preview size is determined in
* setUpCameraOutputs and also 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 || null == parentactivity)
return;
int rotation =parentactivity.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_180 == rotation)
matrix.postRotate(180, centerX, centerY);
mTextureView.setTransform(matrix);
由于我不太明白将预览尺寸缩放到 2960x1440 时出现什么问题,如果有人能帮助我解决这个问题,我将不胜感激。
以防万一我添加了完整代码的链接Pastebin Link to Camera
【问题讨论】:
拉伸是什么意思。你能举例说明它在普通手机和高分辨率手机上的外观吗? @zipo13 没拿到手机截图,明天看看能不能弄到 @zipo13 但它基本上会垂直拉伸视频并使其看起来很难看 【参考方案1】:我认为您需要获得支持预览尺寸并从中计算最佳宽度和高度。
例如
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;
然后在 onMeasure 方法中使用它来获得最佳预览尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, height);
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
if (mSupportedPreviewSizes != null)
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
然后在surfaceChanged里面设置你的相机参数
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
mCamera.setParameters(parameters);
mCamera.startPreview();
【讨论】:
我想说它有效,但由于我使用的是定制的Camera
,我不能使用onMeasure()
,因此我不能使用resolveSize
和getSuggestedMinimumWidth()
的功能以上是关于2960x1440 拉伸 Android 相机预览的主要内容,如果未能解决你的问题,请参考以下文章