[低延迟端点]:完成首句保留和快速结束修正,包含ACK缓冲策略、批量读帧和主说话人端点回归测试
This commit is contained in:
@@ -62,6 +62,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
"pipeline_mode": config.pipeline_mode,
|
||||
"endpoint_mode": config.endpoint_mode,
|
||||
"speaker_profile_ms": config.speaker_profile_ms,
|
||||
"speaker_profile_min_ms": config.speaker_profile_min_ms,
|
||||
"speaker_absent_ms": config.speaker_absent_ms,
|
||||
"speaker_similarity_threshold": config.speaker_similarity_threshold,
|
||||
"speaker_min_rms": config.speaker_min_rms,
|
||||
@@ -164,6 +165,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
pipeline_mode=config.pipeline_mode,
|
||||
endpoint_mode=config.endpoint_mode,
|
||||
speaker_profile_ms=config.speaker_profile_ms,
|
||||
speaker_profile_min_ms=config.speaker_profile_min_ms,
|
||||
speaker_absent_ms=config.speaker_absent_ms,
|
||||
speaker_similarity_threshold=config.speaker_similarity_threshold,
|
||||
speaker_min_rms=config.speaker_min_rms,
|
||||
|
||||
@@ -25,10 +25,11 @@ class AppConfig:
|
||||
wake_kws_threshold: float = 0.15
|
||||
wake_kws_score: float = 1.0
|
||||
wake_ack_text: str = "我在"
|
||||
post_playback_drain_ms: int = 50
|
||||
post_playback_drain_ms: int = 0
|
||||
pipeline_mode: str = "live_turn_based"
|
||||
endpoint_mode: str = "primary_speaker"
|
||||
speaker_profile_ms: int = 600
|
||||
speaker_profile_min_ms: int = 120
|
||||
speaker_absent_ms: int = 300
|
||||
speaker_similarity_threshold: float = 0.70
|
||||
speaker_min_rms: float = 0.012
|
||||
@@ -73,10 +74,11 @@ class AppConfig:
|
||||
wake_kws_threshold=float(get("WAKE_KWS_THRESHOLD", "0.15") or "0.15"),
|
||||
wake_kws_score=float(get("WAKE_KWS_SCORE", "1.0") or "1.0"),
|
||||
wake_ack_text=get("WAKE_ACK_TEXT", "我在") or "我在",
|
||||
post_playback_drain_ms=int(get("POST_PLAYBACK_DRAIN_MS", "50") or "50"),
|
||||
post_playback_drain_ms=int(get("POST_PLAYBACK_DRAIN_MS", "0") or "0"),
|
||||
pipeline_mode=(get("PIPELINE_MODE", "live_turn_based") or "live_turn_based").lower(),
|
||||
endpoint_mode=(get("ENDPOINT_MODE", "primary_speaker") or "primary_speaker").lower(),
|
||||
speaker_profile_ms=int(get("SPEAKER_PROFILE_MS", "600") or "600"),
|
||||
speaker_profile_min_ms=int(get("SPEAKER_PROFILE_MIN_MS", "120") or "120"),
|
||||
speaker_absent_ms=int(get("SPEAKER_ABSENT_MS", "300") or "300"),
|
||||
speaker_similarity_threshold=float(
|
||||
get("SPEAKER_SIMILARITY_THRESHOLD", "0.70") or "0.70"
|
||||
@@ -216,6 +218,7 @@ class AppConfig:
|
||||
)
|
||||
for name, value in {
|
||||
"OWNER_SPEAKER_PROFILE_MS": self.speaker_profile_ms,
|
||||
"OWNER_SPEAKER_PROFILE_MIN_MS": self.speaker_profile_min_ms,
|
||||
"OWNER_SPEAKER_ABSENT_MS": self.speaker_absent_ms,
|
||||
}.items():
|
||||
if value <= 0:
|
||||
|
||||
@@ -357,6 +357,7 @@ def build_live_runtime(config: AppConfig, reporter: RuntimeReporter | None = Non
|
||||
recorder_kwargs.update(
|
||||
{
|
||||
"speaker_profile_ms": config.speaker_profile_ms,
|
||||
"speaker_profile_min_ms": config.speaker_profile_min_ms,
|
||||
"speaker_absent_ms": config.speaker_absent_ms,
|
||||
"similarity_threshold": config.speaker_similarity_threshold,
|
||||
"min_rms": config.speaker_min_rms,
|
||||
|
||||
@@ -223,9 +223,15 @@ class SoundDeviceAudioTransport:
|
||||
return []
|
||||
timeout_s = max(0, timeout_ms) / 1000
|
||||
try:
|
||||
return [self._queue.get(timeout=timeout_s)]
|
||||
frames = [self._queue.get(timeout=timeout_s)]
|
||||
except queue.Empty:
|
||||
return []
|
||||
while True:
|
||||
try:
|
||||
frames.append(self._queue.get_nowait())
|
||||
except queue.Empty:
|
||||
break
|
||||
return frames
|
||||
|
||||
def play_pcm(self, segment: AudioSegment, interrupt: bool = False) -> PlaybackResult:
|
||||
if self._sd is None:
|
||||
|
||||
@@ -287,6 +287,7 @@ class VadRecorder:
|
||||
@dataclass(slots=True)
|
||||
class PrimarySpeakerVadRecorder(VadRecorder):
|
||||
speaker_profile_ms: int = 600
|
||||
speaker_profile_min_ms: int = 120
|
||||
speaker_absent_ms: int = 300
|
||||
similarity_threshold: float = 0.70
|
||||
min_rms: float = 0.012
|
||||
@@ -336,7 +337,7 @@ class PrimarySpeakerVadRecorder(VadRecorder):
|
||||
self.primary_absent_ms = 0
|
||||
else:
|
||||
self.primary_absent_ms += frame_ms
|
||||
if self.primary_absent_ms >= self.speaker_absent_ms and duration >= self.min_duration_ms:
|
||||
if self.primary_absent_ms >= self.speaker_absent_ms:
|
||||
return self._build_segment("primary_speaker_absent")
|
||||
if result.silence_ms >= self.end_silence_ms and duration >= self.min_duration_ms:
|
||||
return self._build_segment("silence")
|
||||
@@ -365,7 +366,7 @@ class PrimarySpeakerVadRecorder(VadRecorder):
|
||||
self.profile.vector = tuple(averaged)
|
||||
|
||||
def _profile_ready(self) -> bool:
|
||||
minimum_ms = min(self.speaker_profile_ms, max(120, self.min_duration_ms))
|
||||
minimum_ms = min(self.speaker_profile_ms, self.speaker_profile_min_ms)
|
||||
return self.profile.speech_ms >= minimum_ms and (
|
||||
self.profile.speaker_id is not None or self.profile.vector is not None
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user