# UsbCameraLib **Repository Path**: rainOver/UsbCamera-android ## Basic Information - **Project Name**: UsbCameraLib - **Description**: 1. android版本的uvc摄像头操作 - **Primary Language**: C++ - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 3 - **Created**: 2025-03-03 - **Last Updated**: 2026-06-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # UsbCamera SDK 接入文档 > Android USB UVC 摄像头开发套件 · 包名 `com.rain.uvc` · 版本 1.0 --- ## 目录 - [一、SDK 简介](#一sdk-简介) - [二、接入准备](#二接入准备) - [2.1 引入依赖](#21-引入依赖) - [2.2 权限声明](#22-权限声明) - [2.3 设备要求](#23-设备要求) - [三、快速上手](#三快速上手) - [四、设备管理](#四设备管理) - [4.1 设备发现](#41-设备发现) - [4.2 打开设备](#42-打开设备) - [4.3 关闭设备](#43-关闭设备) - [4.4 热插拔监听](#44-热插拔监听) - [五、视频预览](#五视频预览) - [5.1 设置预览画面](#51-设置预览画面) - [5.2 设置分辨率](#52-设置分辨率) - [5.3 开始/停止预览](#53-开始停止预览) - [5.4 预览帧数据回调](#54-预览帧数据回调) - [5.5 硬件按钮监听](#55-硬件按钮监听) - [六、拍照](#六拍照) - [七、视频录制](#七视频录制) - [7.1 录制生命周期](#71-录制生命周期) - [7.2 完整示例](#72-完整示例) - [7.3 API 详解](#73-api-详解) - [八、参数控制](#八参数控制) - [8.1 查询支持的参数范围](#81-查询支持的参数范围) - [8.2 获取/设置参数](#82-获取设置参数) - [8.3 参数列表](#83-参数列表) - [九、枚举类型说明](#九枚举类型说明) - [UvcPreviewFormat - 预览解码格式](#uvcpreviewformat---预览解码格式) - [UvcDataFormat - 帧回调数据格式](#uvcdataformat---帧回调数据格式) - [十、数据模型](#十数据模型) - [十一、错误处理](#十一错误处理) - [十二、注意事项与 FAQ](#十二注意事项与-faq) --- ## 一、SDK 简介 UsbCamera SDK 是一套面向 Android 平台的 USB UVC 摄像头控制开发套件,提供从设备发现、预览、拍照、录像到参数调节的完整能力,帮助开发者快速接入 USB 外接摄像头。 ### 核心能力 - **设备管理** — UVC 设备枚举、USB 权限自动申请、设备打开/关闭、热插拔监听 - **视频预览** — 支持 Surface / TextureView / SurfaceView 三种方式渲染预览画面 - **预览数据回调** — 可获取实时帧数据(支持 NV21、RGBA、RGB 等多种格式),用于算法分析 - **拍照** — 一键抓取当前帧 - **视频录制** — 支持 H.264 视频编码 + AAC 音频编码,输出 MP4 文件 - **参数控制** — 亮度、对比度、曝光、白平衡、缩放、对焦、镜像、旋转等 17+ 项参数的查询与设置 ### 技术指标 | 项目 | 说明 | |------|------| | 最低版本 | Android 6.0(API 23) | | 支持架构 | `arm64-v8a`、`armeabi-v7a` | | 开发语言 | Kotlin / Java 均可调用 | | 依赖要求 | Kotlin Coroutines(协程) | --- ## 二、接入准备 ### 2.1 引入依赖 ```groovy // build.gradle (app) dependencies { implementation project(':libusbcamera') // 或使用 aar 包 // implementation files('libs/libusbcamera-release.aar') } ``` ### 2.2 权限声明 在你的 `AndroidManifest.xml` 中添加以下权限(SDK 已自动合并基础权限): ```xml ``` **权限说明:** | 权限 | 是否必需 | 说明 | |------|---------|------| | `CAMERA` | **必需** | 需在运行时动态申请,打开摄像头前必须已授权 | | `RECORD_AUDIO` | 可选 | 仅在录制视频且需要录音时使用 | | USB 设备访问权限 | 自动 | 由 SDK 内部自动弹窗申请,无需手动处理 | ### 2.3 设备要求 - Android 设备需支持 **USB Host** 模式 - 外接摄像头需符合 **UVC(USB Video Class)** 协议标准 --- ## 三、快速上手 以下是一个最简完整流程示例(Kotlin): ```kotlin class CameraActivity : AppCompatActivity() { private var camera: NativeUsbDevice? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) // 运行时申请 CAMERA 权限(略) } /** 第一步:发现并打开设备 */ fun openDevice() { lifecycleScope.launch { // 1. 发现 UVC 摄像头 val devices = CameraControlHelper.loadUvcDevices(this@CameraActivity) val usbDevice = devices?.firstOrNull() ?: return@launch // 2. 打开设备(内部自动申请 USB 权限) val result = CameraControlHelper.openCamera( this@CameraActivity, usbDevice, timeout = 10000 ) camera = result.getOrNull() ?: run { Log.e("Camera", "打开失败: ${result.exceptionOrNull()?.message}") return@launch } // 3. 设置预览画面 camera?.setDisplaySurface(findViewById(R.id.textureView)) // 4. 设置分辨率(先查询支持的分辨率再设置) camera?.setPreviewSize(1280, 720, UvcPreviewFormat.MJPEG) // 5. 开始预览 camera?.startPreview() } } /** 关闭设备 */ fun closeDevice() { camera?.stopPreview() camera?.close() camera = null } override fun onDestroy() { super.onDestroy() closeDevice() } } ``` **Java 调用说明:** `CameraControlHelper` 的静态方法均标注了 `@JvmStatic`,可直接通过 `CameraControlHelper.loadUvcDevices(context)` 调用。注意 `openCamera` 是挂起函数(suspend),Java 中需通过协程桥接调用。 --- ## 四、设备管理 ### 4.1 设备发现 #### 扫描 UVC 设备 ```kotlin val devices: List? = CameraControlHelper.loadUvcDevices(context) ``` 扫描当前已连接的所有 UVC 摄像头设备。返回 `null` 表示 UsbManager 不可用,返回空列表表示未检测到设备。 #### 扫描 V4L2 设备 ```kotlin val paths: Array? = CameraControlHelper.loadV4L2Devices() ``` 获取系统中可用的 V4L2 视频设备路径(如 `/dev/video0`)。 #### 检查设备是否为 UVC 摄像头 ```kotlin val isUvc: Boolean = CameraControlHelper.checkDeviceUvc(usbDevice) ``` 判断给定的 USB 设备是否是 UVC 摄像头类型。 #### 根据厂商 ID / 产品 ID 查找设备 ```kotlin val device: UsbDevice? = CameraControlHelper.getDeviceForId(context, vendorId, productId) ``` 当你已知目标设备的 vendorId 和 productId 时,可直接定位设备。 ### 4.2 打开设备 #### 方式一:通过 UsbDevice 打开(推荐) ```kotlin // suspend 函数,需在协程中调用 val result: Result = CameraControlHelper.openCamera( context = context, usbDevice = usbDevice, timeout = 10000L // USB 权限申请超时时间,默认 10 秒 ) ``` 调用后 SDK 会自动弹出 USB 权限授权对话框。用户点击允许后,返回 `NativeUsbDevice` 实例。 ```kotlin // 处理结果 result.onSuccess { camera -> // 打开成功,camera 即为设备操作对象 }.onFailure { error -> // 打开失败,error.message 包含错误原因 } ``` #### 方式二:通过 V4L2 路径打开 ```kotlin // suspend 函数,需在协程中调用 val result: Result = CameraControlHelper.openCamera( context = context, videoPath = "/dev/video0" ) ``` > **重要:** `openCamera` 是 **suspend 挂起函数**,必须在协程作用域中调用(如 `lifecycleScope.launch { }`)。 ### 4.3 关闭设备 ```kotlin camera.close() ``` 关闭摄像头并释放所有资源(停止预览、释放录制引擎、关闭 USB 连接、解注册广播)。调用后该实例不可复用。 ### 4.4 热插拔监听 ```kotlin camera.setDetachedCloseListener { // USB 摄像头被物理拔出 // SDK 会自动关闭设备并触发此回调 // 建议在此更新 UI 状态 runOnUiThread { Toast.makeText(this, "摄像头已断开", Toast.LENGTH_SHORT).show() } } ``` 当 USB 摄像头被拔出时,SDK 会自动执行关闭操作,随后触发此回调通知业务层。传入 `null` 可移除监听。 --- ## 五、视频预览 ### 5.1 设置预览画面 SDK 支持三种方式设置预览目标,选择其一即可: ```kotlin // 方式一:TextureView(推荐,支持动画变换) camera.setDisplaySurface(textureView) // 方式二:SurfaceView(性能更好) camera.setDisplaySurface(surfaceView) // 方式三:直接传入 Surface 对象 camera.setDisplaySurface(surface) ``` > **提示:** 预览过程中可以随时切换 Surface,SDK 会自动停止再恢复预览。 ### 5.2 设置分辨率 ```kotlin // 先查询设备支持的分辨率 val supportSizes: Array? = camera.getSupportParameters(UvcSupportParameter.PREVIEW_SIZE) // 打印支持的分辨率 supportSizes?.forEach { item -> Log.d("Camera", "格式: ${item.format}, 分辨率列表:") item.sizes.forEach { size -> Log.d("Camera", " ${size.width} x ${size.height}") } } // 设置分辨率和解码格式 val success: Boolean = camera.setPreviewSize(1280, 720, UvcPreviewFormat.MJPEG) ``` **参数说明:** | 参数 | 类型 | 说明 | |------|------|------| | `width` | `Int` | 预览宽度(像素) | | `height` | `Int` | 预览高度(像素) | | `format` | `UvcPreviewFormat` | 解码格式,常用 `MJPEG` 或 `YUY2` | > **注意:** 必须在 `startPreview()` 之前调用。设置的分辨率必须是设备支持的值。 ### 5.3 开始/停止预览 ```kotlin // 开始预览 val started: Boolean = camera.startPreview() // 停止预览 val stopped: Boolean = camera.stopPreview() ``` ### 5.4 预览帧数据回调 如果需要获取实时帧数据(例如用于算法分析、人脸识别等),可设置帧数据监听: ```kotlin // 设置帧回调,指定回调数据格式 camera.setPreviewListener(object : IFrameListener { override fun onFrame(frame: ByteBuffer, width: Int, height: Int) { // frame: 帧数据(DirectByteBuffer,指向 native 内存) // 在此回调中处理或拷贝数据 // 注意:不要持有 frame 引用,回调返回后 buffer 可能被复用 } }, UvcDataFormat.NV21) // 可选:NV21、RGBA、RGB、BGR、YUY2、MJPEG // 移除帧回调 camera.setPreviewListener(null) ``` > **注意:** `frame` 是指向 native 内存的 DirectByteBuffer,回调返回后可能被底层复用。如需保留数据,请在回调内完成拷贝。 ### 5.5 硬件按钮监听 部分 USB 摄像头带有物理按钮,可通过以下方式监听: ```kotlin camera.setButtonListener(object : IButtonListener { override fun buttonClick(type: Int, state: Int) { Log.d("Camera", "按钮事件: type=$type, state=$state") } }) ``` --- ## 六、拍照 ```kotlin // 预览中调用,抓取当前帧 val photoData: ByteArray? = camera.takePicture() if (photoData != null) { // 将帧数据保存为图片文件 val file = File(getExternalFilesDir(null), "photo_${System.currentTimeMillis()}.jpg") file.writeBytes(photoData) Log.d("Camera", "拍照成功: ${file.absolutePath}") } ``` > 必须在预览状态下调用,返回当前帧的原始数据。 --- ## 七、视频录制 ### 7.1 录制生命周期 录制功能需要严格按照以下顺序调用: 1. 调用 `prepareRecord(fps)` 初始化录制管线 **(必须在 startPreview 之前)** 2. 调用 `startPreview()` 开始预览 3. 调用 `startRecord()` 开始录制 4. 调用 `stopRecord()` 停止录制并获取文件路径 5. 可多次重复 3-4 步进行多段录制 6. 调用 `releaseRecord()` 释放录制资源 ### 7.2 完整示例 ```kotlin // ====== 1. 初始化录制(在 startPreview 之前)====== val prepared = camera.prepareRecord(fps = 30) if (!prepared) { Log.e("Camera", "录制初始化失败") return } // ====== 2. 开始预览 ====== camera.startPreview() // ====== 3. 开始录制 ====== val recording = camera.startRecord( context = this, outputPath = null, // null 则自动生成路径 useAudio = true, // 是否录制音频(需 RECORD_AUDIO 权限) fps = 30 // 录制帧率 ) // ====== 4. 停止录制 ====== lifecycleScope.launch { val filePath: String? = camera.stopRecord() // suspend 函数 if (filePath != null) { Log.d("Camera", "录制完成: $filePath") } } // ====== 5. 释放录制资源 ====== camera.releaseRecord() ``` ### 7.3 API 详解 #### `prepareRecord(fps: Int): Boolean` 初始化录制管线,将录制用的 Surface 绑定到 native 渲染管线。此方法决定了 GPU 渲染路径(FBO 通路),一旦调用后在整个预览周期内固定不变。 | 参数 | 类型 | 说明 | |------|------|------| | `fps` | `Int` | 目标录制帧率 | > **必须在 `startPreview()` 之前调用**,预览中或录制中调用会直接返回 `false`。 #### `startRecord(context, outputPath?, useAudio, fps): Boolean` | 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `context` | `Context` | - | Android Context | | `outputPath` | `String?` | `null` | 输出文件路径。为 null 时自动保存到 `{filesDir}/Video/Video_{时间戳}.mp4` | | `useAudio` | `Boolean` | `false` | 是否录制音频 | | `fps` | `Int` | `30` | 录制帧率 | 必须在预览状态下调用。返回 `true` 表示录制已启动。 #### `suspend stopRecord(): String?` 停止录制并等待文件写入完成。返回录制文件的绝对路径,如果当前未在录制则返回 `null`。 > **suspend 函数**,需在协程中调用。 #### `releaseRecord()` 释放所有录制资源(包括绑定到 native 管线的 Surface)。调用后如需重新录制,需重新走 `prepareRecord` -> `startPreview` 流程。 --- ## 八、参数控制 SDK 提供统一的参数查询与设置接口,支持 17+ 项 UVC 标准参数。 ### 8.1 查询支持的参数范围 ```kotlin // 查询亮度的可调范围 val range: IntRange? = camera.getSupportParameters(UvcSupportParameter.BRIGHTNESS) if (range != null) { Log.d("Camera", "亮度范围: ${range.first} ~ ${range.last}") } // 查询是否支持自动曝光 val hasAutoExposure: Boolean? = camera.getSupportParameters(UvcSupportParameter.AUTO_EXPOSURE) // 查询支持的分辨率 val sizes: Array? = camera.getSupportParameters(UvcSupportParameter.PREVIEW_SIZE) ``` ### 8.2 获取/设置参数 ```kotlin // 获取当前亮度 val brightness: Int? = camera.getParameter(UvcCameraParameter.BRIGHTNESS) // 设置亮度为 60 camera.setParameter(UvcCameraParameter.BRIGHTNESS, 60) // 开启自动曝光 camera.setParameter(UvcCameraParameter.AUTO_EXPOSURE, true) // 设置预览旋转 90 度 camera.setParameter(UvcCameraParameter.ORIENTATION, 90) // 开启镜像 camera.setParameter(UvcCameraParameter.MIRROR, true) // 获取当前分辨率 val currentSize: UvcCameraSize? = camera.getParameter(UvcCameraParameter.PREVIEW_SIZE) ``` ### 8.3 参数列表 | 参数 Key | 值类型 | 查询范围类型 | 说明 | |----------|--------|-------------|------| | `BRIGHTNESS` | `Int` | `IntRange` | 亮度 | | `CONTRAST` | `Int` | `IntRange` | 对比度 | | `SATURATION` | `Int` | `IntRange` | 饱和度 | | `HUE` | `Int` | `IntRange` | 色调 | | `GAIN` | `Int` | `IntRange` | 增益 | | `EXPOSURE` | `Int` | `IntRange` | 曝光值 | | `ZOOM` | `Int` | `IntRange` | 缩放级别 | | `FOCUS` | `Int` | `IntRange` | 焦距 | | `IRIS` | `Int` | `IntRange` | 光圈 | | `WHITE_BALANCE` | `Int` | `IntRange` | 白平衡色温 | | `AUTO_EXPOSURE` | `Boolean` | `Boolean` | 自动曝光开关 | | `AUTO_FOCUS` | `Boolean` | `Boolean` | 自动对焦开关 | | `AUTO_HUE` | `Boolean` | `Boolean` | 自动色调开关 | | `AUTO_WHITE_BALANCE` | `Boolean` | `Boolean` | 自动白平衡开关 | | `PRIVACY` | `Boolean` | `Boolean` | 隐私模式(遮盖镜头) | | `ORIENTATION` | `Int` | - | 预览旋转角度(度数) | | `MIRROR` | `Boolean` | - | 水平镜像 | | `PREVIEW_SIZE` | `UvcCameraSize` | `Array` | 预览分辨率 | > **提示:** 不是所有 USB 摄像头都支持全部参数。调用 `getSupportParameters()` 返回 `null` 表示该参数不被当前设备支持。设置不支持的参数会返回 `false`。 --- ## 九、枚举类型说明 ### UvcPreviewFormat - 预览解码格式 用于 `setPreviewSize()` 的 `format` 参数,指定摄像头的采集格式。 | 枚举值 | 编码 | 说明 | |--------|------|------| | `UvcPreviewFormat.MJPEG` | 6 | Motion JPEG 压缩格式(推荐,高分辨率首选) | | `UvcPreviewFormat.YUY2` | 1 | YUY2 未压缩格式(低延迟) | | `UvcPreviewFormat.NV21` | 2 | NV21 格式 | | `UvcPreviewFormat.NV12` | 3 | NV12 格式 | | `UvcPreviewFormat.RGB` | 5 | RGB 格式 | | `UvcPreviewFormat.BGR` | 0 | BGR 格式 | | `UvcPreviewFormat.JPEG` | 7 | JPEG 格式 | | `UvcPreviewFormat.YUV420SP` | 8 | YUV 4:2:0 Semi-Planar | ### UvcDataFormat - 帧回调数据格式 用于 `setPreviewListener()`,指定回调中帧数据的输出格式。 | 枚举值 | 编码 | 说明 | |--------|------|------| | `UvcDataFormat.NV21` | 2 | NV21 格式(默认值,Android 标准格式) | | `UvcDataFormat.RGBA` | 4 | RGBA 格式 | | `UvcDataFormat.RGB` | 5 | RGB 格式 | | `UvcDataFormat.BGR` | 0 | BGR 格式(OpenCV 常用) | | `UvcDataFormat.YUY2` | 1 | YUY2 格式 | | `UvcDataFormat.MJPEG` | 6 | MJPEG 原始数据 | --- ## 十、数据模型 ### UvcCameraSize ```kotlin // com.rain.uvc.mode.UvcCameraSize data class UvcCameraSize( val width: Int, // 宽度(像素) val height: Int // 高度(像素) ) ``` 表示摄像头分辨率或预览尺寸。 ### UvcCameraSupportSize ```kotlin // com.rain.uvc.mode.UvcCameraSupportSize data class UvcCameraSupportSize( val format: UvcPreviewFormat, // 格式类型 val sizes: List // 该格式下支持的分辨率列表 ) ``` 表示某种格式下支持的所有分辨率。通过 `getSupportParameters(UvcSupportParameter.PREVIEW_SIZE)` 获取。 ### IFrameListener ```java // com.rain.uvc.listener.IFrameListener public interface IFrameListener { /** * @param frame 帧数据(DirectByteBuffer) * @param width 帧宽度 * @param height 帧高度 */ void onFrame(ByteBuffer frame, int width, int height); } ``` 预览帧数据回调接口。 ### IButtonListener ```java // com.rain.uvc.listener.IButtonListener public interface IButtonListener { /** * @param type 按钮类型标识 * @param state 按钮状态(按下/释放) */ void buttonClick(int type, int state); } ``` USB 摄像头硬件按钮事件回调接口。 --- ## 十一、错误处理 `openCamera()` 返回 `Result` 类型,可能的失败原因: | 异常类型 | 场景 | 建议处理 | |----------|------|---------| | `IllegalAccessException` | CAMERA 权限未授予 | 引导用户到设置页授权 | | `IllegalAccessException` | USB 临时权限申请失败(用户拒绝或超时) | 提示用户重试 | | `IllegalStateException` | 获取 USB 驱动信息失败 | 检查设备是否正常连接 | | `IllegalStateException` | 打开 USB 设备驱动失败 | 设备可能被其他应用占用 | | `IllegalStateException` | USB 设备 native 层打开失败 | 设备不兼容或驱动异常 | ```kotlin val result = CameraControlHelper.openCamera(context, usbDevice) result.onFailure { error -> when (error) { is IllegalAccessException -> { // 权限问题 Log.e("Camera", "权限错误: ${error.message}") } is IllegalStateException -> { // 设备问题 Log.e("Camera", "设备错误: ${error.message}") } } } ``` --- ## 十二、注意事项与 FAQ ### 1. 线程与协程 - `openCamera()` 和 `stopRecord()` 是 **suspend 函数**,需要在协程中调用 - 其余方法(如 `startPreview()`、`setParameter()`)可在任意线程调用 - `IFrameListener.onFrame()` 回调在 **native 线程**中执行,如需更新 UI 请切换到主线程 ### 2. 录制的调用时序 - `prepareRecord()` **必须**在 `startPreview()` 之前调用,否则 FBO 渲染路径无法正确建立 - 在预览过程中可以多次 `startRecord()` / `stopRecord()` 进行多段录制 - 如果不需要录制功能,无需调用 `prepareRecord()`,预览将使用更轻量的直绘路径 ### 3. Surface 生命周期 - 使用 `TextureView` 时,请确保 `surfaceTexture` 已就绪后再调用 `setDisplaySurface()` - 建议监听 `TextureView.SurfaceTextureListener`,在 `onSurfaceTextureAvailable` 中设置 - SDK 内部会管理 Surface 的释放,开发者无需手动释放 ### 4. 帧数据内存安全 - `IFrameListener.onFrame()` 的 `ByteBuffer` 是 DirectBuffer,指向 native 内存 - 回调返回后 buffer **可能被底层复用**,如需保留数据请在回调内完成拷贝 - 避免在回调中执行耗时操作,以免阻塞帧数据管线 ### 5. 设备生命周期管理 - 建议在 `Activity.onDestroy()` 或 `Fragment.onDestroyView()` 中调用 `close()` 释放资源 - `close()` 调用后 `NativeUsbDevice` 实例不可复用,需重新 `openCamera()` - 设备被物理拔出后 SDK 会自动关闭,通过 `setDetachedCloseListener` 获取通知 ### 6. 常见问题 | 问题 | 解答 | |------|------| | 设置分辨率失败 | 请先通过 `getSupportParameters(UvcSupportParameter.PREVIEW_SIZE)` 查询支持的分辨率,确保设置的值在支持列表中 | | 设置参数返回 false | 该参数可能不被当前摄像头支持,先用 `getSupportParameters()` 确认是否支持 | | 预览黑屏 | 检查是否正确设置了 Surface,以及分辨率是否匹配设备支持的值 | | 录制文件为空 | 确保 `prepareRecord()` 在 `startPreview()` 之前调用 | | 多个摄像头同时使用 | 每个摄像头对应一个独立的 `NativeUsbDevice` 实例,可同时操作 |