和预览相关的相机参数主要有三个:预览尺寸、预览格式以及自动聚焦。
▐ 预览尺寸
预览尺寸,表示每一预览帧的高度与宽度。在Android设备上,设置的预览尺寸必须是相机支持的尺寸,否则在调用Camera.setParameters方法时,会抛出RuntimeException异常。
不同的设备,所支持的预览尺寸不同,可以通过下面的API获取当前设备支持的预览尺寸列表:
android.hardware.Camera.Parameters#getSupportedPreviewSizes()
一般,如果对预览尺寸没有很强制的需求时,可以不用设置该值,直接走当前设备默认的预览尺寸。
▐ 预览格式
大部分Android设备,只支持NV21和YV12两种预览格式;虽然两种格式都属于YUV格式,但是依旧存在一些区别;简单而言,两者的区别是:
-
YV12:存储顺序是先存Y,再存V,最后存U。YYYVVVUUU;
-
NV21:存储顺序是先存Y,再存U,最后存V。YYYVUVUVU
当前设备所支持的预览格式列表的API,如下:
Camera.Parameters#getSupportedPreviewFormats()
与预览尺寸一致,在Android设备上,设置的预览格式必须是相机支持的格式,否则在调用Camera.setParameters方法时,会抛出RuntimeException异常。
▐ 自动聚焦
如果不设置自动聚焦,那么在预览状态下,随着手机设备的前后、左右移动,会出现预览界面模糊的现象。
一般而言,有两种自动聚焦的方法:
-
使用相机自带的自动聚焦模式;
-
使用传感器监听设备的运动情况(静止或移动),然后以此时机,执行相机自带的触摸聚焦API,可参考详情页面(回复页面);
目前,Android Camera 1 只支持以下四种自动聚焦模式:
-
FOCUS_MODE_AUTO
-
FOCUS_MODE_CONTINUOUS_PICTURE
-
- 一般适用于拍照的场景
-
FOCUS_MODE_CONTINUOUS_VIDEO
-
- 一般适用于录屏的场景
-
FOCUS_MODE_MACRO
-
- 一般适用于特写镜头场景
一般而言,会使用FOCUS_MODE_AUTO作为自动聚焦的聚焦模式;原因是因为FOCUS_MODE_CONTINUOUS_PICTURE与FOCUS_MODE_CONTINUOUS_VIDEO模式在部分机型中无法聚焦;至于FOCUS_MODE_MACRO模式,目前暂未发现与FOCUS_MODE_AUTO有什么比较明显的区别。
Android Camera 1通过以下API进行聚焦模式的设置:
Camera.Parameters#setFocusMode(String focusMode)
除了上述四种支持自动聚焦的聚焦模式之外,Android Camera 1还有一些其他场景使用的聚焦模式。
再设置了自动聚焦的聚焦模式后,还需要以下API来完成最终的自动聚焦效果:
// 先取消其他的自动聚焦操作
mCamera.cancelAutoFocus();
mCamera.autoFocus(this);
autoFocus方法的参数是Camera.AutoFocusCallback接口的实现者;该接口提供了一个自动聚焦的回调方法:
public interface AutoFocusCallback{
void onAutoFocus(boolean success, Camera camera);
}
回调方法中的success参数表示聚焦是否成功(成功表示当前预览帧界面比较清晰);
需要注意的是,autoFocus方法的内部并不会循环聚焦;所以,如果要一直保持自动聚焦,则需要在回调方法中再次调用autoFocus方法;如下所示:
@override
void onAutoFocus(boolean success, final Camera camera){
Handler handler = new Handler(Looper.getMainLooper());
// 一般而言,需要延迟1秒再次执行自动聚焦;
// 之所以不马上执行,是因为在部分机型上,马上执行自动聚焦,会引起预览界面闪烁(尤其是后置摄像头)
handler.postDelay(new Runnable(){
@override
public void run(){
camera.autoFocuse(this);
}
}, 1000)
}
还需要注意的一点是,autoFocus方法一定要在Camera#startPreview()方法之后执行,否则autoFocus方法会抛出RuntimeException异常。
▐ 相机显示角度
原始图片
后置摄像头某一帧预览图片
前置摄像头某一帧预览图片
从三张图片可以对不得出,以下两个结论:
-
后置摄像头,返回的预览帧图片相较于原始图片,顺时针旋转90度;
-
前置摄像头,返回的预览帧图片相较于原始图片,逆时针旋转90度;
因此,在处理预览帧需要保证预览帧的方向与屏幕(界面)方向一致;具体而言,可以分为以下两个场景:
-
对于系统渲染预览帧的场景(SurfaceView或TextureView),需要调用Camera#setDisplayOrientation(int degrees)方法来设置相机的显示角度;
-
对于使用GPU来渲染预览帧的场景(GLSurfaceView),则在采样预览帧对应的纹理的时候,需要考虑旋转的兼容;具体的旋转角度可参考Camera.CameraInfo#orientation属性(该属性的含义表示帧图片需要顺时针旋转多少度才能恢复成原始图片);
对于第一种场景,setDisplayOrientation方法的参数值,可通过以下标准方法来准确的获取:
public static int setCameraDisplayOrientation(Activity activity, int cameraId) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
// 一般而言,当前Activity为竖屏模式,degress为0;
// 当前Activity为横屏模式,degress为90;
int degrees;
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;
default:
degrees = 0;
break;
}
// info.orientation表示预览帧图片为了与设备的自然方向对齐,所需的顺时针旋转的角度
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
// compensate the mirror
// 前置摄像头,在顺时针旋转之前,会水平翻转
result = (360 - result) % 360;
} else { // back-facing
result = (info.orientation + (360 - degrees)) % 360;
}
return result;
}
开启预览
当预览相关的参数设置完毕,需要调用以下API开启相机的预览功能:
Camera.startPreview()
该API有三点需要注意的:
-
在调用startPreview方法之前,需确保调用过Camera.setPreviewTexture方法或者Camera.setPreviewDisplay方法;否则,不仅没有预览画面,而且预览回调接口也不会回调。
-
如果调用startPreview方法的Camera对象已经调用了release方法,则会抛出以下异常:
java.lang.RuntimeException: Camera is being used after Camera.release() was called
at android.hardware.Camera.startPreview(Native Method)
- 如果调用startPreview方法之前,有其他应用调用了openCamera,则会抛出以下异常:
java.lang.RuntimeException: startPreview failed
at android.hardware.Camera.startPreview(Native Method)
预览回调
当调用Camera.startPreview方法之后,Android Camera1会回调以下接口:
public interface PreviewCallback
{
void onPreviewFrame(byte[] data, Camera camera);
}
Android Camera 1通常有以下几种方式来设置预览回调接口:
方法名 | 入参 | 说明 |
setOneShotPreviewCallback | PreviewCallback |
|
setPreviewCallbackAllocation | Allocation |
|
setPreviewCallbackWithBuffer | PreviewCallback |
|
setPreviewCallback | PreviewCallback |
|
预览数据显示
将预览数据显示出来,目前有以下两种方式:
-
向Android Camera 1提供一个用来显示的本地窗口(SurfaceHolder或SurfaceTexture);
-
利用OpenGL ES环境来渲染预览数据;
这两种方式之中,前者的实现比较方便,但是无法个性化渲染预览数据;后者的实现较为复杂,但是可以灵活的处理、渲染预览帧数据。
▐ SurfaceHolder&SurfaceTexture
Android Camera 1使用SurfaceHolder对象与SurfaceTexture对象,将预览帧数据分别发送给SurfaceView与TextrueView进行显示。
虽然,两者的开发流程相似,但是背后的原理,还是差异较大。
对于SurfaceHolder(SurfaceView),可以通过下图简略的说明其流程:
首先,SurfaceHolder(或者说SurfaceView)会分配好一块单独的GraphicBuffer用以单独渲染;这块单独的GraphicBuffer,与SurfaceView所在的视图树对应的GraphicBuffer不同,前者在SurfaceFlinger端,也有单独的记录用以管理、合成。
然后,使用相机的应用,会将SurfaceHolder传递给相机服务进程,相机服务进程便以此创建一个Surface对象;
当相机服务进程开始预览,相机服务进程会将预览的YUV数据,绘制至此Surface对象之上,最后通过此Surface对象与SurfaceFlinger进程,跨进程通信,将内容渲染到屏幕之上。
此方式在预览期间,无法使用Surface.lockCanvas方法,会抛出IllegalArgumentException异常;所以,无法额外的在预览界面上进行个性化的渲染。
对于SurfaceTexture(TextureView),可以通过下图简略的说明流程:
首先,TextureView会创建好一个SurfaceTexture对象;这个SurfaceTexture对象,会与一个硬件加速相关的Layer关联起来;这个Layer对象属于TextureView所在的视图树。
然后,使用相机的应用,会将SurfaceTexture传递给相机服务进程,相机服务进程便以此创建一个Surface对象;
当相机服务进程开始预览,相机服务进程会将预览的YUV数据,绘制至此Surface对象之上,最后通过此Surface对象与使用相机的应用,跨进程通信,将内容渲染到TextureView所在的视图树上;可以将SurfaceTexture看做是一个跨进程写入的纹理,相机服务进程持有此纹理的写权限。
最终,通过视图树与SurfaceFlinger进程,通信,将内容渲染到屏幕之上。
此方式在预览期间,无法使用Surface.lockCanvas方法,会抛出IllegalArgumentException异常;所以,无法额外的在预览界面上进行个性化的绘制。
此外该方式还有一个地方需要注意:Camera.setPreviewTexture方法并不会全局持有SurfaceTexture,而SurfaceTexture对象一旦失去引用,被GC掉,那么预览效果同样会失效。
▐ openGL ES渲染预览数据
在openGL环境之中,可以通过以下两种方式渲染预览数据:
-
直接通过预览帧的byte数组渲染;
-
通过预览帧对应的SurfaceTexture对象进行渲染;
两种方式,最大的区别是:是否由android camera自动将预览数据填充至纹理之中。
根据上面的叙述,预览帧byte数组的格式是yuv格式;因此该种方式的核心思路是,在GPU中将yuv数据转换成RGB格式。
相应的转换脚本,在网上很容易找到,大体上的计算方式是一致的,具体如下代码所示:
precision mediump float;
uniform sampler2D tex_y;
uniform sampler2D tex_uv;
// 纹理坐标
varying vec2 texture_coordinate;
void main(){
float r, g, b, y, u, v;
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-sHYJRcKn-1715483972731)]
[外链图片转存中…(img-IkivWWih-1715483972733)]
[外链图片转存中…(img-i2nKh8rh-1715483972734)]
[外链图片转存中…(img-82qdkqxM-1715483972735)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!