恒定滞后喂料:换人切点零串行(用户拍板,静音轮替+无缝都治):
- 音频先进 pendingBuf,识别流滞后 feedLagSamples(1.5s)喂入;分析窗口保持实时(判定时机不变) - 开口即查/SWITCH-FORCE 判定即置 cutRequested:录制线程冻结缓冲→强制断句→补喂冻结音频给新流,旧句在切点干净收尾;旧句已结算则直接补喂开新句;Stop 前补齐缓冲防丢尾 - 效果:静音轮替渗漏1.2s→≈0;无缝插话渗漏2~4s→≈1~2s(受缓冲深度约束);代价=全局实时字幕统一晚约1.5s;日志 ONSET-HOLD/ONSET-FLUSH 对账
This commit is contained in:
parent
cea9892426
commit
97dfa9de17
|
|
@ -219,6 +219,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
private val onsetEnabled = true
|
private val onsetEnabled = true
|
||||||
private val onsetGapSamples = 9600 // 0.6s 前置静音
|
private val onsetGapSamples = 9600 // 0.6s 前置静音
|
||||||
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
private val onsetMinSamples = 19200 // 1.2s 开口窗
|
||||||
|
// 喂料恒定滞后(2026-09-16):识别流滞后 1.2s 喂入,切点前的音频可改道;
|
||||||
|
// 全局字幕延迟 ~1.2s 的代价换取换人零串行
|
||||||
|
private val feedLagSamples = 24000 // 1.5s:覆盖判定链(1.2s采集+计算)的
|
||||||
|
// 余量,开口渗漏≈0;全局字幕延迟即此值,调参范围 1.2~1.5s
|
||||||
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
@Volatile private var onsetSeq = 0L // 行切换/会话开始作废在途开口判定
|
||||||
private var pendingOnsetLabel: String? = null // 主线程:下一新行预置标签
|
private var pendingOnsetLabel: String? = null // 主线程:下一新行预置标签
|
||||||
private var pendingOnsetLabelAt = 0L // 预置标签时间戳(过期作废)
|
private var pendingOnsetLabelAt = 0L // 预置标签时间戳(过期作废)
|
||||||
|
|
@ -230,6 +234,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
// 不再等停顿,直接强判 endpoint 断句。单窗毛刺(音量/瞬时)攒不满 2。
|
// 不再等停顿,直接强判 endpoint 断句。单窗毛刺(音量/瞬时)攒不满 2。
|
||||||
@Volatile private var rollingLowStreak = 0
|
@Volatile private var rollingLowStreak = 0
|
||||||
@Volatile private var pendingForceEndpoint = false
|
@Volatile private var pendingForceEndpoint = false
|
||||||
|
// 滞后喂料的切点冻结请求(判定线程置位,录制线程消费,2026-09-16)
|
||||||
|
@Volatile private var cutRequested = false
|
||||||
private val forceSwitchStreaks = 2
|
private val forceSwitchStreaks = 2
|
||||||
// 强切冷却(仅 speakerCheckHandler 线程访问,无需 volatile)
|
// 强切冷却(仅 speakerCheckHandler 线程访问,无需 volatile)
|
||||||
private var lastForceSwitchAtMs = 0L
|
private var lastForceSwitchAtMs = 0L
|
||||||
|
|
@ -630,6 +636,16 @@ class MainActivity : AppCompatActivity() {
|
||||||
var newSinceCheck = 0
|
var newSinceCheck = 0
|
||||||
var silenceRun = 0
|
var silenceRun = 0
|
||||||
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
var onsetRun = 0 // 开口即查:静音后已累积的开口语音采样数
|
||||||
|
// 恒定滞后喂料(2026-09-16):音频先进 pendingBuf,滞后 feedLagSamples
|
||||||
|
// 再喂识别流/样本缓冲(窗口分析保持实时)。换人决定时冻结缓冲,
|
||||||
|
// 断句后补喂给新流——新人开头音频从不进入旧句子。
|
||||||
|
val pendingBuf = java.util.ArrayDeque<FloatArray>()
|
||||||
|
var pendingSamples = 0
|
||||||
|
var frozen = false
|
||||||
|
fun feedChunk(ch: FloatArray) {
|
||||||
|
pushChunk(ch)
|
||||||
|
stream.acceptWaveform(ch, sampleRate = sampleRateInHz)
|
||||||
|
}
|
||||||
|
|
||||||
while (isRecording) {
|
while (isRecording) {
|
||||||
val ret = audioRecord?.read(buffer, 0, buffer.size)
|
val ret = audioRecord?.read(buffer, 0, buffer.size)
|
||||||
|
|
@ -639,11 +655,26 @@ class MainActivity : AppCompatActivity() {
|
||||||
val wasSilent = silenceRun >= onsetGapSamples
|
val wasSilent = silenceRun >= onsetGapSamples
|
||||||
silenceRun = if (blockRms < silenceRms)
|
silenceRun = if (blockRms < silenceRms)
|
||||||
silenceRun + ret else 0
|
silenceRun + ret else 0
|
||||||
pushChunk(samples)
|
window.push(samples) // 分析窗口实时;识别流滞后喂
|
||||||
window.push(samples)
|
if (!frozen && cutRequested) {
|
||||||
|
cutRequested = false
|
||||||
|
frozen = true
|
||||||
|
if (sentenceId != -1L) pendingForceEndpoint = true
|
||||||
|
Log.i(TAG, "ONSET-HOLD frozen pending=" +
|
||||||
|
"${pendingSamples * 1000 / sampleRateInHz}ms")
|
||||||
|
}
|
||||||
|
pendingBuf.addLast(samples)
|
||||||
|
pendingSamples += samples.size
|
||||||
|
if (!frozen) {
|
||||||
|
while (pendingSamples > feedLagSamples) {
|
||||||
|
val head = pendingBuf.removeFirst()
|
||||||
|
pendingSamples -= head.size
|
||||||
|
feedChunk(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 开口即查(2026-09-16):≥0.6s 静音后的开口攒 1.2s 立即判一次
|
// 开口即查(2026-09-16):≥0.6s 静音后的开口攒 1.2s 立即判一次
|
||||||
if (onsetEnabled && enableRollingSwitch && speakersEnabled) {
|
if (onsetEnabled && enableRollingSwitch && speakersEnabled && !frozen) {
|
||||||
if (blockRms >= silenceRms) {
|
if (blockRms >= silenceRms) {
|
||||||
if (wasSilent && onsetRun == 0) onsetRun = ret
|
if (wasSilent && onsetRun == 0) onsetRun = ret
|
||||||
else if (onsetRun > 0) onsetRun += ret
|
else if (onsetRun > 0) onsetRun += ret
|
||||||
|
|
@ -673,6 +704,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
" label=$label kind=" +
|
" label=$label kind=" +
|
||||||
(if (known != null) "known" else "new") +
|
(if (known != null) "known" else "new") +
|
||||||
" active=${active ?: "-"}")
|
" active=${active ?: "-"}")
|
||||||
|
cutRequested = true
|
||||||
uiHandler.post { onOnsetSwitch(label) }
|
uiHandler.post { onOnsetSwitch(label) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -685,7 +717,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
// P2(2026-09-11):跨句常驻检测,参考 embedding 为"行级"
|
// P2(2026-09-11):跨句常驻检测,参考 embedding 为"行级"
|
||||||
// (appendNewLine 时重置)——新人开口后第一个周期内即可触发换行,
|
// (appendNewLine 时重置)——新人开口后第一个周期内即可触发换行,
|
||||||
// 句间不再依赖 endpoint 的迟到判定
|
// 句间不再依赖 endpoint 的迟到判定
|
||||||
if (enableRollingSwitch && speakersEnabled) {
|
if (enableRollingSwitch && speakersEnabled && !frozen) {
|
||||||
newSinceCheck += ret
|
newSinceCheck += ret
|
||||||
if (newSinceCheck >= checkStep) {
|
if (newSinceCheck >= checkStep) {
|
||||||
newSinceCheck = 0
|
newSinceCheck = 0
|
||||||
|
|
@ -808,6 +840,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
" streak>=$forceSwitchStreaks") +
|
" streak>=$forceSwitchStreaks") +
|
||||||
" label=$label" +
|
" label=$label" +
|
||||||
" owner=sid$owner")
|
" owner=sid$owner")
|
||||||
|
cutRequested = true
|
||||||
uiHandler.post {
|
uiHandler.post {
|
||||||
onOnsetSwitch(label)
|
onOnsetSwitch(label)
|
||||||
}
|
}
|
||||||
|
|
@ -823,7 +856,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.acceptWaveform(samples, sampleRate = sampleRateInHz)
|
|
||||||
while (onlineRecognizer.isReady(stream)) {
|
while (onlineRecognizer.isReady(stream)) {
|
||||||
onlineRecognizer.decode(stream)
|
onlineRecognizer.decode(stream)
|
||||||
}
|
}
|
||||||
|
|
@ -909,9 +941,46 @@ class MainActivity : AppCompatActivity() {
|
||||||
uiHandler.post { closeOnsetLineIfPending() }
|
uiHandler.post { closeOnsetLineIfPending() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 冻结缓冲在断句/重置后补喂给新流(时间轴连续)
|
||||||
|
if (frozen) {
|
||||||
|
var drained = 0
|
||||||
|
while (pendingBuf.isNotEmpty()) {
|
||||||
|
val ch = pendingBuf.removeFirst()
|
||||||
|
drained += ch.size
|
||||||
|
feedChunk(ch)
|
||||||
|
}
|
||||||
|
pendingSamples = 0
|
||||||
|
frozen = false
|
||||||
|
Log.i(TAG, "ONSET-FLUSH " +
|
||||||
|
"${drained * 1000 / sampleRateInHz}ms -> new stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冻结时无句可断(旧句已结算)→ 直接补喂开新句
|
||||||
|
if (frozen && sentenceId == -1L) {
|
||||||
|
var drained = 0
|
||||||
|
while (pendingBuf.isNotEmpty()) {
|
||||||
|
val ch = pendingBuf.removeFirst()
|
||||||
|
drained += ch.size
|
||||||
|
feedChunk(ch)
|
||||||
|
}
|
||||||
|
pendingSamples = 0
|
||||||
|
frozen = false
|
||||||
|
Log.i(TAG, "ONSET-FLUSH(no-sentence) " +
|
||||||
|
"${drained * 1000 / sampleRateInHz}ms")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Stop:补齐滞后缓冲(否则丢最后 ~1.2s),解码后再走软 endpoint
|
||||||
|
if (pendingBuf.isNotEmpty()) {
|
||||||
|
while (pendingBuf.isNotEmpty()) feedChunk(pendingBuf.removeFirst())
|
||||||
|
pendingSamples = 0
|
||||||
|
frozen = false
|
||||||
|
}
|
||||||
|
while (onlineRecognizer.isReady(stream)) {
|
||||||
|
onlineRecognizer.decode(stream)
|
||||||
|
}
|
||||||
|
|
||||||
// Stop 兜底(软 endpoint):最后一句未完的转写同样走定格→判定→精修,
|
// Stop 兜底(软 endpoint):最后一句未完的转写同样走定格→判定→精修,
|
||||||
// 否则丢半句 + 灰"未识别"行滞留(用户反馈 2026-09-11)
|
// 否则丢半句 + 灰"未识别"行滞留(用户反馈 2026-09-11)
|
||||||
if (sentenceId != -1L) {
|
if (sentenceId != -1L) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue