1. 理解UVC摄像头与Android的适配原理
UVC(USB Video Class)是一种免驱摄像头标准协议,市面上大多数USB摄像头都兼容这个协议。这意味着当你在Android设备上插入这类摄像头时,系统会自动识别并加载驱动,无需额外安装驱动程序。不过在实际开发中,我们仍然需要通过Android的USB Host API来管理和控制这些设备。
Android系统从4.0版本开始支持USB Host功能,但直到Android 5.0才真正完善了对UVC摄像头的支持。这里有个常见的误区:很多人以为只要摄像头支持UVC协议就能直接使用,实际上还需要考虑Android设备的兼容性。我测试过几款不同品牌的Android设备,发现有些设备虽然支持USB Host,但内核可能没有编译UVC驱动模块,这种情况下就需要重新编译内核或者更换设备。
在硬件层面,UVC摄像头通过USB接口传输视频数据流。Android系统会将这些摄像头识别为标准的Video4Linux(V4L2)设备。理解这一点很重要,因为后续的CameraHelper实际上就是在和V4L2子系统打交道。我曾经遇到过摄像头能识别但无法预览的情况,后来发现是内核配置问题,缺少了必要的V4L2模块支持。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目配置与权限申请
2.1 添加必要的依赖和权限
首先需要在项目的build.gradle中添加libuvccamera库的依赖。这个开源库封装了与UVC摄像头交互的复杂逻辑,大大简化了我们的开发工作。除了implementation project(':libuvccamera')之外,还需要添加USB Host相关的支持库:
groovy复制dependencies {
implementation 'com.github.saki4510t:libuvccamera:1.1.9'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
}
在AndroidManifest.xml中,我们需要声明USB Host功能权限和必要的特性:
xml复制<uses-feature android:name="android.hardware.usb.host" />
<uses-permission android:name="android.permission.USB_PERMISSION" />
这里有个坑我踩过:如果你的应用目标SDK版本在23(Android 6.0)以上,还需要动态申请USB权限。我建议在Activity的onCreate方法中就检查并请求这些权限:
java复制private static final int REQUEST_USB_PERMISSION = 1;
private UsbManager mUsbManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
// 检查并请求USB权限
checkUsbPermission();
}
private void checkUsbPermission() {
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
if (!deviceList.isEmpty()) {
UsbDevice device = deviceList.values().iterator().next();
if (!mUsbManager.hasPermission(device)) {
PendingIntent permissionIntent = PendingIntent.getBroadcast(this,
REQUEST_USB_PERMISSION,
new Intent(ACTION_USB_PERMISSION),
PendingIntent.FLAG_IMMUTABLE);
mUsbManager.requestPermission(device, permissionIntent);
}
}
}
2.2 处理USB设备插拔事件
为了让应用能够响应USB设备的插拔事件,我们需要注册一个BroadcastReceiver。这个接收器需要监听三个关键动作:
java复制private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
// 处理权限授予结果
} else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
// 处理设备插入
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
// 处理设备拔出
}
}
};
在Activity的生命周期方法中注册和注销这个接收器:
java复制@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mUsbReceiver);
}
3. CameraHelper的使用与生命周期管理
3.1 初始化CameraHelper
CameraHelper是libuvccamera库提供的核心类,负责管理摄像头的整个生命周期。我建议在Activity的onStart方法中初始化它,而不是onCreate,因为这样能更好地处理Activity重建的情况:
java复制private ICameraHelper mCameraHelper;
@Override
public void onStart() {
super.onStart();
resetCameraHelper();
}
private void resetCameraHelper() {
clearCameraHelper();
initCameraHelper();
}
private void initCameraHelper() {
mCameraHelper = new CameraHelper();
mCameraHelper.setStateCallback(mStateCallback);
// 设置预览帧率,一般30fps就足够流畅了
mCameraHelper.setDefaultPreviewFps(UVCCamera.FRAME_RATE_30);
}
3.2 实现状态回调
状态回调是整个预览功能的核心,它处理了从设备连接到开始预览的所有关键事件。根据我的经验,最需要关注的是以下几个回调方法:
java复制private final ICameraHelper.StateCallback mStateCallback = new ICameraHelper.StateCallback() {
@Override
public void onAttach(UsbDevice device) {
// 设备已连接但尚未获得权限
if (mUsbManager.hasPermission(device)) {
mCameraHelper.selectDevice(device);
} else {
// 如果没有权限,需要再次请求
checkUsbPermission();
}
}
@Override
public void onDeviceOpen(UsbDevice device, boolean createNew) {
// 设备已获得权限并打开
mCameraHelper.openCamera();
}
@Override
public void onCameraOpen(UsbDevice device) {
// 摄像头已成功打开,可以开始预览
mCameraHelper.startPreview();
Size size = mCameraHelper.getPreviewSize();
if (size != null) {
// 调整SurfaceView的宽高比以匹配摄像头
binding.surfaceView.setAspectRatio(size.width, size.height);
}
// 添加预览Surface
if (binding.surfaceView.getHolder().getSurface() != null) {
mCameraHelper.addSurface(binding.surfaceView.getHolder().getSurface(), false);
}
}
};
3.3 处理SurfaceView的生命周期
SurfaceView的创建和销毁需要特别小心处理。我遇到过SurfaceView还没准备好就尝试添加Surface导致的崩溃问题。正确的做法是在SurfaceHolder.Callback中处理:
java复制binding.surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mCameraHelper != null) {
mCameraHelper.addSurface(holder.getSurface(), false);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 可以在这里处理旋转等变化
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mCameraHelper != null) {
mCameraHelper.removeSurface(holder.getSurface());
}
}
});
4. 分辨率适配与性能优化
4.1 选择合适的预览分辨率
UVC摄像头通常支持多种分辨率,但并不是所有分辨率都能在所有设备上流畅运行。我建议在onCameraOpen回调中获取支持的分辨率列表,然后选择最适合的一个:
java复制List<Size> supportedSizes = mCameraHelper.getSupportedSizeList();
if (supportedSizes != null && !supportedSizes.isEmpty()) {
// 选择最接近1280x720的分辨率
Size bestSize = findBestMatch(supportedSizes, 1280, 720);
mCameraHelper.setPreviewSize(bestSize.width, bestSize.height);
}
4.2 处理屏幕旋转
当设备旋转时,我们需要重新调整预览方向。这里有个技巧:可以在Activity的onConfigurationChanged方法中处理:
java复制@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mCameraHelper != null && binding.surfaceView.getHolder().getSurface() != null) {
// 先移除旧的Surface
mCameraHelper.removeSurface(binding.surfaceView.getHolder().getSurface());
// 等待SurfaceView调整后重新添加
binding.surfaceView.post(() -> {
mCameraHelper.addSurface(binding.surfaceView.getHolder().getSurface(), false);
});
}
}
4.3 内存泄漏预防
CameraHelper持有大量原生资源,如果不正确释放会导致内存泄漏。我建议在Activity的onDestroy中彻底清理:
java复制@Override
protected void onDestroy() {
super.onDestroy();
clearCameraHelper();
}
private void clearCameraHelper() {
if (mCameraHelper != null) {
mCameraHelper.releaseAll();
mCameraHelper = null;
}
}
在实际项目中,我还发现有些设备在快速插拔USB摄像头时会出现资源未释放的问题。为了解决这个问题,可以在onDetach回调中也调用clearCameraHelper:
java复制@Override
public void onDetach(UsbDevice device) {
clearCameraHelper();
}
