zoukankan      html  css  js  c++  java
  • 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.tmp  spec.captureRate = 0  spec.videoFrameRate = 0  spec.orientationHint = 0  spec.profile.videoFrameRate = 30  spec.profile.videoFrameWidth = 1280  spec.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、拍照

  • 相关阅读:
    kuangbin带你飞 并查集 题解
    kuangbin带你飞 最短路 题解
    kuangbin带你飞 后缀数组 题解
    Kuangbin 带你飞-线段树专题 题解
    HDU 4578 Transformation
    Tarjan & LCA 套题题目题解
    Dancing Links [Kuangbin带你飞] 模版及题解
    二分匹配 大白例题虽有代码
    编程范式:响应式编程
    编程结构:Promise和Future
  • 原文地址:https://www.cnblogs.com/cczheng-666/p/12208368.html
Copyright © 2011-2022 走看看