JLBA202505130001_关于吉林银行新建AI智能培训系统的需求_视频通话显示视频第一次上传
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
||||
package com.example.ty_camera
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.ty_camera.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,408 @@
|
||||
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
package uts.sdk.modules.tyCamera
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.ImageFormat
|
||||
import android.hardware.camera2.CameraCaptureSession
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.hardware.camera2.CameraDevice
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.hardware.camera2.CaptureRequest
|
||||
import android.media.Image
|
||||
import android.media.ImageReader
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.view.Surface
|
||||
import io.dcloud.uniapp.*
|
||||
import io.dcloud.uniapp.extapi.*
|
||||
import io.dcloud.uts.*
|
||||
import io.dcloud.uts.Map
|
||||
import io.dcloud.uts.Set
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import io.dcloud.uts.UTSAndroid
|
||||
open class MyApiOptions (
|
||||
@JsonNotNull
|
||||
open var paramA: Boolean = false,
|
||||
open var success: ((res: String) -> Unit)? = null,
|
||||
open var fail: ((res: MyApiFail) -> Unit)? = null,
|
||||
open var complete: ((res: Any) -> Unit)? = null,
|
||||
) : UTSObject()
|
||||
typealias MyApiErrorCode = Number
|
||||
interface MyApiFail : IUniError {
|
||||
override var errCode: MyApiErrorCode
|
||||
}
|
||||
typealias CameraFacingType = String
|
||||
open class CameraHelper {
|
||||
private var context: Context
|
||||
private var cameraManager: CameraManager
|
||||
private var currentCameraId: String? = null
|
||||
private var totalRotation: Number = 0
|
||||
constructor(context: Context){
|
||||
this.context = context
|
||||
this.cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
if (this.cameraManager == null) {
|
||||
throw UTSError("CameraHelper: 获取CameraManager失败,请检查摄像头权限或设备支持性")
|
||||
}
|
||||
}
|
||||
public open fun getCameraIdByFacing(facing: CameraFacingType): String {
|
||||
try {
|
||||
val cameraIds = this.cameraManager.getCameraIdList()
|
||||
if (cameraIds.size === 0) {
|
||||
console.error("CameraHelper: 无可用摄像头ID")
|
||||
throw UTSError()
|
||||
}
|
||||
var targetCameraId: String? = null
|
||||
for(id in resolveUTSValueIterator(cameraIds)){
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(id)
|
||||
val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
if (lensFacing !== null && lensFacing === (if (facing === "BACK") {
|
||||
CameraCharacteristics.LENS_FACING_BACK
|
||||
} else {
|
||||
CameraCharacteristics.LENS_FACING_FRONT
|
||||
}
|
||||
)) {
|
||||
targetCameraId = id
|
||||
break
|
||||
}
|
||||
}
|
||||
if (targetCameraId === null) {
|
||||
throw UTSError()
|
||||
}
|
||||
console.log("CameraHelper: \u6210\u529F\u83B7\u53D6" + (if (facing === "BACK") {
|
||||
"后置"
|
||||
} else {
|
||||
"前置"
|
||||
}
|
||||
) + "\u6444\u50CF\u5934ID\uFF1A" + targetCameraId)
|
||||
return targetCameraId
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("CameraHelper: \u83B7\u53D6" + (if (facing === "BACK") {
|
||||
"后置"
|
||||
} else {
|
||||
"前置"
|
||||
}
|
||||
) + "\u6444\u50CF\u5934ID\u5F02\u5E38\uFF1A", e.message)
|
||||
throw UTSError()
|
||||
}
|
||||
}
|
||||
public open fun openCameraByFacing(facing: CameraFacingType, callback: CameraDevice.StateCallback, handler: Handler? = null): Unit {
|
||||
var cameraId: String
|
||||
try {
|
||||
cameraId = this.getCameraIdByFacing(facing)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
throw UTSError()
|
||||
}
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(cameraId)
|
||||
val streamConfigurationMap = characteristics.get(android.hardware.camera2.CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
|
||||
if (streamConfigurationMap !== null) {
|
||||
val supportedSizes = streamConfigurationMap.getOutputSizes(ImageFormat.YUV_420_888)
|
||||
for(size in resolveUTSValueIterator(supportedSizes)){
|
||||
console.log(object : UTSJSONObject() {
|
||||
var width = size.getWidth()
|
||||
var height = size.getHeight()
|
||||
})
|
||||
}
|
||||
}
|
||||
this.cameraManager.openCamera(cameraId, callback, handler)
|
||||
}
|
||||
public open fun closeCamera(): Unit {}
|
||||
public open fun getCurrentCameraId(): String? {
|
||||
return this.currentCameraId
|
||||
}
|
||||
public open fun getTotalRotation(): Number {
|
||||
return this.totalRotation
|
||||
}
|
||||
private fun getBackCameraId(cameraIds: UTSArray<String>): String? {
|
||||
for(id in resolveUTSValueIterator(cameraIds)){
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(id)
|
||||
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
if (facing !== null && facing === CameraCharacteristics.LENS_FACING_BACK) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
private fun calculateCameraRotation(cameraId: String): Unit {}
|
||||
public open fun release(): Unit {
|
||||
this.currentCameraId = null
|
||||
this.totalRotation = 0
|
||||
console.log("CameraHelper: 摄像头辅助类资源已释放")
|
||||
}
|
||||
}
|
||||
val width: Int = 720
|
||||
val height: Int = 480
|
||||
var backgroundHandler: Handler? = null
|
||||
var handlerThread: HandlerThread? = null
|
||||
var imageReader: ImageReader? = null
|
||||
var captureSession: CameraCaptureSession? = null
|
||||
var cameraDevice: CameraDevice? = null
|
||||
val runBlock1 = run {
|
||||
console.log("1111", android.os.Build.VERSION.SDK_INT)
|
||||
}
|
||||
fun yuvToJpegBase64(image: android.media.Image): String {
|
||||
val width = image.getWidth()
|
||||
val height = image.getHeight()
|
||||
if (image.getFormat() !== android.graphics.ImageFormat.YUV_420_888) {
|
||||
console.error("仅支持YUV_420_888格式")
|
||||
return ""
|
||||
}
|
||||
val planes = image.getPlanes()
|
||||
val yPlane = planes[0]
|
||||
val uPlane = planes[1]
|
||||
val vPlane = planes[2]
|
||||
val yRowStride = yPlane.getRowStride()
|
||||
val yPixelStride = yPlane.getPixelStride()
|
||||
val uRowStride = uPlane.getRowStride()
|
||||
val uPixelStride = uPlane.getPixelStride()
|
||||
val vRowStride = vPlane.getRowStride()
|
||||
val vPixelStride = vPlane.getPixelStride()
|
||||
val yBuffer = yPlane.getBuffer()
|
||||
val uBuffer = uPlane.getBuffer()
|
||||
val vBuffer = vPlane.getBuffer()
|
||||
val yByteArray = ByteArray(yBuffer.remaining())
|
||||
val uByteArray = ByteArray(uBuffer.remaining())
|
||||
val vByteArray = ByteArray(vBuffer.remaining())
|
||||
yBuffer.get(yByteArray)
|
||||
uBuffer.get(uByteArray)
|
||||
vBuffer.get(vByteArray)
|
||||
val nv21 = ByteArray(width * height * 3 / 2)
|
||||
var nv21Index: Int = 0
|
||||
run {
|
||||
var i: Int = 0
|
||||
while(i < height){
|
||||
run {
|
||||
var j: Int = 0
|
||||
while(j < width){
|
||||
nv21[nv21Index++] = yByteArray[i * yRowStride + j * yPixelStride]
|
||||
j++
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
val halfHeight = Math.floor(height / 2)
|
||||
val halfWidth = Math.floor(width / 2)
|
||||
run {
|
||||
var i: Int = 0
|
||||
while(i < halfHeight){
|
||||
run {
|
||||
var j: Int = 0
|
||||
while(j < halfWidth){
|
||||
val uIndex: Int = i * uRowStride + j * uPixelStride
|
||||
val vIndex: Int = i * vRowStride + j * vPixelStride
|
||||
nv21[nv21Index++] = vByteArray[vIndex]
|
||||
nv21[nv21Index++] = uByteArray[uIndex]
|
||||
j++
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
try {
|
||||
val yuvImage = android.graphics.YuvImage(nv21, android.graphics.ImageFormat.NV21, width, height, null)
|
||||
val outputStream = java.io.ByteArrayOutputStream()
|
||||
val compressSuccess = yuvImage.compressToJpeg(android.graphics.Rect(0, 0, width, height), 80, outputStream)
|
||||
if (!compressSuccess) {
|
||||
console.error("YUV压缩为JPEG失败")
|
||||
outputStream.close()
|
||||
return ""
|
||||
}
|
||||
val jpegBytes = outputStream.toByteArray()
|
||||
val srcBitmap: Bitmap = android.graphics.BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
||||
val matrix = android.graphics.Matrix()
|
||||
matrix.postRotate((-90 as Number).toFloat())
|
||||
val outputStream2 = java.io.ByteArrayOutputStream()
|
||||
val rotatedBitmap = android.graphics.Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight(), matrix, true)
|
||||
rotatedBitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, outputStream2)
|
||||
val BitmapBytes = outputStream2.toByteArray()
|
||||
val base64Raw = android.util.Base64.encodeToString(BitmapBytes, android.util.Base64.DEFAULT)
|
||||
val base64Jpeg = "data:image/jpeg;base64," + base64Raw
|
||||
outputStream.close()
|
||||
return base64Jpeg
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("YUV转JPEG/Base64失败:", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
fun startCameraPreview(options: MyApiOptions): Unit {
|
||||
val context = UTSAndroid.getAppContext()
|
||||
val activity = UTSAndroid.getUniActivity()
|
||||
if (context == null || activity == null) {
|
||||
console.error("错误:上下文或Activity为空")
|
||||
return
|
||||
}
|
||||
handlerThread = HandlerThread("CameraBackground", -8)
|
||||
handlerThread!!!!.start()
|
||||
backgroundHandler = Handler(handlerThread!!!!.getLooper())
|
||||
val cameraHelper = CameraHelper(context)
|
||||
val totalRotation = cameraHelper.getTotalRotation()
|
||||
try {
|
||||
console.log("width, height", width, height)
|
||||
imageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 8)
|
||||
val localImageReader = imageReader
|
||||
if (localImageReader == null) {
|
||||
console.error("ImageReader 创建失败,无法设置监听器")
|
||||
return
|
||||
}
|
||||
val imageAvailableListener = fun(reader: android.media.ImageReader): Unit {
|
||||
val imageProxy: Image? = reader.acquireLatestImage()
|
||||
if (imageProxy === null) {
|
||||
return
|
||||
}
|
||||
options.success?.invoke(yuvToJpegBase64(imageProxy))
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
localImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler)
|
||||
open class CustomCameraStateCallback : android.hardware.camera2.CameraDevice.StateCallback {
|
||||
public constructor() : super() {}
|
||||
override fun onOpened(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机打开成功")
|
||||
cameraDevice = device
|
||||
val localCameraDevice = device
|
||||
val surface = imageReader!!!!.getSurface()
|
||||
val surfaces = utsArrayOf(
|
||||
surface
|
||||
)
|
||||
open class CustomCaptureSessionCallback : android.hardware.camera2.CameraCaptureSession.StateCallback {
|
||||
private var localCameraDevice: android.hardware.camera2.CameraDevice
|
||||
private var surface: android.view.Surface
|
||||
public constructor(cameraDevice: android.hardware.camera2.CameraDevice, surface: android.view.Surface) : super() {
|
||||
this.localCameraDevice = cameraDevice
|
||||
this.surface = surface
|
||||
}
|
||||
override fun onConfigured(session: android.hardware.camera2.CameraCaptureSession): Unit {
|
||||
console.log("采集会话配置成功,启动持续帧采集")
|
||||
captureSession = session
|
||||
val requestBuilder = this.localCameraDevice.createCaptureRequest(android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW)
|
||||
requestBuilder.addTarget(this.surface)
|
||||
requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270)
|
||||
session.setRepeatingRequest(requestBuilder.build(), null, backgroundHandler)
|
||||
}
|
||||
override fun onConfigureFailed(session: android.hardware.camera2.CameraCaptureSession): Unit {
|
||||
console.error("采集会话配置失败")
|
||||
captureSession = null
|
||||
}
|
||||
}
|
||||
val sessionCallback = CustomCaptureSessionCallback(localCameraDevice, surface) as CameraCaptureSession.StateCallback
|
||||
localCameraDevice.createCaptureSession(surfaces, sessionCallback, backgroundHandler)
|
||||
}
|
||||
override fun onDisconnected(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机连接断开")
|
||||
this.releaseCameraResources()
|
||||
}
|
||||
override fun onError(device: android.hardware.camera2.CameraDevice, p1: Int): Unit {
|
||||
console.error("\u76F8\u673A\u6253\u5F00\u5931\u8D25")
|
||||
this.releaseCameraResources()
|
||||
}
|
||||
override fun onClosed(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机已关闭")
|
||||
super.onClosed(device)
|
||||
}
|
||||
private fun releaseCameraResources(): Unit {
|
||||
if (cameraDevice != null) {
|
||||
cameraDevice!!!!.close()
|
||||
cameraDevice = null
|
||||
console.log("相机资源已释放")
|
||||
}
|
||||
}
|
||||
}
|
||||
val cameraStateCallback = CustomCameraStateCallback()
|
||||
cameraHelper.openCameraByFacing("FRONT", cameraStateCallback, backgroundHandler)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("启动摄像头失败:", e)
|
||||
}
|
||||
}
|
||||
fun stopCameraPreview(): Unit {
|
||||
try {
|
||||
console.log("开始释放摄像头资源")
|
||||
if (captureSession != null) {
|
||||
try {
|
||||
captureSession!!!!.stopRepeating()
|
||||
captureSession!!!!.abortCaptures()
|
||||
captureSession!!!!.close()
|
||||
console.log("采集会话已释放")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("释放采集会话时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
captureSession = null
|
||||
}
|
||||
}
|
||||
if (cameraDevice != null) {
|
||||
try {
|
||||
cameraDevice!!!!.close()
|
||||
console.log("相机设备已关闭")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("关闭相机设备时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
cameraDevice = null
|
||||
}
|
||||
}
|
||||
if (imageReader != null) {
|
||||
try {
|
||||
imageReader!!!!.close()
|
||||
console.log("ImageReader已释放")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("释放ImageReader时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
imageReader = null
|
||||
}
|
||||
}
|
||||
if (handlerThread != null) {
|
||||
try {
|
||||
if (backgroundHandler != null) {
|
||||
console.log("先移除所有待处理的消息")
|
||||
backgroundHandler!!!!.removeCallbacksAndMessages(null)
|
||||
backgroundHandler = null
|
||||
}
|
||||
handlerThread!!!!.quitSafely()
|
||||
handlerThread!!!!.join()
|
||||
console.log("后台线程已停止")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("停止后台线程时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
handlerThread = null
|
||||
}
|
||||
}
|
||||
console.log("摄像头资源释放完成")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("释放摄像头资源时发生全局异常:", e)
|
||||
}
|
||||
}
|
||||
open class MyApiOptionsJSONObject : UTSJSONObject() {
|
||||
open var paramA: Boolean = false
|
||||
open var success: UTSCallback? = null
|
||||
open var fail: UTSCallback? = null
|
||||
open var complete: UTSCallback? = null
|
||||
}
|
||||
fun startCameraPreviewByJs(options: MyApiOptionsJSONObject): Unit {
|
||||
return startCameraPreview(MyApiOptions(paramA = options.paramA, success = fun(res: String): Unit {
|
||||
options.success?.invoke(res)
|
||||
}
|
||||
, fail = fun(res: MyApiFail): Unit {
|
||||
options.fail?.invoke(res)
|
||||
}
|
||||
, complete = fun(res: Any): Unit {
|
||||
options.complete?.invoke(res)
|
||||
}
|
||||
))
|
||||
}
|
||||
fun stopCameraPreviewByJs(): Unit {
|
||||
return stopCameraPreview()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.example.ty_camera
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user