[Android] [SnapdragonCamera] 单摄(横屏)阶段总结

2024-09-07 13:28

本文主要是介绍[Android] [SnapdragonCamera] 单摄(横屏)阶段总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        在研高通平台的单摄项目中遇到了很多适配问题,做一下初步的总结,为今后遇到相似的问题,提供参考方案。
        

1. 横屏设置相机预览显示不正常         

     1.1问题现象

             

        1.2分析与解决

             骁龙相机默认的预览方向是“portrait”。在横屏设备上显示的时候就会出现上面效果。实际操作中这样的预览效果很不友好,所以针对这种情况,需要作出如下修改: 我们以XXX-A项目为例,XXX-A的代码是共用V660C的代码,其适用的范围包含单摄像头和双摄像头的机器。所以不能直接修改AndroidManifest.xml的“screenOrientation”参数。需要做一个判断,所以其具体修改下:
a.先移除AndroidManifest.xml中的固定设置:

b. 再在主Activity: CameraActivity.java中添加相关的判断及设置:

   @Overridepublic void onCreate(Bundle state) {super.onCreate(state);if (Build.isLandScapeDevice()) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);} else {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);}if (PersistUtil.isTraceEnable())Trace.beginSection("CameraActivity onCreate");try {//Print version info hereString versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;Log.d(TAG, "snapdragoncamera_version: " + versionName);} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// Check if this is in the secure camera mode.....}

如果是屏幕横置的机器就将预览的Orientation设置成“LANDSCAPE”。如果是BBB的单一项目的代码可以将AndroidManifest.xml中的参数直接修改成“landscape”即可。
修改后的效果:

相机的菜单和拍摄按钮都调整到屏幕的下方。


2. 预览画面和菜单显示不匹配

        问题现象:

   

相机打开的预览画面是“Photo”,但是在下方的模式菜单的选项中,选中的却是“ProMode”. 造成了预览图像和菜单设置的不匹配的现象。

        分析与解决:

         造成这个问题的原因是:由于双摄像头项目在预览画面会有四个选项模式:“Video/HFR/Photo/ProMode”。而单摄项目在前置摄像头的预览时只有三个选项模式:“Video/Photo/ProMode”. 追踪Code,定位到 Camera2ModeAdapter.java 中是设置这个模式的位置:

public class Camera2ModeAdapter extends RecyclerView.Adapter<Camera2ModeAdapter.ViewHolder> {private List<String> mModeList;private int mSelectedPos = 2;  //设置默认的模式  private OnItemClickListener mOnItemClickListener;public Camera2ModeAdapter(List<String> list) {this.mModeList = list;}public void setOnItemClickListener(OnItemClickListener listener) {this.mOnItemClickListener = listener;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.camera2_mode_item,parent, false);ViewHolder viewHolder = new ViewHolder(view);return viewHolder;}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.mCameraModeText.setText(mModeList.get(position));holder.mCameraModeText.setSelected(mSelectedPos == position); //选中预览模式holder.mCameraModeText.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mOnItemClickListener.onItemClick(position) >= 0) {mSelectedPos = position;notifyDataSetChanged();}}});}@Overridepublic int getItemCount() {return mModeList.size();}public interface OnItemClickListener {int onItemClick(int mode);}public void setSelectedPosition(int position) {mSelectedPos = position;notifyDataSetChanged(); }class ViewHolder extends RecyclerView.ViewHolder{protected TextView mCameraModeText;public ViewHolder(View itemView) {super(itemView);mCameraModeText = (TextView) itemView.findViewById(R.id.mode_text);}}
}

mSelectedPos = 2 在四个模式下对应的是“Photo”, 但是在只有三个模式的情况下对应的mode name则是“ProMode”.

继续追Code,发现是CaptureUI.java中使用到这个adapter:

 mCameraModeAdapter = new Camera2ModeAdapter(mModule.getCameraModeList());mCameraModeAdapter.setOnItemClickListener(mModule.getModeItemClickListener());mModeSelectLayout.setAdapter(mCameraModeAdapter);

决定“mModeSelectLayout”中显示的预览模式是“mModule.getCameraModeList()” 其对应的Code则是在CaptureModule.java

public List<String> getCameraModeList() {ArrayList<String> cameraModes = new ArrayList<>();for (SceneModule sceneModule : mSceneCameraIds) {cameraModes.add(mSelectableModes[sceneModule.mode.ordinal()]);}return cameraModes;
}

预设的模式:

private String[] mSelectableModes = {"Video", "HFR", "Photo", "Bokeh", "SAT", "ProMode"};
...
public enum CameraMode {VIDEO,HFR,DEFAULT,RTB,SAT,PRO_MODE}

在初始化的时候,将六种默认的模式都加载到 mSceneCameraIds 中

for (int i = 0; i < mSelectableModes.length; i++) {module = new SceneModule();module.mode = CameraMode.values()[i];mSceneCameraIds.add(module);}

DEFAULT对应的模式则是:“Photo”。接下来程序会根据不同的情况进行筛选:

在initCameraIds()的时候,会默认将所有模式的删除属性设置成“true”:

private void initCameraIds() {CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);boolean isFirstDefault = true;boolean[] removeList = new boolean[mSelectableModes.length];for (int i = 0; i < mSelectableModes.length; i++) {removeList[i] = true;}....for (int i = 0; i < cameraIdList.length; i++) {String cameraId = cameraIdList[i];mCameraId[i] = cameraId;CameraCharacteristics characteristics;try {characteristics = manager.getCameraCharacteristics(cameraId);} catch (CameraAccessException e) {e.printStackTrace();continue;}isFirstDefault = setUpLocalMode(i, characteristics, removeList,isFirstDefault, cameraId);}}       

