MTK-Android13-实现Dialer 调用USB实现预览等视频通话

MTK-Android13-实现Dialer 调用USB实现预览等视频通话

文章目录


前言

MTK Android13 产品、校话机项目,使用了USB外接相机,实测发现系统Dialer无法使用相机进行视频预览,其它App 都可以使用USB相机预览。

一、需求

定制MTK 原生电话Dialer 系统应用,实现视频预览可以用外接USB相机。

二、参考资料

直接参考资料

Camera2和Camera1实现打开USB相机-实现Dialer 调用USB实现预览等视频通话

之前有相关类似的需求,其实基本一致的需求,本质需求就是一摸一样的,可以完全参考。 实测返现 更改无效果,但是实际的修改、基本源码、目标完全一致。 为什么修改没有效果,下面会进行源码分析和业务逻辑分析原因的。

相关联参考资料

对于USB相机和Camera相机相关知识点必须务必掌握:

RK3576-Android15原生相机Camera2 修改USB相机预览和成像方向

RK3576-Android15原生相机Camera2 修改USB相机预览和成像方向

USBCamera手柄按键功能实现

UVC for USBCamera in Android - 篇二

MTK-Android12-13 Camera2 设置默认视频画质功能实现

Android 13.0 MTK Camera2 设置默认拍照尺寸功能实现

Camera2 实现重力感应四个方向调试相机预览

MTK 下 使用Camera1 完成预览-拍照-录像-USBCamera功能

UVC for USBCamera in Android - 篇一

Camera2 预览旋转方向、拍照、录像成像旋转

相机本身就是一个比较专业的知识领域,愿你在实际项目需求中能够不断积累,有所收获。

三、实现方案

1、文件修改路径

java 复制代码
/frameworks/base/core/java/android/hardware/camera2/CameraManager.java 
/frameworks/base/core/java/android/hardware/Camera.java 

/vendor/mediatek/proprietary/packages/apps/Dialer/java/com/android/incallui/InCallCameraManager.java 		
/vendor/mediatek/proprietary/packages/apps/Dialer/java/com/android/incallui/videotech/ims/ImsVideoTech.java 		

2、Framework 层适配Camera1-Camera2 实现打开USB相机

假设底层已经适配成功,那么中间层适配下即可。如果是USB相机,那么切换到Camera 打开USB相机即可。

这里面明白三点即可:

  • 底层实现一定要支持打开USB相机
  • USB相机打开, open(UsbCameraId)即可。 USBCameraId 如何获取,通过getCameraIds ,里面一定存在一个USBCameraCameraId
    所以,针对Camera Camera2Framework层适配如下:

Camera.java 修改

路径:/frameworks/base/core/java/android/hardware/Camera.java

java 复制代码
 public static Camera open(int cameraId) {
        int numberOfCameras = getNumberOfCameras();
        Log.d("huanghb", "Camera open cameraId = "+numberOfCameras);
        //huanghb add
        if(cameraId == 9){
            return new Camera(0);
        }
        File file = new File("/dev/video3");
        if(file.exists()){
            return new Camera(numberOfCameras-1);
        }else if(numberOfCameras ==2){
            return new Camera(1);
        }else if(numberOfCameras == 1){
            return new Camera(0);
        }
        return new Camera(cameraId);
    }

    /**
     * Creates a new Camera object to access the first back-facing camera on the
     * device. If the device does not have a back-facing camera, this returns
     * null. Otherwise acts like the {@link #open(int)} call.
     *
     * @return a new Camera object for the first back-facing camera, or null if there is no
     *  backfacing camera
     * @see #open(int)
     */
    public static Camera open() {
        int numberOfCameras = getNumberOfCameras();
        Log.d("huanghb", "Camera open null = "+numberOfCameras);
        //huanghb add
        File file = new File("/dev/video3");
        if(file.exists()){
            return new Camera(numberOfCameras-1);
        }else if(numberOfCameras ==2){
            return new Camera(1);
        }else if(numberOfCameras == 1){
            return new Camera(0);
        }
        //huanghb end
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                return new Camera(i);
            }
        }
        return null;
    }

这里关注两点:

  • 有节点 /dev/video3 存在,那么一定是USBCamera相机挂载到这个节点了
  • 如果挂载了USBCamera,看打开 usbCamera相机方案: new Camera(numberOfCameras-1), 那是因为USBCameraCameraId 一定是在最后一个。
    所以,如果系统支持,需要打开哪一个相机,就打开哪一个相机的CameraId即可。

Framework层针对Camera2 的Camera 适配

路径:/frameworks/base/core/java/android/hardware/camera2/CameraManager.java

