|
|
@ -48,6 +48,9 @@ class DoubaoE2ETranslateHelper( |
|
|
/** 增量音频片段到达(服务端返回的目标音频) */ |
|
|
/** 增量音频片段到达(服务端返回的目标音频) */ |
|
|
fun onPartialAudio(sessionId: String, data: ByteArray) |
|
|
fun onPartialAudio(sessionId: String, data: ByteArray) |
|
|
|
|
|
|
|
|
|
|
|
/** 单句 TTS 音频结束(TTSSentenceEnd):本句合成音频已全部经 onPartialAudio 下发。默认空实现,可选处理。 */ |
|
|
|
|
|
fun onTtsSentenceEnd(sessionId: String) {} |
|
|
|
|
|
|
|
|
/** 会话结束,返回完整文本与完整合成音频 */ |
|
|
/** 会话结束,返回完整文本与完整合成音频 */ |
|
|
fun onSessionFinished(sessionId: String, finalText: String, finalAudio: ByteArray) |
|
|
fun onSessionFinished(sessionId: String, finalText: String, finalAudio: ByteArray) |
|
|
|
|
|
|
|
|
@ -162,6 +165,8 @@ class DoubaoE2ETranslateHelper( |
|
|
Log.d(TAG, "startContinuousTranslation: sessionId=${sessionId}") |
|
|
Log.d(TAG, "startContinuousTranslation: sessionId=${sessionId}") |
|
|
recvAudio.reset() |
|
|
recvAudio.reset() |
|
|
recvText.clear() |
|
|
recvText.clear() |
|
|
|
|
|
// 丢弃上一 session 未冲刷的残留(mid-sentence 重连时),避免旧尾音串到新 session 头部 |
|
|
|
|
|
audioChunkBuffer = ByteArray(0) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
val connId = UUID.randomUUID().toString() |
|
|
val connId = UUID.randomUUID().toString() |
|
|
@ -257,6 +262,8 @@ class DoubaoE2ETranslateHelper( |
|
|
|
|
|
|
|
|
if (event == Type.SessionFinished) { |
|
|
if (event == Type.SessionFinished) { |
|
|
Log.d(TAG, "onMessage: SessionFinished") |
|
|
Log.d(TAG, "onMessage: SessionFinished") |
|
|
|
|
|
// 兜底:本句若缺 TTSSentenceEnd(事件不完整),会话结束时把残留真实尾音一并冲刷出去,避免整段被截。 |
|
|
|
|
|
flushAudioChunkBuffer() |
|
|
val finalText = recvText.joinToString(" ") |
|
|
val finalText = recvText.joinToString(" ") |
|
|
val finalAudio = recvAudio.toByteArray() |
|
|
val finalAudio = recvAudio.toByteArray() |
|
|
callback?.onSessionFinished(sessionId, finalText, finalAudio) |
|
|
callback?.onSessionFinished(sessionId, finalText, finalAudio) |
|
|
@ -264,6 +271,16 @@ class DoubaoE2ETranslateHelper( |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 单句 TTS 音频结束:本句 TTSResponse 已全部到达(经下方 onPartialAudio 下发), |
|
|
|
|
|
// 通知上层做句尾补偿(往编码器补静音,把滞留的不足一帧真实尾音顶出)。TTSSentenceEnd 不带音频/文本。 |
|
|
|
|
|
if (event == Type.TTSSentenceEnd) { |
|
|
|
|
|
Log.d(TAG, "onMessage: TTSSentenceEnd") |
|
|
|
|
|
// 先冲刷 WS 层滞留的不足一块(1280B)真实尾音进编码器,再触发句尾补偿静音,避免句末被截。 |
|
|
|
|
|
flushAudioChunkBuffer() |
|
|
|
|
|
callback?.onTtsSentenceEnd(sessionId) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (data.isNotEmpty()) { |
|
|
if (data.isNotEmpty()) { |
|
|
recvAudio.write(data) |
|
|
recvAudio.write(data) |
|
|
Log.d(TAG, "onMessage: partial audio appended size=${data.size}") |
|
|
Log.d(TAG, "onMessage: partial audio appended size=${data.size}") |
|
|
@ -387,6 +404,21 @@ class DoubaoE2ETranslateHelper( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 冲刷 [audioChunkBuffer] 里不足对齐粒度(multiple=1280)的残留字节:原样(不补零)全部经 onPartialAudio 下发。 |
|
|
|
|
|
* [processAudioChunk] 只发送 1280 整数倍,句末最后不足一块的真实尾音(最多 <1280B ≈ 40ms@16k/16bit)会滞留在缓冲里; |
|
|
|
|
|
* 若不冲刷,这段尾音要么串到下一句开头、要么在句末/会话末彻底丢失——表现为「部分音频结尾被截」, |
|
|
|
|
|
* 且与句尾补偿静音(feedTailSilence)无关(那段真实尾音根本没进编码器,加大 padding 也补不回来)。 |
|
|
|
|
|
* 须在句尾补偿静音之前调用,保证真实尾音先进编码器、静音再顶在其后。 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun flushAudioChunkBuffer() { |
|
|
|
|
|
val remain = audioChunkBuffer |
|
|
|
|
|
if (remain.isEmpty()) return |
|
|
|
|
|
audioChunkBuffer = ByteArray(0) |
|
|
|
|
|
Log.d(TAG, "flushAudioChunkBuffer: 冲刷残留尾音 size=${remain.size}") |
|
|
|
|
|
callback?.onPartialAudio(sessionId, remain) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 推送一段 PCM/WAV 音频数据到服务端。 |
|
|
* 推送一段 PCM/WAV 音频数据到服务端。 |
|
|
*/ |
|
|
*/ |
|
|
|