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. Python logging记录日志

    Python logging记录日志 调试的几种方法: 使用print()在控制台上输出 使用assert断言 使用logging模块 logging提供了一组便利的函数,用来做简单的日志,(当然也能 ...

  2. 新的起航从这里开始 Encantado!

    大家好,我是一名DBA之前也在其它地方写过blog,但是可惜目前在greatwall之内都不能访问了. 如果有小伙伴可以在墙外访问的话 可以尝试着看看这个地址 https://liuleiit.wix ...

  3. 开发中常见的common.js--1

    common.js 1.基于代码重用的目的,方便在页面中调用: 2.跟普通的js包含文件并没有其他不一样,主要是一些js经常用到的函数,或者字符串.数组方法的简单扩展: 3.封装的兼容性的方法.[po ...

  4. java8种基本数据类型

  5. Cmake知识----编写CMakeLists.txt文件编译C/C++程序(转)

    1.CMake编译原理 CMake是一种跨平台编译工具,比make更为高级,使用起来要方便得多.CMake主要是编写CMakeLists.txt文件,然后用cmake命令将CMakeLists.txt ...

  6. awk sed 命令

    awk awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大. 简单来说awk就是把文件逐行的读入,以 空格或TAB 为默认分隔符 将每行 ...

  7. Vscode开发Python环境安装

    VSCode 开发 Python 使用python,主要是做一些工具和爬虫的操作,语法简单,功能复杂,入手很快. 我们通过在 VSCode 中搜索 Python 插件,发现,开发 python 的话, ...

  8. Vscode开发Java环境搭建

    VSCode 开发 JAVA 微软为 Java 开发者推出了一个 Visual Studio Code 的安装程序.Visual Studio Code 中目前提供了许多 Java 扩展. 该软件包可 ...

  9. 1282 - Leading and Trailing 求n^k的前三位和后三位。

    1282 - Leading and Trailing You are given two integers: n and k, your task is to find the most signi ...

  10. mqttnet3.0用法

    .net常用的mqtt类库有2个,m2mqtt和mqttnet两个类库 当然了,这两个库的教程网上一搜一大把 但mqttnet搜到的教程全是2.7及以下版本的,但3.0版语法却不再兼容,升级版本会导致 ...