vendor\mediatek\proprietary\packages\apps 目錄下有三份相機源碼 分別是
Camera搁进、 Camera1入宦、 Camera2
通過查看 mk 發(fā)現(xiàn)通過 ifeq ($(MTK_CAMERA_APP_VERSION), 3) 來控制編譯哪一個震捣,
MTK_CAMERA_APP_VERSION 宏定義在 device/mediateksample/xxxxxx/ProjectConfig.mk
整體界面相關(guān)
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
}
預(yù)覽布局不延伸到 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();
}
旋轉(zhuǎn)界面圓形圖標(biāo) 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);
}
拍照相關(guān)
預(yù)覽旋轉(zhuǎn) 90
horizontalMirrorData() 和 changePreviewDisplayOrientation() 都是從網(wǎng)上找的簡單矩陣算法判帮,驗證了還真的能達到效果
鏡像的問題底層驅(qū)動修改了,app 就不用處理了
預(yù)覽旋轉(zhuǎn)角度溉箕,根據(jù)實際情況我注釋了 postScale(),這樣導(dǎo)致了橫屏被拉伸了晦墙,人臉變胖了,只需要單純的 postRotate(90) 即可
Camera2\host\src\com\mediatek\camera\ui\preview\TextureViewController.java
//用于水平翻轉(zhuǎn)鏡像
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);
}
//用于旋轉(zhuǎn)預(yù)覽角度
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);
}
.....
}
人臉框位置相關(guān)
因為旋轉(zhuǎn)了屏幕方向肴茄,人臉框的坐標(biāo)位置就不對了晌畅,需要調(diào)整為正確的
通過分析打印日志發(fā)現(xiàn)
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
和人臉框相關(guān)的類有以下幾個
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 轉(zhuǎn)換人臉坐標(biāo)
看到上面打印的日志踩麦,人臉框顯示 1.5 s 后會自動隱藏,這應(yīng)該是 MTK 當(dāng)時遺留的一個 bug
為了讓人臉框一直顯示氓癌,注釋 updateFacesViewByFace() 中的 MSG_FACE_VIEW_HIDE 消息發(fā)送
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 圖片贪婉,來看下坐標(biāo)的計算方法
@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);
}
通過傳遞原始的人臉坐標(biāo)反粥,和當(dāng)前實際預(yù)覽的畫布寬高,是否鏡像進行計算,
最終通過修改 displayOrientation 為 90疲迂,viewWidth 和 viewHeight 由原來的 / 2000f 修改為 /2200f 和 /1500f
當(dāng)然也可能需要根據(jù)你的屏幕實際尺寸調(diào)整
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);
}
錄像相關(guān)
經(jīng)過上面的調(diào)整才顿,錄像預(yù)覽時方向是對的,但保存的視頻播放時依舊是豎屏的尤蒿,這么說我們還需要進一步修改郑气。
通過搜索發(fā)現(xiàn)設(shè)置錄像參數(shù)時 mMediaRecorder.setOrientationHint() 就是控制保存視頻的成像方向。
整個工程搜索找到
./common/src/com/mediatek/camera/common/mode/video/recorder/NormalRecorder.java: mMediaRecorder.setOrientationHint(spec.orientationHint);
通過打印日志發(fā)現(xiàn) orientationHint 果然為 0,豎屏腰池,那么我們只需將 orientationHint 改為 90 應(yīng)該就能為橫屏
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
接下來簡單跟蹤下初始化配置參數(shù)的過程
common\src\com\mediatek\camera\common\mode\video\VideoMode.java
initRecorder() 創(chuàng)建 NormalRecorder 對象尾组,并開始初始化 init,需要傳遞 RecorderSpec 對象(包含很多錄像相關(guān)參數(shù)的 bean)
通過自身 configRecorderSpec() 創(chuàng)建示弓,最終調(diào)用到 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() 中新建一個內(nèi)部類對象 RecorderSpec讳侨,依次給各個 public 字段賦值,默認指定使用 CameraApi.API2
所以獲取 orientationHint 走的如下帶 CameraCharacteristics 參數(shù)的 getRecordingRotation() 方法
由于我們的設(shè)備沒有重力傳感器奏属,mApp.getGSensorOrientation() 一直是 -1跨跨,也就是 ORIENTATION_UNKNOWN
所以最終 rotation = sensorOrientation,打印 sensorOrientation 為 0囱皿,也就符合上面說的 orientationHint 果然為 0
當(dāng)然你也可以在這里修改 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 應(yīng)用參考文章
Android Camera2教程之打開相機勇婴、開啟預(yù)覽忱嘹、實現(xiàn)PreviewCallback、拍照