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

【Android9.0 Camera2 横屏问题修改记录】亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述Android9.0 Camera2 横屏问题修改记录相关的知识,希望能为你提供帮助。
vendormediatekproprietarypackagesapps 目录下有三份相机源码 分别是
Camera、 Camera1、 Camera2
通过查看 mk 发现通过 ifeq ($(MTK_CAMERA_APP_VERSION), 3) 来控制编译哪一个,
MTK_CAMERA_APP_VERSION 宏定义在 device/mediateksample/xxxxxx/ProjectConfig.mk
整体界面相关Camera2 中适配了两套 api, 老版本的 Camera 和新版本的 Camera2, 通过 CameraApiHelper 配置
Camera2commonsrccommediatekcameracommonmodeCameraApiHelper.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)
Camera2hostsrccommediatekcameraQuickActivity.java
@Override protected 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 statusbar getWindow().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
Camera2hostsrccommediatekcameraCameraActivity.java
@Override protected 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)
Camera2commonsrccommediatekcameracommonwidgetRotateImageView.java
@Override protected 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 imageView canvas.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) 即可
Camera2hostsrccommediatekcamerauipreviewTextureViewController.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; .....@Override public 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
和人脸框相关的类有以下几个
Camera2featuresettingfacedetectionsrccommediatekcamerafeaturesettingfacedetectionFaceViewCtrl.java
Camera2featuresettingfacedetectionsrccommediatekcamerafeaturesettingfacedetectionFaceView.java
Camera2commonsrccommediatekcameracommonutilsCoordinatesTransform.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 state if (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 图片,来看下坐标的计算方法
@Override protected 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; //740 previewWidth = h > w ? h : w; //986 } else if (displayOrientation == 90 || displayOrientation == 270) { previewHeight = h > w ? h : w; //986 previewWidth = 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-> 1500 matrix.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.tmpspec.captureRate = 0spec.videoFrameRate = 0spec.orientationHint = 0spec.profile.videoFrameRate = 30spec.profile.videoFrameWidth = 1280spec.profile.videoFrameHeight = 720

接下来简单跟踪下初始化配置参数的过程
commonsrccommediatekcameracommonmodevideoVideoMode.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() 返回值也能达到一样的效果
commonsrccommediatekcameracommonmodevideoVideoHelper.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、拍照

    推荐阅读