Android开发中经常需要重写相机,由此会导致一些旋转的情况(不同的设备摄像头角度是不一样的),此处按照解决思路给出解决方案:

情形一:只需要旋转摄像头方向以及最终的照片,注意两者需要保持一致

1. 获取当前相机摄像头的角度,并进行相应的旋转,方法如下:

此处获取到的摄像头角度可以保存下来,在后面情形二中会用到,这里存到静态变量 orientationDegree 中。

public static int orientationDegree = 0;

   /**
* 适配相机旋转
*
* @param activity
* @param cameraId
* @param camera
*/
public void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = 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;
}
//后置
else {
result = (info.orientation - degrees + 360) % 360;
}
orientationDegree = result;
camera.setDisplayOrientation(result);
}

2. 上述方法中涉及到的参数,第一个Activity,可以传入当前相机Activity的Context,随后进行强转;第二个参数为相机ID(一般设备有多个摄像头,前置,后置等等),下面将给出获取相机ID的方法;

第三个参数为camera对象,当调用open方法的时候就可以获取到 mCamera = Camera.open();

获取相机ID的方法如下:

   /**
* 获取摄像头ID
*
* @return
*/
private int getDefaultCameraId() {
int defaultId = -1;
int numberOfCameras = Camera.getNumberOfCameras(); Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < numberOfCameras; i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
defaultId = i;
}
}
if (defaultId == -1) {
if (numberOfCameras > 0) {
//没有后置摄像头
defaultId = 0;
} else {
Logger.e("没有摄像头");
}
}
return defaultId;
}

情形二:在情形一的基础上,需要旋转拍摄过程中的视频帧,与其他平台对接,涉及到JNI编程。此处参考了stackoverflow上的解答,链接如下:

https://stackoverflow.com/questions/14167976/rotate-an-yuv-byte-array-on-android

拍照回调过程中会产生图片数据,格式为NV21.类型是字节数据。如果需要将此数据传输到其他平台或者保存到本地(本地视频),则也需要进行相应的旋转。

public class CameraPreviewCallback implements Camera.PreviewCallback {
public CameraPreviewCallback(CameraPreviewSend previewSend) {
this.mCameraPreviewSend = previewSend;
} /**
* 在相机预览时每产生一帧时回调
*
* @param data 图片数据,格式 NV21
* @param camera 相机对象
*/
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//todo:业务逻辑
}
}

情形一中获取到的相机旋转角度,此处根据角度来进行相应的旋转。具体方法如下:

switch (orientationDegree) {
case :
localFrameSave(data);
break;
case :
localFrameSave(picRotate90(data, previewWidth, previewHeight));
break;
case :
localFrameSave(picRotate180(data, previewWidth, previewHeight));
break;
case :
localFrameSave(picRotate270(data, previewWidth, previewHeight));
break;
}
    /**
* 旋转图片,顺时针旋转90度
*
* @param data nv21格式的图片
* @param width 图片宽度
* @param height 图片高度
* @return 转换后的图片数据
*/
public native byte[] picRotate90(byte[] data, int width, int height); static {
System.loadLibrary("native-lib");
}

cpp部分:

/**
* 将nv21格式的图片旋转90度
*/
extern "C"
jbyteArray
Java_com_XXX_io_CameraPreviewSend_picRotate90(
JNIEnv *env, jobject /*this*/, jbyteArray data, jint width, jint height) {
jbyte *pBuffer = env->GetByteArrayElements(data, 0);
int length = env->GetArrayLength(data);
jbyte newData[length];//返回的图片数据
//根据原数据pBuffer生成新数据newData
doRotate(pBuffer, newData, width, height); jbyteArray array = env->NewByteArray(length);
env->SetByteArrayRegion(array, 0, length, newData);
env->ReleaseByteArrayElements(data, pBuffer, 0);
return array;
} 
/**
* 将nv21格式的图片旋转90度
* @param data 原图片数据
* @param newData 转换所得图片数据
* @param width 转换后的图片的宽度
* @param height 转换后的图片的高度
*/
void doRotate(jbyte *data, jbyte *newData, jint width, jint height) {
int i = 0;
for (int x = 0; x < width; x++) {
for (int y = height - 1; y >= 0; y--) {
newData[i] = data[y * width + x];
i++;
}
}
i = width * height * 3 / 2 - 1;
for (int x = width - 1; x > 0; x = x - 2) {
for (int y = 0; y < height / 2; y++) {
newData[i] = data[(width * height) + (y * width) + x];
i--;
newData[i] = data[(width * height) + (y * width) + (x - 1)];
i--;
}
}
}

