10、USB视频设备开发:UVC协议与Android端实战
大家好,我是老张。今天我们来聊聊USB视频设备开发,也就是UVC协议。说实话,这个主题在Android开发里算是比较冷门的一块,但一旦你搞懂了,外接摄像头、USB相机这些功能就能信手拈来。
我记得第一次接触UVC是在一个工业平板项目上。客户要求外接一个高清摄像头做质检,我心想这不就是插上就能用吗?结果折腾了两周才搞定。嗯,今天就把这些经验分享给你。
10.1 UVC协议概述
UVC,全称USB Video Class,是USB组织定义的一套视频设备标准协议。说白了,就是让摄像头插上电脑或手机就能直接工作,不需要装驱动。
为什么需要UVC?你想想看,如果没有这个标准,每个摄像头厂商都得自己写驱动,那Android手机怎么支持几百种不同的USB摄像头?UVC就是解决了这个兼容性问题。
核心要点:UVC协议定义了视频流传输、设备控制、格式协商等标准接口。只要设备遵循UVC规范,主机端就能用统一的API操作它。
UVC协议主要包含以下几个层面:
- 视频流接口:负责传输视频帧数据,支持YUV、MJPEG、H.264等格式
- 控制接口:用于设置亮度、对比度、曝光等参数
- 格式描述符:告诉主机摄像头支持哪些分辨率和帧率
- 单元/终端:摄像头内部的功能模块,比如处理单元、扩展单元
我在项目中遇到过一个问题:某款工业摄像头在Windows上工作正常,但插到Android平板上就黑屏。后来发现是它只支持UVC 1.1的某种扩展格式,而Android的UVC驱动默认只支持1.0标准。这种坑,不踩一次真不知道。
10.2 Android端USB摄像头支持
Android从5.0开始就内置了UVC驱动,但真正好用的是从Android 8.0引入的Camera2 API。为什么?因为Camera2 API提供了更精细的摄像头控制能力。
Android系统对USB摄像头的支持分两层:
- 内核层:Linux内核的UVC驱动,负责枚举设备、传输数据
- 应用层:Camera2 API或第三方库,负责预览、拍照、参数设置
避坑指南:我曾经在Android 6.0的设备上调试UVC摄像头,发现Camera2 API根本用不了。后来查资料才知道,Camera2 API在Android 5.0引入,但直到8.0才完整支持USB摄像头。所以如果你还在用老版本系统,建议用第三方库如UVCCamera。
Camera2 API支持USB摄像头的关键类是CameraManager和CameraDevice。当你插入USB摄像头时,系统会把它当作一个独立的摄像头设备注册到CameraManager中。你可以像操作内置摄像头一样操作它。
10.3 UVC设备的枚举与参数设置
枚举UVC设备,说白了就是找到插在手机上的摄像头。我习惯用两种方式:
方式一:通过USB Host API枚举
UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
for (UsbDevice device : deviceList.values()) {
// 检查设备类是否为视频类(0x0E)
if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
// 这就是UVC摄像头
}
}
方式二:通过CameraManager枚举
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
String[] cameraIdList = cameraManager.getCameraIdList();
for (String cameraId : cameraIdList) {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
// 检查是否为外部摄像头
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
// 这就是USB摄像头
}
}
我个人更推荐第二种方式,因为它直接和Camera2 API对接,省去了很多底层操作。
参数设置这块,UVC摄像头支持哪些参数?我列个表给你看:
| 参数名称 | UVC控制ID | 说明 |
|---|---|---|
| 亮度 | PU_BRIGHTNESS | 0-255,默认128 |
| 对比度 | PU_CONTRAST | 0-255,默认128 |
| 饱和度 | PU_SATURATION | 0-255,默认128 |
| 曝光时间 | PU_EXPOSURE_TIME | 微秒级,自动/手动 |
| 白平衡 | PU_WHITE_BALANCE | 色温值,自动/手动 |
注意:不是所有UVC摄像头都支持全部参数。有些廉价摄像头只支持亮度和对比度。设置前最好先查询设备支持的控制项,否则会返回错误。
10.4 实战:外接USB摄像头预览与拍照
好了,理论说完了,我们来写点真东西。下面是一个完整的UVC摄像头预览+拍照示例。
首先,在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.usb.host" />
然后,打开摄像头并创建预览会话:
// 获取CameraManager
CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
String cameraId = null;
// 找到外部摄像头
for (String id : manager.getCameraIdList()) {
CameraCharacteristics chars = manager.getCameraCharacteristics(id);
if (chars.get(CameraCharacteristics.LENS_FACING) ==
CameraCharacteristics.LENS_FACING_EXTERNAL) {
cameraId = id;
break;
}
}
// 打开摄像头
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
// 创建预览请求
SurfaceTexture texture = textureView.getSurfaceTexture();
Surface surface = new Surface(texture);
CaptureRequest.Builder builder = camera.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(surface);
// 创建会话
camera.createCaptureSession(Arrays.asList(surface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
session.setRepeatingRequest(builder.build(), null, null);
}
}, null);
}
}, null);
拍照功能稍微复杂一点。你需要创建一个ImageReader来接收拍照数据:
// 创建ImageReader,分辨率1920x1080,格式JPEG
ImageReader imageReader = ImageReader.newInstance(1920, 1080,
ImageFormat.JPEG, 2);
imageReader.setOnImageAvailableListener(reader -> {
Image image = reader.acquireLatestImage();
// 保存到文件
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
saveToFile(bytes);
image.close();
}, null);
// 创建拍照请求
CaptureRequest.Builder captureBuilder = camera.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
session.capture(captureBuilder.build(), null, null);
个人经验:拍照时别忘了处理旋转问题。USB摄像头通常是横向安装的,但手机可能是竖屏。我习惯在拍照前设置JPEG_ORIENTATION参数,或者拍照后通过Exif信息旋转图片。
最后,别忘了在onPause()中释放资源:
@Override
protected void onPause() {
super.onPause();
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
}
嗯,到这里,一个完整的UVC摄像头预览和拍照功能就实现了。你可能会问:为什么我插上摄像头没反应?最常见的原因是权限问题——Android 6.0以上需要动态申请USB权限。记得在插入设备时弹出权限请求对话框。
我曾经在一个项目里调试了整整一天,最后发现是USB线的问题。有些廉价USB线只支持充电,不支持数据传输。所以如果你遇到设备枚举不到,先换根线试试。
好了,这一章的内容就到这里。UVC开发其实不难,关键是理解协议层次和Android的封装方式。下一章我们会深入讲解如何优化UVC摄像头的帧率和画质,以及如何处理多摄像头同时工作的情况。
公众号:蓝海资料掘金营,微信deep3321