[实时转写]:完成录音期间转写显示,包含partial事件、本地streaming STT和终端实时反馈

This commit is contained in:
mkbk
2026-06-17 22:16:56 +08:00
parent a64bb86da4
commit a77a172412
16 changed files with 331 additions and 17 deletions
+56
View File
@@ -3,10 +3,12 @@ from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from owner_voice_pet.models import AudioFrame, AudioSegment, ErrorCode, ProviderError
from owner_voice_pet.config import AppConfig
from owner_voice_pet.stt import CloudAsrSttProvider, MetadataSttProvider, SherpaOnnxSttProvider, is_valid_transcript_text
from owner_voice_pet.speech_models import stt_model_paths
from owner_voice_pet.vad import EnergyVadProvider, HybridVadProvider, PrimarySpeakerVadRecorder, VadRecorder
from owner_voice_pet.wakeword import (
KeywordWakeWordProvider,
@@ -246,6 +248,60 @@ class WakeVadSttTests(unittest.TestCase):
provider.load()
self.assertEqual(raised.exception.code, ErrorCode.STT_MODEL_MISSING)
def test_sherpa_stt_streaming_session_emits_partial_text(self) -> None:
class FakeResult:
def __init__(self, text: str) -> None:
self.text = text
class FakeStream:
def __init__(self) -> None:
self.ready = False
self.text = ""
def accept_waveform(self, sample_rate, samples) -> None:
self.ready = True
self.text = "" if not self.text else "你好"
class FakeRecognizer:
def create_stream(self):
return FakeStream()
def is_ready(self, stream) -> bool:
return stream.ready
def decode_stream(self, stream) -> None:
stream.ready = False
def get_result(self, stream):
return FakeResult(stream.text)
class FakeOnlineRecognizer:
@staticmethod
def from_transducer(**kwargs):
return FakeRecognizer()
class FakeSherpa:
OnlineRecognizer = FakeOnlineRecognizer
with tempfile.TemporaryDirectory() as tmp:
paths = stt_model_paths(Path(tmp))
for name, path in paths.items():
if name == "model_dir":
continue
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("fake", encoding="utf-8")
provider = SherpaOnnxSttProvider(tmp, sherpa_module=FakeSherpa)
provider.load()
session = provider.start_stream()
first = session.accept_frame(make_frame(1, 0, speech=True))
second = session.accept_frame(make_frame(2, 20, speech=True))
self.assertIsNotNone(first)
self.assertIsNotNone(second)
assert first is not None and second is not None
self.assertEqual(first.text, "")
self.assertEqual(second.text, "你好")
def test_cloud_asr_posts_audio_transcription_request(self) -> None:
class FakeResponse:
def __enter__(self):