旋转180度的情况:

/**
* 将nv21格式的图片旋转180度
* @param data
* @param newData
* @param width
* @param height
*/
void doRotate180(jbyte *data, jbyte *newData, jint width, jint height) {
int i = ;
int count = ;
for (i = width * height - ; i >= ; i--) {
newData[count] = data[i];
count++;
}
for (i = width * height * / - ; i >= width * height; i -= ) {
newData[count++] = data[i - ];
newData[count++] = data[i];
}
} /**
* 将nv21格式的图片旋转180度
*/
extern "C"
jbyteArray
Java_com_XXX_io_CameraPreviewSend_picRotate180(
JNIEnv *env, jobject /*this*/, jbyteArray data, jint width, jint height) {
jbyte *pBuffer = env->GetByteArrayElements(data, );
int length = env->GetArrayLength(data);
jbyte newData[length];//返回的图片数据
//根据原数据pBuffer生成新数据newData
doRotate180(pBuffer, newData, width, height); jbyteArray array = env->NewByteArray(length);
env->SetByteArrayRegion(array, , length, newData);
env->ReleaseByteArrayElements(data, pBuffer, );
return array;
}

旋转270度的情况:

/**
* 将nv21格式的图片旋转270度
* @param data
* @param newData
* @param width
* @param height
*/
void doRotate270(jbyte *data, jbyte *newData, jint width, jint height) {
// Rotate the Y
int i = ;
for (int x = width - ; x >= ; x--) {
for (int y = ; y < height; y++) {
newData[i] = data[y * width + x];
i++;
}
}// Rotate the U and V color components
i = width * height;
for (int x = width - ; x > ; x = x - ) {
for (int y = ; y < height / ; y++) {
newData[i] = data[(width * height) + (y * width) + (x - )];
i++;
newData[i] = data[(width * height) + (y * width) + x];
i++;
}
}
} /**
* 将nv21格式的图片旋转270度
*/
extern "C"
jbyteArray
Java_com_XXX_io_CameraPreviewSend_picRotate270(
JNIEnv *env, jobject /*this*/, jbyteArray data, jint width, jint height) {
jbyte *pBuffer = env->GetByteArrayElements(data, );
int length = env->GetArrayLength(data);
jbyte newData[length];//返回的图片数据
//根据原数据pBuffer生成新数据newData
doRotate270(pBuffer, newData, width, height); jbyteArray array = env->NewByteArray(length);
env->SetByteArrayRegion(array, , length, newData);
env->ReleaseByteArrayElements(data, pBuffer, );
return array;
}

完结,很感谢stackoverflow上的解答。

