SurfaceView cameraPreview = findViewById(R.id.camera_preview);
cameraPreview.getHolder().addCallback(new PreviewSurfaceCallback());
private class PreviewSurfaceCallback implements SurfaceHolder.Callback{
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mPreviewSurface = holder;
mPreviewSurfaceWidth = width;
mPreviewSurfaceHeight = height;
if(mCameraHandler != null){
mCameraHandler.obtainMessage(MSG_SET_PREVIEW_SIZE, width, height).sendToTarget();
mCameraHandler.obtainMessage(MSG_SET_PICTURE_SIZE).sendToTarget();
mCameraHandler.obtainMessage(MSG_SET_PREVIEW_SURFACE, holder).sendToTarget();
mCameraHandler.sendEmptyMessage(MSG_START_PREVIEW);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mPreviewSurface = null;
mPreviewSurfaceWidth = 0;
mPreviewSurfaceHeight = 0;
}
}
打开相机,参数为cameraid
private void openCamera(int cameraId) {
Camera camera = mCamera;
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
//打开相机
mCamera = Camera.open(cameraId);
mCameraId = cameraId;
mCameraInfo = cameraId == mFrontCameraId ? mFrontCameraInfo : mBackCameraInfo;
// 设置相机方向,后面2.1处详细讲述
mCamera.setDisplayOrientation(getCameraDisplayOrientation(mCameraInfo));
}
}
1.3设置预览属性,尺寸、编码格式
查询支持的预览尺寸、编码格式,根据需要设置。
private void setPreviewSize(int shortSide, int longSide) {
if (mCamera != null && shortSide != 0 && longSide != 0){
float aspectRatio = (float)longSide / shortSide;
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
for (Camera.Size previewSize : supportedPreviewSizes) {
//1.设置预览尺寸
if((float)previewSize.width / previewSize.height == aspectRatio && previewSize.height <= shortSide && previewSize.width <= longSide) {
parameters.setPreviewSize(previewSize.width, previewSize.height);
//2.设置预览的编码格式,此处PREVIEW_FORMAT = ImageFormat.NV21
// NV21 即 YUV
if(isPreviewFormatSupported(parameters, PREVIEW_FORMAT)){
parameters.setPreviewFormat(PREVIEW_FORMAT);
int frameWidth = previewSize.width;
int frameHeight = previewSize.height;
int previewFormat = parameters.getPreviewFormat();
PixelFormat pixelFormat = new PixelFormat();
PixelFormat.getPixelFormatInfo(previewFormat, pixelFormat);
int bufferSize = (frameWidth * frameHeight * pixelFormat.bitsPerPixel) / 8;
//3.设置预览的缓冲数组
mCamera.addCallbackBuffer(new byte[bufferSize]);
mCamera.addCallbackBuffer(new byte[bufferSize]);
mCamera.addCallbackBuffer(new byte[bufferSize]);
}
mCamera.setParameters(parameters);
}
}
}
}
1.4设置预览surface,即接收并显示图像的容器
实际设置的是surfaceHolder
private void setPreviewSurface(SurfaceHolder previewSurface) {
if (mCamera != null && previewSurface != null) {
try {
mCamera.setPreviewDisplay(previewSurface);
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.5启动预览
private void startPreview() {
if (mCamera != null && mPreviewSurface != null) {
// 增加callback,便于buffer复用
mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
// 使用完buffer之后回收复用
camera.addCallbackBuffer(data);
}
});
mCamera.startPreview();
}
}
二、相机预览方向校正
因为手机摄像头硬件的设计,不做额外的处理,相机预览的图像角度是错误的,准确的说竖屏状态下,逆时针偏了90°。
上文有一句代码,设置相机预览的旋转方向,此处补充说明.
mCamera.setDisplayOrientation(getCameraDisplayOrientation(mCameraInfo));
完整代码出自于google/Android官方文档 “Android developer”)
private int getCameraDisplayOrientation(Camera.CameraInfo cameraInfo) {
int roration = getWindowManager().getDefaultDisplay().getRotation();
// 屏幕显示方向角度(相对局部坐标Y轴正方向夹角)
int degrees = 0;
switch (roration) {
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 (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
result = (cameraInfo.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (cameraInfo.orientation - degrees + 360) %360;
}
// 相机需要校正的角度
return result;
}
相机预览方向矫正相对复杂些,查阅了许多资料,大多照搬google代码,讲的模棱两可。上面这段代码相信许多朋友都见过,但是对最后result的计算不一定了解。
要讲清楚相机方向矫正,先介绍几个重要的概念
- 手机自然方向
- 局部坐标系
- 显示方向
- 摄像头传感器方向
2.1手机自然方向和局部坐标系
手机默认是竖屏,短边朝上为自然方向,平板默认是横屏,宽边朝上为自然方向。
局部坐标系与手机的自然状态相关,Y轴与手机自然状态时朝上的方向对齐,下图中手机的局部坐标系y轴朝上:
为方便说明,后面讲各个方向,均以局部坐标Y轴正方向为基准
2.2显示方向
显示方向与横竖屏状态有关。竖屏时,显示方向朝上,显示方向与局部坐标Y轴一致,横屏时显示方向朝上与局部坐标x轴对齐。
注意,向左旋转横屏时,显示方向朝上,相对局部坐标Y轴的夹角为90°,即Y轴顺时针旋转90°才能对齐显示方向,向右旋转横屏时,该夹角为270°。
务必理解这个概念,后面计算相机角度校正要用到。
2.3摄像头传感器方向
以后置摄像头为例
开发中,竖屏状态下, window view的坐标系是短边为y轴,长边为x轴
相对手机自然方向,摄像头硬件安装时顺时针旋转了90°,短边为X轴,长边为Y轴。看起来像是专门为pad横屏设计的。(why?我也母鸡。。)
将手机朝左横屏时,两个坐标系刚好对齐,开发中不用适配显示也是对的。
上面代码中 cameraInfo.orientation 获取的就是相机摄像头的方向(相对局部坐标系Y轴)
2.4后置摄像头画面校正
如上所述,因为摄像头安装角度、手机横竖屏状态切换导致的显示方向变化,摄像头采集的图像显示到屏幕上就可能会产生偏角。实际开发中,我们需要计算出这个偏角,以做校正。
如果不做任何处理,degree(显示方向)为0,orientation(摄像头方向)为90°,预览是歪着的。
再次说明,角度均以局部坐标y轴正方向为参考基准
怎么理解呢?你可以想象自己的头是摄像头,你的头向右倒90°看到的图像可不就是歪的么。然后你把看到的图像传给显示屏,显示屏可不知道你是歪着脑袋采集数据的。
校正需要调用 mCamera.setDisplayOrientation(int arg),设置一个角度,将采集的图像顺时针旋转arg角度,以补偿摄像头的偏角。
以向左横屏为例说明:
arg = orientation - degree //所以如果是朝左横屏时
arg = 90 - 90 = 0 //碰巧显示对了,不用校正
可以理解为:摄像头采集的数据超前了90度(相对局部坐标系),而向左横屏造成显示方向超前了90度,如此摄像头方向和显示的方向刚好扯平对齐了。
朝右横屏呢:
// 摄像头相对局部坐标Y轴不变: orientation = 90
// 显示方向朝上,相对局部坐标Y轴顺时针旋转 degree = 270
arg = orientation - degree + 360 = 90 - 270 + 360 = 180
则需要补偿180度,其中+360是为了使旋转方向始终朝顺时钟方向,使arg不为负数,其实-180和+180是一样的。demo里也确实上下颠倒,需要补偿180°。
2.5前置摄像头画面校正
前置稍微麻烦点,区别在于
1. 自拍时自己看到的旋转角度和摄像头得到的真实角度是相反的,即你看到逆时针,真实的是顺时针
2. 相机系统在处理前置拍摄时,会左右镜像,以模拟人照镜子时的效果,所以显示屏上得到的像素是已经左右对调处理了。
这两点造成前置摄像头的校正理解起来稍微费解点,但是代码看起来差不多。
此处有点绕,笔者晚上洗澡时想这个问题走神了,在卫生间发呆了一个多小时,老婆还以为我洗澡出事了。
所以,仅考虑自拍角度相反的因素,
arg = orientation - (-degree) = orientation + degree
再考虑镜像,最终的补偿角度为:
result = 360 - arg,
这就和google官方文档提供的模板代码一致了。朋友,能看到这都理解了,为自己的好学点个赞吧。
看下图,左右镜像后,A镜像为B,A点转到Y轴正方向角度为a,B点转到Y轴正方向为b,a + b = 360,所以镜像后,真正需要补偿的角度为360 - arg
补充:最后取模360,也很好理解,保证角度在一个周期内.
result = (360 - result) % 360;
三、切换摄像头
主要逻辑和第一次打开预览一样,区别是先关掉之前的预览,流程如下:
- 停止预览(实际操作中,没有停止预览也没有报错)
- 关闭当前摄像头
- 重新打开摄像头,使用另一个cameraId
- 设置预览尺寸等属性
- 设置预览surfaceHolder
// demo里用一个button点击来切换camera
Button switchCameraButton = findViewById(R.id.switch_camera);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)
总结
**其实上面说了这么多,钱是永远赚不完的,在这个知识付费的时代,知识技能提升才是是根本!我作为一名8年的高级工程师,知识技能已经学习的差不多。**在看这篇文章的可能有刚刚入门,刚刚开始工作,或者大佬级人物。
像刚刚开始学Android开发小白想要快速提升自己,最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以这里分享一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
这么重要的事情说三遍啦!点赞+点赞+点赞!
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
img-CB06oVcV-1711534312666)]
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
[外链图片转存中…(img-Ybx3QAcS-1711534312666)]