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

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

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

1.1问题现象

1.2分析与解决

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

a.先移除AndroidManifest.xml中的固定设置:

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

java 复制代码
   @Override
    public 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 here
            String 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中是设置这个模式的位置:

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

    @Override
    public 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;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mCameraModeText.setText(mModeList.get(position));
        holder.mCameraModeText.setSelected(mSelectedPos == position); //选中预览模式
        holder.mCameraModeText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener.onItemClick(position) >= 0) {
                    mSelectedPos = position;
                    notifyDataSetChanged();
                }
            }
        });
    }

    @Override
    public 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:

java 复制代码
 mCameraModeAdapter = new Camera2ModeAdapter(mModule.getCameraModeList());
 mCameraModeAdapter.setOnItemClickListener(mModule.getModeItemClickListener());
 mModeSelectLayout.setAdapter(mCameraModeAdapter);

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

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

预设的模式:

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

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

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

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

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

java 复制代码
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方法中会根据预设的情况进行初步的筛选:

java 复制代码
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:// default
                removeList[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 列表中

java 复制代码
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设备可能会丢失已拍摄的图片。这是因为设备的电源管理策略和文件系统缓存机制可能导致图片数据未能及时写入存储设备。

问题解决:

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

java 复制代码
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的行为。

java 复制代码
@Override
        public 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()方法里:

java 复制代码
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:

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() {

                        @Override
                        public 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"。

java 复制代码
if (mCurrentSceneMode == null) {
   int index = mIntentMode == INTENT_MODE_VIDEO ?
       CameraMode.VIDEO.ordinal() : CameraMode.DEFAULT.ordinal(); // CameraMode.DEFAULT.ordinal()的值是2
   if ((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

java 复制代码
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的控件做了一些位置的调整:

java 复制代码
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的竖直方向上做了一些调整。而对于横屏的机器遇到的问题是水平方向上发生了偏移。根据机器在水平方向上的偏移量(这边需要根据不同设备的情况作出相应的数据调整)作出相应的修改:

java 复制代码
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:

bash 复制代码
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到系统中:

bash 复制代码
$(LOCAL_PATH)/effects/ogg/camera_click.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/camera_click.ogg \

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

相关推荐
木头没有瓜3 分钟前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0076 分钟前
springboot 上传图片 转存成webp
android·spring boot·okhttp
江上清风山间明月32 分钟前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet
Crossoads2 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
sunphp开发者3 小时前
黑客攻击网站,篡改首页问题排查修复
android·js
我又来搬代码了3 小时前
【Android Studio】创建新项目遇到的一些问题
android·ide·android studio
ggs_and_ddu8 小时前
Android--java实现手机亮度控制
android·java·智能手机
3DVisionary12 小时前
数字图像相关DIC技术用于机械臂自动化焊接全场变形测量
运维·数码相机·自动化·焊接变形实验·数字图像相关dic技术·自动化焊接全场变形测量·非接触高精度环境适应性全场测量
zhangphil13 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
watl014 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维