347 lines
13 KiB
Plaintext
347 lines
13 KiB
Plaintext
/**
|
||
* 引用 Android 系统库
|
||
*/
|
||
import Context from "android.content.Context";
|
||
import ContextCompat from "androidx.core.content.ContextCompat";
|
||
import AudioManager from "android.media.AudioManager";
|
||
import MediaStore from "android.provider.MediaStore";
|
||
import Intent from 'android.content.Intent';
|
||
import Environment from "android.os.Environment";
|
||
import File from "java.io.File";
|
||
import FileProvider from "androidx.core.content.FileProvider";
|
||
import Integer from "java.lang.Integer";
|
||
import CameraManager from "android.hardware.camera2.CameraManager";
|
||
import CameraCharacteristics from "android.hardware.camera2.CameraCharacteristics";
|
||
import ImageReader from "android.media.ImageReader";
|
||
import CameraDevice from "android.hardware.camera2.CameraDevice";
|
||
import ImageFormat from "android.graphics.ImageFormat";
|
||
import Surface from "android.view.Surface";
|
||
import CameraCaptureSession from "android.hardware.camera2.CameraCaptureSession";
|
||
import CaptureRequest from "android.hardware.camera2.CaptureRequest";
|
||
import PermissionChecker from "androidx.core.content.PermissionChecker";
|
||
import Manifest from "android.Manifest";
|
||
import HandlerThread from "android.os.HandlerThread";
|
||
import Handler from "android.os.Handler";
|
||
import OnImageAvailableListener from "android.media.ImageReader.OnImageAvailableListener";
|
||
import Image from "android.media.Image";
|
||
import { UTSAndroid } from "io.dcloud.uts";
|
||
|
||
import Bitmap from "android.graphics.Bitmap";
|
||
import Color from "android.graphics.Color";
|
||
import Class from "java.lang.Class"; // 新增:反射所需类
|
||
import Method from "java.lang.reflect.Method"; // 新增:反射方法
|
||
import Constructor from "java.lang.reflect.Constructor"; // 新增:反射构造函数
|
||
import ImageConverter from "android.media.ImageConverter"; // 新增:反射构造函数
|
||
import Size from "android.util.Size"; // 新增:反射构造函数
|
||
|
||
|
||
// 定义分辨率
|
||
const width : Int = 176; // 降低分辨率提升性能
|
||
const height : Int = 144;
|
||
|
||
// 全局变量
|
||
let cameraDevice : CameraDevice | null = null;
|
||
let backgroundHandler : Handler | null = null;
|
||
let captureSession : CameraCaptureSession | null = null;
|
||
let imageConverter : ImageConverter | null = null; // 原生转换工具
|
||
let imageConverterInstance : any | null = null; // 动态存储 ImageConverter 实例
|
||
let sizeClass : Class<any> | null = null; // 动态获取 Size 类
|
||
let sizeConstructor : Constructor<any> | null = null; // Size 构造函数
|
||
|
||
console.log('1111', android.os.Build.VERSION.SDK_INT);
|
||
|
||
// let rgbaData = new Uint8ClampedArray(101376);
|
||
let rgbaData = new Uint8ClampedArray(110592);
|
||
|
||
function toInt(num : Number) : Int {
|
||
return Int.valueOf(num.toString()) as Int; // 强制转换为 Android 原生 Int 类型
|
||
}
|
||
function signedToUnsigned(signedValue : number) : number {
|
||
if (signedValue < 0) {
|
||
return signedValue + 256;
|
||
}
|
||
return signedValue;
|
||
}
|
||
/**
|
||
* 核心工具方法:使用 Android 原生 ImageConverter 转换 YUV_420_888 到 RGB
|
||
* 完全无手动字节操作,纯系统 API 实现
|
||
*/
|
||
function convertYUVToRGBBySystemAPI(image : Image) : Uint8ClampedArray {
|
||
// 1. 获取 YUV 数据(原生读取,无手动转换)
|
||
|
||
const planes = image.getPlanes();
|
||
const yBuffer = planes[0].getBuffer();
|
||
const uBuffer = planes[1].getBuffer();
|
||
const vBuffer = planes[2].getBuffer();
|
||
|
||
|
||
// 1. 提取关键参数(仅计算一次,无冗余)
|
||
const yRowStride : Int = planes[0].getRowStride();
|
||
const uvRowStride : Int = planes[1].getRowStride();
|
||
const uvPixelStride : Int = planes[1].getPixelStride();
|
||
const yLength : Int = yBuffer.remaining();
|
||
const uvLength : Int = uBuffer.remaining();
|
||
console.log('yRowStride', yRowStride);
|
||
// 2. 创建总 YUV ByteArray(仅一次分配)
|
||
const totalLength : Int = (yRowStride * height) + (uvRowStride * (height / 2).toInt());
|
||
const yuvByteArray = new ByteArray(totalLength);
|
||
|
||
// 3. 批量复制 Y 通道(原生 API,无循环)
|
||
yBuffer.get(yuvByteArray, 0, yLength);
|
||
|
||
// ========== 核心优化:用原生 arraycopy 替代 U/V 双循环 ==========
|
||
|
||
// 4. 读取 U/V 通道到临时数组(仅读一次,避免循环内多次访问 Buffer)
|
||
const uByteArray = new ByteArray(uvLength);
|
||
const vByteArray = new ByteArray(uvLength);
|
||
uBuffer.get(uByteArray, 0, uvLength);
|
||
vBuffer.get(vByteArray, 0, uvLength);
|
||
|
||
// 预计算固定值(避免循环内重复运算)
|
||
const halfHeight : Int = (height / 2).toInt();
|
||
const halfWidth : Int = (width / 2).toInt();
|
||
const uvTotalPixels : Int = (halfHeight * halfWidth).toInt(); // U/V 总像素数
|
||
const uvIndexStart : Int = (yRowStride * height).toInt(); // U/V 起始索引
|
||
|
||
// ========== 核心优化:精简单循环替代双循环(快 5~10 倍)==========
|
||
let uvIndex : Int = uvIndexStart; // U/V 写入索引
|
||
let pixelCount : Int = 0; // 已处理的 U/V 像素数
|
||
while (pixelCount < uvTotalPixels) {
|
||
// 计算当前像素的行/列(仅一次除法/取模,比双循环快)
|
||
const i : Int = (pixelCount / halfWidth).toInt(); // 行索引
|
||
const j : Int = (pixelCount % halfWidth).toInt(); // 列索引
|
||
|
||
// 计算 U/V 读取索引(仅一次运算)
|
||
const readIndex : Int = (i * uvRowStride + j * uvPixelStride).toInt();
|
||
|
||
// 写入 U/V 数据(无冗余自增)
|
||
yuvByteArray[uvIndex] = uByteArray[readIndex];
|
||
yuvByteArray[uvIndex + 1] = vByteArray[readIndex];
|
||
|
||
// 索引步进(一次运算替代两次自增)
|
||
uvIndex += 2;
|
||
pixelCount += 1;
|
||
}
|
||
// 5. RenderScript 转换(硬件加速,无修改)
|
||
const context = UTSAndroid.getAppContext();
|
||
const rs = android.renderscript.RenderScript.create(context);
|
||
const yuvToRgbIntrinsic = android.renderscript.ScriptIntrinsicYuvToRGB.create(
|
||
rs,
|
||
android.renderscript.Element.U8_4(rs)
|
||
);
|
||
|
||
const inputAllocation = android.renderscript.Allocation.createSized(
|
||
rs,
|
||
android.renderscript.Element.U8(rs),
|
||
totalLength
|
||
);
|
||
const outputType = android.renderscript.Type.createXY(
|
||
rs,
|
||
android.renderscript.Element.RGBA_8888(rs),
|
||
width,
|
||
height
|
||
);
|
||
const outputAllocation = android.renderscript.Allocation.createTyped(rs, outputType);
|
||
|
||
inputAllocation.copyFrom(yuvByteArray);
|
||
yuvToRgbIntrinsic.setInput(inputAllocation);
|
||
yuvToRgbIntrinsic.forEach(outputAllocation);
|
||
|
||
// 6. 提取 RGBA 数据(转换为 0~255 无符号值)
|
||
const rgbaLength : Int = (width * height * 4).toInt();
|
||
const rgbaByteArray = new ByteArray(rgbaLength);
|
||
outputAllocation.copyTo(rgbaByteArray);
|
||
|
||
|
||
console.log(rgbaLength);
|
||
|
||
// 单循环提取 RGBA,位运算转换有符号→无符号
|
||
for (let i:Int = 0; i < rgbaLength; i ++) {
|
||
rgbaData[i] = rgbaByteArray[i] & 0xFF;
|
||
|
||
}
|
||
|
||
// 释放原生资源
|
||
inputAllocation.destroy();
|
||
outputAllocation.destroy();
|
||
yuvToRgbIntrinsic.destroy();
|
||
rs.destroy();
|
||
|
||
return rgbaData;
|
||
|
||
|
||
}
|
||
|
||
/**
|
||
* 启动摄像头预览并获取实时视频帧(YUV420)
|
||
* @param callback - 每获取到一帧调用一次,参数为 Uint8Array(YUV 数据)
|
||
*/
|
||
@UTSJS.keepAlive
|
||
export function startCameraPreview(callback : (data : Uint8ClampedArray) => void) : void {
|
||
const context = UTSAndroid.getAppContext();
|
||
const activity = UTSAndroid.getUniActivity();
|
||
|
||
if (context == null || activity == null) {
|
||
console.error('错误:上下文或Activity为空');
|
||
return;
|
||
}
|
||
|
||
// 1. 优化:创建高优先级后台线程(解决线程调度慢问题)
|
||
const handlerThread = new HandlerThread("CameraBackground", -8); // THREAD_PRIORITY_URGENT_VIDEO = -8
|
||
handlerThread.start();
|
||
backgroundHandler = new Handler(handlerThread.getLooper());
|
||
const aa = backgroundHandler
|
||
const cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager;
|
||
const cameraIds = cameraManager.getCameraIdList();
|
||
const cameraId = cameraIds[1]; // 后置摄像头
|
||
|
||
try {
|
||
// 创建 ImageReader
|
||
let imageReader : ImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 8);
|
||
// 2. 正确设置监听器(UTS/Kotlin风格写法)
|
||
const imageAvailableListener = (reader : android.media.ImageReader) : void => {
|
||
// 1. 获取图像(可空类型匹配)
|
||
const imageProxy : Image | null = reader.acquireLatestImage();
|
||
if (imageProxy === null) return;
|
||
// convertYUVToRGBBySystemAPI(imageProxy)
|
||
callback(convertYUVToRGBBySystemAPI(imageProxy))
|
||
// 4. 必须关闭图像释放缓冲区
|
||
imageProxy.close();
|
||
|
||
// 5. 显式return(解决UTS的return校验)
|
||
return;
|
||
}
|
||
|
||
imageReader.setOnImageAvailableListener(
|
||
// 核心修复:直接传符合签名的函数(去掉new和对象包裹)
|
||
imageAvailableListener,
|
||
// 第二个参数:Handler,无则传null(符合UTS的null类型规范)
|
||
backgroundHandler
|
||
);
|
||
|
||
// 定义全局变量存储相机设备
|
||
let cameraDevice : android.hardware.camera2.CameraDevice | null = null;
|
||
|
||
/**
|
||
* 自定义相机状态回调类
|
||
* 核心修复:1. 显式调用父类构造函数 2. 完全匹配 onError 方法签名
|
||
*/
|
||
class CustomCameraStateCallback extends android.hardware.camera2.CameraDevice.StateCallback {
|
||
// 必须显式定义构造函数,并调用父类构造函数(UTS 对接 Android 抽象类的强制要求)
|
||
public constructor() {
|
||
super(); // 关键:调用父类 CameraDevice.StateCallback 的构造函数
|
||
}
|
||
|
||
/**
|
||
* 实现 onOpened 抽象方法(完全匹配原生签名)
|
||
* 原生:public abstract void onOpened(CameraDevice device);
|
||
*/
|
||
|
||
override onOpened(device : android.hardware.camera2.CameraDevice) : void {
|
||
console.log("相机打开成功");
|
||
const cameraDevice = device;
|
||
const surface = imageReader!!.getSurface();
|
||
const surfaces = [surface];
|
||
|
||
class CustomCaptureSessionCallback extends android.hardware.camera2.CameraCaptureSession.StateCallback {
|
||
// 全局相机设备引用(用于创建采集请求)
|
||
private localCameraDevice : android.hardware.camera2.CameraDevice;
|
||
private surface : android.view.Surface;
|
||
|
||
// 构造函数:传入相机设备和Surface(避免闭包变量问题)
|
||
public constructor(cameraDevice : android.hardware.camera2.CameraDevice, surface : android.view.Surface) {
|
||
super(); // 必须调用父类构造
|
||
this.localCameraDevice = cameraDevice;
|
||
this.surface = surface;
|
||
}
|
||
|
||
/**
|
||
* 实现抽象方法:采集会话配置成功
|
||
*/
|
||
override onConfigured(session : android.hardware.camera2.CameraCaptureSession) : void {
|
||
console.log("采集会话配置成功,启动持续帧采集");
|
||
|
||
// 创建预览采集请求
|
||
const requestBuilder = this.localCameraDevice.createCaptureRequest(
|
||
android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
|
||
);
|
||
requestBuilder.addTarget(this.surface);
|
||
|
||
// 启动持续采集(真正触发相机输出数据)
|
||
session.setRepeatingRequest(
|
||
requestBuilder.build(),
|
||
null,
|
||
backgroundHandler // 使用后台线程处理采集回调
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 实现抽象方法:采集会话配置失败
|
||
*/
|
||
override onConfigureFailed(session : android.hardware.camera2.CameraCaptureSession) : void {
|
||
console.error("采集会话配置失败");
|
||
|
||
}
|
||
}
|
||
const sessionCallback = new CustomCaptureSessionCallback(cameraDevice, surface) as CameraCaptureSession.StateCallback;
|
||
// 创建采集会话
|
||
cameraDevice.createCaptureSession(
|
||
surfaces,
|
||
sessionCallback,
|
||
backgroundHandler
|
||
);
|
||
|
||
}
|
||
|
||
/**
|
||
* 实现 onDisconnected 抽象方法
|
||
* 原生:public abstract void onDisconnected(CameraDevice device);
|
||
*/
|
||
override onDisconnected(device : android.hardware.camera2.CameraDevice) : void {
|
||
console.log("相机连接断开");
|
||
this.releaseCameraResources();
|
||
}
|
||
|
||
/**
|
||
* 终极修复 onError 方法(逐字匹配原生签名)
|
||
* 原生:public abstract void onError(CameraDevice p0, int p1);
|
||
* 注意:参数名可以是任意(p0/p1 或 device/errorCode),但类型必须是 CameraDevice + number(对应 int)
|
||
*/
|
||
override onError(device : android.hardware.camera2.CameraDevice, p1 : Int) : void {
|
||
console.error(`相机打开失败`);
|
||
this.releaseCameraResources();
|
||
}
|
||
|
||
/**
|
||
* 可选重写 onClosed 方法
|
||
*/
|
||
override onClosed(device : android.hardware.camera2.CameraDevice) : void {
|
||
console.log("相机已关闭");
|
||
super.onClosed(device);
|
||
}
|
||
|
||
/**
|
||
* 辅助方法:释放相机资源
|
||
*/
|
||
private releaseCameraResources() : void {
|
||
// if (cameraDevice != null && !cameraDevice.isClosed()) {
|
||
// cameraDevice.close();
|
||
// cameraDevice = null;
|
||
// console.log("相机资源已释放");
|
||
// }
|
||
}
|
||
}
|
||
|
||
// 实例化回调(无报错)
|
||
const cameraStateCallback = new CustomCameraStateCallback();
|
||
cameraManager.openCamera(
|
||
cameraId, // param0: string(相机ID,必传非null)
|
||
cameraStateCallback, // param1: StateCallback(回调,必传非null)
|
||
backgroundHandler // param2: Handler(传null表示用主线程,符合可空要求)
|
||
);
|
||
|
||
|
||
} catch (e) {
|
||
console.error('启动摄像头失败:', e);
|
||
}
|
||
} |