提示:MTK-实现Camera2-Camera1 打开USB相机-实现Dialer 调用USB实现预览等视频通话
文章目录
- 前言-场景-需求
- 一、参考文档-基础补充、资源参考
-
- 架构图了解
- Camera相关专栏
- 零散知识了解
- 部分相机源码参考,学习API使用,梳理流程,偏应用层
- [Camera2 系统相关](#Camera2 系统相关)
- 二、文件修改路径
- 三、实现方案
-
- [1、Framework 层适配Camera1-Camera2 实现打开USB相机](#1、Framework 层适配Camera1-Camera2 实现打开USB相机)
-
- [Framework层针对Camera1 的Camera 适配](#Framework层针对Camera1 的Camera 适配)
- [Framework层针对Camera2 的Camera 适配](#Framework层针对Camera2 的Camera 适配)
- [2、Dialer 默认mipi切换到USB相机](#2、Dialer 默认mipi切换到USB相机)
-
- [InCallCameraManager 方法getActiveCameraId修改为USB,设置为0](#InCallCameraManager 方法getActiveCameraId修改为USB,设置为0)
- [ImsVideoTech 类中-call.getVideoCall().setCamera 设置参数0](#ImsVideoTech 类中-call.getVideoCall().setCamera 设置参数0)
- [四、问题分析-说明- 知识点扩展](#四、问题分析-说明- 知识点扩展)
-
- Framework层适配
- [Dialer 业务修改分析](#Dialer 业务修改分析)
-
- 日志分析
- [日志-Telecom-InCallController( 1095): onSetCamera callId=TC@1, cameraId=null](#日志-Telecom-InCallController( 1095): onSetCamera callId=TC@1, cameraId=null)
- 查看所有call.getVideoTech().setCamera(cameraID)
- 坑点说明
- 总结
前言-场景-需求
这里以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 系统相关
二、文件修改路径
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 应用也需要适配才行,注意学会分析日志,对比日志,针对性 找源码业务逻辑。
- 一定要学会针对性分析问题、分析日志。代码量太大了,短时间内不可能搞明白业务逻辑的。