最终实现的效果如下:
开发板:RK3566
系统:Android11
需求:同时打开两个外接USB相机,屏幕上左右画面要和仪器上左右相机对应
背景:在项目初期用了UVCCamera的库打开了两个摄像头,对so包也做了一点更改,因为不懂C,尝试改了一点(UVCPreview.cpp里stopPreview方法中加了判断,防止插拔闪退),在RK3566上运行正常且可以热插拔,RK3568上运行正常,插拔闪退。因为我们是用3566,所以就没在3568上研究。后来生产的时候,厂家给的安卓板子有问题,USB信号老是会中断,导致摄像头黑屏,然后闪退,板子厂商后来给换了hub芯片,测了几台,目前正常。担心UVC的库不稳定,且CPU占有率很高(4核占了300%的cpu,应该是软解码导致),所以想用原生的Camera1来打开USB相机,于是,就有了以下方案的尝试。在文章末尾会给两种方式的资源包,仅供参考
1. 实现同时打开两个USB相机
首先获取了摄像头的个数
Camera.getNumberOfCameras()
发现获取到的数量是0,然后拿了别的USB摄像头是可以的,跟厂商反馈,给了好几版固件试了,终于能获取到数量了,满怀信心的打开,发现只能同时打开一个,想打开另外一个,就得把另一个关掉。怀疑是带宽的问题,电子和其他同事都觉得不应该,只是两个相机不会占用太多带宽。
继续和摄像头厂商反馈,他让我确定我调用的是图像什么格式的,debug获取预览格式是NV21
camera.getParameters().getPreviewFormat()
关于NV21图像格式属于YUV颜色空间中的YUV420SP格式,简言之就是YUV的一种格式,默认作为Android系统摄像头输出图像格式。
跟厂商反馈后,他们表示要降低30帧可以实现,目前我们对于帧率暂时没太多要求,可以接受。最后固件里给了,1280x720,640x480,640x400的三种分辨率。三种分辨率都可以同时打开两个摄像头,第一步需求完成。
2.固定摄像头显示顺序
打开两个摄像头之后,发现每次开关机,摄像头打开的顺序不一样,Camera类里对于cameraId的描述是从0~相机数量-1来提供的
...
cameraId – the hardware camera to access, between 0 and getNumberOfCameras()-1.
public static Camera open(int cameraId) {
return new Camera(cameraId);
}
那目前两个相机的cameraId分别就是0和1,打开顺序就按照0和1来顺序打开
...
TexturePreviewView previewLeftView = findViewById(R.id.face_preview_view);
TexturePreviewView previewRightView = findViewById(R.id.face_preview_view2);
int cameraLeftId = 0;
int cameraRightId = 1;
cameraControl = new Camera1Control(this, cameraLeftId);
cameraControl.setPreviewView(previewView);
cameraControl.setPreferredPreviewSize(1280,720);
cameraControl.setCameraFacing(ICameraControl.CAMERA_USB);
cameraControl.start();
cameraControl2 = new Camera1Control(this, cameraRightId);
cameraControl2.setPreviewView(previewView);
cameraControl2.setPreferredPreviewSize(1280,720);
cameraControl2.setCameraFacing(ICameraControl.CAMERA_USB);
cameraControl2.start();
然后会出现以下几种情况
a.左Camera镜像显示在左画面上,右Camera显示在右画面上
b. 左Camera显示在右画面上,右Camera镜像显示在左画面上
这两种情况都不是需求想要的, 需求就是左Camera对应显示在左画面,右Camera对应显示在右画面上。查资料通过修改安卓板底层能实现,咨询板子厂商,他们给了一个思路,让adb获取两个相机信息的前后置,是front还是back,然后看屏幕画面的顺序是否会随着读到的顺序变化而变化。
adb shell
dumpsys media.camera
通过若干次开关机上电发现现象如下:
adb获取到的前后置顺序 | cameraId打开顺序 | 屏幕对应的现象 |
front back | 0 1 | a |
back fornt | 0 1 | b |
分析如下:
a现象其实已经和需求接近了,左摄像头对应左画面,右摄像头对应右画面,只是左边摄像头镜像显示了。所以暂定front、back顺序的话,那cameraId对应就是0、1。
b现象是画面显示位置与左右摄像头是相反的,我们能改的就是把cameraId打开的顺序反一下改为1、0,然后就发现屏幕出现的现象就是a了,修改后表格如下
adb获取到的前后置顺序 | cameraId打开顺序 | 屏幕对应的现象 |
front back | 0 1 | a |
back fornt | 1 0 | a |
这样就能得出结论,在打开相机之前, 可以把front和0对应起来,back和1对应起来。
当两个顺序是front、back时,那cameraLeftId = 0、cameraRightId = 1;
当两个顺序是back、front时,那cameraLeftId = 1、cameraRightId = 0
所以在打开相机前,先对cameraLeftId和cameraRightId赋值,按照这个思路,代码如下:
private void getCameraOrder() {
try {
Process p = Runtime.getRuntime().exec("dumpsys media.camera");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
mCameraIdList.clear();
while((line = in.readLine())!= null) {
String deviceInfo = line.trim();
String key = "Facing:";
if (deviceInfo.contains(key)) {
Log.d("333", deviceInfo);
if (!TextUtils.isEmpty(deviceInfo) && deviceInfo.length() > 8) {
String front = deviceInfo.substring(deviceInfo.indexOf(key) + 8, deviceInfo.length());
Log.d("333", "截取到的值=" + front);
if (front.contains("Front")) {
mCameraLeftId = 0;
mCameraRightId = 1;
Log.d("333", "这只能执行一次" + front + "\nmCameraLeftId=" + mCameraLeftId + "\nmCameraRightId=" + mCameraRightId);
break;
}
if (front.contains("Back")) {
mCameraLeftId = 1;
mCameraRightId = 0;
Log.d("333", "这只能执行一次" + front + "\nmCameraLeftId=" + mCameraLeftId + "\nmCameraRightId=" + mCameraRightId);
break;
}
}
}
//对获取的每行的设备信息进行过滤,获得自己想要的。
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
...
TexturePreviewView previewLeftView = findViewById(R.id.face_preview_view);
TexturePreviewView previewRightView = findViewById(R.id.face_preview_view2);
int cameraLeftId = 0;
int cameraRightId = 1;
//--------在打开前更新一下左右的cameraId
getCameraOrder();
cameraControl = new Camera1Control(this, cameraLeftId);
cameraControl.setPreviewView(previewView);
cameraControl.setPreferredPreviewSize(1280,720);
cameraControl.setCameraFacing(ICameraControl.CAMERA_USB);
cameraControl.start();
cameraControl2 = new Camera1Control(this, cameraRightId);
cameraControl2.setPreviewView(previewView);
cameraControl2.setPreferredPreviewSize(1280,720);
cameraControl2.setCameraFacing(ICameraControl.CAMERA_USB);
cameraControl2.start();
至此,每次上电USB的顺序就和屏幕上能对应起来了。
拓展
在此两个外接USB的基础上,又尝试了多接入一个USB摄像头,发现打不开第三个了,查资料说是安卓板子底层限制最大相机数量是2,想多接入就得改底层板子。 好像是在CameraHal_Module.h类中修改CAMERAS_SUPPORT_MAX数量,搞应用层的不太确定,欢迎大家指正。
下面是两种方式的下载链接