setUpLocalMode 方法中会根据预设的情况进行初步的筛选:

private boolean setUpLocalMode(int cameraId, CameraCharacteristics characteristics,boolean[] removeList, boolean isFirstDefault, String physicalId) {Byte type = 0;try {type = characteristics.get(logical_camera_type);} catch (IllegalArgumentException e) {Log.e(TAG, "setUpLocalMode no vendorTag logical_camera_type:" + logical_camera_type);}Log.d(TAG,"init cameraId " + cameraId + " | logical_camera_type = " + type +" | physical id = " + physicalId);int facing = characteristics.get(CameraCharacteristics.LENS_FACING);switch (type) {case TYPE_DEFAULT:// defaultremoveList[CameraMode.DEFAULT.ordinal()] = false;removeList[CameraMode.VIDEO.ordinal()] = false;removeList[CameraMode.PRO_MODE.ordinal()] = false;if (facing == CameraCharacteristics.LENS_FACING_FRONT) {CaptureModule.FRONT_ID = cameraId;mSceneCameraIds.get(CameraMode.DEFAULT.ordinal()).frontCameraId = cameraId;mSceneCameraIds.get(CameraMode.VIDEO.ordinal()).frontCameraId = cameraId;mSceneCameraIds.get(CameraMode.HFR.ordinal()).frontCameraId = cameraId;mSceneCameraIds.get(CameraMode.PRO_MODE.ordinal()).frontCameraId = cameraId;} else {...}... return isFirstDefault;}

上面部分代码将“Photo/Video/ProMode”三个模式的删除属性设置成“false"

最终确定通过下面的筛选方式将需要显示的Mode存放在mSceneCameraIds 列表中

for (int i = 0; i<mSceneCameraIds.size(); i++){Log.i("WKS","START----->mSceneCameraIds[" + i + "]: " + mSceneCameraIds.get(i).mode);}  for (int i = 0; i < removeList.length; i++) {if (!removeList[i]) {continue; //"Photo/Video/ProMode"在上面已经将改属性设置成false}for (SceneModule sceneModule : mSceneCameraIds) {if (sceneModule.mode.ordinal() == i) {mSceneCameraIds.remove(sceneModule); //不需要的Mode在此从mSceneCameraIds中移除break;}}}}   for (int i = 0; i<mSceneCameraIds.size(); i++){Log.i("WKS","END----->mSceneCameraIds[" + i + "]: " + mSceneCameraIds.get(i).mode);}  

通过上面的流程操作,可以看到筛选前mSceneCameraIds里存放的对象是:“VIDEO/HFR/DEFAULT/RTB/SAT/PRO_MODE”

筛选后则是:

“VIDEO/DEFAULT/PRO_MODE”

3. 强制断电后图片无法保存

        问题分析:

在突然断电的情况下,Android设备可能会丢失已拍摄的图片。这是因为设备的电源管理策略和文件系统缓存机制可能导致图片数据未能及时写入存储设备。

        问题解决:

在每次拍照完成后进行一次数据同步。以确保拍摄的图片和视频有同步到储存空间里:

try {Runtime.getRuntime().exec("sync data/misc/apexdata/com.android.media/");}  catch (Exception e) {Log.e(TAG, "Run error:" + e);}

在Android系统中,sync data/misc/apexdata/com.android.media/这个命令的含义是同步com.android.media模块的数据。com.android.media模块通常包含与媒体处理相关的库和资源,例如音频和视频编解码器、媒体播放服务等。

这个问题和单摄/横屏没有直接关系。在其它设备上也能复制出问题,但是由于目前在研的横屏设备没有电池,是通过AC直接供电,操作的时候如果直接移除电源就会比较容易复制出此类的现象。

4. 滑动屏幕不能切换相机的模式

        问题分析:

复制现象的时候发现,点击模式按钮进行切换的时候是正常操作的。结合问题描述的状况看,应该是滑动的时候,程序处理出现了问题。那首先我们需要定位到程序里滑动功能的位置,看其是如何定义和操作的。然后再根据其提供的线索去追踪到模式功能切换的实现位置。

        解决方案:

确定实现滑动的代码在PreviewGestures.java: 在其onScroll的方法,定义了向左isLeftSwipe,向右isRightSwipe以及上下滑动isUpSwipe的行为。

@Overridepublic boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {if (e1 == null) {// e1 can be null if for some cases.return false;}if (mZoomOnly || mMode == MODE_ZOOM) return false;int deltaX = (int) (e1.getX() - e2.getX());int deltaY = (int) (e1.getY() - e2.getY());if((Math.abs(deltaX) > 40 || Math.abs(deltaY) > 40) && Math.abs(e1.getY()) < 1800) {int orientation = 0;if (mCaptureUI != null)orientation = mCaptureUI.getOrientation();if (isLeftSwipe(orientation, deltaX, deltaY)) {waitUntilNextDown = true;if (mCaptureUI != null)mCaptureUI.swipeCameraMode(-1);if (mMultiCameraUI != null)mMultiCameraUI.swipeCameraMode(-1);return true;}if (isRightSwipe(orientation, deltaX, deltaY)) {waitUntilNextDown = true;if (mCaptureUI != null)mCaptureUI.swipeCameraMode(1);if (mMultiCameraUI != null)mMultiCameraUI.swipeCameraMode(1);return true;}if (isUpSwipe(orientation, deltaX, deltaY) ||isDownSwipe(orientation, deltaX, deltaY)) {if (Camera.getNumberOfCameras() == 1 || mModelName.equals(Build.MODEL)) {return false;} else {if (e1.getY() < 200) {return false;}waitUntilNextDown = true;if (mCaptureUI != null)mCaptureUI.switchFrontBackCamera();return true;}}}return false;}

继而追踪到更新UI的位置是在CaptureUI.java的 swipeCameraMode()方法里:

public void swipeCameraMode(int move) {Log.i("TD","---->mModule.getCameraModeSwitcherAllowed(): " + mModule.getCameraModeSwitcherAllowed());  if (mIsVideoUI || !mModule.getCameraModeSwitcherAllowed() ||mModule.getCurrentIntentMode() != CaptureModule.INTENT_MODE_NORMAL) { return;}Log.i("TD","---->mModule.getCurrentModeIndex(): " + mModule.getCurrentModeIndex());int index = mModule.getCurrentModeIndex() + move;int modeListSize = mModule.getCameraModeList().size();if (index >= modeListSize || index == -1) {return;}int mode = index % modeListSize;mModule.setCameraModeSwitcherAllowed(false);mCameraModeAdapter.setSelectedPosition(mode);mModeSelectLayout.smoothScrollToPosition(mode);mModule.selectCameraMode(mode);
}       

在swipeCameraModed方法的关键位置加上log. 发现 mModule.getCurrentModeIndex()的初始值是"2".

此时如果向右滑动,move = 1,index的值就会是“3”,方法直接return. 此时如果向左滑动,move = -1,index的值为“1”。而默认的模式对应的index也是为“1”。这就造成了预览画面没有发生变化,那就无法走到下面代码的方法里去设置mCameraModeSwitcherAllowed值为“true”。而swipeCameraMoed 方法里mCameraModeSwitcherAllowed的值已经设置成“false”.后续继续滑动的话,直接就return出来了。这就造成了滑动无作用的现象。

CaptureModule.java:

private void createSession(final int id) {Log.d(TAG, "createSession,id: " + id + ",mPaused:" + mPaused + ",mCameraOpened:" + !mCameraOpened[id] + ",mCameraDevice:"+ (mCameraDevice[id] == null));if (mPaused || !mCameraOpened[id] || (mCameraDevice[id] == null)) return;List<Surface> list = new LinkedList<Surface>();mState[id] = STATE_PREVIEW;mControlAFMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;try {// We set up a CaptureRequest.Builder with the output Surface.mPreviewRequestBuilder[id] = getRequestBuilder(id);mPreviewRequestBuilder[id].setTag(id);CameraCaptureSession.StateCallback captureSessionCallback =new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession cameraCaptureSession) {if (mPaused || null == mCameraDevice[id] ||cameraCaptureSession == null) {return;}Log.i(TAG, "cameracapturesession - onConfigured "+ id);setCameraModeSwitcherAllowed(true);// When the session is ready, we start displaying the preview....
}                            

定位到造成问题的原因是初始状态下,由于是单个摄像头,mCurrentModeIndex 的值不能直接等于CameraMode数组里Photo对应的坐标。所以需要在下面的代码里作出如下处理:如果是单个摄像头的设备,需要将默认的mCurrentModeIndex 值设置成“1”。

if (mCurrentSceneMode == null) {int index = mIntentMode == INTENT_MODE_VIDEO ?CameraMode.VIDEO.ordinal() : CameraMode.DEFAULT.ordinal(); // CameraMode.DEFAULT.ordinal()的值是2if ((cameraIdList.length == 1 || mModelName.equals(Build.MODEL)) && mIntentMode != INTENT_MODE_VIDEO) {mCurrentModeIndex = mNextModeIndex = 1;} else {mCurrentModeIndex = mNextModeIndex = index;}mCurrentSceneMode = mSceneCameraIds.get(index);
}public int getCurrentModeIndex() {return mCurrentModeIndex;
}

在后续的操作中,会通过下面的方法将index的值传递给mCurrentModeIndex

public void setNextSceneMode(int index) {mNextModeIndex = index;
}private void reinitSceneMode() {mCurrentSceneMode = mSceneCameraIds.get(mNextModeIndex);mCurrentModeIndex = mNextModeIndex;CURRENT_MODE = mCurrentSceneMode.mode;CURRENT_ID = mCurrentSceneMode.getNextCameraId(CURRENT_MODE);Log.d(TAG, "reinitSceneMode: CURRENT_ID :" + CURRENT_ID);
}

5. 相机Promode下UI显示异常

        问题现象:

在横屏机器上,ProMode的选项发生了偏移

        分析与解决:

根据Promode中的按钮控件找到对应的布局文件pro_mode_layout.xml, 再通过布局文件的引用找到对应的java文件:src/com/android/camera/ui/OneUICameraControls.java 

在其初始化的时候有对promode的控件做了一些位置的调整:

private void initializeProMode(boolean promode) {if (!promode) {mProMode.setMode(ProMode.NO_MODE);mProModeLayout.setVisibility(INVISIBLE);return;}mProModeLayout.setVisibility(VISIBLE);mProModeLayout.setY(mHeight - mBottom - mProModeLayout.getHeight() - 48);
}

通过上述代码可以知晓其是对mProModeLayout 的竖直方向上做了一些调整。而对于横屏的机器遇到的问题是水平方向上发生了偏移。根据机器在水平方向上的偏移量(这边需要根据不同设备的情况作出相应的数据调整)作出相应的修改:

private void initializeProMode(boolean promode) {if (!promode) {mProMode.setMode(ProMode.NO_MODE);mProModeLayout.setVisibility(INVISIBLE);return;}mProModeLayout.setVisibility(VISIBLE);if (Camera.getNumberOfCameras() == 1 || mModelName.equals(Build.MODEL)) {if (density >= 280){mProModeLayout.setY(mHeight - mBottom - mProModeLayout.getHeight() + 120);} else {mProModeLayout.setY(mHeight - mBottom - mProModeLayout.getHeight());}mProModeLayout.setX(242);} else {mProModeLayout.setY(mHeight - mBottom - mProModeLayout.getHeight() - 48);}}

上面部分的代码是由于另外一个需求:

Promode的选项和功能部分会发生重叠,影响实际的功能操作。所以需要对竖直方向上坐标位置进行相应的调整。这个还需要考虑到Display Size 的调整会对其控件大小和布局的影响,所以要根据不同的density进行调整规划。 修改后的效果如下:

6.相机拍照/摄像时没有声音

        问题分析:

抓取拍照/录像时的即时log:

8-02 19:43:09.941 V/SnapCam_CaptureUI( 3687): surfaceChanged: width =1280, height = 720
08-02 19:43:09.943 E/SoundPool( 3687): error loading /product/media/audio/ui/VideoRecord.ogg
08-02 19:43:09.943 E/SoundPool( 3687): error loading /system/media/audio/ui/VideoRecord.ogg
08-02 19:43:09.943 E/MediaActionSound( 3687): load() error loading sound: 2
08-02 19:43:09.944 E/SoundPool( 3687): error loading /product/media/audio/ui/VideoStop.ogg
08-02 19:43:09.944 E/SoundPool( 3687): error loading /system/media/audio/ui/VideoStop.ogg
08-02 19:43:09.944 E/MediaActionSound( 3687): load() error loading sound: 3
08-02 19:43:09.944 E/SoundPool( 3687): error loading /product/media/audio/ui/camera_focus.ogg
08-02 19:43:09.945 E/SoundPool( 3687): error loading /system/media/audio/ui/camera_focus.ogg
08-02 19:43:09.945 E/MediaActionSound( 3687): load() error loading sound: 1
08-02 19:43:09.945 E/SoundPool( 3687): error loading /product/media/audio/ui/camera_click.ogg
08-02 19:43:09.945 E/SoundPool( 3687): error loading /system/media/audio/ui/camera_click.ogg
08-02 19:43:09.945 E/MediaActionSound( 3687): load() error loading sound: 0
08-02 19:43:09.946 D/SnapCam_CaptureModule( 3687): Chosen postproc filter id : 0

通过上面log可以看出拍照/摄像时无声的根本原因是加载音频文件的时候出现了异常。进入系统内部检查是否存在这些音频文件:

可以见当前设备的目标位置并未存放相关的资源。 正常可播放声音的机器在当前目录下会存放相关资源:

        解决方案:

找到音频文件的存放位置,将相关资源Copy到系统中:

$(LOCAL_PATH)/effects/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \

另外有些机器考虑到本身的配置比较低,加上这些资源之后可能会影响到性能。会主动移除这些功能,本案最后结合自身情况就选择不加入相关的音频资源。

这篇关于[Android] [SnapdragonCamera] 单摄(横屏)阶段总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145181

相关文章

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

Nginx Location映射规则总结归纳与最佳实践

《NginxLocation映射规则总结归纳与最佳实践》Nginx的location指令是配置请求路由的核心机制,其匹配规则直接影响请求的处理流程,下面给大家介绍NginxLocation映射规则... 目录一、Location匹配规则与优先级1. 匹配模式2. 优先级顺序3. 匹配示例二、Proxy_pa

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

MySQL基本查询示例总结

《MySQL基本查询示例总结》:本文主要介绍MySQL基本查询示例总结,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Create插入替换Retrieve(读取)select(确定列)where条件(确定行)null查询order by语句li

MySQL中的两阶段提交详解(2PC)

《MySQL中的两阶段提交详解(2PC)》:本文主要介绍MySQL中的两阶段提交(2PC),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录引言两阶段提交过程sync_binlog配置innodb_flush_log_at_trx_commit配置总结引言在Inn

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Linux区分SSD和机械硬盘的方法总结

《Linux区分SSD和机械硬盘的方法总结》在Linux系统管理中,了解存储设备的类型和特性是至关重要的,不同的存储介质(如固态硬盘SSD和机械硬盘HDD)在性能、可靠性和适用场景上有着显著差异,本文... 目录一、lsblk 命令简介基本用法二、识别磁盘类型的关键参数:ROTA查询 ROTA 参数ROTA

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整