关于Android Camera几点须知
来源:互联网 发布:手机ed2k用什么软件 编辑:程序博客网 时间:2024/06/03 21:20
当我按照官网给出的例子完成camera程序后,我发现这么几个问题:
1. 从预览界面看到的图像,是实际景象逆时针旋转后的图像;
2. 第一个问题解决后,拍出来的照片依然是被逆时针旋转了90度的图像;
3. 第二个问题也解决后,我发现拍出来的照片虽然方向对了,但没有铺满全屏,换言之,图像比例与屏幕比例不一致。
为解决上面的问题,下面的这几个概念就必须要先搞清楚:
ScreenOrientation 视图的方向
CameraOrientation 摄像头的挂载方向
PreviewSize 预览界面分辨率
PictureSize 最终获取到的图片的分辨率
一些基本概念
自然方向(natrual orientation)
每个设备都有一个自然方向,手机和平板的自然方向不同。android:screenOrientation的默认值unspecified即为自然方向。
关于orientation的两个常见值是这样定义的:
landscape(横屏):the display is wider than it is tall,正常拿着设备的时候,宽比高长,这是平板的自然方向。
portrait(竖屏):the display is taller than it is wide,正常拿设备的时候,宽比高短,这是手机的自然方向。
orientation的值直接影响了Activity.getWindowManager().getDefaultDisplay()的返回值:
activity在landscape下,w*h=1280*720,那么在portrait下就是w*h=720*1280
角度(angle)
所谓屏幕和摄像头角度,都是指相对于自然方向旋转过的角度。根据旋转角度即可获知当前的方向。
保持预览界面与实景一致
对于手机,其自然方向是portrait(0度),而camera默认情况下相对自然方向逆时针旋转了90度,即横屏模式。因此当设置android:screenOrientation=”landscape”时,预览界面的图像与实景方向是一致的。如果是portrait,那么看到的预览界面是逆时针旋转90后的景象。
通过Camera.setDisplayOrientation方法来使预览界面与实景保持一致的方法,官方文档已经给出:
01
public
static
void
setCameraDisplayOrientation(
int
cameraId, android.hardware.Camera camera) {
02
03
android.hardware.Camera.CameraInfo =
new
android.hardware.Camera.CameraInfo();
04
05
android.hardware.Camera.getCameraInfo(cameraId, info);
06
07
int
rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
08
09
int
degrees =
0
;
10
11
switch
(rotation) {
12
13
case
Surface.ROTATION_0: degrees =
0
;
break
;
14
15
case
Surface.ROTATION_90: degrees =
90
;
break
;
16
17
case
Surface.ROTATION_180: degrees =
180
;
break
;
18
19
case
Surface.ROTATION_270: degrees =
270
;
break
;
20
21
}
22
23
int
result;
24
25
if
(info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
26
27
result = (info.orientation + degrees) %
360
;
28
29
result = (
360
- result) %
360
;
// compensate the mirror
30
31
}
else
{
// back-facing
32
33
result = (info.orientation - degrees +
360
) %
360
;
34
35
}
36
37
camera.setDisplayOrientation(result);
38
39
}
如果没有限定activity的方向,那么就是用上面的方法在每次屏幕方向改变后调用。
在部分手机上,如果限定了activity方向,比如portrait,那么这个函数返回的是定值90度。不论前置还是后置摄像头皆可一句话搞定:camera.setDisplayOrientation(90);
计算原理是这样的:
Activity.getWindowManager().getDefaultDisplay().getRotation();
视图转向与设备物理转向刚好相反。比如设备逆时针旋转了90度,系统计算的时候是考虑设备要顺时针旋转多少度才能恢复到自然方向,当然是再顺时针旋转90度,因此该方法此时返回的是90,即Surface.ROTATION_90;如果顺时针旋转90度,那么设备要顺时针旋转270度才能恢复到自然方向,因此返回值为270。因此,想快速计算出getRotation的返回值就这么想:设备逆时针旋转过的角度。手机在portrait下,该方法返回的是0度。
Camera.CameraInfo.orientation
该值是摄像头与自然方向的角度差,即摄像头需要顺时针转过多少度才能恢复到自然方向。如果屏幕是portrait,那么后置摄像头是90度,前置摄像头是270度,这样算下来,最后的result都是90度。
在不同的设备上,Camera.CameraInfo.orientation可能会有差异,所以最好通过上面的函数进行计算,而非设置定值。
在API level14之前,设置预览界面必须要按照如下顺序:Camera.stopPreview——>Camera.setDisplayOrientation——>Camera.startPreview。
保持照片与实景一致
仅仅设置预览界面与实景一致是不够的,还需通过Camera.Parameters.setRotation设置camera旋转的角度,看一下下面两个函数的对比:
Camera.setDisplayOrientation:设置camera的预览界面旋转的角度。该函数会影响预览界面的显示方向,对于竖屏模式的应用非常有用。这个函数不会影响最终拍照所得媒体文件的方向。
Camera.Parameters.setRotation:设置camera顺时针旋转角度。这个函数会影响最终拍出的图片方向。
计算setRotation需要旋转多少度,需要借助OrientationEventListener和Camera.CameraInfo.orientation。首先继承OrientationEventListener,实现onOrientationChanged方法。onOrientationChanged传入的角度是当前设备相对于自然方向顺时针旋转过的角度。OrientationEventListener与Camera.CameraInfo.orientation之和即为后置摄像头应该旋转的角度,差为前置摄像头应该旋转的角度。
官方文档也给出了计算的sample:
01
public
void
onOrientationChanged(
int
orientation) {
02
if
(orientation == ORIENTATION_UNKNOWN)
return
;
03
android.hardware.Camera.CameraInfo info =
04
new
android.hardware.Camera.CameraInfo();
05
android.hardware.Camera.getCameraInfo(cameraId, info);
06
orientation = (orientation +
45
) /
90
*
90
;
07
int
rotation =
0
;
08
if
(info.facing == CameraInfo.CAMERA_FACING_FRONT) {
09
rotation = (info.orientation - orientation +
360
) %
360
;
10
}
else
{
// back-facing camera
11
rotation = (info.orientation + orientation) %
360
;
12
}
13
mParameters.setRotation(rotation);
14
}
这样,拍照后得到的就是正常的图片。
找出最佳预览分辨率
这一步不是必须的。预览界面默认的分辨率可能不是最佳的,这样在拍照的时候,看到的实景就会不够清晰,最好的情况是找到与屏幕分辨率相同的预览分辨率。注意,设置该值不会影响最终拍出的图片的分辨率。
下面是从小米2S上获得的一组数据:
D/mycamera﹕ open the back camera
D/mycamera﹕ screen resolution 720*1280
D/mycamera﹕ camera default resolution 640x480
V/mycamera﹕ Supported preview resolutions: 1920x1088 1280x720 960x720 800x480 720x480 768x432 640x480 576x432 480x320 384x288 352x288 320x240 240x160 176x144
D/mycamera﹕ found preview resolution exactly matching screen resolutions: Point(1280, 720)
首先我们要先获取camera支持的分辨率:Camera.getParameters().getSupportedPreviewSizes();
其次,我们排除分辨率过小的值,这个下限值是个经验值,不一定适合所有的手机;
再次我们尽可能取与屏幕分辨率接近的预览分辨率;
如果找不到合适的,就仍使用默认值。
下面是一个获取最佳预览分辨率的函数:
1
/**
2
* 最小预览界面的分辨率
3
*/
4
private
static
final
int
MIN_PREVIEW_PIXELS =
480
*
320
;
5
6
/**
7
* 最大宽高比差
8
*/
9
private
static
final
double
MAX_ASPECT_DISTORTION =
0.15
;
01
/**
02
* 找出最适合的预览界面分辨率
03
*
04
* @return
05
*/
06
private
Point findBestPreviewResolution() {
07
Camera.Size defaultPreviewResolution = cameraParameters.getPreviewSize();
08
Log.d(TAG,
"camera default resolution "
+ defaultPreviewResolution.width +
"x"
+ defaultPreviewResolution.height);
09
10
List<Camera.Size> rawSupportedSizes = cameraParameters.getSupportedPreviewSizes();
11
if
(rawSupportedSizes ==
null
) {
12
Log.w(TAG,
"Device returned no supported preview sizes; using default"
);
13
return
new
Point(defaultPreviewResolution.width, defaultPreviewResolution.height);
14
}
15
16
// 按照分辨率从大到小排序
17
List<Camera.Size> supportedPreviewResolutions =
new
ArrayList<Camera.Size>(rawSupportedSizes);
18
Collections.sort(supportedPreviewResolutions,
new
Comparator<Camera.Size>() {
19
@Override
20
public
int
compare(Camera.Size a, Camera.Size b) {
21
int
aPixels = a.height * a.width;
22
int
bPixels = b.height * b.width;
23
if
(bPixels < aPixels) {
24
return
-
1
;
25
}
26
if
(bPixels > aPixels) {
27
return
1
;
28
}
29
return
0
;
30
}
31
});
32
33
StringBuilder previewResolutionSb =
new
StringBuilder();
34
for
(Camera.Size supportedPreviewResolution : supportedPreviewResolutions) {
35
previewResolutionSb.append(supportedPreviewResolution.width).append(
'x'
).append(supportedPreviewResolution.height)
36
.append(
' '
);
37
}
38
Log.v(TAG,
"Supported preview resolutions: "
+ previewResolutionSb);
39
40
41
// 移除不符合条件的分辨率
42
double
screenAspectRatio = (
double
) screenResolution.x / (
double
) screenResolution.y;
43
Iterator<Camera.Size> it = supportedPreviewResolutions.iterator();
44
while
(it.hasNext()) {
45
Camera.Size supportedPreviewResolution = it.next();
46
int
width = supportedPreviewResolution.width;
47
int
height = supportedPreviewResolution.height;
48
49
// 移除低于下限的分辨率,尽可能取高分辨率
50
if
(width * height < MIN_PREVIEW_PIXELS) {
51
it.remove();
52
continue
;
53
}
54
55
// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
56
// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
57
// 因此这里要先交换然preview宽高比后在比较
58
boolean
isCandidatePortrait = width > height;
59
int
maybeFlippedWidth = isCandidatePortrait ? height : width;
60
int
maybeFlippedHeight = isCandidatePortrait ? width : height;
61
double
aspectRatio = (
double
) maybeFlippedWidth / (
double
) maybeFlippedHeight;
62
double
distortion = Math.abs(aspectRatio - screenAspectRatio);
63
if
(distortion > MAX_ASPECT_DISTORTION) {
64
it.remove();
65
continue
;
66
}
67
68
// 找到与屏幕分辨率完全匹配的预览界面分辨率直接返回
69
if
(maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
70
Point exactPoint =
new
Point(width, height);
71
Log.d(TAG,
"found preview resolution exactly matching screen resolutions: "
+ exactPoint);
72
73
return
exactPoint;
74
}
75
}
76
77
// 如果没有找到合适的,并且还有候选的像素,则设置其中最大比例的,对于配置比较低的机器不太合适
78
if
(!supportedPreviewResolutions.isEmpty()) {
79
Camera.Size largestPreview = supportedPreviewResolutions.get(
0
);
80
Point largestSize =
new
Point(largestPreview.width, largestPreview.height);
81
Log.d(TAG,
"using largest suitable preview resolution: "
+ largestSize);
82
return
largestSize;
83
}
84
85
// 没有找到合适的,就返回默认的
86
Point defaultResolution =
new
Point(defaultPreviewResolution.width, defaultPreviewResolution.height);
87
Log.d(TAG,
"No suitable preview resolutions, using default: "
+ defaultResolution);
88
89
return
defaultResolution;
90
}
找出最佳图片分辨率
拍出来的照片,通过Camear.getParameters().setPictureSize()来设置。同preview一样,如果不设置图片分辨率,也是使用的默认的分辨率,如果与屏幕分辨率比例不一致,或不太相近,就会出现照片无法铺满全屏的情况。下面是小米2S上的一组数据:
D/mycamera﹕ Supported picture resolutions: 3264x2448 3264x1840 2592x1936 2048x1536 1920x1088 1600x1200 1280x768 1280x720 1024x768 800x600 800x480 720x480 640x480 352x288 320x240 176x144
D/mycamera﹕ default picture resolution 640x480
D/mycamera﹕ using largest suitable picture resolution: Point(3264, 1840)
D/mycamera﹕ set the picture resolution 3264x1840
找出最佳图片分辨率的方法与找出preview的方法非常类似:
01
private
Point findBestPictureResolution() {
02
List<Camera.Size> supportedPicResolutions = cameraParameters.getSupportedPictureSizes();
// 至少会返回一个值
03
04
StringBuilder picResolutionSb =
new
StringBuilder();
05
for
(Camera.Size supportedPicResolution : supportedPicResolutions) {
06
picResolutionSb.append(supportedPicResolution.width).append(
'x'
).append(supportedPicResolution.height).append(
" "
);
07
}
08
Log.d(TAG,
"Supported picture resolutions: "
+ picResolutionSb);
09
10
Camera.Size defaultPictureResolution = cameraParameters.getPictureSize();
11
Log.d(TAG,
"default picture resolution "
+ defaultPictureResolution.width +
"x"
+ defaultPictureResolution.height);
12
13
// 排序
14
List<Camera.Size> sortedSupportedPicResolutions =
new
ArrayList<Camera.Size>(supportedPicResolutions);
15
Collections.sort(sortedSupportedPicResolutions,
new
Comparator<Camera.Size>() {
16
@Override
17
public
int
compare(Camera.Size a, Camera.Size b) {
18
int
aPixels = a.height * a.width;
19
int
bPixels = b.height * b.width;
20
if
(bPixels < aPixels) {
21
return
-
1
;
22
}
23
if
(bPixels > aPixels) {
24
return
1
;
25
}
26
return
0
;
27
}
28
});
29
30
// 移除不符合条件的分辨率
31
double
screenAspectRatio = (
double
) screenResolution.x / (
double
) screenResolution.y;
32
Iterator<Camera.Size> it = sortedSupportedPicResolutions.iterator();
33
while
(it.hasNext()) {
34
Camera.Size supportedPreviewResolution = it.next();
35
int
width = supportedPreviewResolution.width;
36
int
height = supportedPreviewResolution.height;
37
38
// 在camera分辨率与屏幕分辨率宽高比不相等的情况下,找出差距最小的一组分辨率
39
// 由于camera的分辨率是width>height,我们设置的portrait模式中,width<height
40
// 因此这里要先交换然后在比较宽高比
41
boolean
isCandidatePortrait = width > height;
42
int
maybeFlippedWidth = isCandidatePortrait ? height : width;
43
int
maybeFlippedHeight = isCandidatePortrait ? width : height;
44
double
aspectRatio = (
double
) maybeFlippedWidth / (
double
) maybeFlippedHeight;
45
double
distortion = Math.abs(aspectRatio - screenAspectRatio);
46
if
(distortion > MAX_ASPECT_DISTORTION) {
47
it.remove();
48
continue
;
49
}
50
}
51
52
// 如果没有找到合适的,并且还有候选的像素,对于照片,则取其中最大比例的,而不是选择与屏幕分辨率相同的
53
if
(!sortedSupportedPicResolutions.isEmpty()) {
54
Camera.Size largestPreview = sortedSupportedPicResolutions.get(
0
);
55
Point largestSize =
new
Point(largestPreview.width, largestPreview.height);
56
Log.d(TAG,
"using largest suitable picture resolution: "
+ largestSize);
57
return
largestSize;
58
}
59
60
// 没有找到合适的,就返回默认的
61
Point defaultResolution =
new
Point(defaultPictureResolution.width, defaultPictureResolution.height);
62
Log.d(TAG,
"No suitable picture resolutions, using default: "
+ defaultResolution);
63
64
return
defaultResolution;
65
}
按钮随手机旋转自动旋转
在写MyCamera的过程中,顺便模仿小米相机按钮随根据手机方向自动旋转方向的效果。这个逻辑只需要在OrientationEventListener.onOrientationChanged中进行即可:
01
private
class
MyOrientationEventListener
extends
OrientationEventListener {
02
03
public
MyOrientationEventListener(Context context) {
04
super
(context);
05
}
06
07
@Override
08
public
void
onOrientationChanged(
int
orientation) {
09
if
(orientation == ORIENTATION_UNKNOWN)
return
;
10
11
... ...
12
13
14
// 使按钮随手机转动方向旋转
15
// 按钮图片的旋转方向应当与手机的旋转方向相反,这样最终方向才能保持一致
16
int
phoneRotation =
0
;
17
if
(orientation >
315
&& orientation <=
45
) {
18
phoneRotation =
0
;
19
}
else
if
(orientation >
45
&& orientation <=
135
) {
20
phoneRotation =
90
;
21
}
else
if
(orientation >
135
&& orientation <=
225
) {
22
phoneRotation =
180
;
23
}
else
if
(orientation >
225
&& orientation <=
315
) {
24
phoneRotation =
270
;
25
}
26
27
// 恢复自然方向时置零
28
if
(phoneRotation ==
0
&& lastBtOrientation ==
360
) {
29
lastBtOrientation =
0
;
30
}
31
32
// "就近处理":为了让按钮旋转走"捷径",如果起始角度与结束角度差超过180,则将为0的那个值换为360
33
if
((phoneRotation ==
0
|| lastBtOrientation ==
0
) && (Math.abs(phoneRotation - lastBtOrientation) >
180
)) {
34
phoneRotation = phoneRotation ==
0
?
360
: phoneRotation;
35
lastBtOrientation = lastBtOrientation ==
0
?
360
: lastBtOrientation;
36
}
37
38
if
(phoneRotation != lastBtOrientation) {
39
int
fromDegress =
360
- lastBtOrientation;
40
int
toDegrees =
360
- phoneRotation;
41
42
Log.i(TAG,
"fromDegress="
+ fromDegress +
", toDegrees="
+ toDegrees);
43
44
RotateAnimation animation =
new
RotateAnimation(fromDegress, toDegrees,
45
Animation.RELATIVE_TO_SELF,
0
.5f, Animation.RELATIVE_TO_SELF,
0
.5f);
46
animation.setDuration(
1000
);
47
animation.setFillAfter(
true
);
48
buttonAnimation.executeAnimation(animation);
49
lastBtOrientation = phoneRotation;
50
}
51
}
52
}
- 关于Android Camera几点须知
- 关于Android LiveCD的几点问题
- 关于Android线程的几点说明
- 关于Android线程的几点说明
- Android 关于Service的几点理解
- QQ防盗防病毒的几点须知
- 关于项目时间管理的六点须知
- 关于Android流畅度不如iOS的几点看法
- 关于android sdk安装的几点看法
- 关于android的JNI几点注意问题。
- Android中关于线程使用的几点注意事项
- 几点关于iOS应用程序开发与Android应用程序开发
- Android中关于线程使用的几点注意事项
- 关于Android 权限管理的几点认识
- 关于android dropbox API的几点学习
- Android 关于创建桌面快捷图标的几点笔记
- android 关于定位我的几点分享?
- android关于屏幕适配的几点建议
- 自我学习&&迁移学习
- JMail发送邮件
- Android string.xml中的一点说明
- Redis 之 sort排序
- 蓝屏dump分析教程,附分析工具WinDbg
- 关于Android Camera几点须知
- eclipse在windows下没有Android SDK and AVD Manager
- lua table 元素删除
- 学习真不是那么简单
- cocos2dx 自定义抛物线action
- RTMP协议以及提取RTMP视频流组成H264视频文件
- zend framework 视频教程网站
- bat运行错误解决方案集合
- 在IOS 7中使用自动布局