MTK-Android13-实现Dialer 调用USB实现预览等视频通话
文章目录
- 前言
- 一、需求
- 二、参考资料
- 三、实现方案
-
- 1、文件修改路径
- [2、Framework 层适配Camera1-Camera2 实现打开USB相机](#2、Framework 层适配Camera1-Camera2 实现打开USB相机)
-
- [Camera.java 修改](#Camera.java 修改)
- [Framework层针对Camera2 的Camera 适配](#Framework层针对Camera2 的Camera 适配)
- [3、Dialer 默认mipi切换到USB相机](#3、Dialer 默认mipi切换到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相机预览和成像方向
UVC for USBCamera in Android - 篇二
MTK-Android12-13 Camera2 设置默认视频画质功能实现
Android 13.0 MTK Camera2 设置默认拍照尺寸功能实现
MTK 下 使用Camera1 完成预览-拍照-录像-USBCamera功能
UVC for USBCamera in Android - 篇一
相机本身就是一个比较专业的知识领域,愿你在实际项目需求中能够不断积累,有所收获。
三、实现方案
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,里面一定存在一个USBCamera的CameraId。
所以,针对Camera Camera2在Framework层适配如下:
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), 那是因为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即可。
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 功能的。 当你设置cameraId 为0 时候, 实际上 这个USBCameraId 可能恰好就是0 ,所以刚好可用。
但是,当前需求的时候, 设备硬件环境本身是没有HDMIN 接口的,所以 实际测试 时候明明连接了一个USB 一个 MIPI 相机,USB 相机居然打开可用了。
但是,当你把MIPI相机拿掉,你会发现无论设置CameraId 为多少,USBCamera 相机始终无法视频预览。
实际解决思路-优化
结合以上的方案对比,那么其实回归到本质,打开USBCamera,你直接打开USBCamera对应的ID 不就行了嘛。
如下,还真有现成的方法获取到USB的cameraId ,那么拿到这个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层,在其它模块分离实现, 如果有具体需求再针对性看。