使用AndroidUSBCamera库开发安卓多摄像头预览和录制应用

使用AndroidUSBCamera库开发安卓多摄像头预览和录制应用

使用AndroidUSBCamera库开发多摄像头预览和录制应用

在现代Android应用开发中,支持多摄像头预览和录制的需求越来越常见,特别是在监控、视频会议和工业检测等领域。本文将详细介绍如何使用开源库AndroidUSBCamera开发一个支持3个摄像头同时预览和录制的应用,并分享在开发过程中遇到的关键问题和解决方案。

1. 项目背景与依赖配置

1.1 依赖配置的坑

在使用AndroidUSBCamera库时,我们首先遇到了依赖配置的问题。按照官方文档,应该在[build.gradle](file://C:\Users\xxx\AndroidStudioProjects\UVC3Camera\app\libs\AndroidUSBCamera-3.3.3\app\build.gradle)中添加JitPack仓库和依赖:

// 在settings.gradle.kts中添加JitPack仓库

dependencyResolutionManagement {

repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google()

mavenCentral()

maven { url = uri("https://jitpack.io") }

}

}

// 在app/build.gradle.kts中添加依赖

dependencies {

implementation("com.github.jiangdongguo:AndroidUSBCamera:3.3.3")

}

但在实际操作中,我们发现JitPack方式无法下载依赖。通过深入研究,我们发现需要使用Liferay仓库才能成功下载:

// 在settings.gradle.kts中添加Liferay仓库

dependencyResolutionManagement {

repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google()

mavenCentral()

// 添加Liferay仓库以解决特定依赖问题

maven { url = uri("https://repository.liferay.com/nexus/content/repositories/public") }

}

}

这个坑让我们花费了不少时间,所以特别提醒大家在遇到类似问题时可以尝试这个解决方案。

2. 核心功能实现

2.1 自定义UI与摄像头连接

