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

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

文章目录


前言-场景-需求

这里以MTK平台 Android13版本为例说明

需求

这里其实有两个直接需求

  • 系统 Dialer 默认情况下打电话的视频通话功能里面 包含预览、视频通话等默认用的是MIPI相机实现的,现在实际产品用的是USB相机,没有MIPI相机的,那么就需要在这里插入代码片切换到USB相机来实现预览和视频通话视频效果
  • MTK 平台默认Camera2,默认情况下是不支持USB相机的。 也就是说 如果应用APP Dialer 使用的Camera1 切换到USB相机不会有问题,如果用的是Camera2 打开相机的话,如何支持Camera2 能够打开USB相机。

场景

客户用MTK平台的产品,去掉MIPI相机,使用外接的USB相机,需要保证Dialer 打电话功能能够视频通话,实际发现视频通话看不到画面。

所以,这里需要解决:

  • 分析 Dialer App ,使用USB来打开相机
  • MTK平台下系统要保证能够打开USB相机

备注:系统底层驱动层务必支持Camera API 能够打开USB相机。

一、参考文档-基础补充、资源参考

默认情况下 MTK平台是支持Camera1 来打开USB相机的,但是不支持Camera2,之前笔记提到过。
MTK 下 使用Camera1 完成预览-拍照-录像-USBCamera功能

对于相机开发,还是建议无论是UVC协议开发USBCamera,还是 Android SDK API框架下Camera api 来开发相机,都需要了解最基本的相关资料。

相机整个模块确实太专业、复杂了。 无论从硬件外设、驱动【USBCamera免驱】、相机 都比较专业、覆盖面及广,针对思考中的问题 给出自己认为比较好相关博客,方便了解,助于梳理流程、提升认知。

对于上层应用或者Framework 系统应用开发者,只需要了解基本的架构、API、使用方法,当然这些也不简单的

下面提供部分资源,方便快速了解,充电:

架构图了解

MTKCamera2相机架构
Camera2架构
Android Camera架构简析

Camera相关专栏

Camera Framework 专栏
小驰私房菜系列
小驰私房菜MTK系列
小驰Camera 开发系列
Camera 相机开发
展讯平台 Camera
官方文档:谷歌官方 API 描述

零散知识了解

MTK 相机UI介绍
Camera2 相机认知
Camera2学习笔记
camera2关于拍照预览方向旋转90度和拍照图片镜像功能实现
Camera2 预览集成、简单拍照:熟悉预览步骤流程比较有用
Camera镜像上下左右颠倒问题的解决办法
MTK相机成像质量差
Camera应用分析

部分相机源码参考,学习API使用,梳理流程,偏应用层

极客相机 Camera2 API
Camera2 API详解
极客相机源码
Camera2 相机Demo
Camera2 专业相机Demo
拍照、预览、录像Demo
使用Camera2 拍照

Camera2 系统相关

Camera2 Service 启动

二、文件修改路径

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 		

三、实现方案

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

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

这里面明白三点即可:

  • 底层实现一定要支持打开USB相机
  • USB相机打开,这一届open(UsbCameraId)即可。 USBCameraId 如何获取,通过getCameraIds ,里面一定存在一个USBCamera 的CameraId 的。

所以,针对Camera Camera2 在Framework层适配如下:

Framework层针对Camera1 的Camera 适配

路径: /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); , 那是因为USBCamera 的CameraId 一定是在最后一个。

所以,如果系统支持,需要打开哪一个相机,就打开哪一个相机的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); , 那是因为USBCamera 的CameraId 一定是在最后一个,不也是打开最后一个的嘛。

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

2、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();
  }

四、问题分析-说明- 知识点扩展

Framework层适配

  • Framework层适配,本身是基于底层的,底层支持才行
  • 有人疑问,默认的前后置相机和USB相机不是0/1/2 吗 ? 在 实际中 0 、1 确实代表了前后置,但是USB相机可不是2 能够代替了。
  • 代码中有9 ,代表什么呢? 这里只是一个Flag 标签,实际中有HDMIN 功能,直接打开0即可,底层已适配。

Dialer 业务修改分析

日志分析

首先看usb 相机打开失败视频,无法用Dialer 使用,并打印一些基本日志,查看日志如下:

因为没有MIPI,默认就应该是MIPI相机了,看 cameraId null , 大概猜到没设置相机ID 导致的。

日志-Telecom-InCallController( 1095): onSetCamera callId=TC@1, cameraId=null

如上有这样的日志,这个其实就是Telecom 通讯层的调用,跨进程调用了Dialer 里面方法,找到方法,如下:

查看所有call.getVideoTech().setCamera(cameraID)

实际发现在设置之前先去获取 cameraId,如:cameraManager.getActiveCameraId()

坑点说明

  • 直接从Dialer 源码着手,去查看Camera 相关内容,恰好找到CameraManager 相关,如下: 而且好多方法看着像会用到的,可实际发现没什么用,都不打印日志,都没有相关方法调用。
  • 如上,发现源码里面相关内容没有打印,就需要怀疑根本没用到时候 就要仔细看日志了,然后反推业务流程。

总结

  • 实现需求需要两步骤:平台MTK底层支持才行或者底层自己调通。
  • Dialer 应用也需要适配才行,注意学会分析日志,对比日志,针对性 找源码业务逻辑。
  • 一定要学会针对性分析问题、分析日志。代码量太大了,短时间内不可能搞明白业务逻辑的。