更新记录
1.4.1(2026-07-30)
修改说明
1.4.0(2026-07-29)
- 对完全相同的设备列表进行签名去重,避免页面启动时重复触发
deviceschange。 - 修复旋转角度后拍照比例不正确的问题
- 新增
statechange事件,在事件中返回 ready、opened、recording、format、rotation、mirror。
平台兼容性
uni-app
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | - | - | - | - | - | 5.0 | × | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | 5.0 | × | × | - |
uni-app UVC USB 摄像头插件
基于 nvue 原生组件的 UVC USB 摄像头插件,可在 uni-app App 端完成 USB 摄像头发现、授权、画面预览、拍照、录像、分辨率切换、图像旋转和镜像等操作。
功能特性
- 自动发现 UVC USB 摄像头
- USB 设备访问权限申请
- 相机、麦克风运行时权限处理
- MJPEG、YUYV 预览格式切换
- 自定义预览宽度、高度和帧率
- 获取设备支持的分辨率列表
- 运行时切换预览分辨率
- 拍照并返回本地文件路径
- 视频录制,可选择是否录制麦克风声音
- 支持 0°、90°、180°、270° 旋转
- 支持水平镜像
- 获取组件当前运行状态
- 提供日志、状态和错误事件
- 页面隐藏时关闭摄像头,页面销毁时释放资源
运行环境
- uni-app App 项目
nvue页面- Android 真机
- 支持 UVC 协议的 USB 摄像头
- 手机或平板支持 USB Host/OTG
- 已正确安装并配置本插件
建议使用真机调试。连接摄像头前,请确认设备已开启 OTG,并使用支持数据传输的转接线。
安装与配置
- 将插件导入 uni-app 项目。
- 按插件包要求完成原生插件或 UTS 插件配置。
- 在
nvue页面中直接使用<uvc-camera>组件。 - 重新制作自定义调试基座或云打包后,在 Android 真机运行。
插件涉及 USB、相机和可选的麦克风能力。具体 Android 权限与 manifest.json 配置应以插件包内的原生配置要求为准。
快速开始
1. 添加摄像头组件
<template>
<view class="page">
<uvc-camera
ref="uvc"
class="preview"
:auto-start="true"
:auto-request-permission="true"
:preview-width="1920"
:preview-height="1080"
:preview-fps="30"
format="mjpeg"
:bandwidth-quirk="true"
:rotation="90"
:mirror="false"
@deviceschange="onDevicesChange"
@sizeschange="onSizesChange"
@opened="onOpened"
@closed="onClosed"
@photosaved="onPhotoSaved"
@recordingstart="onRecordingStart"
@recordingsaved="onRecordingSaved"
@statechange="onStateChange"
@error="onError"
/>
</view>
</template>
<script>
export default {
data() {
return {
devices: [],
sizes: [],
recording: false,
photoPath: ''
}
},
methods: {
camera() {
return this.$refs.uvc
},
// 兼容普通对象、event.detail 和 UTS Map 风格事件数据。
value(event, key, fallback) {
if (!event) return fallback
let data = event
if (event.detail != null) {
data = event.detail
} else if (typeof event.get === 'function') {
const detail = event.get('detail')
if (detail != null) data = detail
}
if (data && typeof data.get === 'function') {
const result = data.get(key)
return result == null ? fallback : result
}
if (data && data[key] != null) return data[key]
return fallback
},
refreshDevices() {
this.camera().refreshDevices()
},
openFirstDevice() {
if (this.devices.length === 0) {
uni.showToast({ title: '未发现 USB 摄像头', icon: 'none' })
return
}
this.camera().open(Number(this.devices[0].deviceId))
},
takePhoto() {
this.camera().takePhotoAuto()
},
startRecording() {
// false:不录制麦克风;true:录制麦克风并按需申请录音权限。
this.camera().startRecordingAuto(false)
},
stopRecording() {
this.camera().stopRecording()
},
closeCamera() {
this.camera().close()
},
onDevicesChange(event) {
this.devices = this.value(event, 'devices', [])
},
onSizesChange(event) {
this.sizes = this.value(event, 'sizes', [])
},
onOpened(event) {
const device = this.value(event, 'device', {})
const size = this.value(event, 'size', {})
console.log('摄像头已打开', device, size)
},
onClosed() {
this.recording = false
},
onPhotoSaved(event) {
const path = String(this.value(event, 'path', '') || '')
this.photoPath = path
if (path) {
uni.previewImage({
urls: [`file://${path}`]
})
}
},
onRecordingStart(event) {
this.recording = true
console.log('开始录像', this.value(event, 'path', ''))
},
onRecordingSaved(event) {
this.recording = false
console.log('录像已保存', this.value(event, 'path', ''))
},
onStateChange(event) {
console.log('摄像头状态', event.detail || event)
},
onError(event) {
const code = this.value(event, 'code', 'UNKNOWN')
const message = this.value(event, 'message', '未知错误')
console.error(code, message)
uni.showToast({ title: message, icon: 'none' })
}
},
onHide() {
if (this.$refs.uvc) {
this.$refs.uvc.close()
}
},
onUnload() {
if (this.$refs.uvc) {
this.$refs.uvc.release()
}
}
}
</script>
<style>
.page {
flex: 1;
background-color: #111111;
}
.preview {
width: 750rpx;
height: 562rpx;
background-color: #000000;
}
</style>
2. 刷新并打开设备
this.$refs.uvc.refreshDevices()
// deviceschange 返回设备列表后,传入 deviceId。
this.$refs.uvc.open(Number(this.devices[0].deviceId))
当 auto-start 为 true 时,组件会尝试自动启动相关流程;仍建议监听 deviceschange、usbpermission、opened 和 error 事件,以便向用户展示准确状态。
组件属性
| 属性 | 类型 | 示例值 | 说明 |
|---|---|---|---|
auto-start |
Boolean | true |
组件初始化后是否自动开始设备发现或打开流程。 |
auto-request-permission |
Boolean | true |
是否自动申请所需权限。 |
preview-width |
Number | 2592 |
期望的预览宽度。最终值以摄像头实际协商结果为准。 |
preview-height |
Number | 1944 |
期望的预览高度。最终值以摄像头实际协商结果为准。 |
preview-fps |
Number | 30 |
期望预览帧率。最终值取决于设备、格式和带宽。 |
format |
String | mjpeg |
视频格式,可选 mjpeg 或 yuyv。 |
bandwidth-quirk |
Boolean | true |
是否启用 USB 带宽兼容处理,部分设备需要开启。 |
rotation |
Number | 90 |
画面旋转角度,可选 0、90、180、270。 |
mirror |
Boolean | false |
是否水平镜像预览画面。 |
预览参数是期望值,不保证摄像头一定支持。打开成功后,应以
opened事件中的size为实际协商结果。
组件方法
通过组件引用调用方法:
const camera = this.$refs.uvc
refreshDevices()
重新扫描已连接的 UVC USB 摄像头。
camera.refreshDevices()
扫描结果通过 deviceschange 事件返回。
open(deviceId)
打开指定 USB 摄像头。
camera.open(Number(deviceId))
| 参数 | 类型 | 说明 |
|---|---|---|
deviceId |
Number | deviceschange 事件返回的设备 ID。 |
成功后触发 opened,失败时触发 error。
close()
关闭当前摄像头和预览。
camera.close()
关闭完成后触发 closed。
release()
释放组件持有的原生资源。建议在页面 onUnload 中调用。
camera.release()
组件释放后,如需继续使用,应重新进入页面或重新创建组件实例。
takePhotoAuto()
拍摄照片并自动保存。
camera.takePhotoAuto()
拍照结果统一通过 photosaved 事件返回,不要将 JavaScript 回调函数作为参数跨 UTS 原生组件边界传递。
startRecordingAuto(withAudio)
开始录像并自动创建保存路径。
camera.startRecordingAuto(false)
| 参数 | 类型 | 说明 |
|---|---|---|
withAudio |
Boolean | true 表示录制麦克风声音,false 表示仅录制视频。 |
使用声音时可能触发录音权限申请,并通过 audiopermission 返回结果。
stopRecording()
停止当前录像。
camera.stopRecording()
保存完成后触发 recordingsaved。
setTransform(rotation, mirror)
设置预览画面的旋转和镜像。
camera.setTransform(90, true)
| 参数 | 类型 | 说明 |
|---|---|---|
rotation |
Number | 旋转角度。 |
mirror |
Boolean | 是否水平镜像。 |
changeFormat(format)
切换摄像头数据格式。
camera.changeFormat('mjpeg')
支持:
mjpegyuyv
格式切换后,设备支持的分辨率列表可能发生变化。应等待新的 sizeschange 事件,再调用 switchSize()。
switchSize(width, height)
切换预览分辨率。
camera.switchSize(1920, 1080)
建议只传入 sizeschange 事件返回的受支持尺寸。
getState()
读取组件当前状态。
camera.getState()
状态通过 statechange 事件异步返回,通常包含:
{
opened: true,
recording: false,
format: 'mjpeg',
rotation: 90,
mirror: false
}
openPermissionSettings()
打开应用权限设置页面,便于用户手动开启被拒绝的权限。
camera.openPermissionSettings()
组件事件
所有事件都建议使用兼容方法读取数据。不同运行环境中,事件数据可能表现为普通对象、event.detail 或带 get() 方法的 Map 对象。
log
原生日志事件。
{
message: '日志内容'
}
deviceschange
USB 摄像头设备列表发生变化或扫描完成。
{
devices: [
{
deviceId: 1,
productName: 'USB Camera',
deviceName: '/dev/bus/usb/...'
}
]
}
常用字段:
| 字段 | 说明 |
|---|---|
deviceId |
打开设备时使用的 ID。 |
productName |
USB 产品名称,部分设备可能为空。 |
deviceName |
系统设备名称或路径。 |
usbpermission
USB 设备访问授权结果。
{
granted: true
}
sizeschange
当前设备和视频格式支持的分辨率列表发生变化。
{
sizes: [
{
width: 1920,
height: 1080,
fps: 30
}
]
}
opened
摄像头打开并完成参数协商。
{
device: {
deviceId: 1,
productName: 'USB Camera'
},
size: {
width: 1920,
height: 1080,
fps: 30
}
}
closed
摄像头已关闭。该事件通常不需要读取参数。
photosaved
照片已保存。
{
path: '/storage/emulated/0/.../photo.jpg'
}
预览本地照片:
uni.previewImage({
urls: [`file://${path}`]
})
recordingstart
录像已开始。
{
path: '/storage/emulated/0/.../video.mp4'
}
recordingsaved
录像停止并保存完成。
{
path: '/storage/emulated/0/.../video.mp4'
}
camerapermission
相机运行时权限结果。
{
granted: true
}
audiopermission
麦克风运行时权限结果,仅在需要录制声音时使用。
{
granted: true
}
statechange
getState() 的异步返回事件。
{
opened: true,
recording: false,
format: 'mjpeg',
rotation: 90,
mirror: false
}
error
组件执行失败时触发。
{
code: 'ERROR_CODE',
message: '错误说明'
}
建议始终监听该事件并向用户展示 message。
完整调用流程
推荐按照以下流程使用插件:
- 页面加载并创建
<uvc-camera>。 - 调用
refreshDevices(),或启用auto-start。 - 在
deviceschange中取得设备列表。 - 调用
open(deviceId)。 - 处理 USB、相机权限事件。
- 在
opened中读取实际协商的分辨率和帧率。 - 使用拍照、录像、格式切换、旋转或镜像功能。
- 页面隐藏时调用
close()。 - 页面销毁时调用
release()。
格式与分辨率
MJPEG
优点:
- 相同 USB 带宽下通常可支持更高分辨率或更高帧率
- 多数常见 UVC 摄像头支持
注意事项:
- 需要设备提供 MJPEG 输出
- 实际性能取决于设备解码能力
YUYV
优点:
- 未压缩原始图像格式
- 画面处理链路较直接
注意事项:
- USB 带宽占用较高
- 高分辨率下可能只能使用较低帧率
切换格式后,必须重新关注 sizeschange 返回的分辨率列表,因为 MJPEG 和 YUYV 支持的尺寸与帧率通常不同。
权限说明
插件可能涉及三类权限:
| 权限 | 使用场景 |
|---|---|
| USB 设备授权 | 打开外接 UVC 摄像头。 |
| 相机权限 | 原生相机相关能力或系统要求。 |
| 麦克风权限 | startRecordingAuto(true) 录制声音。 |
权限被拒绝后,可调用:
this.$refs.uvc.openPermissionSettings()
USB 设备授权通常与当前连接的 USB 设备相关。重新插拔设备或更换设备后,系统可能再次弹出授权提示。
生命周期建议
为了避免摄像头被占用、页面退出后仍在工作或原生资源泄漏,建议至少处理以下生命周期:
onHide() {
if (this.$refs.uvc) {
this.$refs.uvc.close()
}
},
onUnload() {
if (this.$refs.uvc) {
this.$refs.uvc.release()
}
}
录像过程中退出页面前,建议先调用 stopRecording(),等待 recordingsaved 后再关闭摄像头。
常见问题
1. 扫描不到 USB 摄像头
请检查:
- 手机或平板是否支持 USB Host/OTG
- OTG 功能是否已开启
- 摄像头是否符合 UVC 标准
- 转接线是否支持数据传输
- 摄像头供电是否充足
- 是否已调用
refreshDevices() - 是否在 Android 真机和自定义基座中运行
2. 已发现设备,但无法打开
请检查 usbpermission 和 error 事件。常见原因包括 USB 授权被拒绝、设备被其他应用占用、摄像头输出格式不兼容或 USB 带宽不足。
3. 请求的分辨率没有生效
preview-width、preview-height 和 preview-fps 是期望参数。组件会根据设备能力进行协商,实际结果应以 opened 事件中的 size 为准。
需要精确切换时,请先读取 sizeschange,再使用其中的宽高调用 switchSize()。
4. 切换 MJPEG/YUYV 后画面异常
切换格式后,应等待新的 sizeschange 事件,并选择该格式支持的分辨率。高分辨率 YUYV 可能因 USB 带宽不足而无法稳定预览。
5. 拍照方法调用后没有立即返回路径
takePhotoAuto() 是异步操作,文件路径通过 photosaved 事件返回。不要依赖方法返回值,也不要向原生组件方法传入 JavaScript 回调函数。
6. 本地照片无法预览
原生路径通常需要增加 file://:
uni.previewImage({
urls: [`file://${path}`]
})
同时确认文件仍然存在,并且应用具有对应文件访问能力。
7. 录像没有声音
开始录像时需传入 true:
this.$refs.uvc.startRecordingAuto(true)
并确认 audiopermission 返回 granted: true。
8. 页面退出后摄像头仍被占用
在 onHide 中调用 close(),并在 onUnload 中调用 release()。不要只隐藏组件而不释放原生摄像头资源。
调试建议
开发阶段建议监听全部状态事件:
<uvc-camera
@log="onLog"
@deviceschange="onDevicesChange"
@usbpermission="onUsbPermission"
@sizeschange="onSizesChange"
@opened="onOpened"
@closed="onClosed"
@photosaved="onPhotoSaved"
@recordingstart="onRecordingStart"
@recordingsaved="onRecordingSaved"
@camerapermission="onCameraPermission"
@audiopermission="onAudioPermission"
@statechange="onStateChange"
@error="onError"
/>
当设备兼容性存在问题时,请记录以下信息:
- 手机或平板型号及 Android 版本
- 摄像头品牌与型号
- USB 转接方式及供电情况
- 当前格式:MJPEG 或 YUYV
- 请求与实际协商的分辨率、帧率
log和error事件内容
注意事项
- 仅使用摄像头实际支持的格式和分辨率。
- 不同 UVC 摄像头的能力差异较大,建议在目标设备上充分测试。
- 录像未保存完成前不要立即释放组件。
- 使用带声音录像时,应向用户明确说明麦克风用途。
- 应用进入后台或页面离开时,应及时关闭摄像头。
bandwidth-quirk是否需要开启取决于摄像头及设备兼容性。
API 速查
const camera = this.$refs.uvc
camera.refreshDevices()
camera.open(deviceId)
camera.close()
camera.release()
camera.takePhotoAuto()
camera.startRecordingAuto(false)
camera.stopRecording()
camera.setTransform(90, false)
camera.changeFormat('mjpeg')
camera.switchSize(1920, 1080)
camera.getState()
camera.openPermissionSettings()

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 9
赞赏 0
下载 12468951
赞赏 1936
赞赏
京公网安备:11010802035340号