项目中我们创建了一个自定义的[CameraFragment](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/src/main/java/com/cz/uvc3camera/CameraFragment.kt#L89-L1267)类,继承自MultiCameraFragment以支持多摄像头功能。核心实现包括:

UI布局设计:使用三个[AspectRatioTextureView](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/libs/AndroidUSBCamera-3.3.3/libausbc/src/main/java/com/jiangdg/ausbc/widget/AspectRatioTextureView.kt#L43-L236)组件分别显示三个摄像头的预览画面,并为每个摄像头配备独立的录制和停止按钮。

摄像头连接管理:通过重写[onCameraConnected](file://C:\Users\xxx\AndroidStudioProjects\UVC3Camera\app\src\main\java\com\cz\uvc3camera\CameraFragment.kt#L509-L558)方法处理摄像头连接事件,为每个连接的摄像头分配预览视图:

override fun onCameraConnected(camera: MultiCameraClient.ICamera) {

addLog("摄像头已连接: ${camera.getUsbDevice().deviceName}")

// 为摄像头分配预览视图

val (textureView, cameraIndex) = when {

camera1 == null -> {

camera1 = camera

Pair(textureView1, 1)

}

camera2 == null -> {

camera2 = camera

Pair(textureView2, 2)

}

camera3 == null -> {

camera3 = camera

Pair(textureView3, 3)

}

else -> {

addLog("已达到最大摄像头数量")

return

}

}

// 等待TextureView准备好后再打开摄像头

if (textureView.isAvailable) {

openCamera(camera, textureView)

} else {

textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {

override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {

addLog("TextureView surface available for camera: ${camera.getUsbDevice().deviceName}")

openCamera(camera, textureView)

}

// ... 其他方法

}

}

}

2.2 重要注意事项:TextureView vs AspectRatioTextureView

在开发过程中,我们遇到了一个关键问题:使用普通的TextureView无法正常显示摄像头画面。通过深入研究源码和反复测试,我们发现AndroidUSBCamera库要求使用其自定义的[AspectRatioTextureView](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/libs/AndroidUSBCamera-3.3.3/libausbc/src/main/java/com/jiangdg/ausbc/widget/AspectRatioTextureView.kt#L43-L236)组件。

[AspectRatioTextureView](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/libs/AndroidUSBCamera-3.3.3/libausbc/src/main/java/com/jiangdg/ausbc/widget/AspectRatioTextureView.kt#L43-L236)相比普通TextureView的优势:

自动处理画面比例,确保预览画面不变形

与AndroidUSBCamera库深度集成,提供了必要的接口和回调

内部处理了OpenGL ES渲染相关逻辑

// 正确的使用方式

private lateinit var textureView1: AspectRatioTextureView

private lateinit var textureView2: AspectRatioTextureView

private lateinit var textureView3: AspectRatioTextureView

2.3 录制功能实现

项目实现了每个摄像头的独立录制功能,通过以下关键步骤:

开始录制:

private fun startRecording(cameraIndex: Int) {

// 生成文件名

val currentTime = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())

val cameraName = when (cameraIndex) {

1 -> "camera_1"

2 -> "camera_2"

3 -> "camera_3"

else -> "camera_$cameraIndex"

}

val fileName = "camera_${cameraIndex}_${currentTime}__$cameraName.mp4"

// 选择存储路径

val savePath = getOptimalStoragePath(fileName)

// 实际执行录制

startCameraRecording(cameraIndex, savePath)

}

fun startCameraRecording(cameraIndex: Int, filePath: String) {

val camera = when (cameraIndex) {

1 -> camera1

2 -> camera2

3 -> camera3

else -> null

}

camera?.let { cam ->

try {

cam.captureVideoStart(object : ICaptureCallBack {

override fun onBegin() {

activity?.runOnUiThread {

addLog("摄像头${cameraIndex}开始录制")

}

}

override fun onError(error: String?) {

// 处理错误

}

override fun onComplete(path: String?) {

// 处理录制完成

}

}, filePath, 0)

} catch (e: Exception) {

// 异常处理

}

}

}

3. 前台服务实现后台录制

为了确保应用在后台也能持续录制,我们实现了前台服务[CameraRecordService](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/src/main/java/com/cz/uvc3camera/CameraRecordService.kt#L15-L259):

class CameraRecordService : Service() {

companion object {

private const val TAG = "CameraRecordService"

const val CHANNEL_ID = "CameraRecordServiceChannel"

const val NOTIFICATION_ID = 2

// Intent actions

const val ACTION_START_RECORDING = "com.cz.uvc3camera.START_RECORDING"

const val ACTION_STOP_RECORDING = "com.cz.uvc3camera.STOP_RECORDING"

const val ACTION_STOP_ALL_RECORDING = "com.cz.uvc3camera.STOP_ALL_RECORDING"

}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

when (intent?.action) {

ACTION_START_RECORDING -> {

val cameraIndex = intent.getIntExtra(EXTRA_CAMERA_INDEX, -1)

val filePath = intent.getStringExtra(EXTRA_FILE_PATH)

if (cameraIndex != -1 && filePath != null) {

startRecording(cameraIndex, filePath)

}

}

// ... 其他action处理

}

startForeground(NOTIFICATION_ID, createNotification())

return START_STICKY

}

private fun createNotification(): android.app.Notification {

return NotificationCompat.Builder(this, CHANNEL_ID)

.setContentTitle("摄像头录制服务")

.setContentText("正在后台录制摄像头视频")

.setSmallIcon(R.drawable.ic_recording)

.build()

}

}

前台服务的关键优势:

提高应用进程优先级,防止被系统杀死

显示持续通知,让用户知道应用正在后台运行

符合Android后台执行限制的规范

4. 状态管理与同步

项目中实现了复杂的状态管理机制,确保UI状态与服务状态保持同步:

// 使用原子操作确保录制状态的线程安全

private val isRecording1 = AtomicBoolean(false)

private val isRecording2 = AtomicBoolean(false)

private val isRecording3 = AtomicBoolean(false)

// 通过广播接收器同步状态

private val recordingBroadcastReceiver = object : BroadcastReceiver() {

override fun onReceive(context: Context?, intent: Intent?) {

when (intent?.action) {

CameraRecordService.ACTION_RECORDING_STATUS_RESULT -> {

// 处理录制状态查询结果

val isRecording1 = intent.getBooleanExtra("is_recording_1", false)

val isRecording2 = intent.getBooleanExtra("is_recording_2", false)

val isRecording3 = intent.getBooleanExtra("is_recording_3", false)

// 更新本地状态

this@CameraFragment.isRecording1.set(isRecording1)

this@CameraFragment.isRecording2.set(isRecording2)

this@CameraFragment.isRecording3.set(isRecording3)

// 同步UI状态

syncRecordingButtonStates()

}

}

}

}

5. 总结

通过本项目实践,我们总结了以下关键要点:

依赖配置:在使用AndroidUSBCamera库时,可能需要使用Liferay仓库而非JitPack

UI组件选择:必须使用库提供的[AspectRatioTextureView](file:///C:/Users/xxx/AndroidStudioProjects/UVC3Camera/app/libs/AndroidUSBCamera-3.3.3/libausbc/src/main/java/com/jiangdg/ausbc/widget/AspectRatioTextureView.kt#L43-L236)而非普通TextureView

状态管理:合理使用原子操作和广播机制确保状态同步

后台执行:通过前台服务确保录制任务在后台持续运行

异常处理:完善的异常处理机制确保应用稳定性

这个项目为多摄像头应用开发提供了完整的解决方案,可以作为类似需求的参考实现。通过AndroidUSBCamera库,我们可以快速构建功能完善的UVC摄像头应用,大大减少了开发工作量。

相关推荐

365sport 三星、苹果技术落伍,连高频调光都做不了?背后原因不简单!
365beat提现流程 龙之谷世界平民职业选择推荐 龙之谷手游选什么职业好
必发365一些奖金 活化钢带扣

活化钢带扣

📅 10-11 👁️ 2417