bug修改

This commit is contained in:
dzc committed 2026-08-09 14:45:18 +08:00
1 parent adbd915b45
commit 2ee78a1561
38 files changed
+2069 -240

No files matched your search

@@ -20,6 +20,7 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import io.flutter.view.TextureRegistry;
@@ -65,6 +66,13 @@ final class FacePreviewRenderer implements SurfaceTexture.OnFrameAvailableListen
private Handler renderHandler;
private final Object lock = new Object();
private final Map<Long, OutputTarget> outputs = new HashMap<>();
/**
* 当前主、副 Flutter Engine 实际持有的预览输出数量。
* Camera 回调线程只读取该原子值,不跨线程直接访问渲染线程拥有的 outputs。
*/
private final AtomicInteger activeOutputCount = new AtomicInteger();
private final float[] textureMatrix = new float[16];
private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
@@ -127,6 +135,7 @@ final class FacePreviewRenderer implements SurfaceTexture.OnFrameAvailableListen
throw new IllegalStateException("无法创建 Flutter 人脸预览 EGLSurface");
}
outputs.put(outputId, new OutputTarget(entry, surface, eglSurface));
activeOutputCount.set(outputs.size());
Log.i(TAG, "预览输出已添加 outputId=" + outputId + ";flutterTextureId=" + entry.id() + ";outputCount=" + outputs.size());
});
return outputId;
@@ -138,6 +147,15 @@ final class FacePreviewRenderer implements SurfaceTexture.OnFrameAvailableListen
handler.post(() -> releaseOutput(outputId));
}
/**
* 判断是否仍有界面需要实时人脸框。
*
* @return 主屏或副屏至少持有一个有效 Texture 输出时返回 true
*/
boolean hasActiveOutputs() {
return activeOutputCount.get() > 0;
}
/**
* 成功后继续保留 Camera 热资源,只暂停向主副屏 Texture 提交新帧。
* 这样界面会停在通过阈值的最后一帧,下一位顾客开始时再恢复,不需要重新开 Camera。
@@ -311,6 +329,7 @@ final class FacePreviewRenderer implements SurfaceTexture.OnFrameAvailableListen
private void releaseOutput(long outputId) {
OutputTarget output = outputs.remove(outputId);
if (output == null) return;
activeOutputCount.set(outputs.size());
if (eglDisplay != EGL14.EGL_NO_DISPLAY && output.eglSurface != EGL14.EGL_NO_SURFACE) {
EGL14.eglDestroySurface(eglDisplay, output.eglSurface);
}
@@ -331,6 +350,7 @@ final class FacePreviewRenderer implements SurfaceTexture.OnFrameAvailableListen
if (released) return;
released = true;
for (Long textureId : outputs.keySet().toArray(new Long[0])) releaseOutput(textureId);
activeOutputCount.set(0);
if (cameraTexture != null) {
cameraTexture.setOnFrameAvailableListener(null);
cameraTexture.release();
@@ -9,6 +9,7 @@ import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
@@ -45,8 +46,19 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
private static final long WARM_TIMEOUT_MS = 20L * 60L * 1000L;
private static final int TARGET_WIDTH = 1280;
private static final int TARGET_HEIGHT = 720;
/** 人脸框事件的最小发送间隔,避免主副屏因高频 Map 消息持续重建。 */
private static final long FACE_BOX_THROTTLE_MS = 100L;
/**
* 虹软在运动模糊或角度变化时允许的短暂漏检时间。
* 每张脸分别保留最近坐标,连续漏检超时后才确认该人员已经离开。
*/
private static final long FACE_BOX_MISS_GRACE_MS = 600L;
/** 结果展示期仅做人脸检测的最小帧间隔,限制热预览的 CPU 消耗。 */
private static final long PREVIEW_ONLY_DETECTION_INTERVAL_MS = 100L;
public enum State {
COLD("cold"),
INITIALIZING("initializing"),
@@ -87,6 +99,8 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
thread.setPriority(Thread.NORM_PRIORITY + 1);
return thread;
});
/** Camera 回调是否已有唯一帧任务在执行或排队,由成功取得 CAS 的任务负责释放。 */
private final AtomicBoolean frameInFlight = new AtomicBoolean(false);
private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
private final Object sessionLock = new Object();
@@ -105,8 +119,20 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
private String activeScene;
private Listener activeOwner;
private long revision;
/** 进程生命周期内单调递增的事件序号,用于主副屏丢弃跨业务会话迟到的状态。 */
private long eventSequence;
private long sessionGeneration;
private long lastFaceBoxEmitAt;
/** 结果展示期最近一次提交“仅检测”任务的时间戳,单位为毫秒。 */
private long lastPreviewOnlyDetectionAt;
/** 每个虹软跟踪编号最近一次真实检测到的时间戳,使用单调时钟且单位为毫秒。 */
private Map<Integer, Long> lastFaceBoxSeenAt = new HashMap<>();
/** 当前向 Flutter 展示的人脸框,包含真实检测框和仍在漏检宽限内的单人缓存。 */
private List<Map<String, Object>> lastFaceBoxes = new ArrayList<>();
private boolean recognitionConfigurationDirty;
private Handler warmReleaseHandler = new Handler(Looper.getMainLooper());
@@ -189,7 +215,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
revision = 0;
state = State.INITIALIZING;
reloadConfiguration = recognitionConfigurationDirty;
emitState("正在启动摄像头", null, null);
emitState("正在启动摄像头", recentFaceBoxesLocked(SystemClock.elapsedRealtime()), null);
}
try {
// 保存配置时不打断上一位顾客;下一次 Session 启动前再重建引擎并读取新值。
@@ -213,7 +239,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
throw new IllegalStateException("当前人脸会话已结束");
}
state = State.DETECTING;
emitState("请正对摄像头", null, null);
emitState("请正对摄像头", recentFaceBoxesLocked(SystemClock.elapsedRealtime()), null);
Log.i(TAG, "人脸会话进入识别状态 sessionId=" + sessionId);
return snapshot();
}
@@ -223,7 +249,10 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
synchronized (sessionLock) {
ensureOwner(sessionId);
state = State.INITIALIZING;
emitState(warmResourcesReady ? "正在重置识别状态" : "正在重新启动摄像头", null, null);
emitState(
warmResourcesReady ? "正在重置识别状态" : "正在重新启动摄像头",
recentFaceBoxesLocked(SystemClock.elapsedRealtime()),
null);
}
try {
ensureWarmResources();
@@ -241,7 +270,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
synchronized (sessionLock) {
ensureOwner(sessionId);
state = State.DETECTING;
emitState("请调整位置后重新识别", null, null);
emitState("请调整位置后重新识别", recentFaceBoxesLocked(SystemClock.elapsedRealtime()), null);
return snapshot();
}
}
@@ -252,6 +281,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
if (activeSessionId == null) return snapshot();
if (sessionId != null && !sessionId.isEmpty() && !Objects.equals(activeSessionId, sessionId)) return snapshot();
state = State.CANCELLED;
clearFaceBoxCacheLocked();
emitState(message == null ? "已取消人脸识别" : message, null, null);
processorToStop = detachActiveSession();
}
@@ -266,14 +296,23 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
/**
* 结束当前业务识别但保留热 Camera,并恢复 Texture 的实时帧提交。
*
* <p>成功回调会冻结最后一帧供独立弹框确认;调用方结束 Session 后必须解冻,
* 持续查询页面才能在十秒菜单展示期继续显示实时画面,同时又不处理下一位人脸。</p>
*/
public Map<String, Object> finishRecognition(String sessionId) {
FaceDiscernViewModel processorToStop;
FacePreviewRenderer renderer;
synchronized (sessionLock) {
if (activeSessionId == null) return snapshot();
if (sessionId != null && !sessionId.isEmpty() && !Objects.equals(activeSessionId, sessionId)) return snapshot();
processorToStop = detachActiveSession();
renderer = previewRenderer;
}
stopProcessor(processorToStop);
if (renderer != null) renderer.setFrozen(false);
if (isRecognitionConfigurationDirty()) {
releaseResources(true, true);
} else {
@@ -297,9 +336,16 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
/**
* 返回当前原生资源和业务会话快照;检测、成功及热预览状态会附带可继续展示的人脸框。
*
* @return 可供 MethodChannel 或新 EventChannel 订阅者立即渲染的完整状态
*/
public Map<String, Object> getState() {
synchronized (sessionLock) {
return snapshot();
Map<String, Object> result = snapshot();
result.put("faceBoxes", currentStateFaceBoxesLocked(SystemClock.elapsedRealtime()));
return result;
}
}
@@ -429,15 +475,43 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
displayOrientation, info.facing, mirror, true, true));
}
/**
* 接收 Camera1 的 NV21 回调,并按当前会话状态提交识别或仅检测任务。
* 未被选中的帧会立即归还 Camera 缓冲区,避免高频回调积压。
*
* @param data Camera1 复用的 NV21 帧缓冲区
* @param callbackCamera 产生该帧的 Camera 实例
*/
@Override
@SuppressWarnings("deprecation")
public void onPreviewFrame(byte[] data, Camera callbackCamera) {
if (data == null || callbackCamera == null) return;
boolean recognize;
boolean recognize = false;
boolean previewOnly = false;
FaceDiscernViewModel frameProcessor = null;
Camera.Size framePreviewSize = null;
long frameGeneration = 0L;
long now = SystemClock.elapsedRealtime();
synchronized (sessionLock) {
recognize = state == State.DETECTING && activeSessionId != null && processor != null;
// Camera、处理器和业务代次必须在入队时固定,禁止上一轮已经回调的 NV21 帧跨轮次进入新会话。
if (callbackCamera == camera && processor != null && previewSize != null) {
recognize = state == State.DETECTING && activeSessionId != null;
previewOnly = !recognize
&& state == State.WARM_IDLE
&& activeSessionId == null
&& warmResourcesReady
&& previewRenderer != null
&& previewRenderer.hasActiveOutputs()
&& now - lastPreviewOnlyDetectionAt >= PREVIEW_ONLY_DETECTION_INTERVAL_MS;
if (recognize || previewOnly) {
frameProcessor = processor;
framePreviewSize = previewSize;
frameGeneration = sessionGeneration;
}
if (previewOnly) lastPreviewOnlyDetectionAt = now;
}
}
if (!recognize || !frameInFlight.compareAndSet(false, true)) {
if ((!recognize && !previewOnly) || !frameInFlight.compareAndSet(false, true)) {
try {
callbackCamera.addCallbackBuffer(data);
} catch (RuntimeException ignored) {
@@ -445,10 +519,22 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
return;
}
try {
final boolean doRecognize = recognize;
final FaceDiscernViewModel expectedProcessor = frameProcessor;
final Camera.Size expectedPreviewSize = framePreviewSize;
final long expectedGeneration = frameGeneration;
recognitionExecutor.execute(() -> {
try {
processFrame(data);
processFrame(
data,
doRecognize,
callbackCamera,
expectedProcessor,
expectedPreviewSize,
expectedGeneration);
} finally {
// 只有成功取得 CAS 的任务可以释放标记,状态切换不得提前清零并让第二帧并发入队。
frameInFlight.set(false);
try {
callbackCamera.addCallbackBuffer(data);
} catch (RuntimeException ignored) {
@@ -464,77 +550,213 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
}
}
private void processFrame(byte[] data) {
FaceDiscernViewModel currentProcessor;
long frameGeneration;
synchronized (sessionLock) {
if (state != State.DETECTING || activeSessionId == null || processor == null) {
frameInFlight.set(false);
return;
}
currentProcessor = processor;
frameGeneration = sessionGeneration;
}
/**
* 处理识别帧或热预览检测帧;两种模式复用同一执行器和处理锁,禁止并发调用虹软引擎。
*
* @param data Camera1 回传的 NV21 原始帧
* @param doRecognize true 时允许特征比对;false 时只获取人脸坐标用于画框
* @param expectedCamera 帧入队时所属的 Camera,冷重启后必须与当前实例一致
* @param expectedProcessor 帧入队时所属的虹软处理器
* @param expectedPreviewSize 帧入队时使用的 NV21 尺寸
* @param expectedGeneration 帧入队时所属的业务会话代次
*/
private void processFrame(
byte[] data,
boolean doRecognize,
Camera expectedCamera,
FaceDiscernViewModel expectedProcessor,
Camera.Size expectedPreviewSize,
long expectedGeneration) {
try {
List<FacePreviewInfo> faces;
List<Map<String, Object>> boxes;
synchronized (processorLock) {
if (!isCurrentDetectingProcessor(currentProcessor, frameGeneration)) return;
faces = currentProcessor.onPreviewFrame(data, true);
if (!isCurrentFrameProcessor(
expectedCamera,
expectedProcessor,
expectedGeneration,
doRecognize)) return;
List<FacePreviewInfo> faces = expectedProcessor.onPreviewFrame(data, doRecognize);
// FaceHelper 返回内部可变列表;必须在处理锁内复制坐标和状态,避免 finish/start 清理列表时产生空框或并发遍历。
boxes = buildFaceBoxes(faces, expectedPreviewSize, expectedProcessor);
}
emitFaceBoxes(faces, frameGeneration);
emitFaceBoxes(boxes, expectedGeneration, doRecognize);
} catch (Exception error) {
Log.e(TAG, "处理人脸预览帧失败", error);
handleFatalFailure("人脸识别引擎异常,请重试", "RECOGNITION_ENGINE_ERROR", frameGeneration);
} finally {
frameInFlight.set(false);
handleFatalFailure("人脸识别引擎异常,请重试", "RECOGNITION_ENGINE_ERROR", expectedGeneration);
}
}
private boolean isCurrentDetectingProcessor(FaceDiscernViewModel expectedProcessor, long expectedGeneration) {
/**
* 判断当前状态是否允许处理指定模式的 Camera 帧;调用方必须持有 sessionLock。
*
* @param doRecognize true 表示业务识别帧,false 表示结果期热预览检测帧
* @return 当前处理器、会话与预览输出均仍有效时返回 true
*/
private boolean canProcessFrameLocked(boolean doRecognize) {
if (doRecognize) {
return state == State.DETECTING && activeSessionId != null;
}
return state == State.WARM_IDLE
&& activeSessionId == null
&& warmResourcesReady
&& previewRenderer != null
&& previewRenderer.hasActiveOutputs();
}
/**
* 在耗时检测前二次校验处理器与会话代次,防止 finish/start 过渡帧串入新状态。
*
* @param expectedCamera 任务创建时持有的 Camera
* @param expectedProcessor 任务创建时持有的虹软处理器
* @param expectedGeneration 任务创建时的业务会话代次
* @param doRecognize true 表示业务识别,false 表示仅检测
* @return 任务仍属于当前状态时返回 true
*/
private boolean isCurrentFrameProcessor(
Camera expectedCamera,
FaceDiscernViewModel expectedProcessor,
long expectedGeneration,
boolean doRecognize) {
synchronized (sessionLock) {
return processor == expectedProcessor
return camera == expectedCamera
&& processor == expectedProcessor
&& sessionGeneration == expectedGeneration
&& state == State.DETECTING
&& activeSessionId != null;
&& canProcessFrameLocked(doRecognize);
}
}
private void emitFaceBoxes(List<FacePreviewInfo> faces, long frameGeneration) {
long now = System.currentTimeMillis();
if (now - lastFaceBoxEmitAt < FACE_BOX_THROTTLE_MS) return;
lastFaceBoxEmitAt = now;
/**
* 将虹软当前帧结果复制为 Flutter 通道使用的归一化框。
* 相似度和活体状态只决定颜色与标签,不参与框的过滤。
*
* @param faces 虹软当前帧检测到的全部人脸
* @param currentPreviewSize NV21 原始预览尺寸
* @param currentProcessor 当前虹软处理器,用于读取最近识别状态
* @return 与内部 FaceHelper 列表生命周期无关的人脸框快照
*/
private List<Map<String, Object>> buildFaceBoxes(
List<FacePreviewInfo> faces,
Camera.Size currentPreviewSize,
FaceDiscernViewModel currentProcessor) {
List<Map<String, Object>> boxes = new ArrayList<>();
Camera.Size currentPreviewSize;
FaceDiscernViewModel currentProcessor;
synchronized (sessionLock) {
currentPreviewSize = previewSize;
currentProcessor = processor;
if (faces == null) return boxes;
for (FacePreviewInfo face : faces) {
// 传原始 NV21 坐标,Flutter 再与 Texture 使用同一套旋转/镜像变换,避免框和画面各自补偿后错位。
Rect rect = face.getFaceInfoRgb() == null ? null : face.getFaceInfoRgb().getRect();
if (rect == null) continue;
Map<String, Object> box = new HashMap<>();
box.put("trackId", face.getTrackId());
box.put("left", clamp(rect.left / (double) currentPreviewSize.width));
box.put("top", clamp(rect.top / (double) currentPreviewSize.height));
box.put("right", clamp(rect.right / (double) currentPreviewSize.width));
box.put("bottom", clamp(rect.bottom / (double) currentPreviewSize.height));
Float similarity = currentProcessor.getFaceSimilarity(face.getTrackId());
if (similarity != null) box.put("similarity", similarity.doubleValue());
box.put("recognizeStatus", recognizeStatusName(currentProcessor.getFaceRecognizeStatus(face.getTrackId())));
box.put("livenessStatus", livenessStatusName(currentProcessor.getFaceLiveness(face.getTrackId())));
boxes.add(box);
}
if (faces != null && currentPreviewSize != null) {
for (FacePreviewInfo face : faces) {
// 传原始 NV21 坐标,Flutter 再与 Texture 使用同一套旋转/镜像变换,避免框和画面各自补偿后错位。
Rect rect = face.getFaceInfoRgb() == null ? null : face.getFaceInfoRgb().getRect();
if (rect == null) continue;
Map<String, Object> box = new HashMap<>();
box.put("left", clamp(rect.left / (double) currentPreviewSize.width));
box.put("top", clamp(rect.top / (double) currentPreviewSize.height));
box.put("right", clamp(rect.right / (double) currentPreviewSize.width));
box.put("bottom", clamp(rect.bottom / (double) currentPreviewSize.height));
if (currentProcessor != null) {
Float similarity = currentProcessor.getFaceSimilarity(face.getTrackId());
if (similarity != null) box.put("similarity", similarity.doubleValue());
box.put("recognizeStatus", recognizeStatusName(currentProcessor.getFaceRecognizeStatus(face.getTrackId())));
box.put("livenessStatus", livenessStatusName(currentProcessor.getFaceLiveness(face.getTrackId())));
}
boxes.add(box);
return boxes;
}
/**
* 发送稳定后的人脸框。短暂空帧或人数下降会继续沿用上一组框,持续超过宽限才清除。
*
* @param detectedBoxes 当前检测帧复制出的全部人脸框
* @param frameGeneration 当前处理任务的会话代次
* @param doRecognize true 表示活动识别会话,false 表示 WARM_IDLE 仅检测预览
*/
private void emitFaceBoxes(
List<Map<String, Object>> detectedBoxes,
long frameGeneration,
boolean doRecognize) {
long now = SystemClock.elapsedRealtime();
synchronized (sessionLock) {
if (sessionGeneration != frameGeneration || !canProcessFrameLocked(doRecognize)) return;
if (now - lastFaceBoxEmitAt < FACE_BOX_THROTTLE_MS) return;
lastFaceBoxEmitAt = now;
List<Map<String, Object>> visibleBoxes = stabilizeFaceBoxesLocked(detectedBoxes, now);
emitState(doRecognize ? "请正对摄像头" : "摄像头预览中", visibleBoxes, null);
}
}
/**
* 按虹软跟踪编号分别合并当前检测框与短时缓存,避免一个人漏检时冻结其他人的新坐标。
* 调用方必须持有 sessionLock。
*
* @param detectedBoxes 当前帧检测框,不包含任何相似度过滤
* @param now 当前单调业务时间戳,单位为毫秒
* @return 本帧应向主副屏展示的稳定框列表
*/
private List<Map<String, Object>> stabilizeFaceBoxesLocked(
List<Map<String, Object>> detectedBoxes,
long now) {
List<Map<String, Object>> visibleBoxes = new ArrayList<>(detectedBoxes);
Map<Integer, Map<String, Object>> detectedByTrackId = new HashMap<>();
Map<Integer, Long> nextSeenAt = new HashMap<>();
for (Map<String, Object> box : detectedBoxes) {
Integer trackId = faceTrackId(box);
if (trackId == null) continue;
detectedByTrackId.put(trackId, box);
nextSeenAt.put(trackId, now);
}
for (Map<String, Object> previousBox : lastFaceBoxes) {
Integer trackId = faceTrackId(previousBox);
if (trackId == null || detectedByTrackId.containsKey(trackId)) continue;
Long lastSeenAt = lastFaceBoxSeenAt.get(trackId);
if (lastSeenAt != null && now - lastSeenAt < FACE_BOX_MISS_GRACE_MS) {
visibleBoxes.add(previousBox);
nextSeenAt.put(trackId, lastSeenAt);
}
}
synchronized (sessionLock) {
if (state == State.DETECTING && activeSessionId != null && sessionGeneration == frameGeneration) {
lastFaceBoxes = boxes;
emitState("请正对摄像头", boxes, null);
}
lastFaceBoxes = visibleBoxes;
lastFaceBoxSeenAt = nextSeenAt;
return lastFaceBoxes;
}
/**
* 读取原生框快照中的虹软跟踪编号,仅用于进程内稳定框,不属于 Flutter 必填协议字段。
*
* @param box 单张人脸框快照
* @return 可用于逐人缓存的跟踪编号;历史或异常数据缺失时返回 null
*/
private Integer faceTrackId(Map<String, Object> box) {
Object value = box.get("trackId");
return value instanceof Number ? ((Number) value).intValue() : null;
}
/**
* 返回仍在漏检宽限内的最近人脸框,超时则同步清理缓存。
* 调用方必须持有 sessionLock。
*
* @param now 当前时间戳,单位为毫秒
* @return 可安全用于初始化或资源状态事件的人脸框
*/
private List<Map<String, Object>> recentFaceBoxesLocked(long now) {
return stabilizeFaceBoxesLocked(new ArrayList<>(), now);
}
/**
* 返回适合新订阅者的当前框快照;成功态保留冻结结果,实时态只保留仍在漏检宽限内的框。
* 调用方必须持有 sessionLock。
*
* @param now 当前单调业务时间戳,单位为毫秒
* @return 与内部列表解耦、可直接放入通道 Map 的人脸框列表
*/
private List<Map<String, Object>> currentStateFaceBoxesLocked(long now) {
if (state == State.SUCCESS) {
return new ArrayList<>(lastFaceBoxes);
}
if (state == State.INITIALIZING || state == State.DETECTING || state == State.WARM_IDLE) {
return new ArrayList<>(recentFaceBoxesLocked(now));
}
return new ArrayList<>();
}
/** 清空人脸框和漏检计时;取消、冷释放及显式失败不得继续展示旧人员。 */
private void clearFaceBoxCacheLocked() {
lastFaceBoxes = new ArrayList<>();
lastFaceBoxSeenAt = new HashMap<>();
}
private double clamp(double value) {
@@ -567,6 +789,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
successBox.put("recognizeStatus", "success");
successBoxes.add(successBox);
}
lastFaceBoxes = successBoxes;
// 只冻结预览输出,不释放 Camera 热资源;成功框和相似度保留至少 3 秒供顾客确认。
if (previewRenderer != null) previewRenderer.setFrozen(true);
emitState("识别成功,正在读取信息", successBoxes, null);
@@ -583,6 +806,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
preserveSession = activeSessionId != null;
if (preserveSession) {
state = State.FAILED;
clearFaceBoxCacheLocked();
emitState(message, null, errorCode);
}
}
@@ -605,11 +829,10 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
FacePreviewRenderer renderer;
synchronized (sessionLock) {
renderer = previewRenderer;
lastFaceBoxes = new ArrayList<>();
lastFaceBoxEmitAt = 0;
lastPreviewOnlyDetectionAt = 0;
}
if (renderer != null) renderer.setFrozen(false);
frameInFlight.set(false);
lastFaceBoxEmitAt = 0;
}
/**
@@ -621,7 +844,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
activeSessionId = null;
activeScene = null;
activeOwner = null;
frameInFlight.set(false);
// 若旧帧仍在执行,必须由该任务的 finally 自行释放标记;提前清零会让 Camera 重复排队并占满回调缓冲。
state = warmResourcesReady ? State.WARM_IDLE : State.COLD;
scheduleWarmRelease();
return processorToStop;
@@ -656,7 +879,6 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
synchronized (sessionLock) {
cancelWarmRelease();
sessionGeneration++;
frameInFlight.set(false);
cameraToRelease = camera;
processorToRelease = processor;
rendererToRelease = previewRenderer;
@@ -666,6 +888,9 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
previewSize = null;
warmResourcesReady = false;
recognitionConfigurationDirty = false;
clearFaceBoxCacheLocked();
lastFaceBoxEmitAt = 0L;
lastPreviewOnlyDetectionAt = 0L;
if (clearActiveSession) {
activeSessionId = null;
activeScene = null;
@@ -712,6 +937,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
Map<String, Object> event = snapshot();
event.put("message", message);
event.put("revision", ++revision);
event.put("eventSequence", ++eventSequence);
event.put("faceBoxes", boxes == null ? new ArrayList<>() : boxes);
if (errorCode != null) event.put("errorCode", errorCode);
for (Listener listener : listeners) listener.onSessionEvent(event);
@@ -724,8 +950,11 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
Map<String, Object> event;
synchronized (sessionLock) {
event = snapshot();
event.put("eventSequence", ++eventSequence);
event.put("message", message);
event.put("faceBoxes", new ArrayList<>());
event.put("faceBoxes", state == State.WARM_IDLE
? recentFaceBoxesLocked(SystemClock.elapsedRealtime())
: new ArrayList<>());
}
for (Listener listener : listeners) listener.onSessionEvent(event);
}
@@ -742,6 +971,7 @@ public final class FaceRecognitionSessionManager implements Camera.PreviewCallba
result.put("scene", activeScene == null ? "" : activeScene);
result.put("state", state.code);
result.put("revision", revision);
result.put("eventSequence", eventSequence);
result.put("width", previewSize == null ? TARGET_WIDTH : previewSize.width);
result.put("height", previewSize == null ? TARGET_HEIGHT : previewSize.height);
result.put("rotation", displayOrientation);
@@ -334,7 +334,7 @@ public class FaceHelper implements FaceListener {
*
* @param rgbNv21 可见光相机预览回传的NV21数据
* @param irNv21 红外相机预览回传的NV21数据
* @param doRecognize 是否进行识别
* @param doRecognize 是否进入口罩、活体、特征提取和比对链;false 时只做人脸检测与跟踪
* @return 实时人脸处理结果,封装添加了一个trackId,trackId的获取依赖于faceId,用于记录人脸序号并保存
*/
public List<FacePreviewInfo> onPreviewFrame(@NonNull byte[] rgbNv21, @Nullable byte[] irNv21, boolean doRecognize) {
@@ -353,7 +353,9 @@ public class FaceHelper implements FaceListener {
clearLeftFace(facePreviewInfoList);
return facePreviewInfoList;
}
if (!onlyDetectLiveness) {
// 结果展示期只需要实时框,跳过口罩与后续识别链可以降低常驻 Camera 的 CPU 占用,
// 也避免口罩引擎的单帧异常把已经成功检测到的人脸误报为空列表。
if (doRecognize && !onlyDetectLiveness) {
code = maskEngine.process(rgbNv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList,
FaceEngine.ASF_MASK_DETECT);
if (code == ErrorInfo.MOK) {
@@ -917,7 +919,7 @@ public class FaceHelper implements FaceListener {
/**
* 异步活体任务的构造函数
*
* @param nv21Data 可见光或红外图像数据
* @param nv21Data 可见光或红外图像数据;构造时复制,避免 Camera 复用缓冲区覆盖异步任务
* @param faceInfo 可见光人脸检测得到的人脸信息
* @param width 图像宽度
* @param height 图像高度
@@ -930,7 +932,7 @@ public class FaceHelper implements FaceListener {
if (nv21Data == null) {
return;
}
this.nv21Data = nv21Data;
this.nv21Data = Arrays.copyOf(nv21Data, nv21Data.length);
this.faceInfo = new FaceInfo(faceInfo.getFaceInfoRgb());
this.width = width;
this.height = height;