diff --git a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt index 3001ac0..383c4dd 100644 --- a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt +++ b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt @@ -194,12 +194,9 @@ class MainActivity : AppCompatActivity() { // 0.14~0.19,0.22 取安全带中枢——深低首票即切(无缝插话 4~6s→3~4s); // 0.22~0.35 浅低维持连续两票,毛刺照旧被误断防护拦下 private val deepLowSwitch = 0.18f - // 最近邻兜底(P1 收紧,2026-09-11):精确匹配不中→top≥0.42 且领先第二名 - // ≥0.06 才归入最相似身份;模糊判新人(修"错并到别人行") + // 切后命名的"确认无人"线(2026-09-16):纯净窗最好分 <0.42 → 判新面孔, + // 发新号。二次识别兜底(最近邻硬归入)已按用户拍板取消 private val verdictFallbackMin = 0.42f - private val nearestMargin = 0.06f - // 滚动检测 fast 空间的兜底阈值(仅影响换行标签显示,endpoint 仍权威) - private val rollingFallbackMin = 0.36f // P3 防漂移:只有高置信同人窗(≥0.5)才吸收进参考,权重也降到 15% private val emaAbsorbMin = 0.50f // 滚动检测在 fast 空间查"这人是谁"仅用于**显示标签**,阈值放宽到 0.4; @@ -1145,12 +1142,10 @@ class MainActivity : AppCompatActivity() { snap.copyOfRange(snap.size - 32000, snap.size) else snap SpeakerRecognition.computeEmbeddingFast(tailSlice, sampleRateInHz) } else null + // 精确匹配制(2026-09-16 用户拍板取消声纹二次识别兜底):建中即得, + // 不中即新——不再对"最相似的人"做二次硬归入(错并之源) val label = if (emb == null) null else - (SpeakerRecognition.registry.search(emb, speakerThreshold) - // 兜底②(P1 收紧):最近邻+优势差;模糊→注册新人 - ?: nearestAttribution( - emb, SpeakerRecognition.registry, verdictFallbackMin - )) + SpeakerRecognition.registry.search(emb, speakerThreshold) ?: run { // 开口即查已在 fast 空间给新人占过号 → 优先复用,防同一 // 人双号(开口 S0 / 结算 S1);命中即顺带补全准确空间镜像 @@ -1170,7 +1165,7 @@ class MainActivity : AppCompatActivity() { Log.i(TAG, "VERDICT-SCORE sid=$sid" + " best=" + "%.3f".format( SpeakerRecognition.registry.bestScore(emb)) + - " thr=$speakerThreshold fallback=$verdictFallbackMin" + + " thr=$speakerThreshold" + " label=$label dur=${snap.size / sampleRateInHz}s") } uiHandler.post { confirmSpeaker(sid, label, emb, fastEmb) } @@ -1546,18 +1541,6 @@ class MainActivity : AppCompatActivity() { return FloatArray(n) { a[it] * 0.85f + b[it] * 0.15f } } - // 最近邻归属(P1):top≥minScore 且对第二名优势≥nearestMargin 才归入, - // 两个已注册者难分高下时返回 null → 按新人处理(宁多勿错) - private fun nearestAttribution( - emb: FloatArray, registry: SpeakerRegistry, minScore: Float, - ): String? { - val r = registry.nearestRank(emb) - val name = r.name ?: return null - return if (r.score >= minScore && r.score - r.secondScore >= nearestMargin) { - name - } else null - } - private fun flattenSamples(): FloatArray = flattenPrefix(samplesBuffer.size) // fresh copy of the first k chunks — never hand the shared buffer to threads @@ -2305,20 +2288,13 @@ class MainActivity : AppCompatActivity() { } } - // 滚动窗口查"这是谁":有 fast 模型时查 fast 镜像(0.4 宽松阈值,仅供显示); - // 没有 base 文件时滚动检测本身回退在准确模型空间 → 直查准确 registry。 - // 修复:base 缺失时镜像库恒空、换行标签全 null、误断防护失效的问题。 + // 滚动窗口查"这是谁"(2026-09-16 精确匹配制):建中即得,不中即 null, + // 不做二次识别。单 CAM++ 下 fastReady=false,走准确注册表(0.5 阈值) private fun rollingSpeakerSearch(emb: FloatArray): String? = if (SpeakerRecognition.fastReady()) SpeakerRecognition.fastRegistry.search(emb, fastSearchThreshold) - ?: nearestAttribution( - emb, SpeakerRecognition.fastRegistry, rollingFallbackMin - ) else SpeakerRecognition.registry.search(emb, speakerThreshold) - ?: nearestAttribution( - emb, SpeakerRecognition.registry, verdictFallbackMin - ) // ③ 滚动检测查到了已知人、但当前行还没有归属(占位"未识别"): // 直接把活跃归属采纳为他,等 endpoint 权威判定给行冠名,不再多切一行