[真实全双工运行时]:完成run-agent-live可打断语音闭环,包含持续监听、软件回声抑制和播放取消验证
This commit is contained in:
+77
-7
@@ -10,19 +10,21 @@ from owner_voice_pet.transport import MemoryAudioTransport
|
||||
from owner_voice_pet.vad import EnergyVadProvider
|
||||
|
||||
|
||||
def speech_frame(idx: int, speaker_id: str, partial: str = "等一下") -> AudioFrame:
|
||||
def speech_frame(idx: int, speaker_id: str, partial: str | None = "等一下") -> AudioFrame:
|
||||
metadata = {
|
||||
"duration_ms": 20,
|
||||
"speech": True,
|
||||
"speaker_id": speaker_id,
|
||||
}
|
||||
if partial is not None:
|
||||
metadata["partial_transcript"] = partial
|
||||
return AudioFrame(
|
||||
b"\xff\x7f" * 320,
|
||||
16000,
|
||||
1,
|
||||
idx * 20,
|
||||
idx,
|
||||
{
|
||||
"duration_ms": 20,
|
||||
"speech": True,
|
||||
"speaker_id": speaker_id,
|
||||
"partial_transcript": partial,
|
||||
},
|
||||
metadata,
|
||||
)
|
||||
|
||||
|
||||
@@ -127,6 +129,74 @@ class BargeInTests(unittest.TestCase):
|
||||
|
||||
self.assertTrue(monitor.interrupted)
|
||||
|
||||
def test_async_monitor_interrupts_without_realtime_partial(self) -> None:
|
||||
gate = BargeInSpeakerGate(
|
||||
enabled=True,
|
||||
user_similarity_threshold=0.62,
|
||||
assistant_reject_threshold=0.72,
|
||||
min_rms=0.001,
|
||||
)
|
||||
gate.remember_user_segment(segment_for_speaker("owner"))
|
||||
transport = MemoryAudioTransport([speech_frame(1, "owner", None), speech_frame(2, "owner", None)])
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=None,
|
||||
speaker_gate=gate,
|
||||
assistant_profile=gate.assistant_profile(segment_for_speaker("assistant")),
|
||||
echo_guard_ms=0,
|
||||
min_speech_ms=40,
|
||||
listen_interval_ms=1,
|
||||
)
|
||||
monitor.vad_provider.load()
|
||||
|
||||
monitor.start()
|
||||
deadline = time.monotonic() + 1
|
||||
while not monitor.interrupted and time.monotonic() < deadline:
|
||||
time.sleep(0.005)
|
||||
monitor.stop()
|
||||
|
||||
self.assertTrue(monitor.interrupted)
|
||||
self.assertEqual(len(monitor.pending_frames()), 2)
|
||||
|
||||
def test_render_reference_echo_does_not_interrupt(self) -> None:
|
||||
gate = BargeInSpeakerGate(
|
||||
enabled=True,
|
||||
user_similarity_threshold=0.62,
|
||||
assistant_reject_threshold=0.72,
|
||||
min_rms=0.001,
|
||||
)
|
||||
assistant = AudioSegment(b"\x01\x20\x02\x20" * 320, 16000, 1, 0, 40)
|
||||
echo_frame = AudioFrame(
|
||||
assistant.pcm[: 640 * 2],
|
||||
16000,
|
||||
1,
|
||||
20,
|
||||
1,
|
||||
{"duration_ms": 40, "speech": True},
|
||||
)
|
||||
transport = MemoryAudioTransport([echo_frame])
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=None,
|
||||
speaker_gate=gate,
|
||||
assistant_profile=gate.assistant_profile(assistant),
|
||||
assistant_reference=assistant,
|
||||
echo_guard_ms=0,
|
||||
min_speech_ms=40,
|
||||
listen_interval_ms=1,
|
||||
)
|
||||
monitor.vad_provider.load()
|
||||
|
||||
monitor.start()
|
||||
time.sleep(0.05)
|
||||
monitor.stop()
|
||||
|
||||
self.assertFalse(monitor.interrupted)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user