Android9.0 Camera2 横屏问题修改记录

2024-08-22 06:32

本文主要是介绍Android9.0 Camera2 横屏问题修改记录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

vendor\mediatek\proprietary\packages\apps 目录下有三份相机源码 分别是

Camera、 Camera1、 Camera2

通过查看 mk 发现通过 ifeq ($(MTK_CAMERA_APP_VERSION), 3) 来控制编译哪一个,

MTK_CAMERA_APP_VERSION 宏定义在 device/mediateksample/xxxxxx/ProjectConfig.mk

整体界面相关

Camera2 中适配了两套 api, 老版本的 Camera 和新版本的 Camera2, 通过 CameraApiHelper 配置

Camera2\common\src\com\mediatek\camera\common\mode\CameraApiHelper.java

public static CameraApi getCameraApiType(@Nullable String modeName) {return CameraApi.API2;
}public enum CameraApi {/** Use the {@link android.hardware.Camera} class. */API1,/** Use the {@link android.hardware.camera2} package. */API2
}

预览布局不延伸到 navigation 中,不显示 statusbar

增加 requestWindowFeature(Window.FEATURE_NO_TITLE)

Camera2\host\src\com\mediatek\camera\QuickActivity.java

@Overrideprotected final void onCreate(Bundle bundle) {LogHelper.i(TAG, "onCreate()");IPerformanceProfile profile = PerformanceTracker.create(TAG, "onCreate").start();mStartupOnCreate = true;super.onCreate(bundle);//cczheng add for don't show statusbargetWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);requestWindowFeature(Window.FEATURE_NO_TITLE);mMainHandler = new Handler(getMainLooper());onPermissionCreateTasks(bundle);profile.stop();}

注释 setSystemUiVisibility(View.SYSTEM_UI_LAYOUT_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_STABLE

Camera2\host\src\com\mediatek\camera\CameraActivity.java

@Overrideprotected void onCreateTasks(Bundle savedInstanceState) {if (!isThirdPartyIntent(this) && !isOpenFront(this)) {CameraUtil.launchCamera(this);}IPerformanceProfile profile = PerformanceTracker.create(TAG, "onCreate").start();super.onCreateTasks(savedInstanceState);//cczheng annotation for layout forbbiden into navigationbar area/*getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_LAYOUT_FLAGS| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);*/setContentView(R.layout.activity_main);mOrientationListener = new OrientationEventListenerImpl(this);//create common ui module.mCameraAppUI = new CameraAppUI(this);profile.mark("CameraAppUI initialized.");mCameraAppUI.onCreate();profile.mark("CameraAppUI.onCreate done.");mIModeListener = new ModeManager();mIModeListener.create(this);profile.mark("ModeManager.create done.");profile.stop();}

旋转界面圆形图标 90 度, 闪光灯、HDR、拍照模式等

canvas.rotate(90)

Camera2\common\src\com\mediatek\camera\common\widget\RotateImageView.java

 @Overrideprotected void onDraw(Canvas canvas) {Drawable drawable = getDrawable();if (drawable == null) {return;}Rect bounds = drawable.getBounds();int w = bounds.right - bounds.left;int h = bounds.bottom - bounds.top;....// canvas.rotate(-mCurrentDegree);canvas.rotate(90);//cczheng change 90 for rotate all imageViewcanvas.translate(-w / 2, -h / 2);if (mDrawableBitmap != null) {canvas.drawBitmap(mDrawableBitmap, 0, 0, null);} else {drawable.draw(canvas);}canvas.restoreToCount(saveCount);}

拍照相关

预览旋转 90

horizontalMirrorData() 和 changePreviewDisplayOrientation() 都是从网上找的简单矩阵算法,验证了还真的能达到效果

镜像的问题底层驱动修改了,app 就不用处理了

预览旋转角度,根据实际情况我注释了 postScale(),这样导致了横屏被拉伸了,人脸变胖了,只需要单纯的 postRotate(90) 即可

Camera2\host\src\com\mediatek\camera\ui\preview\TextureViewController.java


//用于水平翻转镜像
private void horizontalMirrorData(){LogHelper.d(TAG, "updatePreviewSize horizontalMirrorData()");Matrix matrix = mTextureView.getTransform(new Matrix());matrix.setScale(-1, 1);int width = mTextureView.getWidth();matrix.postTranslate(width, 0);mTextureView.setTransform(matrix);
}//用于旋转预览角度
private void changePreviewDisplayOrientation() {int mTextureViewWidth = mTextureView.getWidth();int mTextureViewHeight = mTextureView.getHeight();int rotation = mApp.getActivity().getWindowManager().getDefaultDisplay().getRotation();LogHelper.d(TAG,"rotation="+rotation);LogHelper.e(TAG,"mPreviewWidth="+mPreviewWidth+" mPreviewHeight="+mPreviewHeight);LogHelper.e(TAG,"textureWidth="+mTextureViewWidth+" textureHeight="+mTextureViewHeight);Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, mTextureViewWidth, mTextureViewHeight);RectF bufferRect = new RectF(0, 0, mPreviewHeight, mPreviewWidth);float centerX = viewRect.centerX();float centerY = viewRect.centerY();if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {LogHelper.e(TAG,"Surface.ROTATION_90 ROTATION_270");/*bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);float scale = Math.max((float) mTextureViewHeight / mPreviewHeight,(float) mTextureViewWidth / mPreviewWidth);LogHelper.d(TAG,"scale="+scale);matrix.postScale(scale, scale, centerX, centerY);*/matrix.postRotate((90 * (rotation - 2)) % 360, centerX, centerY);} else if (Surface.ROTATION_180 == rotation) {LogHelper.d(TAG,"Surface.ROTATION_180 =");matrix.postRotate(180, centerX, centerY);}mTextureView.setTransform(matrix);
}private class SurfaceChangeCallback implements TextureView.SurfaceTextureListener {private ISurfaceStatusListener mListener;.....@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {//cczheng add for mirror preview data//horizontalMirrorData();changePreviewDisplayOrientation();mIsSurfaceCreated = true;surface.setDefaultBufferSize(mPreviewWidth, mPreviewHeight);if (mListener != null) {mListener.surfaceChanged(surface, mPreviewWidth, mPreviewHeight);}LogHelper.d(TAG, "onSurfaceTextureAvailable surface  = " + surface +" width " + width + " height " + height);}.....
}

人脸框位置相关

因为旋转了屏幕方向,人脸框的坐标位置就不对了,需要调整为正确的

通过分析打印日志发现

CamAp_FaceViewCtrl: [updateFacesViewByFace] new face num = 1, clear hide msg, show view right now

CamAp_FaceViewCtrl: [updateFacesViewByFace] new face num = 1, clear hide msg, send hide msg delay 1500 ms

和人脸框相关的类有以下几个

Camera2\feature\setting\facedetection\src\com\mediatek\camera\feature\setting\facedetection\FaceViewCtrl.java

Camera2\feature\setting\facedetection\src\com\mediatek\camera\feature\setting\facedetection\FaceView.java

Camera2\common\src\com\mediatek\camera\common\utils\CoordinatesTransform.java

FaceViewCtrl 控制显示隐藏, FaceView 绘制人脸框(其实是 ic_face_detection_focusing.9.png 图片),CoordinatesTransform 转换人脸坐标

看到上面打印的日志,人脸框显示 1.5 s 后会自动隐藏,这应该是 MTK 当时遗留的一个 bug

为了让人脸框一直显示,注释 updateFacesViewByFace() 中的 MSG_FACE_VIEW_HIDE 消息发送


private void updateFacesViewByFace(Face[] faces) {if (!mIsEnable) {LogHelper.e(TAG, "[updateFacesViewByFace] mIsEnable is false, ignore this time");return;}if (faces != null && faces.length > 0&& mFaceViewState == FaceViewState.STATE_INIT) {// Check if face view has really been shown, if not , not hide view this time.// Why to do this check?// Maybe higher priority view is shown when face view wants to show, after higher// priority view is not shown, maybe face num is not changed too, it's time to hide// face view. So face view has no chance to show out.if (mHideViewWhenFaceCountNotChange && faces.length == mFaceNum&& mFaceView.hasReallyShown()) {// if face view is hide now, not send message, only update wait stateif (mFaceView.getVisibility() != View.VISIBLE) {mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);mWaitFocusState = WaitFocusState.WAIT_NOTHING;} else if (!mMainHandler.hasMessages(MSG_FACE_VIEW_HIDE)) {// if there is not hide msg in queue, send delay message to hide//cczheng annotation don't auto hide faceview 1.5s/*mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);LogHelper.e(TAG, "[updateFacesViewByFace] new face num = " + faces.length +", clear hide msg, send hide msg delay "+ HIDE_VIEW_TIMEOUT_WAIT_AF_SCAN + " ms");mMainHandler.sendEmptyMessageDelayed(MSG_FACE_VIEW_HIDE,HIDE_VIEW_TIMEOUT_WAIT_AF_SCAN);*/}} else {LogHelper.e(TAG, "[updateFacesViewByFace] new face num = " + faces.length +", clear hide msg, show view right now");mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);mWaitFocusState = WaitFocusState.WAIT_PASSIVE_SCAN;showView();mFaceView.resetReallyShown();}mFaceView.setFaces(faces);mFaceNum = faces.length;}}

FaceView 中的 onDraw() 通过遍历人脸集合,绘制人脸框,mFaceIndicator 就是上面说的 .9 图片,来看下坐标的计算方法

@Overrideprotected void onDraw(Canvas canvas) {LogHelper.i(TAG, "[FaceView onDraw]");mReallyShown = true;if (mFaces != null && mFaces.length > 0) {for (int i = 0; i < mFaces.length; i++) {Rect rect = CoordinatesTransform.normalizedPreviewToUi(mFaces[i].rect,mPreviewWidth, mPreviewHeight,mDisplayOrientation, mMirror);mFaceIndicator.setBounds(rect.left, rect.top,rect.right, rect.bottom);mFaceIndicator.draw(canvas);}}super.onDraw(canvas);}

通过传递原始的人脸坐标,和当前实际预览的画布宽高,是否镜像进行计算,

最终通过修改 displayOrientation 为 90,viewWidth 和 viewHeight 由原来的 / 2000f 修改为 /2200f 和 /1500f

当然也可能需要根据你的屏幕实际尺寸调整

public static Rect normalizedPreviewToUi(Rect rect, int w, int h,int displayOrientation, boolean isMirror) {int previewHeight = 0;int previewWidth = 0;if (displayOrientation == 0 || displayOrientation == 180) {previewHeight = h > w ? w : h;//740previewWidth = h > w ? h : w;//986} else if (displayOrientation == 90 || displayOrientation == 270) {previewHeight = h > w ? h : w;//986previewWidth = h > w ? w : h;//740}coordinatesLog(TAG, "normalizedPreviewToUi, w = " + w + ", h = " + h+ ", orientation = " + displayOrientation+ ", mirror = " + isMirror);coordinatesLog(TAG, "normalizedPreviewToUi, previewWidth = " + previewWidth + ", previewHeight = " + previewHeight);coordinatesLog(TAG, "normalizedPreviewToUi, rect = (" + rect.left + ", " + rect.top + ", "+ rect.right + ", " + rect.bottom + ")");Matrix matrix = new Matrix();prepareMatrix(matrix, isMirror, displayOrientation, previewWidth, previewHeight);RectF rectf = new RectF(rect);matrix.mapRect(rectf);Rect resultRect = new Rect();rectf.round(resultRect);coordinatesLog(TAG, "normalizedPreviewToUi, result_rect = (" + resultRect.left + ", "+ resultRect.top + ", "+ resultRect.right + ", " + resultRect.bottom + ")");return resultRect;}private static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,int viewWidth, int viewHeight) {// Need mirror for front camera.matrix.setScale(mirror ? -1 : 1, 1);// This is the value for android.hardware.Camera.setDisplayOrientation.matrix.postRotate(90 /*displayOrientation*/);// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).// UI coordinates range from (0, 0) to (width, height).// matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);//cczheng change displayOrientation 0 to 90, scale 2000->2200 2000->1500matrix.postScale(viewWidth / 2200f, viewHeight / 1500f);matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);}

录像相关

经过上面的调整,录像预览时方向是对的,但保存的视频播放时依旧是竖屏的,这么说我们还需要进一步修改。

通过搜索发现设置录像参数时 mMediaRecorder.setOrientationHint() 就是控制保存视频的成像方向。

整个工程搜索找到

./common/src/com/mediatek/camera/common/mode/video/recorder/NormalRecorder.java:        mMediaRecorder.setOrientationHint(spec.orientationHint);

通过打印日志发现 orientationHint 果然为 0,竖屏,那么我们只需将 orientationHint 改为 90 应该就能为横屏

2019-11-21 08:30:09.601 3937-3937/com.mediatek.camera D/CamAp_VideoHelper: [getVideoTempPath] mTempPath = /storage/emulated/0/DCIM/Camera/.videorecorder.3gp.tmp
2019-11-21 08:30:09.648 3937-3937/com.mediatek.camera D/CamAp_NormalRecorder: [init]   filePath = /storage/emulated/0/DCIM/Camera/.videorecorder.3gp.tmp  spec.captureRate = 0  spec.videoFrameRate = 0  spec.orientationHint = 0  spec.profile.videoFrameRate = 30  spec.profile.videoFrameWidth = 1280  spec.profile.videoFrameHeight = 720

接下来简单跟踪下初始化配置参数的过程

common\src\com\mediatek\camera\common\mode\video\VideoMode.java

initRecorder() 创建 NormalRecorder 对象,并开始初始化 init,需要传递 RecorderSpec 对象(包含很多录像相关参数的 bean)

通过自身 configRecorderSpec() 创建,最终调用到 VideoHelper 的 configRecorderSpec()

protected boolean initRecorder(boolean isStartRecording) {LogHelper.d(TAG, "[initRecorder]");releaseRecorder();mRecorder = new NormalRecorder();try {mRecorder.init(configRecorderSpec(isStartRecording));setMediaRecorderParameters();initForHal3(isStartRecording);} catch (RuntimeException e) {e.printStackTrace();releaseRecorder();return false;}return true;}private IRecorder.RecorderSpec configRecorderSpec(boolean isStartRecording) {IRecorder.RecorderSpec recorderSpec = mVideoHelper.configRecorderSpec(getProfile(), mCameraId, mCameraApi, mSettingManager);mOrientationHint = recorderSpec.orientationHint;recorderSpec.infoListener = mOnInfoListener;recorderSpec.errorListener = mOnErrorListener;recorderSpec.releaseListener = mOnInfoListener;recorderSpec = modifyRecorderSpec(recorderSpec, isStartRecording);return recorderSpec;}

configRecorderSpec() 中新建一个内部类对象 RecorderSpec,依次给各个 public 字段赋值,默认指定使用 CameraApi.API2

所以获取 orientationHint 走的如下带 CameraCharacteristics 参数的 getRecordingRotation() 方法

由于我们的设备没有重力传感器,mApp.getGSensorOrientation() 一直是 -1,也就是 ORIENTATION_UNKNOWN

所以最终 rotation = sensorOrientation,打印 sensorOrientation 为 0,也就符合上面说的 orientationHint 果然为 0

当然你也可以在这里修改 getRecordingRotation() 返回值也能达到一样的效果

common\src\com\mediatek\camera\common\mode\video\VideoHelper.java

public IRecorder.RecorderSpec configRecorderSpec(CamcorderProfile profile, String cameraId,CameraDeviceManagerFactory.CameraApi api, ISettingManager settingManager) {sProfile = profile;IRecorder.RecorderSpec recorderSpec = new IRecorder.RecorderSpec();if (mCameraDevice.getCamera() != null) {mCameraDevice.unLockCamera();recorderSpec.camera = mCameraDevice.getCamera().getCamera();}if (api == CameraDeviceManagerFactory.CameraApi.API1) {recorderSpec.videoSource = MediaRecorder.VideoSource.CAMERA;recorderSpec.orientationHint = getRecordingRotation(mApp.getGSensorOrientation(),mCameraDevice.getCameraInfo(Integer.parseInt(cameraId)));} else {recorderSpec.videoSource = MediaRecorder.VideoSource.SURFACE;recorderSpec.orientationHint = getRecordingRotation(mApp.getGSensorOrientation(),getCameraCharacteristics(mApp.getActivity(), cameraId));}if (VALUE_ON.equals(settingManager.getSettingController().queryValue("key_microphone"))) {recorderSpec.isRecordAudio = true;recorderSpec.audioSource = MediaRecorder.AudioSource.CAMCORDER;} else {recorderSpec.isRecordAudio = false;}recorderSpec.profile = sProfile;recorderSpec.maxDurationMs = 0;recorderSpec.maxFileSizeBytes = getRecorderMaxSize();recorderSpec.location = mCameraContext.getLocation();recorderSpec.outFilePath = getVideoTempPath();return recorderSpec;}public static int getRecordingRotation(int orientation, CameraCharacteristics characteristics) {int rotation = -1;int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);boolean facingFront = characteristics.get(CameraCharacteristics.LENS_FACING)== CameraCharacteristics.LENS_FACING_FRONT;if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {if (facingFront) {rotation = (sensorOrientation - orientation + 360) % 360;} else {rotation = (sensorOrientation + orientation) % 360;}} else {rotation = sensorOrientation;}LogHelper.e(TAG, "[getRecordingRotation] orientation = " +orientation + " sensorOrientation = " + sensorOrientation + " rotation = "  + rotation);return rotation;}

APP 应用参考文章

Android Camera2 API和拍照与录像过程

Android Camera2教程之打开相机、开启预览、实现PreviewCallback、拍照

这篇关于Android9.0 Camera2 横屏问题修改记录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095506

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

题目1254:N皇后问题

题目1254:N皇后问题 时间限制:1 秒 内存限制:128 兆 特殊判题:否 题目描述: N皇后问题,即在N*N的方格棋盘内放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在同一斜线上。因为皇后可以直走,横走和斜走如下图)。 你的任务是,对于给定的N,求出有多少种合法的放置方法。输出N皇后问题所有不同的摆放情况个数。 输入

vscode中文乱码问题,注释,终端,调试乱码一劳永逸版

忘记咋回事突然出现了乱码问题,很多方法都试了,注释乱码解决了,终端又乱码,调试窗口也乱码,最后经过本人不懈努力,终于全部解决了,现在分享给大家我的方法。 乱码的原因是各个地方用的编码格式不统一,所以把他们设成统一的utf8. 1.电脑的编码格式 开始-设置-时间和语言-语言和区域 管理语言设置-更改系统区域设置-勾选Bata版:使用utf8-确定-然后按指示重启 2.vscode

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚: