实现了双屏摄像头显示,添加了部分代码

This commit is contained in:
zxj
2025-07-31 11:40:56 +08:00
parent 56e0a85a4c
commit de4c5f1c75
86 changed files with 3446 additions and 809 deletions
@@ -1,156 +0,0 @@
package com.sw.dualscreen.activity
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
import android.os.Bundle
import android.util.Log
import android.view.Surface
import android.view.TextureView
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.sw.dualscreen.R
class Camera2Activity : AppCompatActivity() {
private lateinit var cameraManager: CameraManager
private var cameraIdList: Array<String> = arrayOf()
private var currentCameraIdIndex = 0
private lateinit var textureView: TextureView
private var cameraDevice: CameraDevice? = null
private var cameraCaptureSession: CameraCaptureSession? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera2)
textureView = findViewById(R.id.textureView)
cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
// 获取摄像头列表
cameraIdList = cameraManager.cameraIdList
if (cameraIdList.isEmpty()) {
Toast.makeText(this, "没有可用的摄像头", Toast.LENGTH_SHORT).show()
finish()
}
// 设置TextureView监听
textureView.surfaceTextureListener = surfaceTextureListener
// 切换摄像头按钮
findViewById<Button>(R.id.switchButton).setOnClickListener {
switchCamera()
}
}
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
openCamera(cameraIdList[currentCameraIdIndex])
}
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture,
width: Int,
height: Int
) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean = true
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}
private fun openCamera(cameraId: String) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED
) {
return
}
try {
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
createCameraPreviewSession()
}
override fun onDisconnected(device: CameraDevice) {
device.close()
cameraDevice = null
}
override fun onError(device: CameraDevice, error: Int) {
device.close()
cameraDevice = null
}
}, null)
} catch (e: Exception) {
Log.e("Camera2", "打开摄像头失败", e)
}
}
private fun createCameraPreviewSession() {
val surfaceTexture = textureView.surfaceTexture
surfaceTexture?.setDefaultBufferSize(textureView.width, textureView.height)
val surface = Surface(surfaceTexture)
try {
val previewRequestBuilder = cameraDevice?.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW
)?.apply {
addTarget(surface)
}
cameraDevice?.createCaptureSession(
listOf(surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
cameraCaptureSession = session
previewRequestBuilder?.let { builder ->
builder.set(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
)
session.setRepeatingRequest(builder.build(), null, null)
}
}
override fun onConfigureFailed(session: CameraCaptureSession) {
Toast.makeText(this@Camera2Activity, "配置失败", Toast.LENGTH_SHORT).show()
}
},
null
)
} catch (e: Exception) {
Log.e("Camera2", "创建预览会话失败", e)
}
}
private fun switchCamera() {
if (cameraIdList.size <= 1) {
Toast.makeText(this, "只有一个摄像头", Toast.LENGTH_SHORT).show()
return
}
// 关闭当前摄像头
cameraCaptureSession?.close()
cameraCaptureSession = null
cameraDevice?.close()
cameraDevice = null
// 切换到下一个摄像头
currentCameraIdIndex = (currentCameraIdIndex + 1) % cameraIdList.size
openCamera(cameraIdList[currentCameraIdIndex])
}
override fun onDestroy() {
super.onDestroy()
cameraCaptureSession?.close()
cameraDevice?.close()
}
}
@@ -0,0 +1,50 @@
package com.sw.dualscreen.activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.databinding.ActivityInitBinding
import com.sw.dualscreen.viewmodel.DeviceViewModel
import com.sw.plate.utils.AppUtil
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
class InitActivity : AppCompatActivity() {
private lateinit var binding: ActivityInitBinding
private val viewModel by viewModels<DeviceViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityInitBinding.inflate(layoutInflater)
setContentView(binding.root)
val deviceId = AppUtil.getUDID(this)
val isSuccess = viewModel.checkEquipmentInfo()
if (isSuccess) {
goMainActivity()
}
binding.tvDeviceId.text = deviceId
binding.btConfirm.setOnClickListener {
viewModel.getDeviceToken()
}
registerDataChange()
}
private fun registerDataChange() {
lifecycleScope.launch {
viewModel.deviceInfoResult.drop(1).collect {
if (it != true) return@collect
goMainActivity()
}
}
}
private fun goMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
}
@@ -6,10 +6,14 @@ import android.hardware.display.DisplayManager
import android.os.Bundle
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.AdapterView
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
@@ -17,14 +21,23 @@ import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.databinding.ActivityMainBinding
import com.sw.dualscreen.ext.dp
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
import com.sw.dualscreen.viewmodel.MyViewModel
import com.sw.dualscreen.view.CustomBottomSheetDialog
import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.AppUtil
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import timber.log.Timber
import java.io.File
import java.util.concurrent.Executors
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private val viewModel = MyViewModel()
private val viewModel by viewModels<UserViewModel>()
private lateinit var imageCapture: ImageCapture
private var isAnalyzing = true // 控制是否进行图像分析
private lateinit var presentation: SecondaryScreenPresentation
private val executor = Executors.newSingleThreadExecutor()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -32,51 +45,136 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.main)
Timber.d("UDID = ${AppUtil.getUDID(this)}")
initView()
startCamera()
setupSecondaryDisplay()
setupCamera()
initData()
}
private fun initData() {
viewModel.getEquipmentToken()
}
private fun initView() {
viewModel.activeEngine()
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
// outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
outline.setRoundRect(0, 0, view.width, view.height, 280f.dp)
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
}
}
binding.previewView.clipToOutline = true
binding.tvToSearch.setOnClickListener {
val bottomSheetDialog = CustomBottomSheetDialog.newInstance()
bottomSheetDialog.show(supportFragmentManager, "CustomBottomSheetDialog")
}
binding.spinner.setItems(arrayOf("即放即取", "持续取餐"))
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
}
private fun startCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
private fun setupCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 创建预览用例
// 1. 创建预览用例
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(binding.previewView.surfaceProvider)
}
// 选择后置摄像头
// 2. 创建图像分析用例(用于副屏显示)
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
.build()
.also { analysis ->
analysis.setAnalyzer(executor) { imageProxy ->
if (isAnalyzing) {
val bitmap = imageProxy.toBitmap()
runOnUiThread {
presentation.updateImage(bitmap) // 更新副屏
}
}
imageProxy.close() // 必须关闭以释放资源
}
}
// 3. 创建拍照用例
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
// .setTargetResolution(Size(1920, 1080)) // 设置拍照分辨率
.build()
// 4. 选择摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// 解绑所有用例
cameraProvider.unbindAll()
// 绑定到生命周期
// 绑定三个用例到生命周期
cameraProvider.bindToLifecycle(
this, cameraSelector, preview
this, cameraSelector, preview, imageAnalysis, imageCapture
)
} catch (exc: Exception) {
Timber.e(exc, "Use case binding failed")
Timber.e(exc.message)
}
}, ContextCompat.getMainExecutor(this))
}
// 拍照功能
private fun takePhoto() {
// 临时停止分析以避免干扰
isAnalyzing = false
// 创建输出选项
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(
File(
cacheDir.path, "IMG_${System.currentTimeMillis()}.jpg"
)
).build()
// 执行拍照
imageCapture.takePicture(
outputFileOptions,
executor,
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
runOnUiThread {
ToastUtils.showToast("照片保存成功: ${outputFileResults.savedUri}")
}
// 恢复图像分析
isAnalyzing = true
}
override fun onError(exception: ImageCaptureException) {
Timber.e("拍照失败: ${exception.message}")
runOnUiThread {
ToastUtils.showToast("拍照失败: ${exception.message}")
}
// 恢复图像分析
isAnalyzing = true
}
}
)
}
private fun setupSecondaryDisplay() {
val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val displays = displayManager.displays
@@ -85,7 +183,7 @@ class MainActivity : AppCompatActivity() {
if (displays.size > 1) {
val secondaryDisplay = displays[1]
// 创建副屏的Presentation
val presentation =
presentation =
SecondaryScreenPresentation(
activity = this,
this,