重写Android相机适配不同的设备,对于相机旋转角度问题解决方案的更多相关文章

  1. Android重写getResources规避用户调整系统字体大小影响Android屏幕适配

    Android屏幕适配一直是一个头疼的问题.除此之外还要考虑APP在实际应用场景中,用户千奇百怪的设置,最常见的用户设置行为就是设置手机的字体大小,比如把字体设置成超大或者超小,这对屏幕适配又带来额外 ...

  2. Android适配不同的设备

    感谢原作者的整理: http://blog.csdn.net/chenyjays/article/details/41308887 适配不同的语言 把UI中的字符串存储在外部文件,通过代码提取. 创建 ...

  3. android Activity生命周期(设备旋转、数据恢复等)与启动模式

    1.Activity生命周期     接下来将介绍 Android Activity(四大组件之一) 的生命周期, 包含运行.暂停和停止三种状态,onCreate.onStart.onResume.o ...

  4. 【收藏】Android屏幕适配全攻略(最权威的Google官方适配指导)

    来源:http://blog.csdn.net/zhaokaiqiang1992 更多:Android AutoLayout全新的适配方式, 堪称适配终结者 Android的屏幕适配一直以来都在折磨着 ...

  5. Android屏幕适配全攻略(最权威的官方适配指导)(转),共大家分享。

    Android的屏幕适配一直以来都在折磨着我们这些开发者,本篇文章以Google的官方文档为基础,全面而深入的讲解了Android屏幕适配的原因.重要概念.解决方案及最佳实践,我相信如果你能认真的学习 ...

  6. Android屏幕适配全攻略(最权威的官方适配指导) (转)

    招聘信息: Cocos2d-X 前端主程 [新浪微博]手机客户端iOS研发工程师 20k-40k iOS 开发工程师 iOS高级开发工程师(中国排名第一的企业级移动互联网云计算公司 和创科技 红圈营销 ...

  7. 腾讯优测-优社区干货精选 |  那些年,我们在Android机型适配上遇到的坑之Camera拍照时快门咔嚓声

    文/腾讯优测研发工程师 吴宇焕 优测小优有话说: android机型适配的坑自然是不少,不想掉坑快来优测优社区~ 现在Android手机一般都会带有照相功能,有很多朋友就发现手机照相时快门声音很响,想 ...

  8. 腾讯优测| 让Android屏幕适配开发更简单-Google百分比布

    文/腾讯优测工程师 吴宇焕 腾讯优测优社区干货精选~ 相信开发同学都被安卓设备碎片化的问题折磨过,市面上安卓手机的主流屏幕尺寸种类繁多,给适配造成很大的困难.就算搞定了屏幕尺寸问题,各种分辨率又让人眼 ...

  9. Android 屏幕适配(一)百分比布局库(percent-support-lib) 解析与扩展

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/46695347: 本文出自:[张鸿洋的博客] 一.概述 周末游戏打得过猛,于是周 ...

随机推荐

  1. 第一个AngularJS控制器

    <!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...

  2. Tomcat启动排查

    Tomcat启动排查 一.参考 https://blog.csdn.net/baidu_32739019/article/details/64155136

  3. java基础IO流 复制键盘录入的目录,复制其中的.java文件到指定目录,指定目录中有重名,则改名 对加密文件计算字母个数

    package com.swift.jinji; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; im ...

  4. mysql,oracle表数据相互导入

    mysql导入oracle: 例如mysql中有ts_user_info表,现在要导入到oracle中的user_info表 1:导出mysql表数据到data.txt文件 mysql> sel ...

  5. Redis ----------String的操作

    set    key   value 设置key对应的值为String类型的value mset    key   value 一次设置多个 key对应的值 mget    key   value 一 ...

  6. 使用python写一个最基本的mapreduce程序

    一个mapreduce程序大致分成三个部分,第一部分是mapper文件,第二个就是reducer文件,第三部分就是使用hadoop command 执行程序. 在这个过程中,困惑我最久的一个问题就是在 ...

  7. Poweroj:来自学长的善意:ZQ的杀龙之旅(状压BFS)

    传送门:https://www.oj.swust.edu.cn/problem/show/2794 来自学长的善意:ZQ的杀龙之旅 Time Limit: 15000 MS Memory Limit: ...

  8. 27-Middleware管道介绍

    1-Middleware管道介绍,. 如果匹配上/task,则界面只会显示i am task. public void Configure(IApplicationBuilder app, IHost ...

  9. 【文件处理】xml 文件 SAX解析

    SAX的全称是Simple APIs for XML,也即XML简单应用程序接口. 与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式. 当使用SAX分析器对XML文档 ...

  10. Android面试收集录2 Broadcast Receiver详解

    1.Broadcast Receiver广播接收器简单介绍 1.1.定义 Broadcast Receiver(广播接收器),属于Android四大组件之一 在Android开发中,Broadcast ...