java 复制代码
 /**
     * Open a connection to a camera with the given ID, on behalf of another application
     * specified by clientUid.
     *
     * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
     * the caller to specify the UID to use for permission/etc verification. This can only be
     * done by services trusted by the camera subsystem to act on behalf of applications and
     * to forward the real UID.</p>
     *
     * @param clientUid
     *             The UID of the application on whose behalf the camera is being opened.
     *             Must be USE_CALLING_UID unless the caller is a trusted service.
     *
     * @hide
     */
    public void openCameraForUid(@NonNull String cameraId,
            @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
            int clientUid) throws CameraAccessException {
            //huanghb modify
            Log.d("huanghb","openCameraForUid cameraId = "+cameraId);
            int i = getCameraIdList().length;
            String[] fiseCameraId = getCameraIdList();
            
            File file = new File("/dev/video3");
            if(cameraId.equals("9")){
                Log.d("huanghb","openCameraForUid HDMI IN");
                openCameraForUid("0", callback, executor, clientUid, /*oomScoreOffset*/0);
            }else if (file.exists()) {
                //Log.d("huanghb", "video3 true");
                i = i-1;
                openCameraForUid(fiseCameraId[i], callback, executor, clientUid, /*oomScoreOffset*/0);
            }else if(i == 2){
                openCameraForUid("1", callback, executor, clientUid, /*oomScoreOffset*/0);
            }else{
                openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0);
            }
    }
	
	    /**
         * Get a list of all camera IDs that are at least PRESENT; ignore devices that are
         * NOT_PRESENT or ENUMERATING, since they cannot be used by anyone.
         */
        public String[] getCameraIdList() {
            String[] cameraIds = null;
            synchronized (mLock) {
                // Try to make sure we have an up-to-date list of camera devices.
                connectCameraServiceLocked();
                cameraIds = extractCameraIdListLocked();
            }
            sortCameraIds(cameraIds);
            return cameraIds;
        }

这里关注两点:

  • 有节点 /dev/video3 存在,那么一定是USBCamera相机挂载到这个节点了
  • 如果挂载了USBCamera,看打开 usbCamera相机方案: openCameraForUid(fiseCameraId[i], callback, executor, clientUid, /oomScoreOffset/0); , 那是因为USBCameraCameraId 一定是在最后一个,不也是打开最后一个的嘛。

所以,如果系统支持,需要打开哪一个相机,就打开哪一个相机的CameraId即可。

3、Dialer 默认mipi切换到USB相机

InCallCameraManager 方法getActiveCameraId修改为USB,设置为0

InCallCameraManager 类,路径 /vendor/mediatek/proprietary/packages/apps/Dialer/java/com/android/incallui/InCallCameraManager.java

代码如下:

java 复制代码
/**
   * Determines the active camera ID.
   *
   * @return The active camera ID.
   */
   public String getActiveCameraId() {
    maybeInitializeCameraList(context);

	// modify by fangchen start 
    if (useFrontFacingCamera) {
      android.util.Log.d(TAG, "=====getActiveCameraId=====useFrontFacingCamera  frontFacingCameraId:"+frontFacingCameraId);
      //return frontFacingCameraId;
      return "0";
    } else {
      android.util.Log.d(TAG, "=====getActiveCameraId=====rearFacingCameraId  rearFacingCameraId:"+rearFacingCameraId);
     // return rearFacingCameraId;
      return "0";
    }
    // modify by fangchen end 
  }

ImsVideoTech 类中-call.getVideoCall().setCamera 设置参数0

ImsVideoTech 路径: /vendor/mediatek/proprietary/packages/apps/Dialer/java/com/android/incallui/videotech/ims/ImsVideoTech.java

java 复制代码
  @Override
  public void setCamera(@Nullable String cameraId) {
    savedCameraId = cameraId;
    if (call.getVideoCall() == null) {
      LogUtil.w("ImsVideoTech.setCamera", "video call no longer exist");
      return;
    }
  
    // modify by fangchen start 
    //call.getVideoCall().setCamera(cameraId);
    call.getVideoCall().setCamera("0");
	 // modify by fangchen end 
    call.getVideoCall().requestCameraCapabilities();
  }

四、思考-视频预览依然不可用思考

实际实测发现 Camera2和Camera1实现打开USB相机-实现Dialer 调用USB实现预览等视频通话 这里解决了那个项目的实际问题,但是实际移植代码到当前项目并没有解决实际问题,视频预览依然不可用。

方案对比

Camera2和Camera1实现打开USB相机-实现Dialer 调用USB实现预览等视频通话 需求中解决了那个项目实际问题 但是 无法解决当前项目实际问题,那就特别奇怪了。 通过阅读代码和实际验证测试、业务再细分: 发现 之前项目需求里面本身有两个摄像头的 其中一个摄像头是 HDMIN 作为摄像头来使用HDMIN 功能的。 当你设置cameraId0 时候, 实际上 这个USBCameraId 可能恰好就是0 ,所以刚好可用。

