乐观快切(用户拍板:第一次识别先切、出错由并回自愈):
- 开口即查去掉'等准确复核'挡切换:fast 偏离即切+占号,复核降级为善后 - 新增快速滚动通道:2s 尾窗/1s 周期,快模型偏离锚点 0.32 即乐观快切(无缝场景检测延迟 3~5s→1.5~2.5s 目标) - accurateRecheckAfterSwitch 善后:认出当前人→留痕(判定链并回自愈);真新人→补写准确空间防重发号;认出别人→留痕(判定链迁回) - 冷却/锚点自弃/滞后喂料/标签钉住全套保留;fastRollDrop 为灵敏度钮
This commit is contained in:
parent
699680a347
commit
70acc03ce4
|
|
@ -221,6 +221,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
||||||
// 喂料恒定滞后(2026-09-16):识别流滞后 1.2s 喂入,切点前的音频可改道;
|
// 喂料恒定滞后(2026-09-16):识别流滞后 1.2s 喂入,切点前的音频可改道;
|
||||||
// 全局字幕延迟 ~1.2s 的代价换取换人零串行
|
// 全局字幕延迟 ~1.2s 的代价换取换人零串行
|
||||||
|
// 快速滚动通道(2026-09-16 用户拍板乐观快切):2s 尾窗/1s 周期/偏离
|
||||||
|
// 0.32 即切;准确复核只善后,切错由判定链并回自愈
|
||||||
|
private val fastRollEnabled = true
|
||||||
|
private val fastRollDrop = 0.32f
|
||||||
|
private val fastStepSamples = 16000 // 1s
|
||||||
|
private val fastWinSamples = 32000 // 2s 尾窗
|
||||||
|
private val fastMinSamples = 24000 // ≥1.5s 才判
|
||||||
private val feedLagSamples = 24000 // 1.5s:覆盖判定链(1.2s采集+计算)的
|
private val feedLagSamples = 24000 // 1.5s:覆盖判定链(1.2s采集+计算)的
|
||||||
// 余量,开口渗漏≈0;全局字幕延迟即此值,调参范围 1.2~1.5s
|
// 余量,开口渗漏≈0;全局字幕延迟即此值,调参范围 1.2~1.5s
|
||||||
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
||||||
|
|
@ -636,6 +643,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
var newSinceCheck = 0
|
var newSinceCheck = 0
|
||||||
var silenceRun = 0
|
var silenceRun = 0
|
||||||
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
||||||
|
var fastSince = 0 // 快速滚动通道:1s 周期的采样计数
|
||||||
// 恒定滞后喂料(2026-09-16):音频先进 pendingBuf,滞后 feedLagSamples
|
// 恒定滞后喂料(2026-09-16):音频先进 pendingBuf,滞后 feedLagSamples
|
||||||
// 再喂识别流/样本缓冲(窗口分析保持实时)。换人决定时冻结缓冲,
|
// 再喂识别流/样本缓冲(窗口分析保持实时)。换人决定时冻结缓冲,
|
||||||
// 断句后补喂给新流——新人开头音频从不进入旧句子。
|
// 断句后补喂给新流——新人开头音频从不进入旧句子。
|
||||||
|
|
@ -688,48 +696,25 @@ class MainActivity : AppCompatActivity() {
|
||||||
speakerCheckHandler.post {
|
speakerCheckHandler.post {
|
||||||
if (rseq != onsetSeq) return@post
|
if (rseq != onsetSeq) return@post
|
||||||
val anchor = windowRefEmb ?: return@post
|
val anchor = windowRefEmb ?: return@post
|
||||||
// 一级:快锚点相似度(灵敏);贴近锚点=同人,免准确复核
|
|
||||||
val fastEmb = SpeakerRecognition.computeEmbeddingFast(
|
val fastEmb = SpeakerRecognition.computeEmbeddingFast(
|
||||||
slice, sampleRateInHz) ?: return@post
|
slice, sampleRateInHz) ?: return@post
|
||||||
if (rseq != onsetSeq) return@post
|
if (rseq != onsetSeq) return@post
|
||||||
val simFast = SpeakerRecognition.similarity(
|
val simFast = SpeakerRecognition.similarity(
|
||||||
fastEmb, anchor)
|
fastEmb, anchor)
|
||||||
if (simFast >= emaAbsorbMin) return@post
|
if (simFast >= emaAbsorbMin) return@post
|
||||||
// 二级:准确空间复核(2026-09-16 误报实锤:A 暂停
|
// 乐观快切(2026-09-16 用户拍板:第一次识别
|
||||||
// 续说,fast 短窗 simA<0.5 误判新人→A 的续说句被切
|
// 先切,出错由判定链并回自愈;准确复核不再
|
||||||
// 成"已识别人2"首句)。同人暂停后短窗声纹弱,fast
|
// 挡在切换前,只做善后)
|
||||||
// 不可信;CAM++ 准确空间同人 0.77~0.93 强分离:
|
|
||||||
// 注册表认得出(且是当前人)→ 撤销切换;认得出别人
|
|
||||||
// → 切给他;谁都认不出 → 才当新人
|
|
||||||
val emb = SpeakerRecognition.computeEmbedding(
|
|
||||||
slice, sampleRateInHz) ?: return@post
|
|
||||||
if (rseq != onsetSeq) return@post
|
|
||||||
val active = activeLabelForWindow
|
val active = activeLabelForWindow
|
||||||
val known = SpeakerRecognition.registry.search(
|
val label = SpeakerRecognition.nextAutoLabel().also {
|
||||||
emb, speakerThreshold)
|
SpeakerRecognition.fastRegistry.addAuto(it, fastEmb)
|
||||||
?: nearestAttribution(
|
|
||||||
emb, SpeakerRecognition.registry,
|
|
||||||
verdictFallbackMin)
|
|
||||||
if (known != null && known == active) {
|
|
||||||
// 复核撤销(同人暂停续说):留痕便于验证
|
|
||||||
Log.i(TAG, "ONSET-CANCEL fastSim=" +
|
|
||||||
"%.2f".format(simFast) +
|
|
||||||
" label=$known(active)")
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
val label = known
|
|
||||||
?: SpeakerRecognition.nextAutoLabel().also {
|
|
||||||
SpeakerRecognition.registry.addAuto(it, emb)
|
|
||||||
SpeakerRecognition.fastRegistry.addAuto(
|
|
||||||
it, fastEmb)
|
|
||||||
}
|
}
|
||||||
Log.i(TAG, "ONSET-SWITCH fastSim=" +
|
Log.i(TAG, "ONSET-SWITCH fastSim=" +
|
||||||
"%.2f".format(simFast) +
|
"%.2f".format(simFast) +
|
||||||
" label=$label kind=" +
|
" label=$label active=${active ?: "-"}")
|
||||||
(if (known != null) "known" else "new") +
|
|
||||||
" active=${active ?: "-"}")
|
|
||||||
cutRequested = true
|
cutRequested = true
|
||||||
uiHandler.post { onOnsetSwitch(label) }
|
uiHandler.post { onOnsetSwitch(label) }
|
||||||
|
accurateRecheckAfterSwitch(slice, label, active, "ONSET")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -738,6 +723,51 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 快速滚动通道(2026-09-16 用户拍板:第一次识别先切、错由
|
||||||
|
// 并回自愈):2s 尾窗/1s 周期;快模型偏离锚点即乐观快切;
|
||||||
|
// 准确复核只善后(注册/留痕);3s 冷却与强切通道共用
|
||||||
|
if (fastRollEnabled && enableRollingSwitch && speakersEnabled &&
|
||||||
|
!frozen
|
||||||
|
) {
|
||||||
|
fastSince += ret
|
||||||
|
if (fastSince >= fastStepSamples) {
|
||||||
|
fastSince = 0
|
||||||
|
val winF = window.snapshot()
|
||||||
|
val nF = fastWinSamples.coerceAtMost(winF.size)
|
||||||
|
if (nF >= fastMinSamples && rmsOf(winF) >= minWindowRms) {
|
||||||
|
val tailF = winF.copyOfRange(winF.size - nF, winF.size)
|
||||||
|
val rseq = onsetSeq
|
||||||
|
speakerCheckHandler.post {
|
||||||
|
if (rseq != onsetSeq) return@post
|
||||||
|
val anchor = windowRefEmb ?: return@post
|
||||||
|
val emb = SpeakerRecognition.computeEmbeddingFast(
|
||||||
|
tailF, sampleRateInHz) ?: return@post
|
||||||
|
if (rseq != onsetSeq) return@post
|
||||||
|
val sim = SpeakerRecognition.similarity(emb, anchor)
|
||||||
|
if (sim >= fastRollDrop) return@post
|
||||||
|
val nowMs = SystemClock.elapsedRealtime()
|
||||||
|
if (nowMs - lastForceSwitchAtMs < 3_000L) {
|
||||||
|
Log.i(TAG, "FASTROLL suppressed (cooldown)")
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
lastForceSwitchAtMs = nowMs
|
||||||
|
val active = activeLabelForWindow
|
||||||
|
val label = SpeakerRecognition.nextAutoLabel().also {
|
||||||
|
SpeakerRecognition.fastRegistry.addAuto(it, emb)
|
||||||
|
}
|
||||||
|
Log.i(TAG, "FASTROLL-SWITCH sim=" +
|
||||||
|
"%.2f".format(sim) +
|
||||||
|
" label=$label active=${active ?: "-"}")
|
||||||
|
cutRequested = true
|
||||||
|
uiHandler.post { onOnsetSwitch(label) }
|
||||||
|
accurateRecheckAfterSwitch(tailF, label, active, "FASTROLL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fastSince = 0
|
||||||
|
}
|
||||||
|
|
||||||
// P2(2026-09-11):跨句常驻检测,参考 embedding 为"行级"
|
// P2(2026-09-11):跨句常驻检测,参考 embedding 为"行级"
|
||||||
// (appendNewLine 时重置)——新人开口后第一个周期内即可触发换行,
|
// (appendNewLine 时重置)——新人开口后第一个周期内即可触发换行,
|
||||||
// 句间不再依赖 endpoint 的迟到判定
|
// 句间不再依赖 endpoint 的迟到判定
|
||||||
|
|
@ -1759,6 +1789,26 @@ class MainActivity : AppCompatActivity() {
|
||||||
Log.i(TAG, "ONSET-CLOSE no-sentence forced endpoint")
|
Log.i(TAG, "ONSET-CLOSE no-sentence forced endpoint")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 乐观切换后的准确空间善后(2026-09-16 用户拍板:先切后改):切换已
|
||||||
|
* 发出,本函数只做善后——认得出是当前人 → 留痕(切错由判定链"并回"
|
||||||
|
* 自愈);真新人 → 补写准确空间(防后续重发号);认出别人 → 留痕(判定
|
||||||
|
* 链迁回)。在 speakerCheckHandler 线程执行。 */
|
||||||
|
private fun accurateRecheckAfterSwitch(
|
||||||
|
samples: FloatArray, label: String, active: String?, tag: String,
|
||||||
|
) {
|
||||||
|
val acc = SpeakerRecognition.computeEmbedding(samples, sampleRateInHz)
|
||||||
|
?: return
|
||||||
|
val pre = SpeakerRecognition.registry.search(acc, speakerThreshold)
|
||||||
|
?: nearestAttribution(acc, SpeakerRecognition.registry, verdictFallbackMin)
|
||||||
|
when {
|
||||||
|
pre != null && pre == active ->
|
||||||
|
Log.i(TAG, tag + "-CANCEL-LATE known=$pre(active) revert-via-verdict")
|
||||||
|
pre != null ->
|
||||||
|
Log.i(TAG, tag + "-RECHECK known=$pre label=$label (verdict migrates)")
|
||||||
|
else -> SpeakerRecognition.registry.addAuto(label, acc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun appendNewLine(label: String?): SubtitleLine {
|
private fun appendNewLine(label: String?): SubtitleLine {
|
||||||
// 新行出现即封掉之前的开放行,并把其中"未定格的进行中句子"整体迁移
|
// 新行出现即封掉之前的开放行,并把其中"未定格的进行中句子"整体迁移
|
||||||
// 过来 —— 硬保证 in-progress 只可能发生在最底行(修复:声纹判定搬家后,
|
// 过来 —— 硬保证 in-progress 只可能发生在最底行(修复:声纹判定搬家后,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue