三星 Galaxy S3、S4、S5 的 Android 相机/图片方向问题
Posted
技术标签:
【中文标题】三星 Galaxy S3、S4、S5 的 Android 相机/图片方向问题【英文标题】:Android camera/picture orientation issues with Samsung Galaxy S3, S4, S5 【发布时间】:2015-02-15 09:43:12 【问题描述】:我正在为 android API 16 到 21 开发一个相机应用程序,其主要和唯一目的是拍摄 portrait 照片。我可以使用多种设备(Nexus 4、Nexus 5、HTC...)拍照并让它们正确定向(这意味着我的预览在大小和方向上都等于所拍照片)。
但是,我已经在其他几台设备上测试了我的应用程序,其中一些给我带来了很多麻烦:三星 Galaxy S3/S4/S5。
在这三个设备上,预览是正确显示的,但是onPictureTaken(final byte[] jpeg, Camera camera)
方法返回的图片总是横向的。
这是从byte[] jpeg
创建的位图,并在将其保存到磁盘之前在 ImageView 中显示给我的用户:
这是保存在磁盘上的图像:
如您所见,图像在预览中被完全拉伸,并且在保存到磁盘后错误地旋转。
这是我的 CameraPreview 类(我混淆了其他方法,因为它们与相机参数无关):
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback
private SurfaceHolder surfaceHolder;
private Camera camera;
// Removed unnecessary code
public void surfaceCreated(SurfaceHolder holder)
camera.setPreviewDisplay(holder);
setCameraParameters();
camera.startPreview();
private void setCameraParameters()
Camera.Parameters parameters = camera.getParameters();
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
int rotation = windowManager.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 rotate = (info.orientation - degrees + 360) % 360;
parameters.setRotation(rotate);
// Save Parameters
camera.setDisplayOrientation(90);
camera.setParameters(parameters);
为什么这段代码也适用于三星设备以外的其他设备?
我试图在以下 SO 帖子中找到答案,但到目前为止没有任何帮助: this one 和 this other one。
编辑
实施 Joey Chong 的回答不会改变任何事情:
public void onPictureTaken(final byte[] data, Camera camera)
try
File pictureFile = new File(...);
Bitmap realImage = BitmapFactory.decodeByteArray(data, 0, data.length);
FileOutputStream fos = new FileOutputStream(pictureFile);
realImage.compress(Bitmap.CompressFormat.JPEG, 100, fos);
int orientation = -1;
ExifInterface exif = new ExifInterface(pictureFile.toString());
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (exifOrientation)
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = 90;
break;
case ExifInterface.ORIENTATION_NORMAL:
orientation = 0;
break;
default:
break;
fos.close();
这是我得到的工作设备的 EXIF 结果:
方向:0这里是 S4 的结果:
方向:0【问题讨论】:
setRotation(90) to take picture in portrait mode does not work on samsung devices的可能重复 【参考方案1】:这是因为手机仍然以横向保存并将元数据放置为90度。 您可以尝试检查 exif,在放入图像视图之前旋转位图。要检查 exif,请使用以下内容:
int orientation = -1;
ExifInterface exif = new ExifInterface(imagePath);
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (exifOrientation)
case ExifInterface.ORIENTATION_ROTATE_270:
orientation = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
orientation = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
orientation = 90;
break;
case ExifInterface.ORIENTATION_NORMAL:
orientation = 0;
break;
default:
break;
【讨论】:
感谢您的回答,我实施了您的解决方案(请参阅编辑),但 exifOrientation 在工作和非工作设备上始终等于 0。 ImageView 的 scaleType 设置是什么?如果设置为center
,结果是什么?
预览与问题无关,问题是在某些三星设备上,图像保存为横向而不是像在 Nexus 5 中那样的纵向。
对不起,我的建议无济于事,到目前为止,我使用 s3 和 s4 进行测试的项目似乎还可以。简单测试一下,如果把parameters.setRotation(rotate);
改成parameters.setRotation(90);
,有什么不同吗?【参考方案2】:
我在保存的图像方面遇到了类似的问题。
我使用了类似于用户 kinghsmit 在此处描述的 https://github.com/googlesamples/android-vision/issues/124 的内容(2016 年 9 月 15 日的评论)。
我会复制到这里,以防万一。
private CameraSource.PictureCallback mPicture = new CameraSource.PictureCallback()
@Override
public void onPictureTaken(byte[] bytes)
int orientation = Exif.getOrientation(bytes);
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
switch(orientation)
case 90:
bitmapPicture= rotateImage(bitmap, 90);
break;
case 180:
bitmapPicture= rotateImage(bitmap, 180);
break;
case 270:
bitmapPicture= rotateImage(bitmap, 270);
break;
case 0:
// if orientation is zero we don't need to rotate this
default:
break;
//write your code here to save bitmap
public static Bitmap rotateImage(Bitmap source, float angle)
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
以下类用于从 byte[] 数据中获取方向。
public class Exif
private static final String TAG = "CameraExif";
// Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
public static int getOrientation(byte[] jpeg)
if (jpeg == null)
return 0;
int offset = 0;
int length = 0;
// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF)
int marker = jpeg[offset] & 0xFF;
// Check if the marker is a padding.
if (marker == 0xFF)
continue;
offset++;
// Check if the marker is SOI or TEM.
if (marker == 0xD8 || marker == 0x01)
continue;
// Check if the marker is EOI or SOS.
if (marker == 0xD9 || marker == 0xDA)
break;
// Get the length and check if it is reasonable.
length = pack(jpeg, offset, 2, false);
if (length < 2 || offset + length > jpeg.length)
Log.e(TAG, "Invalid length");
return 0;
// Break if the marker is EXIF in APP1.
if (marker == 0xE1 && length >= 8 &&
pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
pack(jpeg, offset + 6, 2, false) == 0)
offset += 8;
length -= 8;
break;
// Skip other markers.
offset += length;
length = 0;
// JEITA CP-3451 Exif Version 2.2
if (length > 8)
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A)
Log.e(TAG, "Invalid byte order");
return 0;
boolean littleEndian = (tag == 0x49492A00);
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length)
Log.e(TAG, "Invalid offset");
return 0;
offset += count;
length -= count;
// Get the count and go through all the elements.
count = pack(jpeg, offset - 2, 2, littleEndian);
while (count-- > 0 && length >= 12)
// Get the tag and check if it is orientation.
tag = pack(jpeg, offset, 2, littleEndian);
if (tag == 0x0112)
// We do not really care about type and count, do we?
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
switch (orientation)
case 1:
return 0;
case 3:
return 180;
case 6:
return 90;
case 8:
return 270;
Log.i(TAG, "Unsupported orientation");
return 0;
offset += 12;
length -= 12;
Log.i(TAG, "Orientation not found");
return 0;
private static int pack(byte[] bytes, int offset, int length, boolean littleEndian)
int step = 1;
if (littleEndian)
offset += length - 1;
step = -1;
int value = 0;
while (length-- > 0)
value = (value << 8) | (bytes[offset] & 0xFF);
offset += step;
return value;
它对我有用,除了 Nexus 5x,但那是因为该设备由于其构造而存在一个特殊问题。
希望对你有帮助!
【讨论】:
实际上,您不需要从 JPEG 中读取 EXIF 字节 - 您也可以在拍摄照片时跟踪设备方向。是的,恶意用户可以快速将设备旋转到横向和向后,您将报告错误的方向。但是相机会根据完全相同的逻辑设置 EXIF 标志(如果有的话),并且很容易被愚弄。 只检查一次(拍照时)不是更有效吗? 你担心什么样的效率?如果用户在按下 capture 按钮后快速更改设备方向,您可能会得到错误的结果。双重检查可以使您的选择更加可靠。无论如何,如果报告的角度是45°,你会选择横向还是纵向?其实TS的目的是拍portrait的照片。在这种情况下,他可以简单地选择 90。【参考方案3】:我用过这个AndroidCameraUtil。 它在这个问题上帮助了我很多。
【讨论】:
【参考方案4】:您可以尝试使用相机参数来解决旋转问题。
Camera.Parameters parameters = camera.getParameters();
parameters.set("orientation", "portrait");
parameters.setRotation(90);
camera.setParameters(parameters);
【讨论】:
在我测试过的三星 Galaxy S4 上没有做任何事情。以上是关于三星 Galaxy S3、S4、S5 的 Android 相机/图片方向问题的主要内容,如果未能解决你的问题,请参考以下文章
三星galaxy s3的处理器到底是Exynos4212还是Exynos4412