但是,当前需求的时候, 设备硬件环境本身是没有HDMIN 接口的,所以 实际测试 时候明明连接了一个USB 一个 MIPI 相机,USB 相机居然打开可用了。

但是,当你把MIPI相机拿掉,你会发现无论设置CameraId 为多少,USBCamera 相机始终无法视频预览。

实际解决思路-优化

结合以上的方案对比,那么其实回归到本质,打开USBCamera,你直接打开USBCamera对应的ID 不就行了嘛。

如下,还真有现成的方法获取到USBcameraId ,那么拿到这个id, 直接设置进去就可以了。

java 复制代码
  /**
   * Get the list of cameras available for use.
   *
   * @param context The context.
   */
  private void maybeInitializeCameraList(Context context) {
    if (isInitialized || context == null) {
      return;
    }

    Log.v(TAG, "initializeCameraList");
    /// M: [ALPS03966547] Set fake camera id to test video call. @{
    if (InCallUtils.MTK_NO_CAMERA_MODE) {
      Log.v(TAG, "initializeCameraList, set fake camera id in no camera mode.");
      frontFacingCameraId = FAKE_FRONT_FACING_CAMERA_ID;
      rearFacingCameraId = FAKE_REAR_FACING_CAMERA_ID;
      isInitialized = true;
      return;
    }
    /// @}
    CameraManager cameraManager = null;
    try {
      cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
    } catch (Exception e) {
      Log.e(this, "Could not get camera service.");
      return;
    }
    if (cameraManager == null) {
      return;
    }
    String[] cameraIds = {};
    try {
      cameraIds = cameraManager.getCameraIdList();
	  for (String id : cameraIds) {
		    Log.d(TAG,"=========maybeInitializeCameraList===Camera ID: :"+id);	 
       }
	  
	
    } catch (CameraAccessException e) {
      Log.d(this, "Could not access camera: " + e);
      // Camera disabled by device policy.
      return;
    }

    /// M: ALPS03636982 reset the value for further using @{
    frontFacingCameraId = null;
    rearFacingCameraId = null;
    /// @}

    for (int i = 0; i < cameraIds.length; i++) {
      CameraCharacteristics c = null;
      try {
        c = cameraManager.getCameraCharacteristics(cameraIds[i]);
      } catch (IllegalArgumentException e) {
        // Device Id is unknown.
      } catch (CameraAccessException e) {
        // Camera disabled by device policy.
      }
      if (c != null) {
        int facingCharacteristic = c.get(CameraCharacteristics.LENS_FACING);
        /// M: ALPS03636982 Workaround for projects with three camera sensors, the
        /// camera found the first one. @{
        if (facingCharacteristic == CameraCharacteristics.LENS_FACING_FRONT
            && frontFacingCameraId == null) {
          frontFacingCameraId = cameraIds[i];
        } else if (facingCharacteristic == CameraCharacteristics.LENS_FACING_BACK
            && rearFacingCameraId == null) {
          rearFacingCameraId = cameraIds[i];
        }
        /// @}
      }
    }

    isInitialized = true;
	
	 Log.d(TAG,"=========maybeInitializeCameraList===frontFacingCameraId:"+frontFacingCameraId+"    rearFacingCameraId:"+rearFacingCameraId);	
    Log.v(TAG, "initializeCameraList : done");
  }

所以,现在要做的就是把获取得到的cameraId,保存起来,然后在需要设置的地方设置进去就可以了。

java 复制代码
1、 getActiveCameraId() 返回获取得到的cameraId
2、 call.getVideoCall().setCamera("0");  直接替换为获取得到的UsbCameraId 就可以了。 

特别特别注意: 这个 ImsVideoTech.java 类中的方案其实可以不改,强烈建议不用改。

场景:在视频通话过程中,界面有一个视频关闭和打开的按钮,如果强制写死就会一直打开没有关闭的功能了。

实际测试发现:只需要适配了InCallCameraManager.java 类中cameraId,那么视频通话过程中打开关闭会在setCamera 方法中回调正确的cameraId,用于实现本地视频打开与关闭的功能。

反问:为什么第三方app 都视频预览成功了,就是Dialer 不成功呢?

细看源码发现,Dialer 并不支持UsbCamera 的相机进行视频预览,它里面的所有获取并分离其实都是前后置摄像头,这就是本质原因。第三方之所有可以进行视频预览,那是因为他们获取的就是真实实在的UsbCameraId 并打开。

总结

  • 经验:多打一些日志,方便分析流程,Camera 调试中 cameraIds 直接看病定位比较高效
  • 之前的经验,项目需求:本以为借此直接用,实际发现其实还是有本质的区别,硬件环境不一致 误打误撞,这里其实深挖了基本知识点,直接拿USBCameraId 来打开它的。
  • 你会发现,Dialer App 中其实并没有openCamera 的操作,那究竟在哪里操作的呢? 在 Framework层,在其它模块分离实现, 如果有具体需求再针对性看。