[异步播报打断]:完成播放中麦克风监听和音色隔离,包含后台监听、助手回放抑制和用户音色打断测试
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from owner_voice_pet.barge_in import AsyncBargeInMonitor, BargeInSpeakerGate
|
||||
from owner_voice_pet.models import AudioFrame, AudioSegment
|
||||
from owner_voice_pet.stt import MetadataSttProvider
|
||||
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:
|
||||
return AudioFrame(
|
||||
b"\xff\x7f" * 320,
|
||||
16000,
|
||||
1,
|
||||
idx * 20,
|
||||
idx,
|
||||
{
|
||||
"duration_ms": 20,
|
||||
"speech": True,
|
||||
"speaker_id": speaker_id,
|
||||
"partial_transcript": partial,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def segment_for_speaker(speaker_id: str) -> AudioSegment:
|
||||
return AudioSegment(
|
||||
b"\xff\x7f" * 640,
|
||||
16000,
|
||||
1,
|
||||
0,
|
||||
40,
|
||||
{"speaker_id": speaker_id},
|
||||
)
|
||||
|
||||
|
||||
class BargeInTests(unittest.TestCase):
|
||||
def test_async_monitor_ignores_assistant_playback_voice(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, "assistant"), speech_frame(2, "assistant")])
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=MetadataSttProvider(),
|
||||
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()
|
||||
time.sleep(0.05)
|
||||
monitor.stop()
|
||||
|
||||
self.assertFalse(monitor.interrupted)
|
||||
self.assertEqual(monitor.pending_frames(), [])
|
||||
|
||||
def test_async_monitor_accepts_user_voice_and_buffers_frames(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"), speech_frame(2, "owner")])
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=MetadataSttProvider(),
|
||||
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([frame.metadata["speaker_id"] for frame in monitor.pending_frames()], ["owner", "owner"])
|
||||
|
||||
def test_user_profile_missing_falls_back_to_non_assistant_with_partial(self) -> None:
|
||||
gate = BargeInSpeakerGate(
|
||||
enabled=True,
|
||||
user_similarity_threshold=0.62,
|
||||
assistant_reject_threshold=0.72,
|
||||
min_rms=0.001,
|
||||
)
|
||||
transport = MemoryAudioTransport([speech_frame(1, "guest"), speech_frame(2, "guest")])
|
||||
transport.start_input()
|
||||
monitor = AsyncBargeInMonitor(
|
||||
transport=transport,
|
||||
vad_provider=EnergyVadProvider(threshold=1),
|
||||
realtime_stt=MetadataSttProvider(),
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user