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

@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

Camera2\host\src\com\mediatek\camera\CameraActivity.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)

Camera2\common\src\com\mediatek\camera\common\widget\RotateImageView.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) 即可

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; ..... @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

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

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 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

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

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 横屏问题修改记录的更多相关文章

  1. SpUtil多样加密存储,兼容android9.0

    代码地址如下:http://www.demodashi.com/demo/15058.html 前言 在android系统不断升级的过程中,Sharepreferences存储出现多中问题,其中有些是 ...

  2. android9.0适配HTTPS:not permitted by network security policy'

    app功能接口正常,其他手机运行OK,但是在Android9.0的手机上报错 CLEARTEXT communication to 192.168.1.xx not permitted by netw ...

  3. Android9.0新特性曝光,你准备好了吗

    Android9.0最早出现在2018年1月25日的谷歌官网上,初步代号已经确定为“Pistachio Ice Cream”(开心果冰淇淋),不过按照Google的惯例,如此长的三个单词代号,通常都只 ...

  4. 使用appium在android9.0真机上测试程序时报错command failed shell “ps ‘uiautomator’”的解决办法

    appium目前最新的windows版本是1.4.16,在android9.0真机上测试程序时会报错:command failed shell “ps ‘uiautomator’”. 网上大多数人的解 ...

  5. Android9.0 如何区分SDK接口和非 SDK接口

    刚刚有同学问我,不太了解 "非SDK接口" 是什么意思?android9.0有什么限制 ?apache的http也有限制 ? 而且现在的大部分系统都升级上来了,黑名单.灰名单和白名 ...

  6. 很带劲,Android9.0可以在i.MX8开发板上这样跑

    米尔MYD-JX8MX开发板移植了Android9.0操作系统,现阶段最高版本的Android9.0操作系统将给您的产品在安全与稳定性方面带来更大的提升.可惜了,这里不能上传视频在i.MX8开发板跑A ...

  7. Android9.0 MTK 平板横屏方案修改(强制app横屏 + 开机logo/动画+关机充电横屏 + RecoveryUI 横屏)

    文章较长建议先收藏再看 拆解步骤 1.app 强制横屏显示,无视 android:screenOrientation="portrait" 属性 2.屏幕触摸坐标修改为横屏 3.开 ...

  8. cocos2dx 2.0+ 版本,IOS6.0+设置横屏

    使用cocos2dx 自带的xcode模板,是不能正常的设置为横屏的. 一共修改了三个地方: 在项目属性中:Deployment Info中,勾选上 Landscape left,以及 Landsca ...

  9. Android4.0强制横屏竖屏

    Android的启动默认是横屏或者竖屏我们的TV本来是横屏显示,但是有客户竟然要竖屏显示,昨天快下班收到的需求,竟然说7.19就要搞定.思路有2个,一个就是修改LCD的默认输出,但是这个不是我这个水平 ...

随机推荐

  1. CCF_201612-2_火车购票

    http://115.28.138.223/view.page?gpid=T46 水. #include<iostream> #include<cstring> #includ ...

  2. re模块 常用函数

    1. findall() 函数 find('正则表达式',‘待匹配的字符串’) #返回匹配到字符串,并存放在列表中 详解见:https://www.cnblogs.com/nbk-zyc/p/1111 ...

  3. 用python制作训练集和测试集的图片名列表文本

    # -*- coding: utf-8 -*- from pathlib import Path #从pathlib中导入Path import os import fileinput import ...

  4. 2018icpc南京网络赛-E AC Challenge(状压+dfs)

    题意: n道题,每道题有ai和bi,完成这道题需要先完成若干道题,完成这道题可以得到分数t*ai+bi,其中t是时间 1s, n<=20 思路: 由n的范围状压,状态最多1e6 然后dfs,注意 ...

  5. HDU 1004 Let the Balloon Rise(STL初体验之map)

    Problem Description Contest time again! How excited it is to see balloons floating around. But to te ...

  6. Jmeter之设置动态关联

    前言 在Jmeter中,如何进行接口关联(上一个接口的返回参数作为下一个接口的入参使用)测试呢?下面我们一起来学习吧! 需求:需要利用商品信息接口的返回结果skuName值作为下一个登录接口参数Use ...

  7. 某cms审计思路,以及ci框架如何找寻注入点

    某cms审计思路,以及ci框架如何找寻注入点 ABOUT 之前闲着没事的时候审的某cms,之前看一群大表哥刷过一次这个cms,想着看看还能不能赶得上分一杯羹,还是审计出来些东西,来说一说一个前台注入吧 ...

  8. 15-cookie技术和session技术的联系和区别

    ​1. 联系: *session实现依赖于Cookie 2. session问题: * 由服务器创建,存储在服务器 * 当浏览器关闭时,服务器不关闭,再次打开浏览器时, 默认获得的不是同一个sessi ...

  9. rc.local 启动内容不生效

    系统版本  CentOS Linux release 7.2.1511 问题 :/etc/rc.local  中的内容 启动机器后不生效 经过检查 /etc/rc.local 是 /etc/rc.d/ ...

  10. Qps从300到1500的优化过程

    最近压测一项目,遇到的性能问题比较典型,过程记录下来,给大家做定位调优参考: 表象: 单接口负载测试,qps最高到300,响应时间200ms,应用cpu达到90%以上,8c机器,如下图,写到这里可能有 ...